farm-layout-model / DESIGN_LOGIC.md
spacedout-bits's picture
Change 1: Implement global lateral orientation with compute_farm_axis
8050706

A newer version of the Gradio SDK is available: 6.15.2

Upgrade

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:

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:

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:

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:

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):

"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:

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:

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:

# 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:

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:

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.