///|
/// Complete cumulative development table, preserving every observed value.
/// Includes an additional tail column only when tail>1.
pub fn Estimate::projected_cumulative(
  self : Estimate,
  triangle : Triangle,
) -> Array[Array[Double]] raise RunoffError {
  compatible(triangle, self.pattern)
  let out : Array[Array[Double]] = []
  for origin in 0.. 1.0 {
      row.push(self.origins[origin].ultimate)
    }
    // Ultimate is exact at the final development or tail column.
    row[row.length() - 1] = self.origins[origin].ultimate
    out.push(row)
  }
  out
}

///|
/// Add portfolios cell-wise before fitting. This does not average fitted factors.
pub fn Triangle::combine(
  self : Triangle,
  other : Triangle,
) -> Triangle raise RunoffError {
  if self.order() != other.order() {
    raise Shape("combined triangles must have matching geometry")
  }
  let rows : Array[Array[Double]] = []
  for i in 0.. Triangle raise RunoffError {
  if weights.length() != self.order() {
    raise Shape("restatement weight count differs from order")
  }
  let rows : Array[Array[Double]] = []
  for i in 0..