Spaces:
Sleeping
Sleeping
| # Drip Irrigation Design Logic & Accuracy Guide | |
| ## Architecture Overview | |
| The design pipeline has **3 independent but sequential stages**: | |
| ``` | |
| Input (farm boundary + crop zones + pump) | |
| β | |
| [1] VALVE PLACEMENT ENGINE (valve_engine.py) | |
| β’ Decides: how many valves, where, why | |
| β | |
| [2] DRIP LAYOUT GENERATOR (drip_engine.py) | |
| β’ For each valve zone: generate main + laterals | |
| β | |
| [3] BOM & SUMMARY (drip_engine.py) | |
| β’ Compute costs, material counts, flow rates | |
| β | |
| Output (GeoJSON with valves, zones, drips, BOM) | |
| ``` | |
| Each stage can be improved independently. This doc explains the logic, current assumptions, and where accuracy hurts most. | |
| --- | |
| ## Stage 1: Valve Placement (THE MOST CRITICAL STAGE) | |
| ### Decision Matrix (4-layer hierarchy) | |
| When to split the farm into multiple zones? The engine applies these rules **in order**, taking the **maximum** zone count that satisfies all constraints: | |
| ```python | |
| num_zones = max( | |
| num_zones_capacity, # [1] Pump can't deliver all emitter demand at once | |
| num_zones_crop, # [2] Different crops in different zones | |
| num_zones_area, # [3] Area-based minimum (crop density floor) | |
| num_zones_topo, # [4] Elevation split | |
| ) | |
| ``` | |
| ### Layer 1: Capacity Constraint | |
| **Logic:** | |
| ```python | |
| total_emitter_flow_lph = sum(area_m2 * emitter_density * emitter_flow_lph per crop) | |
| pump_flow_lph = HP_TO_LPH[pump_hp] # lookup table | |
| num_zones_capacity = ceil(total_emitter_flow_lph / pump_flow_lph) | |
| ``` | |
| **Current assumptions:** | |
| - Emitter density derived from `lateral_spacing Γ emitter_spacing` (see `CROP_FLOW_PARAMS` in `valve_engine.py:47-54`) | |
| - Tomato: 0.5m rows, 0.3m emitter spacing β 4.17 emitters/mΒ² | |
| - Lettuce: 0.4m rows, 0.2m spacing β 12.5 emitters/mΒ² (intensive) | |
| - Orchard: 2.0m rows, 1.0m spacing β 0.5 emitters/mΒ² (sparse) | |
| - Pump HP β L/h curve is linear interpolation between discrete values (`HP_TO_LPH` dict) | |
| **Accuracy issues:** | |
| - **Emitter density is naive**: assumes rows are perpendicular to flow, which isn't always true on irregular plots. | |
| - **Pump curve is a lookup**: real pumps have pressure-dependent flow; we ignore head loss over long laterals. | |
| - **No derating for pressure drop**: a 200m lateral with 4.17 emitters/mΒ² may have 30%+ flow drop by the end; we ignore this. | |
| **When to improve:** | |
| - If you're seeing valve zones with highly uneven emitter counts, increase `CROP_FLOW_PARAMS[crop]["emitter_density_m2"]` for intensive crops. | |
| - If the pump can't deliver enough flow, you likely **underestimated density**. | |
| ### Layer 2: Crop Type Constraint | |
| **Logic:** | |
| ```python | |
| num_zones_crop = len(set(z["crop"] for z in crop_zones)) | |
| ``` | |
| **Current behavior:** | |
| - Each **unique crop name** gets its own zone (minimum). | |
| - Example: if you have tomato in plot_1 and lettuce in plot_2, you'll get β₯2 zones. | |
| **Accuracy issues:** | |
| - This is **exact** β no assumptions. | |
| - But it forces fragmentation if you have many small plots with different crops, even if their water needs are similar. | |
| **When to improve:** | |
| - If you want to group similar crops (e.g., tomato + pepper both ~0.5m spacing), add a crop "family" system. | |
| ### Layer 3: Area-Density Floor (THE MOST OPINIONATED) | |
| **Logic:** | |
| ```python | |
| density_valves_per_ha = VALVE_DENSITY_PER_HA[primary_crop] # e.g., 6 for tomato | |
| num_zones_area = ceil(farm_area_ha * density) | |
| ``` | |
| **Current densities (`VALVE_DENSITY_PER_HA`):** | |
| ```python | |
| "tomato": 6, # ~0.17 ha/valve = 1700 mΒ² per valve | |
| "pepper": 6, | |
| "lettuce": 7, | |
| "cucumber": 4, | |
| "orchard": 2, | |
| "generic": 5, # β2 valves/acre, standard default | |
| ``` | |
| **Why this matters:** | |
| - A 1-hectare tomato field gets **at least 6 zones**, even if the pump could handle it in 1. | |
| - This is **not** capacity-driven; it's **operational best practice**: narrower zones β shorter laterals β less pressure drop β more uniform water. | |
| **Current assumption:** | |
| - Rule of thumb from FAO irrigation guides: 1 valve per 1500β2000 mΒ² for row crops. | |
| - But these are **empirical, not physics-based**. | |
| **Accuracy issues:** | |
| - **These numbers are guessed**, not calibrated to your farms. | |
| - Real farms might benefit from 3 valves/ha or 10 valves/ha depending on topography, soil, crop sensitivity. | |
| - **No feedback loop**: the engine doesn't learn from actual irrigation records. | |
| **When to improve (HIGH IMPACT):** | |
| 1. **Collect data**: run 5-10 farms and measure which density gives the most uniform soil moisture or yield. | |
| 2. **Stratify by local conditions**: clay soils may tolerate coarser zones (lower density); sandy soils need more (higher density). | |
| 3. **Make it configurable**: add `valve_density_override` to the API so users can tune. | |
| ### Layer 4: Topography Constraint | |
| **Logic:** | |
| ```python | |
| should_split_topo = (max_elevation_m - min_elevation_m) > ELEVATION_DELTA_THRESHOLD_M # 5m | |
| if should_split_topo: | |
| num_zones_required += 1 # Just adds 1 zone; not sophisticated | |
| ``` | |
| **Current assumption:** | |
| - If elevation differs by >5m across the farm, you need separate high/low zones. | |
| - This avoids excessive pressure imbalance (5m β 0.5 bar). | |
| **Accuracy issues:** | |
| - **Very coarse**: if your field has 20m elevation span, splitting into high/low is crude. | |
| - **No secondary valve placement logic**: we don't intelligently place the "high zone" valve uphill. | |
| - **Ignores soil variability**: even flat farms have water-retention differences. | |
| **When to improve:** | |
| - Implement a real topographic split: use Voronoi diagram based on elevation contours. | |
| --- | |
| ## Stage 2: Drip Layout Generation (PURELY GEOMETRIC) | |
| ### Main Line Selection | |
| **Logic:** | |
| ```python | |
| edges = polygon.exterior edges (between consecutive vertices) | |
| edge_lengths = [distance(v1, v2) for each edge] | |
| main_idx = argmax(edge_lengths) # or argmin if main_line_edge="shortest" | |
| main_line = edges[main_idx] | |
| ``` | |
| **Current assumption:** | |
| - The longest edge is the best place for the main pipe (supplies all laterals perpendicular to it). | |
| - This works well for **rectangular fields**; poor for **irregular/triangular** fields. | |
| **Accuracy issues:** | |
| - **Doesn't optimize for minimal piping**: placing the main along the longest edge doesn't minimize total pipe (main + laterals). | |
| - **Doesn't consider pump location**: if pump is far from the longest edge, the main should be closer to the pump. | |
| **When to improve:** | |
| - Implement a "cost-optimal main placement" algorithm: | |
| ``` | |
| for each edge: | |
| cost = main_length + avg_lateral_length_if_main_on_this_edge | |
| best_edge = min(cost) | |
| ``` | |
| - Result: main placement adapts to both field shape and pump location. | |
| ### Lateral Generation & Clipping | |
| **Logic:** | |
| ```python | |
| # Generate parallel lines perpendicular to main, spaced at crop-specific intervals | |
| spacing = params["lateral_spacing_m"] # e.g., 0.5m for tomato | |
| num_laterals = ceil(main_length / spacing) | |
| for i in range(num_laterals): | |
| point_on_main = main.interpolate(i * spacing) | |
| lateral = perpendicular_line(point_on_main, direction=perp) | |
| clipped_lateral = lateral.intersection(polygon) # Clip to field boundary | |
| ``` | |
| **Current assumption:** | |
| - Equally-spaced laterals perpendicular to the main. | |
| - Works perfectly for **rectangles**, OK for **gentle polygons**, poor for **irregular shapes** (some laterals clip to very short lengths, wasting water). | |
| **Accuracy issues:** | |
| - **Uneven emitter distribution**: clipping can leave some laterals very short (e.g., 10m) while others are 100m, causing huge flow imbalance. | |
| - **Dead zones**: if the field is very irregular (e.g., L-shaped), the corners near the short clipped laterals will be under-irrigated. | |
| **When to improve:** | |
| 1. **Adaptive lateral spacing**: instead of fixed spacing, space laterals so each has ~similar length (within 20% variation). | |
| 2. **Lateral grouping**: group short laterals and feed them from a single branch main, not from the primary main. | |
| 3. **Visualization**: highlight in the output which laterals are "problematic" (too short, too long). | |
| ### Headland Buffer | |
| **Logic:** | |
| ```python | |
| buffered_polygon = polygon.buffer(-headland_buffer_m) | |
| ``` | |
| **Current assumption:** | |
| - Shrink the field inward by a fixed distance. | |
| - Avoids putting drip on field edges (where machinery turns). | |
| **Accuracy issues:** | |
| - **All headland is the same**: real farms have varied headland: one side might be a road (no drip), other side a boundary (need drip). | |
| - **No input for headland shape**: assume rectangular; doesn't account for irregular field edges. | |
| **When to improve:** | |
| - Accept a **per-edge headland map**: `{ "north": 1.5, "east": 1.0, "south": 2.0, "west": 0.5 }`. | |
| --- | |
| ## Stage 3: BOM & Summary (COST ESTIMATION) | |
| ### Current Cost Model | |
| **Logic:** | |
| ```python | |
| main_pipe_cost = main_length_m * PIPE_COSTS["main_line_16mm"] | |
| drip_tape_cost = drip_length_m * PIPE_COSTS["drip_tape_16mm"] | |
| emitter_cost = emitter_count * PIPE_COSTS["emitter_inline"] | |
| valve_cost = num_valves * 15 # Fixed $ per valve | |
| total_cost = main + drip + emitter + valve | |
| ``` | |
| **Current assumption:** | |
| - All pipes are 16mm. | |
| - All emitters are $0.08 each. | |
| - All valves are $15 each. | |
| - These are **order-of-magnitude guesses** from generic sourcing. | |
| **Accuracy issues:** | |
| - **No regional pricing**: India vs. Kenya vs. Brazil have very different pipe costs. | |
| - **No volume discounts**: a 1-hectare vs. 100-hectare farm have different unit costs. | |
| - **No installation labor**: only materials, no digging, trenching, connection labor. | |
| - **No waste allowance**: we assume 100% of pipe is used; real installations have ~5-10% waste. | |
| **When to improve:** | |
| 1. **Collect regional pricing data**: build a cost table by country/region. | |
| 2. **Add waste factor**: multiply final quantities by 1.10 (10% waste). | |
| 3. **Surface cost per hectare & cost per emitter**: help users compare. | |
| --- | |
| ## Summary: Accuracy Levers (in order of impact) | |
| | Lever | Current | Improvement | Impact | | |
| |-------|---------|-------------|--------| | |
| | **Valve density (Layer 3)** | Fixed 6 valves/ha for tomato | Calibrate to local data + user input | π΄ HIGHEST β wrong density = under/over-designed | | |
| | **Lateral spacing uniformity** | Clipped equally-spaced lines | Adaptive spacing by target lateral length | π΄ HIGH β short laterals = dead zones | | |
| | **Main line placement** | Longest edge | Cost-optimal (main + laterals) | π‘ MEDIUM β 10-20% improvement typical | | |
| | **Pump head loss** | Ignored | Model pressure drop over lateral length | π‘ MEDIUM β matters >100m laterals | | |
| | **Headland map** | Fixed inward buffer | Per-edge buffer (N/E/S/W) | π‘ MEDIUM β irregular fields | | |
| | **Cost calibration** | Guessed $ per unit | Regional/seasonal sourcing data | π‘ MEDIUM β users need local confidence | | |
| | **Topography split** | Elevation delta > 5m β +1 zone | Voronoi split + contour-aware placement | π’ LOW β only needed for >10m deltas | | |
| --- | |
| ## How to Test & Validate Your Improvements | |
| ### 1. Unit Tests | |
| - Each improvement should have a test in `test_drip_engine.py` or `test_valve_engine.py`. | |
| - Example: if you improve lateral spacing uniformity, add a test that checks `max(lateral_lengths) / min(lateral_lengths) < 1.5` (no more than 50% variation). | |
| ### 2. Visual Inspection | |
| - Use the Gradio UI or REST API to generate designs for **known farms**. | |
| - Compare your design to hand-drawn designs or existing irrigation schemes. | |
| - Look for: | |
| - Balanced lateral lengths β | |
| - Valves placed logically β | |
| - No huge dead zones β | |
| ### 3. Field Metrics | |
| - Once you have real farm data, collect: | |
| - Soil moisture at 0-30cm depth (3-5 spots per field) | |
| - Before / after irrigation water use | |
| - Crop yield uniformity | |
| - Correlate with design metrics: | |
| - `avg_lateral_length`, `lateral_length_std_dev` | |
| - `emitters_per_zone` | |
| - Compare uniform designs vs. non-uniform designs | |
| ### 4. Sensitivity Analysis | |
| - For each improvement, run the design with: | |
| - `-10% parameter` β output | |
| - `nominal parameter` β output | |
| - `+10% parameter` β output | |
| - Measure sensitivity: `(output_+10% - output_-10%) / output_nominal` | |
| - Example: "Reducing valve density from 6β5.4 /ha increases avg_lateral_length by 8%; this is acceptable for loose clay soils but risky for sandy soils." | |
| --- | |
| ## Next Steps | |
| 1. **Identify your priority**: | |
| - Cost accuracy? β Fix regional pricing (Step 3). | |
| - Design uniformity? β Adaptive lateral spacing (Step 2). | |
| - Operational realism? β Tune valve density + add headland map (Step 1). | |
| 2. **Collect data**: 5-10 real farms (geometry, crops, pump, existing irrigation scheme if any). | |
| 3. **Iterate**: | |
| - Improve one lever at a time. | |
| - Validate against your test farms. | |
| - Document assumptions. | |
| 4. **Share improvements**: push back to the repo so the next user benefits. | |