Functional Weave
Code in Python

agri.seed-rate

Seed rate in kg/ha from target plants per m², thousand-grain weight and expected establishment.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 17 tests, run in TypeScript, Python and Rust.

What it does

How much seed to drill, in kg/ha, to end up with a target number of established plants per square metre:

seed rate (kg/ha) = target plants/m² x thousand-grain weight (g) / expected establishment (%)

For example

  • seed_rate(300, 50, 80%) → kg per hectare 187.5, seeds per square metre 375 300 plants/m² at TGW 50 g and 80% establishment is 187.5 kg/ha
  • seed_rate(225, 40, 76.5%) → kg per hectare 117.6, seeds per square metre 294.1 FAR spring barley example: 225 plants, TGW 40 g, 90% germination x 85% emergence (76.5%) is about 118 kg/ha
  • seed_rate(260, 48, 70%) → kg per hectare 178.3, seeds per square metre 371.4 AHDB wheat benchmark: 260 plants/m² at 70% establishment, TGW 48 g

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def seed_rate(target_plants_per_square_metre: int, thousand_grain_weight_grams: float, establishment_basis_points: int) -> SeedRate
target_plants_per_square_metreintestablished plants wanted, 1 to 100000
thousand_grain_weight_gramsfloatweight of 1000 seeds in grams, taken to 0.01 g; from the seed bag
establishment_basis_pointsintshare of seeds sown expected to become plants, 7000 = 70%; 1 to 10000
returnsSeedRate

The type it declares, generated into your project

@dataclass(frozen=True)
class SeedRate:
    """What to set the drill to."""

    #: plants x TGW / establishment %, to 0.1 kg, half up
    kg_per_hectare: float
    #: seeds to sow per m², to 0.1, half up
    seeds_per_square_metre: float

Your code names it in one line, in the file that uses it

from fune.agri.seed_rate import seed_rate  # agri.seed-rate@^1
impl/python.py · 31 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import math

from .agri_seed_rate_types import SeedRate
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .math_round_float import round_float  ← from math.round-float ^1.0.0 · built alongside by fune


