feat(landscape): computation graph, params and per-pixel kernels
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
153
internal/landscape/graph.go
Normal file
153
internal/landscape/graph.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Package landscape defines the Landscape Program (ЛП) computation graph — the
|
||||
// per-pixel formulas that turn ecological (Z), vulnerability (U) and
|
||||
// suitability (P) class rasters into constraints (E), conflicts (C) and
|
||||
// functional zoning (V1–V3) — together with a window-based executor.
|
||||
package landscape
|
||||
|
||||
import "sort"
|
||||
|
||||
const (
|
||||
// NoData is the output nodata value of every LP raster.
|
||||
NoData int16 = -9999
|
||||
// CategoryCode is the dataset category holding every LP dataset.
|
||||
CategoryCode = "landscape"
|
||||
)
|
||||
|
||||
// Group codes. Slots are leaf inputs users upload; the rest are node outputs.
|
||||
const (
|
||||
SlotZBiodiversity = "lp-z-biodiversity"
|
||||
SlotZLCR = "lp-z-lcr"
|
||||
SlotZSoilProtection = "lp-z-soil-protection"
|
||||
SlotZWR = "lp-z-wr"
|
||||
SlotUSurfWater = "lp-u-surf-water"
|
||||
SlotUSoil = "lp-u-soil"
|
||||
SlotUCrop = "lp-u-crop"
|
||||
SlotUVeg = "lp-u-veg"
|
||||
SlotPIrrigation = "lp-p-irrigation"
|
||||
SlotPSnow = "lp-p-snow"
|
||||
SlotPCrop = "lp-p-crop"
|
||||
SlotPPasture = "lp-p-pasture"
|
||||
SlotPTourism = "lp-p-tourism"
|
||||
|
||||
OutZIntegrated = "lp-z-integrated"
|
||||
OutUIntegrated = "lp-u-integrated"
|
||||
OutZMax = "lp-z-max"
|
||||
OutUMax = "lp-u-max"
|
||||
OutE = "lp-e"
|
||||
OutCIrrigation = "lp-c-irrigation"
|
||||
OutCSnow = "lp-c-snow"
|
||||
OutCCrop = "lp-c-crop"
|
||||
OutCPasture = "lp-c-pasture"
|
||||
OutCTourism = "lp-c-tourism"
|
||||
OutPMax = "lp-p-max"
|
||||
OutV1 = "lp-v1-conservative"
|
||||
OutV2 = "lp-v2-balanced"
|
||||
OutV3 = "lp-v3-development"
|
||||
OutCMax = "lp-c-max"
|
||||
)
|
||||
|
||||
type Slot struct{ Code, Title string }
|
||||
type Output struct{ Code, Title string }
|
||||
|
||||
// Kernel computes one window. in[i] follows Node.Inputs order and out[j]
|
||||
// follows Node.Outputs order; every slice has exactly window-pixel length.
|
||||
type Kernel func(in [][]float64, out [][]int16, p Params)
|
||||
|
||||
// Node is one function of the graph. Stage = first digit of Code.
|
||||
type Node struct {
|
||||
Code, Name string
|
||||
Stage int
|
||||
Inputs []string
|
||||
Outputs []string
|
||||
Kernel Kernel
|
||||
}
|
||||
|
||||
var Slots = []Slot{
|
||||
{SlotZBiodiversity, "Z Biodiversity"}, {SlotZLCR, "Z LCR"}, {SlotZSoilProtection, "Z Soil Protection"}, {SlotZWR, "Z Water Resources"},
|
||||
{SlotUSurfWater, "U Surface Water"}, {SlotUSoil, "U Soil"}, {SlotUCrop, "U Crop"}, {SlotUVeg, "U Vegetation"},
|
||||
{SlotPIrrigation, "P Irrigation"}, {SlotPSnow, "P Snow"}, {SlotPCrop, "P Crop"}, {SlotPPasture, "P Pasture"}, {SlotPTourism, "P Tourism"},
|
||||
}
|
||||
|
||||
var Outputs = []Output{
|
||||
{OutZIntegrated, "Z Integrated"}, {OutUIntegrated, "U Integrated"}, {OutZMax, "Z Max"}, {OutUMax, "U Max"},
|
||||
{OutE, "E Ecological Constraint"},
|
||||
{OutCIrrigation, "C Irrigation"}, {OutCSnow, "C Snow"}, {OutCCrop, "C Crop"}, {OutCPasture, "C Pasture"}, {OutCTourism, "C Tourism"},
|
||||
{OutPMax, "P Max"},
|
||||
{OutV1, "V1 Conservative"}, {OutV2, "V2 Balanced"}, {OutV3, "V3 Development"}, {OutCMax, "C Max"},
|
||||
}
|
||||
|
||||
var zInputs = []string{SlotZBiodiversity, SlotZLCR, SlotZSoilProtection, SlotZWR}
|
||||
var uInputs = []string{SlotUSurfWater, SlotUSoil, SlotUCrop, SlotUVeg} // order matters: water, soil, crop, veg
|
||||
var pInputs = []string{SlotPIrrigation, SlotPSnow, SlotPCrop, SlotPPasture, SlotPTourism} // sector order 1..5
|
||||
var cOutputs = []string{OutCIrrigation, OutCSnow, OutCCrop, OutCPasture, OutCTourism}
|
||||
|
||||
var Nodes = []Node{
|
||||
{Code: "00", Name: "z_integrated", Stage: 0, Inputs: zInputs, Outputs: []string{OutZIntegrated}, Kernel: kernelZIntegrated},
|
||||
{Code: "01", Name: "u_integrated", Stage: 0, Inputs: uInputs, Outputs: []string{OutUIntegrated}, Kernel: kernelUIntegrated},
|
||||
{Code: "02", Name: "z_max", Stage: 0, Inputs: zInputs, Outputs: []string{OutZMax}, Kernel: kernelZMax},
|
||||
{Code: "03", Name: "u_max", Stage: 0, Inputs: uInputs, Outputs: []string{OutUMax}, Kernel: kernelUMax},
|
||||
{Code: "10", Name: "e_ecological_constraint", Stage: 1, Inputs: []string{OutZIntegrated, OutUIntegrated}, Outputs: []string{OutE}, Kernel: kernelE},
|
||||
{Code: "20", Name: "c_conflicts", Stage: 2, Inputs: append(append([]string{}, pInputs...), OutE), Outputs: cOutputs, Kernel: kernelCConflicts},
|
||||
{Code: "21", Name: "p_max", Stage: 2, Inputs: pInputs, Outputs: []string{OutPMax}, Kernel: kernelMaxAnyValid},
|
||||
{Code: "30", Name: "func_zoning", Stage: 3, Inputs: append([]string{OutE}, cOutputs...), Outputs: []string{OutV1, OutV2, OutV3}, Kernel: kernelFuncZoning},
|
||||
{Code: "31", Name: "c_max", Stage: 3, Inputs: cOutputs, Outputs: []string{OutCMax}, Kernel: kernelMaxAnyValid},
|
||||
}
|
||||
|
||||
func NodeByCode(code string) (Node, bool) {
|
||||
for _, n := range Nodes {
|
||||
if n.Code == code {
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
return Node{}, false
|
||||
}
|
||||
|
||||
func SlotByCode(code string) (Slot, bool) {
|
||||
for _, s := range Slots {
|
||||
if s.Code == code {
|
||||
return s, true
|
||||
}
|
||||
}
|
||||
return Slot{}, false
|
||||
}
|
||||
|
||||
func OutputByCode(code string) (Output, bool) {
|
||||
for _, o := range Outputs {
|
||||
if o.Code == code {
|
||||
return o, true
|
||||
}
|
||||
}
|
||||
return Output{}, false
|
||||
}
|
||||
|
||||
// NodeForOutput returns the node producing the given output group.
|
||||
func NodeForOutput(group string) (Node, bool) {
|
||||
for _, n := range Nodes {
|
||||
for _, o := range n.Outputs {
|
||||
if o == group {
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return Node{}, false
|
||||
}
|
||||
|
||||
// Stages groups nodes by stage, ascending; within a stage nodes are sorted by code.
|
||||
func Stages() [][]Node {
|
||||
byStage := map[int][]Node{}
|
||||
for _, n := range Nodes {
|
||||
byStage[n.Stage] = append(byStage[n.Stage], n)
|
||||
}
|
||||
keys := make([]int, 0, len(byStage))
|
||||
for k := range byStage {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys)
|
||||
out := make([][]Node, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
ns := byStage[k]
|
||||
sort.Slice(ns, func(i, j int) bool { return ns[i].Code < ns[j].Code })
|
||||
out = append(out, ns)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user