File size: 7,463 Bytes
ea5e723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f834679
ea5e723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def initialize_parcels(parcel_map, cluster_labels):
    parcel_dict = {}
    for i in range(parcel_map.shape[0]):
        for j in range(parcel_map.shape[1]):
            cluster_id = parcel_map[i, j]
            land_type = cluster_labels.get(cluster_id, "Unknown")

            parcel_dict[(i, j)] = {
                "land_type": land_type,
                "forage": None,
                "health": 1.0,
                "degraded": False,
                "cattle_grazing": {
                    "conservative": land_type == "Productive Grass",
                    "moderate": land_type in ["Productive Grass", "Pasture/Desert"],
                    "aggressive": land_type not in ["Water"]
                },
                "elk_grazing": {
                    "default": land_type in ["Riparian Sensitive Zone", "Productive Grass"]
                }
            }
    return parcel_dict

def get_land_forage_rates():
    return {
        'Productive Grass': 1.0,
        'Pasture/Desert': 0.4,
        'Riparian Sensitive Zone': 1.2,
        'Rocky Area': 0.2,
        'Water': 0.0
    }


def assign_initial_forage(parcel_dict, land_forage_rates):
    for parcel in parcel_dict.values():
        rate = land_forage_rates.get(parcel["land_type"], 0.0)
        parcel["forage"] = rate * 100  # initial AUMs

import numpy as np
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
from matplotlib.colors import ListedColormap
import matplotlib.patches as mpatches

def simulate_period(parcel_dict, grazing_strategy="moderate", cattle_stocking_rate=1000, elk_pressure=3000):
    print(f"\n🟢 Running LP-based simulation using grazing strategy: **{grazing_strategy.upper()}**")

    import numpy as np
    from scipy.optimize import linprog

    keys = list(parcel_dict.keys())
    n_rows = max(i for i, _ in keys) + 1
    n_cols = max(j for _, j in keys) + 1
    num_cells = n_rows * n_cols

    # Step 1: Regrowth
    land_growth_rates = {
        'Productive Grass': 1.0,
        'Pasture/Desert': 0.4,
        'Riparian Sensitive Zone': 1.2,
        'Rocky Area': 0.2,
        'Water': 0.0
    }
    for parcel in parcel_dict.values():
        base_growth = land_growth_rates.get(parcel["land_type"], 0.0)
        weather = np.random.normal(1.0, 0.15)
        regrowth = base_growth * weather * 1
        regrowth *= parcel["health"]
        parcel["forage"] = min(parcel["forage"] + regrowth, 100)

    # Step 2: Subtract uniform elk grazing from all parcels
    elk_grazing_per_parcel = elk_pressure / num_cells
    for parcel in parcel_dict.values():
        parcel["forage"] -= elk_grazing_per_parcel
        parcel["forage"] = max(parcel["forage"], 0.0)

    # Step 3: LP for cattle
    cost = []
    bounds = []
    eligible_keys = []
    for i in range(n_rows):
        for j in range(n_cols):
            p = parcel_dict[(i, j)]
            if not p["cattle_grazing"].get(grazing_strategy, False):
                cost.append(0)
                bounds.append((0, 0))
                continue
            if grazing_strategy in {"conservative", "moderate"} and p["land_type"] == "Riparian Sensitive Zone":
                cost.append(0)
                bounds.append((0, 0))
                continue
            cost.append((i + j) * 0.02)
            bounds.append((0, p["forage"]))
            eligible_keys.append((i, j))

    A_eq = [1.0 if b[1] > 0 else 0.0 for b in bounds]
    b_eq = [cattle_stocking_rate]

    result = linprog(c=cost, A_eq=[A_eq], b_eq=b_eq, bounds=bounds, method="highs")
    if not result.success:
        raise RuntimeError("Grazing LP failed: " + result.message)

    grazing_values = result.x

    # Step 4: Apply grazing and your specified health rule
    for idx, ((i, j), x) in enumerate(zip(parcel_dict.keys(), grazing_values)):
        parcel = parcel_dict[(i, j)]
        parcel["forage"] -= x

        # ✅ Your health rule (fully time-dynamic)
        if parcel["forage"] <= 0:
            parcel["health"] = max(parcel["health"] - 0.25, 0.0)
        elif parcel["forage"] < 20:
            parcel["health"] = max(parcel["health"] - 0.1, 0.0)
        else:
            parcel["health"] = min(parcel["health"] + 0.02, 1.0)

