feat: polish & windviz & deploy

This commit is contained in:
Anatoly Antonov 2026-05-30 06:29:39 +09:00
parent 81b8e763bd
commit 465ad00f7b
78 changed files with 20622 additions and 2154 deletions

View file

@ -8,39 +8,40 @@ import (
)
// Wind is a WindField backed by a GFS dataset file.
//
// The cube is addressed in flat element units with fixed strides so the
// sampler can compute the eight horizontal interpolation corners once and
// reach any (level, variable) by adding constant strides — avoiding the
// five-multiply offset computation per corner per evaluation.
type Wind struct {
file *File
hourAxis numerics.Axis
latAxis numerics.Axis
lngAxis numerics.Axis
hourStride int64 // elements between successive hours
levelStride int64 // elements between successive pressure levels
varStride int64 // elements between successive variables
latStride int64 // elements between successive latitudes
}
// NewWind returns a Wind backed by file. The axes are constructed from the
// file's variant geometry.
// NewWind returns a Wind backed by file. Axes and strides are derived from
// the file's variant geometry.
func NewWind(file *File) *Wind {
v := file.variant
nLat := v.NumLatitudes()
nLng := v.NumLongitudes()
nLev := v.NumLevels()
return &Wind{
file: file,
hourAxis: numerics.Axis{
Left: 0,
Step: float64(v.HourStep),
N: v.NumHours(),
Name: "hour",
},
latAxis: numerics.Axis{
Left: LatStart,
Step: v.Resolution,
N: v.NumLatitudes(),
Name: "lat",
},
lngAxis: numerics.Axis{
Left: LonStart,
Step: v.Resolution,
N: v.NumLongitudes(),
Wrap: true,
Name: "lng",
},
file: file,
hourAxis: numerics.Axis{Left: 0, Step: float64(v.HourStep), N: v.NumHours(), Name: "hour"},
latAxis: numerics.Axis{Left: LatStart, Step: v.Resolution, N: nLat, Name: "lat"},
lngAxis: numerics.Axis{Left: LonStart, Step: v.Resolution, N: nLng, Wrap: true, Name: "lng"},
hourStride: int64(nLev) * NumVariables * int64(nLat) * int64(nLng),
levelStride: NumVariables * int64(nLat) * int64(nLng),
varStride: int64(nLat) * int64(nLng),
latStride: int64(nLng),
}
}
@ -72,38 +73,53 @@ func (w *Wind) Wind(t, lat, lng, alt float64) (weather.Sample, error) {
if err != nil {
return weather.Sample{}, err
}
bs := [3]numerics.Bracket{bh, bla, bln}
height := func(level int) func(i, j, k int) float64 {
return func(i, j, k int) float64 {
return float64(w.file.Val(i, level, VarHeight, j, k))
weights := numerics.TrilinearWeights([3]numerics.Bracket{bh, bla, bln})
// Flat element index of each of the eight horizontal corners, at level 0
// variable 0, in the canonical TrilinearWeights order (hour outer, lng
// inner). Reaching a given (level, variable) corner only adds constant
// strides.
var base [8]int64
hours2 := [2]int64{int64(bh.Lo) * w.hourStride, int64(bh.Hi) * w.hourStride}
lats2 := [2]int64{int64(bla.Lo) * w.latStride, int64(bla.Hi) * w.latStride}
lngs2 := [2]int64{int64(bln.Lo), int64(bln.Hi)}
i := 0
for _, h := range hours2 {
for _, la := range lats2 {
for _, ln := range lngs2 {
base[i] = h + la + ln
i++
}
}
}
sample := func(level int, varIdx int64) float64 {
off := int64(level)*w.levelStride + varIdx*w.varStride
var vals [8]float64
for k := range 8 {
vals[k] = float64(w.file.ValByElem(base[k] + off))
}
return numerics.Dot8(&weights, &vals)
}
// Largest pressure level whose interpolated geopotential height is below alt.
levelIdx := numerics.Bisect(0, w.file.variant.NumLevels()-2, alt, func(level int) float64 {
return numerics.EvalTrilinear(bs, height(level))
return sample(level, VarHeight)
})
lowerHGT := numerics.EvalTrilinear(bs, height(levelIdx))
upperHGT := numerics.EvalTrilinear(bs, height(levelIdx+1))
lowerHGT := sample(levelIdx, VarHeight)
upperHGT := sample(levelIdx+1, VarHeight)
var altFrac float64
altFrac := 0.5
if lowerHGT != upperHGT {
altFrac = (upperHGT - alt) / (upperHGT - lowerHGT)
} else {
altFrac = 0.5
}
component := func(level, variable int) float64 {
return numerics.EvalTrilinear(bs, func(i, j, k int) float64 {
return float64(w.file.Val(i, level, variable, j, k))
})
}
lowerU := component(levelIdx, VarWindU)
upperU := component(levelIdx+1, VarWindU)
lowerV := component(levelIdx, VarWindV)
upperV := component(levelIdx+1, VarWindV)
lowerU := sample(levelIdx, VarWindU)
upperU := sample(levelIdx+1, VarWindU)
lowerV := sample(levelIdx, VarWindV)
upperV := sample(levelIdx+1, VarWindV)
return weather.Sample{
U: lowerU*altFrac + upperU*(1-altFrac),