Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- Sim_Engine (7).py +207 -0
- app (21).py +122 -0
- feedback_fcns (7).py +167 -0
Sim_Engine (7).py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def initialize_parcels(parcel_map, cluster_labels):
|
| 2 |
+
parcel_dict = {}
|
| 3 |
+
for i in range(parcel_map.shape[0]):
|
| 4 |
+
for j in range(parcel_map.shape[1]):
|
| 5 |
+
cluster_id = parcel_map[i, j]
|
| 6 |
+
land_type = cluster_labels.get(cluster_id, "Unknown")
|
| 7 |
+
|
| 8 |
+
parcel_dict[(i, j)] = {
|
| 9 |
+
"land_type": land_type,
|
| 10 |
+
"forage": None,
|
| 11 |
+
"health": 1.0,
|
| 12 |
+
"degraded": False,
|
| 13 |
+
"cattle_grazing": {
|
| 14 |
+
"conservative": land_type == "Productive Grass",
|
| 15 |
+
"moderate": land_type in ["Productive Grass", "Pasture/Desert"],
|
| 16 |
+
"aggressive": land_type not in ["Water"]
|
| 17 |
+
},
|
| 18 |
+
"elk_grazing": {
|
| 19 |
+
"default": land_type in ["Riparian Sensitive Zone", "Productive Grass"]
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
return parcel_dict
|
| 23 |
+
|
| 24 |
+
def get_land_forage_rates():
|
| 25 |
+
return {
|
| 26 |
+
'Productive Grass': 1.0,
|
| 27 |
+
'Pasture/Desert': 0.4,
|
| 28 |
+
'Riparian Sensitive Zone': 1.2,
|
| 29 |
+
'Rocky Area': 0.2,
|
| 30 |
+
'Water': 0.0
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def assign_initial_forage(parcel_dict, land_forage_rates):
|
| 35 |
+
for parcel in parcel_dict.values():
|
| 36 |
+
rate = land_forage_rates.get(parcel["land_type"], 0.0)
|
| 37 |
+
parcel["forage"] = rate * 100 # initial AUMs
|
| 38 |
+
|
| 39 |
+
import numpy as np
|
| 40 |
+
import numpy as np
|
| 41 |
+
import matplotlib.pyplot as plt
|
| 42 |
+
from collections import Counter
|
| 43 |
+
from matplotlib.colors import ListedColormap
|
| 44 |
+
import matplotlib.patches as mpatches
|
| 45 |
+
|
| 46 |
+
def simulate_period(parcel_dict, grazing_strategy="moderate", cattle_stocking_rate=100000, elk_pressure=3000):
|
| 47 |
+
print(f"\n🟢 Running LP-based simulation using grazing strategy: **{grazing_strategy.upper()}**")
|
| 48 |
+
|
| 49 |
+
import numpy as np
|
| 50 |
+
from scipy.optimize import linprog
|
| 51 |
+
|
| 52 |
+
keys = list(parcel_dict.keys())
|
| 53 |
+
n_rows = max(i for i, _ in keys) + 1
|
| 54 |
+
n_cols = max(j for _, j in keys) + 1
|
| 55 |
+
num_cells = n_rows * n_cols
|
| 56 |
+
|
| 57 |
+
# Step 1: Regrowth
|
| 58 |
+
land_growth_rates = {
|
| 59 |
+
'Productive Grass': 1.0,
|
| 60 |
+
'Pasture/Desert': 0.4,
|
| 61 |
+
'Riparian Sensitive Zone': 1.2,
|
| 62 |
+
'Rocky Area': 0.2,
|
| 63 |
+
'Water': 0.0
|
| 64 |
+
}
|
| 65 |
+
for parcel in parcel_dict.values():
|
| 66 |
+
base_growth = land_growth_rates.get(parcel["land_type"], 0.0)
|
| 67 |
+
weather = np.random.normal(1.0, 0.15)
|
| 68 |
+
regrowth = base_growth * weather * 1
|
| 69 |
+
regrowth *= parcel["health"]
|
| 70 |
+
parcel["forage"] = min(parcel["forage"] + regrowth, 100)
|
| 71 |
+
|
| 72 |
+
# Step 2: Subtract uniform elk grazing from all parcels
|
| 73 |
+
elk_grazing_per_parcel = elk_pressure / num_cells
|
| 74 |
+
for parcel in parcel_dict.values():
|
| 75 |
+
parcel["forage"] -= elk_grazing_per_parcel
|
| 76 |
+
parcel["forage"] = max(parcel["forage"], 0.0)
|
| 77 |
+
|
| 78 |
+
# Step 3: LP for cattle
|
| 79 |
+
cost = []
|
| 80 |
+
bounds = []
|
| 81 |
+
eligible_keys = []
|
| 82 |
+
for i in range(n_rows):
|
| 83 |
+
for j in range(n_cols):
|
| 84 |
+
p = parcel_dict[(i, j)]
|
| 85 |
+
if not p["cattle_grazing"].get(grazing_strategy, False):
|
| 86 |
+
cost.append(0)
|
| 87 |
+
bounds.append((0, 0))
|
| 88 |
+
continue
|
| 89 |
+
if grazing_strategy in {"conservative", "moderate"} and p["land_type"] == "Riparian Sensitive Zone":
|
| 90 |
+
cost.append(0)
|
| 91 |
+
bounds.append((0, 0))
|
| 92 |
+
continue
|
| 93 |
+
cost.append((i + j) * 0.02)
|
| 94 |
+
bounds.append((0, p["forage"]))
|
| 95 |
+
eligible_keys.append((i, j))
|
| 96 |
+
|
| 97 |
+
A_eq = [1.0 if b[1] > 0 else 0.0 for b in bounds]
|
| 98 |
+
b_eq = [cattle_stocking_rate]
|
| 99 |
+
|
| 100 |
+
result = linprog(c=cost, A_eq=[A_eq], b_eq=b_eq, bounds=bounds, method="highs")
|
| 101 |
+
if not result.success:
|
| 102 |
+
raise RuntimeError("Grazing LP failed: " + result.message)
|
| 103 |
+
|
| 104 |
+
grazing_values = result.x
|
| 105 |
+
|
| 106 |
+
# Step 4: Apply grazing and your specified health rule
|
| 107 |
+
for idx, ((i, j), x) in enumerate(zip(parcel_dict.keys(), grazing_values)):
|
| 108 |
+
parcel = parcel_dict[(i, j)]
|
| 109 |
+
parcel["forage"] -= x
|
| 110 |
+
|
| 111 |
+
# ✅ Your health rule (fully time-dynamic)
|
| 112 |
+
if parcel["forage"] <= 0:
|
| 113 |
+
parcel["health"] = max(parcel["health"] - 0.25, 0.0)
|
| 114 |
+
elif parcel["forage"] < 20:
|
| 115 |
+
parcel["health"] = max(parcel["health"] - 0.1, 0.0)
|
| 116 |
+
else:
|
| 117 |
+
parcel["health"] = min(parcel["health"] + 0.02, 1.0)
|
| 118 |
+
|
| 119 |
+
def simulate_periodold(parcel_dict, grazing_strategy="moderate", cattle_stocking_rate=5000, elk_pressure=3000):
|
| 120 |
+
print(f"\n🟢 Running simulation using cattle grazing strategy: **{grazing_strategy.upper()}**")
|
| 121 |
+
|
| 122 |
+
land_growth_rates = {
|
| 123 |
+
'Productive Grass': 1.0,
|
| 124 |
+
'Pasture/Desert': 0.4,
|
| 125 |
+
'Riparian Sensitive Zone': 1.2,
|
| 126 |
+
'Rocky Area': 0.2,
|
| 127 |
+
'Water': 0.0
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
# 1. Simulate forage regrowth for 8 months
|
| 131 |
+
for parcel in parcel_dict.values():
|
| 132 |
+
base_growth = land_growth_rates.get(parcel["land_type"], 0.0)
|
| 133 |
+
weather = np.random.normal(1.0, 0.15)
|
| 134 |
+
regrowth = base_growth * weather * 8 # ← 8 months, as you said
|
| 135 |
+
regrowth *= parcel["health"] # degrade means slower regrowth
|
| 136 |
+
parcel["forage"] = min(parcel["forage"] + regrowth, 100)
|
| 137 |
+
|
| 138 |
+
# 2. Count eligible parcels
|
| 139 |
+
total_grazed_parcels = sum(
|
| 140 |
+
1 for parcel in parcel_dict.values() if parcel["cattle_grazing"].get(grazing_strategy, False)
|
| 141 |
+
)
|
| 142 |
+
if total_grazed_parcels == 0:
|
| 143 |
+
print("⚠️ No parcels match the selected grazing strategy.")
|
| 144 |
+
return
|
| 145 |
+
|
| 146 |
+
cattle_grazing_per_parcel = cattle_stocking_rate / total_grazed_parcels
|
| 147 |
+
elk_grazing_per_parcel = elk_pressure / len(parcel_dict)
|
| 148 |
+
|
| 149 |
+
# 3. Simulate grazing and degradation
|
| 150 |
+
for parcel in parcel_dict.values():
|
| 151 |
+
if not parcel["cattle_grazing"].get(grazing_strategy, False):
|
| 152 |
+
continue
|
| 153 |
+
|
| 154 |
+
total_grazing = cattle_grazing_per_parcel + elk_grazing_per_parcel
|
| 155 |
+
|
| 156 |
+
if total_grazing > parcel["forage"]:
|
| 157 |
+
parcel["degraded"] = True
|
| 158 |
+
parcel["health"] = max(parcel["health"] - 0.1, 0.0)
|
| 159 |
+
else:
|
| 160 |
+
parcel["health"] = min(parcel["health"] + 0.02, 1.0)
|
| 161 |
+
|
| 162 |
+
parcel["forage"] = max(parcel["forage"] - total_grazing, 0)
|
| 163 |
+
|
| 164 |
+
def get_forage_map(parcel_dict, n_rows, n_cols):
|
| 165 |
+
return np.array([[parcel_dict[(i, j)]["forage"] for j in range(n_cols)] for i in range(n_rows)])
|
| 166 |
+
|
| 167 |
+
def get_health_map(parcel_dict, n_rows, n_cols):
|
| 168 |
+
"""
|
| 169 |
+
Returns a 2D numpy array representing the health of each parcel.
|
| 170 |
+
"""
|
| 171 |
+
return np.array([[parcel_dict[(i, j)]["health"] for j in range(n_cols)] for i in range(n_rows)])
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def plot_health_map(health_map, title="Parcel Health Levels", save_path=None):
|
| 175 |
+
"""
|
| 176 |
+
Plots a heatmap of the parcel health values.
|
| 177 |
+
"""
|
| 178 |
+
import matplotlib.pyplot as plt
|
| 179 |
+
|
| 180 |
+
plt.figure(figsize=(8, 6))
|
| 181 |
+
plt.imshow(health_map, cmap='RdYlGn', origin='upper', vmin=0, vmax=1)
|
| 182 |
+
plt.colorbar(label="Health Index (0–1)")
|
| 183 |
+
plt.title(title)
|
| 184 |
+
plt.axis('off')
|
| 185 |
+
plt.tight_layout()
|
| 186 |
+
if save_path:
|
| 187 |
+
plt.savefig(save_path)
|
| 188 |
+
plt.show()
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def plot_forage_map(forage_map, title="Parcel Forage Levels"):
|
| 193 |
+
plt.figure(figsize=(8, 6))
|
| 194 |
+
plt.imshow(forage_map, cmap='YlGn', origin='upper')
|
| 195 |
+
plt.colorbar(label="Forage AUMs")
|
| 196 |
+
plt.title(title)
|
| 197 |
+
plt.axis('off')
|
| 198 |
+
plt.tight_layout()
|
| 199 |
+
plt.show()
|
| 200 |
+
|
| 201 |
+
def run_full_simulation(parcel_map, cluster_labels, n_rows, n_cols, strategy="moderate"):
|
| 202 |
+
parcel_dict = initialize_parcels(parcel_map, cluster_labels)
|
| 203 |
+
land_forage_rates = get_land_forage_rates()
|
| 204 |
+
assign_initial_forage(parcel_dict, land_forage_rates)
|
| 205 |
+
simulate_period(parcel_dict, grazing_strategy=strategy)
|
| 206 |
+
return parcel_dict
|
| 207 |
+
|
app (21).py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import gradio as gr
|
| 3 |
+
import numpy as np
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
from Sim_Setup_Fcns import (
|
| 6 |
+
load_and_crop_image, cluster_image, build_parcel_map,
|
| 7 |
+
get_cluster_labels, get_land_colors, plot_parcel_map_to_file
|
| 8 |
+
)
|
| 9 |
+
from Sim_Engine import run_full_simulation
|
| 10 |
+
from feedback_fcns import (
|
| 11 |
+
summarize_initial_conditions, plot_forage_map_to_file,
|
| 12 |
+
elk_feedback, usfs_feedback, simulate_and_summarize, full_response
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from zone_utils import (
|
| 16 |
+
identify_zones, plot_labeled_zones,
|
| 17 |
+
assign_zone_labels, save_zone_info_to_excel,override_zone_id_and_label
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
# === Setup on Launch ===
|
| 21 |
+
img = load_and_crop_image("Carson_map.png")
|
| 22 |
+
clustered_img = cluster_image(img)
|
| 23 |
+
parcel_map, n_rows, n_cols = build_parcel_map(clustered_img)
|
| 24 |
+
cluster_labels = get_cluster_labels()
|
| 25 |
+
land_colors = get_land_colors()
|
| 26 |
+
plot_parcel_map_to_file(parcel_map, cluster_labels, land_colors, save_path="clustered_map.png")
|
| 27 |
+
|
| 28 |
+
# === Zoning ===
|
| 29 |
+
|
| 30 |
+
# 1. Identify contiguous zones
|
| 31 |
+
zone_map, zone_to_cluster = identify_zones(parcel_map, connectivity="queen")
|
| 32 |
+
|
| 33 |
+
# 2. Assign human-readable labels (before override)
|
| 34 |
+
zone_labels = assign_zone_labels(zone_to_cluster)
|
| 35 |
+
# === Manual override for mislabeled riparian zone ===
|
| 36 |
+
# First, update the zone label once
|
| 37 |
+
for zid, lbl in zone_labels.items():
|
| 38 |
+
if lbl == "A" and zone_to_cluster[zid] == 1:
|
| 39 |
+
zone_labels[zid] = "Riparian A1"
|
| 40 |
+
if lbl == "M" and zone_to_cluster[zid] == 1:
|
| 41 |
+
zone_labels[zid] = "Riparian A2"
|
| 42 |
+
# Then update all matching parcels
|
| 43 |
+
for i in range(n_rows):
|
| 44 |
+
for j in range(n_cols):
|
| 45 |
+
zone_id = zone_map[i, j]
|
| 46 |
+
if zone_labels.get(zone_id) == "Riparian A1":
|
| 47 |
+
parcel_map[i, j] = 2
|
| 48 |
+
if zone_labels.get(zone_id) == "Riparian A2":
|
| 49 |
+
parcel_map[i, j] = 2
|
| 50 |
+
|
| 51 |
+
#
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# ⬇️ Add this block right after the override
|
| 55 |
+
zone_to_cluster = {}
|
| 56 |
+
for zone_id in np.unique(zone_map):
|
| 57 |
+
indices = np.argwhere(zone_map == zone_id)
|
| 58 |
+
if len(indices) > 0:
|
| 59 |
+
i, j = indices[0]
|
| 60 |
+
zone_to_cluster[zone_id] = parcel_map[i, j]
|
| 61 |
+
|
| 62 |
+
# 6. Plot labeled zones after override and mapping
|
| 63 |
+
plot_labeled_zones(zone_map, zone_labels, zone_to_cluster, save_path="zones_labeled.png")
|
| 64 |
+
|
| 65 |
+
# 5. Define cluster-to-class mapping (should stay after override)
|
| 66 |
+
cluster_to_class = {
|
| 67 |
+
0: "desert",
|
| 68 |
+
1: "pasture",
|
| 69 |
+
2: "riparain",
|
| 70 |
+
3: "sensitive riparian",
|
| 71 |
+
4: "wetland",
|
| 72 |
+
5: "water"
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
# 7. Save zone info to Excel
|
| 76 |
+
zone_excel_path = "zone_info.xlsx"
|
| 77 |
+
save_zone_info_to_excel(
|
| 78 |
+
parcel_map, zone_map, zone_labels, zone_to_cluster, cluster_to_class,
|
| 79 |
+
save_path=zone_excel_path
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
| 84 |
+
|
| 85 |
+
# === Gradio App ===
|
| 86 |
+
with gr.Blocks() as demo:
|
| 87 |
+
gr.Markdown("# AGEC 3052 — Grazing Strategy Simulation")
|
| 88 |
+
gr.Image(value="clustered_map.png", label="Initial 25×25 Parcel Layout")
|
| 89 |
+
gr.Image(value="zones_labeled.png", label="Labeled Pasture & Riparian Zones")
|
| 90 |
+
# ✅ Downloadable Excel
|
| 91 |
+
gr.File(value=zone_excel_path, label="Download Zone Info (Excel)")
|
| 92 |
+
|
| 93 |
+
plan = gr.Radio(["Conservative", "Normal", "Aggressive"], label="Grazing Plan")
|
| 94 |
+
essay = gr.Textbox(lines=8, label="Your Essay Justifying the Plan")
|
| 95 |
+
|
| 96 |
+
elk_output = gr.Textbox(label="Elk Stakeholder Feedback")
|
| 97 |
+
usfs_output = gr.Textbox(label="USFS Feedback")
|
| 98 |
+
sim_output = gr.Textbox(label="Simulation Results", lines=2)
|
| 99 |
+
sim_image = gr.Image(label="Forage Map After Simulation", type="filepath")
|
| 100 |
+
health_image = gr.Image(label="Health Map After Simulation", type="filepath")
|
| 101 |
+
|
| 102 |
+
round_counter = gr.State(value=1)
|
| 103 |
+
history = gr.State(value=[summarize_initial_conditions(n_rows, n_cols)])
|
| 104 |
+
|
| 105 |
+
def submit_handler(plan_choice, essay_text, history_val):
|
| 106 |
+
return full_response(plan_choice, essay_text, history_val[-1])
|
| 107 |
+
|
| 108 |
+
def sim_handler(plan_choice, round_val, history_val):
|
| 109 |
+
summary, map_path, health_path, new_round = simulate_and_summarize(
|
| 110 |
+
plan_choice, round_val, parcel_map, cluster_labels, n_rows, n_cols,
|
| 111 |
+
run_full_simulation, history_val
|
| 112 |
+
)
|
| 113 |
+
history_val.append(summary)
|
| 114 |
+
return summary, map_path, health_path, new_round, history_val
|
| 115 |
+
|
| 116 |
+
submit_btn = gr.Button("Submit Grazing Plan")
|
| 117 |
+
sim_btn = gr.Button("Run Simulation")
|
| 118 |
+
|
| 119 |
+
submit_btn.click(fn=submit_handler, inputs=[plan, essay, history], outputs=[elk_output, usfs_output])
|
| 120 |
+
sim_btn.click(fn=sim_handler, inputs=[plan, round_counter, history], outputs=[sim_output, sim_image, health_image, round_counter, history])
|
| 121 |
+
|
| 122 |
+
demo.launch()
|
feedback_fcns (7).py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
import numpy as np
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
from openai import OpenAI
|
| 6 |
+
from Sim_Engine import simulate_period
|
| 7 |
+
|
| 8 |
+
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
| 9 |
+
|
| 10 |
+
def summarize_initial_conditions(n_rows, n_cols):
|
| 11 |
+
num_parcels = n_rows * n_cols
|
| 12 |
+
avg_forage = round(random.uniform(6.5, 8.0), 1)
|
| 13 |
+
degraded_pct = round(random.uniform(0.0, 2.0), 1)
|
| 14 |
+
riparian_health = random.choice(["excellent", "moderate", "fragile"])
|
| 15 |
+
elk_corridor_status = random.choice([
|
| 16 |
+
"completely intact and lightly used",
|
| 17 |
+
"intact but under slight pressure from cattle movement",
|
| 18 |
+
"showing signs of fragmentation near key crossings"
|
| 19 |
+
])
|
| 20 |
+
rainfall_outlook = random.choice(["normal", "below average", "above average"])
|
| 21 |
+
|
| 22 |
+
return (
|
| 23 |
+
f"There are {num_parcels} parcels in total. Grazing has not yet occurred.\n"
|
| 24 |
+
f"Average forage availability is {avg_forage} AUMs per parcel, with about {degraded_pct}% of land already degraded due to prior conditions.\n"
|
| 25 |
+
f"Riparian zone condition is {riparian_health}, and the elk movement corridor is {elk_corridor_status}.\n"
|
| 26 |
+
f"The seasonal rainfall outlook is {rainfall_outlook}."
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
def plot_forage_map_to_file(parcel_dict, n_rows, n_cols, title="Forage Map", save_path="forage_map.png"):
|
| 30 |
+
forage_map = np.array([
|
| 31 |
+
[parcel_dict[(i, j)]["forage"] for j in range(n_cols)]
|
| 32 |
+
for i in range(n_rows)
|
| 33 |
+
])
|
| 34 |
+
fig, ax = plt.subplots(figsize=(8, 6))
|
| 35 |
+
cax = ax.imshow(forage_map, cmap='YlGn', origin='upper')
|
| 36 |
+
fig.colorbar(cax, label="Forage AUMs")
|
| 37 |
+
ax.set_title(title)
|
| 38 |
+
ax.axis('off')
|
| 39 |
+
plt.tight_layout()
|
| 40 |
+
plt.savefig(save_path)
|
| 41 |
+
plt.close(fig)
|
| 42 |
+
|
| 43 |
+
def elk_feedback(plan_choice, current_summary):
|
| 44 |
+
prompt = f"""
|
| 45 |
+
You represent a coalition of elk-related interests: conservationists, hunting advocates, and the hospitality/lodging industry.
|
| 46 |
+
A student has selected the **'{plan_choice}'** cattle grazing strategy. Below are the current ecological conditions:
|
| 47 |
+
-----
|
| 48 |
+
{current_summary}
|
| 49 |
+
-----
|
| 50 |
+
Please do the following:
|
| 51 |
+
- Explicitly choose **one elk management strategy** from the list:
|
| 52 |
+
- **Preserve**: strict elk protections, no hunting, unrestricted movement
|
| 53 |
+
- **Cooperate**: shared use corridor, some riparian restrictions, sustainable elk population
|
| 54 |
+
- **Exploit**: prioritize hunting/tourism, tolerate reduced elk numbers and access
|
| 55 |
+
- Reflect each group's view briefly, but unify the final position.
|
| 56 |
+
- Justify your strategy choice based on the above ecological indicators.
|
| 57 |
+
"""
|
| 58 |
+
response = client.chat.completions.create(
|
| 59 |
+
model="gpt-3.5-turbo",
|
| 60 |
+
messages=[{"role": "user", "content": prompt}],
|
| 61 |
+
temperature=0.8
|
| 62 |
+
)
|
| 63 |
+
return response.choices[0].message.content
|
| 64 |
+
|
| 65 |
+
def usfs_feedback(plan_choice, student_essay, current_summary):
|
| 66 |
+
# Extract the AUM line from the summary
|
| 67 |
+
aum_line = ""
|
| 68 |
+
for line in current_summary.split("\n"):
|
| 69 |
+
if "Average forage availability" in line:
|
| 70 |
+
aum_line = line.strip()
|
| 71 |
+
break
|
| 72 |
+
|
| 73 |
+
prompt = f"""
|
| 74 |
+
You are a USFS land management agent evaluating a student’s cattle grazing proposal.
|
| 75 |
+
|
| 76 |
+
The student selected the **'{plan_choice}'** strategy and submitted this justification:
|
| 77 |
+
-----
|
| 78 |
+
{student_essay}
|
| 79 |
+
-----
|
| 80 |
+
|
| 81 |
+
Here are the current rangeland conditions:
|
| 82 |
+
-----
|
| 83 |
+
{current_summary}
|
| 84 |
+
-----
|
| 85 |
+
|
| 86 |
+
🚨 **Must Include Block**:
|
| 87 |
+
You must include this sentence exactly in your response:
|
| 88 |
+
→ "{aum_line}"
|
| 89 |
+
|
| 90 |
+
Then provide your evaluation:
|
| 91 |
+
- Comment on forage, degradation, riparian and corridor health
|
| 92 |
+
- State whether the plan is ecologically sound
|
| 93 |
+
- Suggest improvements if needed
|
| 94 |
+
- Keep the tone professional, clear, and grounded in the data
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
response = client.chat.completions.create(
|
| 98 |
+
model="gpt-3.5-turbo",
|
| 99 |
+
messages=[{"role": "user", "content": prompt}],
|
| 100 |
+
temperature=0.7
|
| 101 |
+
)
|
| 102 |
+
return response.choices[0].message.content
|
| 103 |
+
|
| 104 |
+
def simulate_and_summarizeold(plan_choice, round_counter, parcel_dict, parcel_map, cluster_labels, n_rows, n_cols, elk_pressure):
|
| 105 |
+
# Interpret strategy
|
| 106 |
+
strategy_map = {
|
| 107 |
+
"conservative": "conservative",
|
| 108 |
+
"normal": "moderate",
|
| 109 |
+
"aggressive": "aggressive"
|
| 110 |
+
}
|
| 111 |
+
strategy = strategy_map.get(plan_choice.lower(), "moderate")
|
| 112 |
+
|
| 113 |
+
# ✅ Reuse incoming parcel_dict — do not reset
|
| 114 |
+
simulate_period(parcel_dict, grazing_strategy=strategy, elk_pressure=elk_pressure)
|
| 115 |
+
|
| 116 |
+
# Extract summary
|
| 117 |
+
forage_vals = [p["forage"] for p in parcel_dict.values()]
|
| 118 |
+
avg_forage = sum(forage_vals) / len(forage_vals)
|
| 119 |
+
degraded_pct = 100 * sum(p["health"] < 0.5 for p in parcel_dict.values()) / len(parcel_dict)
|
| 120 |
+
|
| 121 |
+
summary = (
|
| 122 |
+
f"After Round {round_counter} with the '{plan_choice}' plan:\n"
|
| 123 |
+
f"Avg forage: {avg_forage:.1f} AUMs, Parcels with low health (<0.5): {degraded_pct:.1f}%"
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
# Generate map visuals
|
| 127 |
+
from Sim_Engine import get_forage_map, get_health_map, plot_forage_map, plot_health_map
|
| 128 |
+
forage_map = get_forage_map(parcel_dict, n_rows, n_cols)
|
| 129 |
+
health_map = get_health_map(parcel_dict, n_rows, n_cols)
|
| 130 |
+
plot_forage_map(forage_map, title=f"Forage Map (Round {round_counter})", save_path="forage_map.png")
|
| 131 |
+
plot_health_map(health_map, title=f"Health Map (Round {round_counter})", save_path="health_map.png")
|
| 132 |
+
|
| 133 |
+
return summary, "forage_map.png", "health_map.png", round_counter + 1, parcel_dict
|
| 134 |
+
|
| 135 |
+
def simulate_and_summarize(plan_choice, round_counter, parcel_map, cluster_labels, n_rows, n_cols, run_full_simulation, history):
|
| 136 |
+
|
| 137 |
+
strategy_map = {
|
| 138 |
+
"conservative": "conservative",
|
| 139 |
+
"normal": "moderate",
|
| 140 |
+
"aggressive": "aggressive"
|
| 141 |
+
}
|
| 142 |
+
strategy = strategy_map.get(plan_choice.lower())
|
| 143 |
+
parcel_dict = run_full_simulation(parcel_map, cluster_labels, n_rows, n_cols, strategy=strategy)
|
| 144 |
+
|
| 145 |
+
plot_forage_map_to_file(parcel_dict, n_rows, n_cols, title=f"Round {round_counter} Forage Map")
|
| 146 |
+
|
| 147 |
+
# ✅ ADD THIS BLOCK
|
| 148 |
+
from Sim_Engine import get_health_map, plot_health_map
|
| 149 |
+
health_map = get_health_map(parcel_dict, n_rows, n_cols)
|
| 150 |
+
plot_health_map(health_map, title=f"Round {round_counter} Health Map", save_path="health_map.png")
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
forage_vals = [p["forage"] for p in parcel_dict.values()]
|
| 154 |
+
avg_forage = sum(forage_vals) / len(forage_vals)
|
| 155 |
+
degraded_pct = 100 * sum(p["degraded"] for p in parcel_dict.values()) / len(parcel_dict)
|
| 156 |
+
|
| 157 |
+
summary = (
|
| 158 |
+
f"After Round {round_counter} with the '{plan_choice}' plan:\n"
|
| 159 |
+
f"Avg forage: {avg_forage:.1f} AUMs, Degraded parcels: {degraded_pct:.1f}%"
|
| 160 |
+
)
|
| 161 |
+
return summary, "forage_map.png", "health_map.png", round_counter + 1
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def full_response(plan_choice, essay_text, current_summary):
|
| 165 |
+
elk_resp = elk_feedback(plan_choice, current_summary)
|
| 166 |
+
usfs_resp = usfs_feedback(plan_choice, essay_text, current_summary)
|
| 167 |
+
return elk_resp, usfs_resp
|