def simulate_periodold(parcel_dict, grazing_strategy="moderate", cattle_stocking_rate=5000, elk_pressure=3000):
    print(f"\n🟢 Running simulation using cattle grazing strategy: **{grazing_strategy.upper()}**")

    land_growth_rates = {
        'Productive Grass': 1.0,
        'Pasture/Desert': 0.4,
        'Riparian Sensitive Zone': 1.2,
        'Rocky Area': 0.2,
        'Water': 0.0
    }

    # 1. Simulate forage regrowth for 8 months
    for parcel in parcel_dict.values():
        base_growth = land_growth_rates.get(parcel["land_type"], 0.0)
        weather = np.random.normal(1.0, 0.15)
        regrowth = base_growth * weather * 8  # ← 8 months, as you said
        regrowth *= parcel["health"]  # degrade means slower regrowth
        parcel["forage"] = min(parcel["forage"] + regrowth, 100)

    # 2. Count eligible parcels
    total_grazed_parcels = sum(
        1 for parcel in parcel_dict.values() if parcel["cattle_grazing"].get(grazing_strategy, False)
    )
    if total_grazed_parcels == 0:
        print("⚠️ No parcels match the selected grazing strategy.")
        return

    cattle_grazing_per_parcel = cattle_stocking_rate / total_grazed_parcels
    elk_grazing_per_parcel = elk_pressure / len(parcel_dict)

    # 3. Simulate grazing and degradation
    for parcel in parcel_dict.values():
        if not parcel["cattle_grazing"].get(grazing_strategy, False):
            continue

        total_grazing = cattle_grazing_per_parcel + elk_grazing_per_parcel

        if total_grazing > parcel["forage"]:
            parcel["degraded"] = True
            parcel["health"] = max(parcel["health"] - 0.1, 0.0)
        else:
            parcel["health"] = min(parcel["health"] + 0.02, 1.0)

        parcel["forage"] = max(parcel["forage"] - total_grazing, 0)

def get_forage_map(parcel_dict, n_rows, n_cols):
    return np.array([[parcel_dict[(i, j)]["forage"] for j in range(n_cols)] for i in range(n_rows)])

def get_health_map(parcel_dict, n_rows, n_cols):
    """
    Returns a 2D numpy array representing the health of each parcel.
    """
    return np.array([[parcel_dict[(i, j)]["health"] for j in range(n_cols)] for i in range(n_rows)])


def plot_health_map(health_map, title="Parcel Health Levels", save_path=None):
    """
    Plots a heatmap of the parcel health values.
    """
    import matplotlib.pyplot as plt

    plt.figure(figsize=(8, 6))
    plt.imshow(health_map, cmap='RdYlGn', origin='upper', vmin=0, vmax=1)
    plt.colorbar(label="Health Index (0–1)")
    plt.title(title)
    plt.axis('off')
    plt.tight_layout()
    if save_path:
        plt.savefig(save_path)
    plt.show()



def plot_forage_map(forage_map, title="Parcel Forage Levels"):
    plt.figure(figsize=(8, 6))
    plt.imshow(forage_map, cmap='YlGn', origin='upper')
    plt.colorbar(label="Forage AUMs")
    plt.title(title)
    plt.axis('off')
    plt.tight_layout()
    plt.show()

def run_full_simulation(parcel_map, cluster_labels, n_rows, n_cols, strategy="moderate"):
    parcel_dict = initialize_parcels(parcel_map, cluster_labels)
    land_forage_rates = get_land_forage_rates()
    assign_initial_forage(parcel_dict, land_forage_rates)
    simulate_period(parcel_dict, grazing_strategy=strategy)
    return parcel_dict