///|
pub(all) struct Term {
  name : String
  powers : Array[Int32]
} derive(Debug, Eq)

///|
pub extend Term with @debug.Debug::{to_repr}

///|
pub extend Term with Eq::{equal, not_equal}

///|
pub fn terms(
  factors : Array[Factor],
  order : Int32,
) -> Array[Term] raise DesignError {
  validate_factors(factors)
  if order < 0 || order > 2 {
    raise Invalid("model order must be 0, 1 or 2")
  }
  let k = factors.length()
  let result : Array[Term] = [{ name: "Intercept", powers: Array::make(k, 0), }]
  if order >= 1 {
    for j in 0.. Unit raise DesignError {
  if model.length() == 0 || model.length() > 128 {
    raise Budget("term count must be 1..128")
  }
  for i, t in model {
    if t.name.length() == 0 || t.name.length() > 256 || t.powers.length() != k {
      raise Invalid("invalid term name/dimension")
    }
    let mut degree = 0
    for n in t.powers {
      if n < 0 || n > 8 {
        raise Invalid("powers must be 0..8")
      }
      degree += n
    }
    if degree > 16 {
      raise Budget("term total degree exceeds 16")
    }
    for j in 0.. Array[Double] raise DesignError {
  if point.length() == 0 || point.length() > 16 {
    raise Budget("point dimension must be 1..16")
  }
  validate_terms(model, point.length())
  for x in point {
    require_number(x)
  }
  let result = []
  for t in model {
    let mut value = 1.0
    for j, n in t.powers {
      for _ in 0.. Array[Array[Double]] raise DesignError {
  validate_plan(p)
  validate_terms(model, p.factors.length())
  [
    for r in p.runs => evaluate_terms(r.point, model)
  ]
}