Skip to content

Architecture

This page describes how the chebpy source is organised: the two runtime type hierarchies, the layered module import graph and its direction rule, where the Chebfun implementation lives, and which files this repository owns versus those synced from the Rhiza template.

Type hierarchies

chebpy has two abstract hierarchies that meet in Classicfun. One describes a function on the reference interval [-1, 1]; the other places such a function onto an arbitrary interval [a, b].

Onefun (abstract)                 a function on the reference interval [-1, 1]
└─ Smoothfun (abstract)
   ├─ Chebtech                    Chebyshev series      (aperiodic functions)
   └─ Trigtech                    Fourier series        (periodic functions)

Fun (abstract)
└─ Classicfun (abstract)          maps [-1, 1] ↔ [a, b]; wraps one Onefun
   ├─ Bndfun                      bounded interval, affine map
   ├─ CompactFun                  (semi-)infinite interval via numerical-support truncation
   └─ Singfun                     endpoint singularities via a non-affine clustering map

A Classicfun holds an Onefun (its representation on [-1, 1]) together with the interval or map that places it on [a, b]. The choice of Onefun subclass is orthogonal to the choice of Classicfun subclass: e.g. a Bndfun usually wraps a Chebtech, but a periodic piece wraps a Trigtech.

The Chebfun container

Chebfun is the user-facing class. It is not part of the Fun hierarchy; it is a container holding an ordered array of Fun pieces over a Domain (one piece per sub-interval between breakpoints). Piecewise operations (arithmetic, calculus, root-finding, conv, …) fan out over the pieces.

Chebfun ──holds──▶ [ Fun, Fun, … ]      one piece per sub-interval
                     each Fun ──holds──▶ Onefun on [-1, 1]

Module layering

Modules are organised into layers. A module imports only from strictly lower layers (plus, in a few noted cases, siblings in its own layer). Lower layers never import upper layers at module scope — see Import direction for the one audited exception.

L7  High-level API / apps   api · quasimatrix · gpr
L6  Container + impl         chebfun · _convolution · _pointwise ·
                             _singular_construction · _ufuncs
L5  Classicfun pieces        bndfun · compactfun · singfun
L4  Interval-mapped base     classicfun
L3  Reference reps (Onefun)  chebtech · trigtech
L2  Numerical kernels        algorithms · chebyshev
L1  Primitives               utilities · plotting · smoothfun
L0  Foundations              settings · exceptions · decorators · maps · fun · onefun

chebpy/__init__.py sits above everything as the package facade, re-exporting the public surface (chebfun, chebpts, pwc, trigfun, CompactFun, Singfun, Trigtech, Quasimatrix, gpr, …).

Selected module dependencies (top-level imports):

Module Imports
utilities decorators, exceptions, settings
algorithms decorators, settings, utilities
chebtech / trigtech algorithms*/decorators, plotting, settings, smoothfun, utilities
classicfun chebtech, trigtech, fun, decorators, exceptions, plotting, settings, utilities
bndfun / compactfun / singfun classicfun (+ exceptions/settings/utilities/maps)
chebfun bndfun, _ufuncs, decorators, exceptions, plotting, settings, utilities
api algorithms, bndfun, chebfun, settings, utilities
gpr algorithms, chebfun, quasimatrix, settings

*trigtech does not import algorithms at module scope; it defers that import (see below).

The Chebfun implementation modules

Chebfun is large, so its self-contained algorithms live in dedicated modules. The public Chebfun methods are thin, documented wrappers that delegate:

Module Provides Fronted by
_convolution.py Hale–Townsend / Gauss–Legendre convolution Chebfun.conv
_pointwise.py root-splitting step ops Chebfun.abs, sign, ceil, floor, maximum, minimum
_singular_construction.py endpoint-singularity piece construction the sing= Chebfun constructors
_ufuncs.py registers elementwise NumPy ufunc methods Chebfun.sin, exp, …

These modules operate on a Chebfun passed in as an argument (constructing results via f.__class__(...)) and reference Chebfun only under TYPE_CHECKING, so importing them never creates a runtime cycle.

Import direction and deferred imports

The layering rule is enforced by keeping upward references out of module scope. Function-local (deferred) imports are used for three reasons:

  1. Typing only — _convolution, _pointwise, _ufuncs import Chebfun under TYPE_CHECKING.
  2. Breaking sibling cycles — e.g. singfun/compactfun import bndfun inside restrict; trigtech imports chebtech inside roots.
  3. Lazy/optional heavy paths — chebfun imports its implementation modules (_convolution, _pointwise, _singular_construction) inside the relevant methods.

Known exception (tech debt)

utilities.generate_funs() contains a function-local from .compactfun import CompactFun — an L1 module reaching up to L5. This is the one genuine layering back-edge (a hidden utilities ↔ compactfun cycle), deferred so it does not manifest at import time. It is tracked in issue #417; the intended fix is to inject the compact constructor rather than import it here.

Source ownership: this repo vs Rhiza

This project syncs its development infrastructure from the jebel-quant/rhiza template. The files: block of .rhiza/template.lock is the authoritative list of template-owned paths. It is regenerated by every sync, so read it there rather than trusting a summary kept in prose — a hand-maintained copy of that list goes stale the first time the template adds or drops a file.

Do not edit those files in place: change them upstream in Rhiza (or via .rhiza/template.yml) and re-sync.

Everything outside that block — and outside .rhiza/ itself, which is the template's own config and fixtures — is owned here and may be edited freely: src/chebpy/**, pyproject.toml, README.md, mkdocs.yml, the project notebooks, and this repo's own tests and docs. Note that the block does reach into tests/ and docs/, so check it before assuming a file there is ours.

Two seams exist for repo-specific settings that would otherwise tempt an edit to a template-owned file:

  • The root Makefile is template-owned, but it is only a shim over uv run rhiza-task <task>. Repo-specific make variables and one-off targets belong in an uncommitted local.mk, which the shim -includes.
  • Task settings that differ from the CLI defaults (the coverage floor, extra mkdocs packages, ...) live in [tool.rhiza-task] in pyproject.toml, which is owned here.

When scoring or reviewing the repo, attribute gaps in Rhiza-managed files to the template (fix upstream), and gaps in the owned files to this project.