def seed_rate(target_plants_per_square_metre: int, thousand_grain_weight_grams: float, establishment_basis_points: int) -> SeedRate:
    """Seed rate (kg/ha) = target plants/m2 x TGW (g) / expected establishment (%).

    The TGW is taken to the centigram and the establishment is in basis points,
    so kg/ha is exactly plants x centigrams / basis points and is rounded once.
    Doing it in floats gets 100 x 1.15 / 100 = 1.1499999999999999, which rounds
    to 1.1 kg/ha instead of 1.2.
    """
    plants = target_plants_per_square_metre
    if isinstance(plants, bool) or not isinstance(plants, int) or plants < 1 or plants > 100000:
        raise ValueError("targetPlantsPerSquareMetre must be a whole number from 1 to 100000, received %r" % (plants,))
    tgw = thousand_grain_weight_grams
    if isinstance(tgw, bool) or not isinstance(tgw, (int, float)) or not math.isfinite(tgw) or tgw <= 0 or tgw > 1000:
        raise ValueError("thousandGrainWeightGrams must be a finite number greater than 0 and at most 1000, received %r" % (tgw,))
    centigrams = int(round(round_float(float(tgw), 2) * 100))
    if centigrams < 1:
        raise ValueError("thousandGrainWeightGrams must be at least 0.01 grams, received %r" % (tgw,))
    bp = establishment_basis_points
    if isinstance(bp, bool) or not isinstance(bp, int) or bp < 1 or bp > 10000:
        raise ValueError("establishmentBasisPoints must be a whole number from 1 to 10000, received %r" % (bp,))
    return SeedRate(
        kg_per_hectare=round_div(plants * centigrams * 10, bp, "half-up") / 10,
        seeds_per_square_metre=round_div(plants * 100000, bp, "half-up") / 10,
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add agri.seed-rate
Download for Python agri.seed-rate-1.0.0-python.fune · 9,413 bytes sha256 a61870e4fc3473d781a6727da2c8485fd10c8d366a3fc06b9d78a650d88a1962

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./agri.seed-rate-1.0.0-python.fune, or fetch it from a terminal with fune pull agri.seed-rate@1.0.0:python.

The whole function, every language, is one file too: agri.seed-rate-1.0.0.fune, 13,674 bytes, sha256 0cb3c657ec361c750a3bb5c33e092b800dbfa3b731ee943c781c2f8d4c3f0672. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before agri.seed-rate

after — your function gets the result and the arguments, and returns the final result.

# fune: after agri.seed-rate

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace math.round-div in agri.seed-rate
# fune: replace math.round-float in agri.seed-rate

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show agri.seed-rate --steps.

# fune: step agri.seed-rate after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
300 plants/m² at TGW 50 g and 80% establishment is 187.5 kg/ha 300, 50, 80% → kg per hectare 187.5, seeds per square metre 375
FAR spring barley example: 225 plants, TGW 40 g, 90% germination x 85% emergence (76.5%) is about 118 kg/ha 225, 40, 76.5% → kg per hectare 117.6, seeds per square metre 294.1
AHDB wheat benchmark: 260 plants/m² at 70% establishment, TGW 48 g 260, 48, 70% → kg per hectare 178.3, seeds per square metre 371.4
oilseed rape: 50 plants, TGW 5.5 g, 60% establishment 50, 5.5, 60% → kg per hectare 4.6, seeds per square metre 83.3
field beans: 30 plants, TGW 550 g, 90% establishment 30, 550, 90% → kg per hectare 183.3, seeds per square metre 33.3
100% establishment sows exactly the target 400, 45, 100% → kg per hectare 180, seeds per square metre 400
an exact half tenth rounds up: 100 x 45.25 / 100 = 45.25 100, 45.25, 100% → kg per hectare 45.3, seeds per square metre 100
TGW 1.15 g is 1.15 kg/ha exactly and rounds to 1.2, where a float product says 1.1499999999999999 100, 1.15, 100% → kg per hectare 1.2, seeds per square metre 100
TGW is taken to the centigram: 48.004 g is 48 g 260, 48.004, 70% → kg per hectare 178.3, seeds per square metre 371.4
one plant at 0.01% establishment 1, 1, 0.01% → kg per hectare 100, seeds per square metre 10,000
Show the other 7 tests
CaseArgumentsExpected
zero plants is an error 0, 50, 80% → error: targetPlantsPerSquareMetre must be a whole number from 1 to 100000
a fractional plant count is an error 250.5, 50, 80% → error: targetPlantsPerSquareMetre must be a whole number from 1 to 100000
zero TGW is an error 300, 0, 80% → error: thousandGrainWeightGrams must be a finite number greater than 0 and at most 1000
a TGW that rounds to zero centigrams is an error 300, 0.004, 80% → error: thousandGrainWeightGrams must be at least 0.01 grams
zero establishment is an error 300, 50, 0% → error: establishmentBasisPoints must be a whole number from 1 to 10000
establishment over 100% is an error 300, 50, 100.01% → error: establishmentBasisPoints must be a whole number from 1 to 10000
establishment given as a percentage fraction is an error 300, 50, 0.805% → error: establishmentBasisPoints must be a whole number from 1 to 10000

More from the author

`seedRate(260, 48, 7000)` is 178.3 kg/ha and 371.4 seeds/m²: 260 plants at 70% establishment needs 371.4 seeds/m², and 1000 seeds weigh 48 g.

The formula follows from the units: seeds/m² = plants / establishment; one seed weighs TGW / 1000 g; and 1 g/m² is 10 kg/ha. It is the calculation behind AHDB's seed rate conversion tool (seeds/m² against TGW, read off as kg/ha) and the one UK agronomy guidance quotes.

## Establishment

`establishmentBasisPoints` is the share of seeds sown that become plants: germination and field losses (slugs, seedbed, sowing date) together. If you have a germination figure and a separate field-loss estimate, multiply them: 90% germination and 85% emergence is 76.5%, `7650`. AHDB's benchmarks are around 70% for well-timed winter wheat, falling as drilling gets later.

## Exactness

The thousand-grain weight is taken to the nearest 0.01 g (half away from zero, `math.round-float`), so the rate is exactly plants x centigrams / basis points and is rounded once, half up, to 0.1 kg/ha. A float calculation gets 1.15 g at 100 plants and 100% as 1.1499999999999999 and rounds it to 1.1; this gives 1.2.

It does not choose the target population, adjust for sowing date or soil, or round to what a drill can be set to.

## Limits

Plants 1 to 100,000 per m² (grass and small seeds are sown in thousands); TGW more than 0 and at most 1000 g, and at least 0.01 g after rounding; establishment 1 to 10,000 basis points.

## Sources

- AHDB, "Seed rate calculator – conversion tool", https://ahdb.org.uk/seed-rate-conversion-tool (seeds/m² to kg/ha by TGW). - AHDB, "Establishment in wheat: germination and emergence (GS0–GS2)", https://ahdb.org.uk/knowledge-library/establishment-in-wheat-germination-and-emergence-gs0-gs2 (the 70% establishment benchmark). - Foundation for Arable Research, "Seed rate calculation", https://www.far.org.nz/resources/seed-rate-calculation: sowing rate = target plants x TGW x 100 / (% germination x % emergence); its worked example (225 plants, TGW 40 g, 90% x 85%, about 118 kg/ha) is a vector.

Files

PathBytes
README.md2,306
impl/python.py1,789
impl/rust.rs2,341
impl/typescript.ts1,763
vectors.json2,841