
    Bpj                        S r SSKrSSKJr  SSKJrJrJrJ	r	  SSK
Jr  SSKJr  SSKJr  S!S	 jrS
 r " S S5      rS r S"S jr " S S5      rSSSS.S jrS rS rS#S jrSrS rSSSSSSSS.S jrSSSSSS.S  jrg)$u  
Regrid (2-D smoothing B-splines via separable 1-D FITPACK kernels)
==================================================================

1) Overview
-----------
This module fits a bivariate tensor-product B-spline surface to gridded data
`Z[i, j]` sampled on strictly increasing coordinates `x[i]`, `y[j]`. It mirrors
the adaptive spirit of FITPACK's REGRID/fpgrre (knot growth + smoothing
parameter search to meet a target residual `s`) while keeping the control flow
explicit in Python and returning a 2-D `NdBSpline`.

**Key conventions**
- **Penalty scaling is 1/p** - *same as FITPACK*. The augmented system stacks
  `(D / p)` under the data matrix. Smaller `p` -> stronger smoothing; larger `p`
  -> weaker smoothing (approaching interpolation).
- `p == -1` is used **as a sentinel for `p = ∞`** (interpolatory limit): when
  seen, the solver omits penalty rows entirely.

2) Mathematics (and how this differs from FITPACK's REGRID)
-----------------------------------------------------------
Let `A_x`, `A_y` be banded 1-D design matrices and `D_x`, `D_y` the banded
1-D roughness (difference) matrices returned by FITPACK/Dierckx's 1-D APIs
(`data_matrix`, `disc`). The 2-D smoothing objective is

    minimize  ||A c - z||^2 + (1/p) ||D c||^2,            (1)

implemented by the **augmented** least squares system

    [ A ] c ~ [ z ]
    [D/p]     [ 0 ].

**Same as FITPACK:** Equation (1) uses the *1/p* convention.
**Different in this module:** We realize the 2-D problem by composing **1-D**
banded operators in two separable passes (x then y), instead of calling a
monolithic 2-D routine. This makes the mathematics transparent while producing
the same normal equations structure that REGRID targets internally.

3) Residual energy: definition and use
--------------------------------------
After solving on the current knots (initially at the interpolatory limit,
`p = ∞`), we evaluate the surface and form

    R = Z - Zhat,                         fp = sum(R[i, j]^2).

We then **project** residual energy to knot spans along each axis:

- Row energy  `row_energy[i] = sum(R[i, j]^2, j)`  -> accumulated into `fpintx`.
- Column energy `col_energy[j] = sum(R[i, j]^2, i)` -> accumulated into `fpinty`.

These per-span energies guide **adaptive knot insertion**: the algorithm picks
high-energy spans (skipping zero-length ones) and inserts data-aligned knots
(e.g., median sample within the span), with simple batch sizing and headroom
limits. This is conceptually the same idea as REGRID's `fpint` arrays; here it
is implemented explicitly and vectorized in Python for clarity.

4) Solver used here vs. FITPACK's REGRID
----------------------------------------
**This module (separable 1-D composition):**
- Uses **1-D FITPACK/Dierckx kernels** (`data_matrix`, `disc`, `qr_reduce`,
  `fpback`) to solve 2-D via two passes:
  1) augment/QR/backsolve along **x**, producing an intermediate;
  2) augment/QR/backsolve along **y**, producing the coefficient grid `C`.
- The augmented rows are stacked as `[A; D/p]` (1/p scaling, **same as FITPACK**).
- If `fp > s` after knot growth, performs a **scalar search in `p`** using a
  ratio-of-roots routine; `p == -1` is treated as `p = ∞` for the interpolatory
  reference without penalty rows.

**FITPACK REGRID (monolithic 2-D routine):**
- Implements the same mathematical objective and **1/p** penalty scaling,
  but inside a specialized, fully 2-D Fortran routine with in-place Givens/QR
  and a rational update for `p`.
- Handles residual partitioning, knot insertion, and `p` updates internally.

**Practical difference:** We build the 2-D solve from **1-D building blocks**,
which keeps each stage observable/testable and uses the same low-level kernels
as FITPACK, but without relying on the single monolithic REGRID entry point.

5) Execution flow (who calls whom)
----------------------------------

**Top-level**
1. `_regrid`
   - Validates shapes/monotonicity and normalizes `bbox`.
   - Dispatches to `_regrid_fitpack(...)`.

**Core driver**
2. `_regrid_fitpack`
   - Clips inputs to `bbox` via `_apply_bbox_grid` -> `(x_fit, y_fit, Z_fit)`.
   - If `s == 0` (interpolation path):
     - Build initial not-a-knot vectors: `tx = _not_a_knot(x_fit, kx)`,
       `ty = _not_a_knot(y_fit, ky)`.
     - Build design matrices: `(Ax, Ay, Q) = build_design_matrices(...)`.
     - Solve once at `p = -1` (inf): `C0, fp = _solve_2d_fitpack(...)`.
     - Return `return_NdBSpline(...)`.
   - Else (`s > 0`, smoothing with adaptive knots):
     - Initialize clamped no-interior-knot vectors with
       `_initialise_knots` (for x and y).
     - **Knot-growth loop** (up to `len(x_fit)+len(y_fit)` iterations):
       1) Build design matrices: `(Ax, Ay, Q) = build_design_matrices(...)`.
       2) Interpolatory reference solve (`p = -1`): `C0, fp = _solve_2d_fitpack(...)`.
       3) If both `tx`, `ty` are at minimal size, record `fp0 = fp`
          (used to bracket the p-search).
       4) If `fp < s`: **stop growth** and proceed to p-search / finalize.
       5) Compute residuals for knot insertion:
          - Convert packed to CSR once per axis for evaluation:
            `_Ax = Ax.tocsr(...)`, `_Ay = Ay.tocsr(...)`
          - Evaluate `Z0 = _Ax @ C0 @ _Ay.T`, residual `R = Z_fit - Z0`.
       6) Insert knots on alternating axes using energy heuristics:
          - If last axis was `y`, grow `tx` with
            `_add_knots(..., residuals=np.sum(R**2, axis=1), ...)`.
          - Else, grow `ty` with `_add_knots(..., residuals=np.sum(R**2, axis=0), ...)`.
          - Update `fpold`, `nplus{ x|y }`, `last_axis`.
     - If growth ended with both axes still minimal: return `return_NdBSpline(...)`.
     - **Finite-p smoothing (if needed):**
       - Build 1-D penalty operators: `(Drx, _, ncx) = disc(tx, kx)`,
         `(Dry, _, ncy) = disc(ty, ky)`, wrap as `PackedMatrix`.
       - Rebuild design matrices on the final `(tx, ty)`.
       - Run `_p_search_hit_s(...)` to find `p*` such that `fp(p*) ~ s`:
         - Internally constructs `F(...)` which evaluates `fp(p)`
           by calling `_solve_2d_fitpack(...)`.
         - Uses `root_rati` on `g(p) = fp(p) - s` with the interpolatory
           reference at `p = inf` and `fp0` bracket.
       - Return `return_NdBSpline(...)`.

**Separable solver (used by both interpolation and p-search)**
3. `_solve_2d_fitpack`
   - Forms augmented banded systems via `_stack_augmented_fitpack`:
     - X-side: `Ax_aug = [Ax; Dx/p]` if `p != -1`, else just `Ax`.
     - Y-side: `Ay_aug = [Ay; Dy/p]` if `p != -1`, else just `Ay`.
     - Pads RHS `Q` with zeros when penalties are stacked.
   - **Stage X**: `_dierckx.qr_reduce(Ax_aug, ...)` then
     `_dierckx.fpback(...)` -> intermediate `T`.
   - Transpose `T` to feed Y solves column-wise; pad if needed.
   - **Stage Y**: `_dierckx.qr_reduce(Ay_aug, ...)` then
     `_dierckx.fpback(...)` -> coefficients `C`.
   - Build CSR design matrices once: `_Ax = Ax.tocsr(...)`, `_Ay = Ay.tocsr(...)`.
   - Evaluate `zhat = _Ax @ C.T @ _Ay.T`, compute `fp = ||Z_fit - zhat||^2`.
   - Return `C.T` (in `(nx_coef, ny_coef)` layout) and `fp`.

**Helpers**
- `_apply_bbox_grid(...)` - slices to `(x_fit, y_fit, Z_fit)`.
- `build_design_matrices(...)` - wraps `_dierckx.data_matrix` and
  returns `PackedMatrix` wrappers + `Q`.
- `_initialise_knots(...)`, `_add_knots(...)` - FITPACK-style knot bookkeeping/growth.
- `disc(...)` - 1-D roughness operators (packed band form).
- `F` + `root_rati` - maps `p -> fp(p)` and finds `p*` with `fp(p*) ~ s`.
- `return_NdBSpline(...)` - packs `(tx, ty, C)` into an `NdBSpline`.
    N)	NdBSpline)	root_ratidiscadd_knot_not_a_knot   )_dierckx)	csr_array)_validate_intc                 z   [        U R                  5      S:w  a  [        S5      e[        US5      n[        US5      nUS:  d  US:  a  [        S5      eU R                  R
                  SS n[        R                  " U5      n[        R                  " U5      nU(       Ga@  UR                  S:X  d  UR                  S:X  aD  [        R                  " UR                  UR                  4U-   U R                  R                  S9nU$ UR                  S:  a=  [        R                  " [        R                  " U5      S	:  5      (       d  [        S
5      eUR                  S:  a=  [        R                  " [        R                  " U5      S	:  5      (       d  [        S5      e[        R                  " XSS9u  p[        R                  " X4SS9n
U " XU4U R                  S9nU$ UR
                  UR
                  :w  a  [        R                   " X5      u  pUR                  S:X  a6  [        R                  " UR
                  U-   U R                  R                  S9$ [        R                  " UR#                  5       UR#                  5       4SS9n
U " XU4U R                  S9nUR%                  UR
                  U-   5      $ )a  
Evaluate a 2D `NdBSpline` like a classical bivariate API.

Parameters
----------
ndbs : NdBSpline
    A 2D spline object (``len(ndbs.t) == 2``).
x, y : array_like
    Sample locations. If ``grid=True``, these must be 1-D strictly
    increasing vectors. If ``grid=False``, they can be broadcastable
    arrays of the same shape.
dx, dy : int, optional
    Derivative orders along `x` and `y` respectively, by default 0.
grid : bool, optional
    If True, evaluate on the cartesian product of `x` and `y`;
    otherwise treat `(x, y)` as paired coordinates, by default True.

Returns
-------
ndarray or (ndarray, dict)
    Evaluated values with shape:
    - ``(len(x), len(y), ...)`` if ``grid=True``.
    - ``x.shape + ...`` if ``grid=False``.

Raises
------
ValueError
    If `ndbs` is not 2D, derivatives are negative, or monotonicity checks fail.

Notes
-----
This is a thin convenience wrapper around ``NdBSpline.__call__`` with input
validation and optional profiling.
   z*ndbs must be a 2D NdBSpline (len(t) == 2).dxdyr   z,order of derivative must be positive or zeroNdtype        z1x must be strictly increasing when `grid` is Truez1y must be strictly increasing when `grid` is Trueij)indexingaxis)nuextrapolate)lent
ValueErrorr   cshapenpasarraysizezerosr   alldiffmeshgridstackr   broadcast_arraysravelreshape)ndbsxyr   r   gridtrailingvalsXYxis              U/var/www/html/pdf-tiff/venv/lib/python3.13/site-packages/scipy/interpolate/_regrid.py_ndbspline_call_like_bivariater4      s   F 466{aEFF	r4	 B	r4	 B	AvaGHHvv||ABH


1A


1A66Q;!&&A+88QVVQVV,x7tvv||LDKFFaK"&&s):";";PQQFFaK"&&s):";";PQQ{{1$/XXqf2&B81A1AB77agg&&q,DA66Q;88AGGh.dffllCCXXqwwy!''),26B81A1AB||AGGh.//    c                     [        US   5      [        US   5      pCUu  pVUS   R                  X5-
  S-
  XF-
  S-
  5      n[        US   US   4Xr5      $ )aO  
Build a 2D ``NdBSpline`` from knot vectors and a coefficient grid.

Parameters
----------
fp : float
    Residual sum of squares of the produced fit (kept for upstream use).
tck : tuple
    Tuple ``(tx, ty, C)`` where ``tx``, ``ty`` are knot vectors and ``C``
    is a coefficient array with shape ``(nx - kx - 1, ny - ky - 1)`` or
    a compatible shape that can be reshaped to that.
degrees : tuple of int
    Degrees ``(kx, ky)`` along x and y.

Returns
-------
NdBSpline
    The constructed 2D spline.

Notes
-----
Only repacks the coefficient grid; ``fp`` is not used internally here.
r   r   r   )r   r)   r   )fptckdegreesnxnykxkyr   s           r3   return_NdBSpliner>      s\    0 Q[#c!f+FBArw{BGaK0Ac!fc!f%q22r5   c                   :    \ rS rSrSrS r\S 5       rS rS r	Sr
g)	PackedMatrixi  a_  A simplified CSR format for when non-zeros in each row are consecutive.

Assuming that each row of an `(m, nc)` matrix 1) only has `nz` non-zeros, and
2) these non-zeros are consecutive, we only store an `(m, nz)` matrix of
non-zeros and a 1D array of row offsets. This way, a row `i` of the original
matrix A is ``A[i, offset[i]: offset[i] + nz]``.

c                 (    Xl         X l        X0l        g N)aoffsetnc)selfrC   rD   rE   s       r3   __init__PackedMatrix.__init__  s    r5   c                 L    U R                   R                  S   U R                  4$ )Nr   )rC   r   rE   )rF   s    r3   r   PackedMatrix.shape  s    vv||A''r5   c                 n   [         R                  " U R                  5      nU R                  R                  S   n[	        UR                  S   5       H_  n[        U R                  U R                  U   -
  U5      nU R                  US U24   XU R                  U   U R                  U   U-   24'   Ma     U$ )Nr   r   )r   r"   r   rC   rangeminrE   rD   )rF   outneleminels        r3   todensePackedMatrix.todense  s    hhtzz"Qsyy|$AdggA.6C:>&&DSD/C4;;q>$++a.3"6667 % 
r5   c                    U R                   R                  5       n[        R                  " U R                  US-   5      R                  SUS-   5      nU[        R                  " US-   U R                  R                  S9-   nUR                  5       n[        R                  " SUS-   US-   -  US-   U R                  R                  S9n[        XEU4X#U-
  S-
  4S9$ )Nr   r   r   r   )r   )	rC   r(   r   repeatrD   r)   aranger   r
   )rF   kmlen_tdataindicesindptrs          r3   tocsrPackedMatrix.tocsr   s    vv||~ ))DKK1-55b!A#>BIIac1B1BCC--/1q1uQ/Q!%!2!24 F#ai!m$ 	r5   )rC   rE   rD   N)__name__
__module____qualname____firstlineno____doc__rG   propertyr   rR   r]   __static_attributes__ r5   r3   r@   r@     s*    
 ( (r5   r@   c                    US:X  a5  U R                   R                  5       U R                  R                  5       U4$ US-   n[        R                  " X!R
                  S   -   US-   4[        S9nU R                   SU2SS24   USU2SU24'   UR                   U-  XbS2SS24'   [        R                  " U R                  UR                  45      nXgU4$ )a  
Builds augmented banded matrix.

Parameters
----------
A : PackedMatrix
    Banded data/design matrix for one axis (from `_dierckx.data_matrix`).
D : PackedMatrix
    Banded roughness (difference) penalty matrix for the same axis
    (from `disc`).
nc : int
    Number of top (data) rows from `A` to include.
k : int
    Spline degree (used only for sizing in the current implementation).
p : float
    Smoothing parameter. The effective penalization term is scaled as **1/p**:
    larger `p` means *less* smoothing (approaching interpolation).
    If `p == -1`, it signals *p -> inf*, i.e. a pure interpolatory system
    with **no** penalty rows appended.

Returns
-------
AA : ndarray
    Augmented banded matrix with `A` stacked over `(D / p)` when `p != -1`.
offset : ndarray
    Concatenated band offsets for the augmented matrix.
nc : int
    Returned unchanged for downstream convenience.
r   r   r   r   r   N)rC   copyrD   r   r"   r   floatconcatenate)ADrE   rW   pnzAArD   s           r3   _stack_augmented_fitpackrp   2  s    < 	Bwssxxz188==?B..	
QB	2
?AE*%	8B33ssAv;BssCRCxLqBsAvJ^^QXXqxx01Fr>r5   c                    [         R                  " U5      n[         R                  " U	5      n[        XU R                  S   XC5      u  nnnU R                  n[        XUR                  S   Xs5      u  nnnUR                  nUS:w  aK  [         R
                  " U[         R                  " UR                  S   UR                  S   4[        S9/5      n[        R                  " UUUU5        [        R                  " UUUX%XMUS5	      u  n  n[         R                  " UR                  5      nUS:w  aK  [         R
                  " U[         R                  " UR                  S   UR                  S   4[        S9/5      n[        R                  " UUUU5        [        R                  " UUXXUUS5	      u  nnnU R                  XFR                  S   [        U5      5      nUR                  XyR                  S   [        U5      5      nUUR                  -  UR                  -  nU
U-
  n[         R                  " [         R                   " U5      5      nUR                  UU4$ )a  
Solve the 2-D tensor-product spline system using separable banded QR.

================================================================
Mathematical model (step by step, plain text)
================================================================

Shapes:
    Z      : (mx, my)  -> original data
    Ax, Ay : design matrices for x and y
    Dx, Dy : roughness penalty matrices for x and y
    C      : (nx, ny)  -> spline coefficients to solve for

Surface approximation:
    Zhat = Ax * C * Ay^T

Objective (smoothing formulation):
    minimize ||Ax*C*Ay^T - Z||^2 + (1/p)*(||Dx*C||^2 + ||C*Dy^T||^2)

In practice (FITPACK-style separable approach), we solve this in two stages:

--------------------------------------------------------
Stage 1 (x-direction solve for all y-columns together):
--------------------------------------------------------

    For each column of Z:
        minimize ||Ax*T - Z||^2 + (1/p)*||Dx*T||^2

    This is equivalent to the augmented least-squares system:
        [Ax]       [Z]
        [Dx/p] * T = [0]

    i.e.  minimize || [Ax; Dx/p]*T - [Z; 0] ||^2

    The solution T is obtained by QR reduction and back-substitution.

--------------------------------------------------------
Stage 2 (y-direction solve using transposed result):
--------------------------------------------------------
    Now treat T^T as the new RHS for the y-direction:
        minimize ||Ay*C^T - T^T||^2 + (1/p)*||Dy*C^T||^2

    Equivalent to augmented system:
        [Ay]       [T^T]
        [Dy/p] * C^T = [0]

    i.e.  minimize || [Ay; Dy/p]*C^T - [T^T; 0] ||^2

    Solving this gives C^T (then transposed back to C).

--------------------------------------------------------
Interpolation limit:
--------------------------------------------------------
    If p == -1, penalties are omitted (Dx, Dy are not stacked).
    The solver behaves as a near-interpolating system.

--------------------------------------------------------
Residual computation:
--------------------------------------------------------
    Zhat = Ax * C * Ay^T
    R    = Z - Zhat
    fp   = sum(R^2)

Parameters
----------
Ax, Ay : PackedMatrix
    Banded data matrices for the x and y axes.
Q : ndarray, shape (mx, my)
    RHS data grid (copied from `Z`).
p : float
    Smoothing parameter. The penalty term is scaled as **1/p**.
    Setting `p == -1` signals *p -> inf* (interpolation, omit penalty).
kx, ky : int
    Spline degrees along x and y.
tx, ty : ndarray
    Knot vectors along x and y.
x_x, x_y : ndarray
    Sample coordinates.
z : ndarray
    Original data grid for residual evaluation.
Dx, Dy : ndarray
    Banded roughness penalty matrices for x and y.
    Optional, Only needed when ``p != -1``.

Returns
-------
C : ndarray
    2-D B-spline coefficient grid.
fp : float
    Residual sum of squares between fitted surface and `z`.
R : ndarray, shape (mx, my)
    Residual matrix ``z - zhat``, where ``zhat = Ax @ C @ Ay.T``.

Notes
-----
This performs two separable QR solves (x then y), each augmented by
`(D / p)` when `p != -1`.  Setting `p = -1` skips all penalty rows,
yielding an interpolatory surface.  The resulting coefficients and residual
follow the same conventions as FITPACK's `fpgrre`.
r   r   r   r   F)r   	ones_likerp   r   rE   vstackr"   ri   r	   	qr_reducefpbackascontiguousarrayTr]   r   sumsquare)AxAyQrm   r<   txx_xr=   tyx_yzDxDyw_xw_yAx_augoffset_aug_xnc_augxnc_xAy_augoffset_aug_ync_augync_yrw   _Cr7   _Ax_AyzhatRs                                  r3   _solve_2d_fitpackr   Z  s   R ,,s
C
,,s
C %=
R%$!FL'55D %=
R%$!FL'55D 	Bw IIq"((BHHQK#<EJKL v|Wa8 ooc	r	5GAq! 	QSS!A 	BwIIq"((BHHQK#<EJKL v|Wa8 		HAq" ((2yy|SW
-C
((2yy|SW
-C 9suuD
 	
DA			!	B 33A:r5   c                   $    \ rS rSrSrS rS rSrg)Fi&  a#  
Callable wrapper for computing `fp(p)` for a fixed spline configuration.

Parameters
----------
Ax, Ay : PackedMatrix
    Banded data matrices.
Dx, Dy : PackedMatrix
    Banded penalty matrices.
kx, ky : int
    Degrees along x and y.
tx, ty : ndarray
    Knot vectors along x and y.
x_x, x_y : ndarray
    Sample coordinates.
w_x, w_y : ndarray
    Weights (usually ones).
z : ndarray
    Data grid for computing the residual.

Attributes
----------
C : ndarray
    Coefficient matrix from the most recent solve.
fp : float
    Residual value from the most recent solve.

Notes
-----
The penalty is applied as **1/p**, so smaller `p` values yield heavier
smoothing. Setting `p == -1` corresponds to *p = inf*, i.e. interpolation.
Intended for use by `_p_search_hit_s` to iteratively evaluate `fp(p)`.
c                     Xl         X l        X0l        X@l        XPl        X`l        Xpl        Xl        Xl        Xl	        Xl
        Xl        g rB   )rz   r   r{   r   r|   r<   r}   r~   r=   r   r   r   )rF   rz   r   r{   r   r|   r<   r}   r~   r=   r   r   r   s                r3   rG   
F.__init__I  s@     r5   c                 X   [        U R                  U R                  U R                  R	                  5       XR
                  U R                  U R                  U R                  U R                  U R                  U R                  U R                  U R                  S9u  p#nX l        X0l        U$ )N)r   r   )r   rz   r{   r|   rh   r<   r}   r~   r=   r   r   r   r   r   r   r7   )rF   rm   r   r7   r   s        r3   __call__
F.__call__Y  su    
 %GGTWWdffkkmwwGGTWWdhhww477	$q
 	r5   )rz   r{   r   r   r   r|   r7   r<   r=   r}   r   r~   r   r   N)r_   r`   ra   rb   rc   rG   r   re   rf   r5   r3   r   r   &  s     D r5   r   g      ?gMbP?(   )p_inittol_relmaxitc                   ^^ [        XX#XEXgXX5      mUU4S jnU" S5      nSUT-
  4[        R                  U44n[        TU-  S5      n[	        UUUUUS9nUR
                  nT" U5      nTR                  nUUU4$ )a(  
Search for a smoothing parameter `p` such that `fp(p) ~ s`.

Parameters
----------
Ax, Ay : PackedMatrix
    Banded data matrices.
Dx, Dy : PackedMatrix
    Banded penalty matrices.
Q : ndarray
    RHS data grid (copy of `Z`).
kx, ky : int
    Spline degrees.
tx, ty : ndarray
    Knot vectors.
x_x, x_y : ndarray
    Sample coordinates.
w_x, w_y : ndarray
    Sample weights.
z : ndarray
    Original data grid for residuals.
s : float
    Target smoothing residual (`fp` target).
fp0 : float or None
    Residual at `p = inf` (interpolatory limit,
                           represented by `p == -1`).
p_init : float, optional
    Starting guess for the finite `p` search, default 1.0.
tol_rel : float, optional
    Relative tolerance for matching `fp(p)` to `s`.
maxit : int, optional
    Maximum iterations for the root search.

Returns
-------
p_star : float
    Smoothing parameter for which `fp(p_star)` ~ `s`.
C_star : ndarray
    Coefficient grid corresponding to `p_star`.
fp_star : float
    Residual at `p_star`.

Notes
-----
The solver treats `p == -1` as *p = inf* (interpolatory, no penalty).
For finite `p`, the penalty scales as **1/p** - smaller `p` increases
smoothing. A ratio-of-roots search (`root_rati`) iteratively adjusts `p`
until the residual `fp(p)` matches the target `s` within tolerance.
c                    > T" U 5      T-
  $ rB   rf   )rm   fp_atss    r3   g_p_search_hit_s.<locals>.g  s    Qx!|r5   r   r   g-q=)r   )r   r   infmaxr   rootr   )rz   r   r{   r   r|   r<   r}   r~   r=   r   r   r   r   fp0r   r   r   r   fpmsbracketftolrp_starfp_starC_starr   s               `            @r3   _p_search_hit_sr   h  s    l bbars'E R5DS1W~~.Gq7{E"D!VWd%8AVVFFmGWWF67""r5   c                    [        U Vs/ s H  oDSL PM     sn5      (       a  XU[        S5      [        S5      4$ Uu  pVpxXV:  a  Xx:  d  [        S5      e[        R                  " X:  X:*  -  5      S   n	[        R                  " X:  X:*  -  5      S   n
U	R
                  S:X  d  U
R
                  S:X  a  [        S5      eX	   X   U[        R                  " X5         [        R                  U	   [        R                  U
   4$ s  snf )a  
Restrict (x, y, Z) to a rectangular bounding box.

Parameters
----------
x, y : ndarray
    Monotonic sample coordinates.
Z : ndarray, shape (len(x), len(y))
    Data grid.
bbox : sequence of 4 scalars or None
    ``(xb, xe, yb, ye)``; any element may be None to skip clipping.

Returns
-------
x_fit, y_fit, Z_fit : ndarray
    Sliced arrays restricted to bbox.
ix, iy : slice or ndarray
    Indexers mapping from full arrays to the restricted ones.

Raises
------
ValueError
    If bbox is invalid or excludes all samples along an axis.
Nz%bbox must satisfy xb < xe and yb < yer   z$bbox excludes all samples in x or y.)r#   slicer   r   wherer!   ix_s_)r+   r,   Zbboxbboxixbxeybyeixiys              r3   _apply_bbox_gridr     s    2 t,teTMt,--QdU4[00NBBG@AA	17qw'	(	+B	17qw'	(	+B	ww!|rww!|?@@5!%266">*BEE"IruuRy@@ -s   Dc                    [         R                  " U 5      n[         R                  " U5      n[        R                  " XXW5      u  pn[        R                  " XXh5      u  pnUR	                  5       n[        XU5      [        XU5      U4$ rB   )r   rr   r	   data_matrixrh   r@   )r+   r,   r   r}   r   r<   r=   r   r   rz   offset_xr   r{   offset_yr   r|   s                   r3   _build_design_matricesr     sv    
,,q/C
,,q/C!--aR=B$!--aR=B$	At,t, r5   c                     Uc  [        X-   S-   SU-  S-   5      nO%USUS-   -  :  a  [        SU< SSUS-   -   S35      eSUS-   -  nX-   S-   n[        R                  " U/US-   -  U/US-   -  -   5      nXtXV4$ )a  
Initialize a non-periodic knot vector.

Parameters
----------
m : int
    Number of data points (equivalent to len(x) if x were provided).
xb, xe : float
    Domain endpoints used to seed the initial knot vector with no internal knots.
k : int
    Spline degree.
nest : int, optional
    Storage cap for knots. If None, defaults to max(m + k + 1, 2*k + 3).
    Must satisfy nest >= 2*(k + 1); otherwise a ValueError is raised.

Returns
-------
t : 1-D ndarray
    Initial knot vector with no internal knots: [xb]*(k+1) + [xe]*(k+1).
nest : int
    The finalized storage cap for knots.
nmin : int
    Lower bound on knot count.
nmax : int
    Upper bound on knot count.

What this does
--------------
- Computes defaults and bounds used by FITPACK-style knot growth:
    * nest: storage cap for knots (defaults to max(m + k + 1, 2*k + 3))
    * nmin: minimal knot count (2*(k+1))
    * nmax: maximal knot count (m + k + 1)
- Returns an initial knot vector with no internal knots:
    t = [xb]*(k+1) + [xe]*(k+1)
r   r      z`nest` too small: nest = z < 2*(k+1) = .)r   r   r   r    )rX   r   r   rW   nestnminnmaxr   s           r3   _initialise_knotsr     s    H |1519acAg&!QU)9$-1Q3yPQRSSa!e9D519D 	

B41:ac
*+ADr5   c                 T   U[         -  nUR                  nXr-
  nX:X  a  Sn
O:X-
  nX:  a  [        X-  U-  5      OU
S-  n[        U
S-  [	        XS-  S5      5      n
[        U
5       H<  n[        XX5      nUR                  S   nX:  a  [        X5      U
4s  $ X:  d  M9  X:4s  $    X:4$ )aE  
Knot-growth helper for knot-finding loop (non-periodic).

Parameters
----------
x : 1-D ndarray
    Strictly increasing sample coordinates.
k : int
    Spline degree.
s : float
    Target smoothing.
t : 1-D ndarray
    Current knot vector to be grown.
nmin, nmax : int
    Lower/upper bounds on knot count (from initialisation).
nest : int
    Storage cap for total knots.
fp, fpold : float
    Current and previous residual sums of squares. Used to update nplus.
residuals : 1-D ndarray
    Most recent residual signal used by `add_knot` to decide placement.
nplus : int
    Previous iteration's proposed number of knots; used to update the next nplus.

Returns
-------
t_new, nplus : tuple
    Updated knot vector and the nplus chosen for this step.
    If n >= nmax, t_new is a not-a-knot layout. If n >= nest, t_new is the
    current vector respecting the storage cap.

What this function does
-----------------------
- Assumes the caller has already decided to GROW (i.e., checks
  like |fp - s| < acc or fp < s has FAILED).
- Updates nplus (how many knots to add next) using the FITPACK heuristic
  based on the previous improvement (delta = fpold - fp).
- Inserts up to nplus new internal knots using `add_knot(x, t, k, residuals)`.
- Stops early if storage or interpolation caps are reached:
    * if n >= nmax: switch to interpolation layout (not-a-knot) and return
    * if n >= nest: return current t respecting storage cap

How it compares with _fitpack_repro.py::_generate_knots_impl
-------------------------------------------------------------
Similarities:
  1) Same growth logic for nplus:
     - Use delta = fpold - fp with ratio fpms/delta
     - Apply min/max caps (doubling and halving behavior)
  3) Same storage guard:
     - If n reaches nest, stop and return current t
  4) Same end behavior at the "interpolating" cap:
     - When n >= nmax, switch to not-a-knot layout and return

Differences:
  1) API style:
     - _generate_knots_impl is a generator that yields trial knot vectors and
       recomputes residuals/fp internally on each iteration.
     - `_add_knots` is a stateful helper that only grows knots; it expects the
       caller to handle residual computation and fp/fpold updates between calls.
  2) Periodicity:
     - _generate_knots_impl supports periodic=True.
     - `_add_knots` is non-periodic only; it uses not-a-knot when n >= nmax.
  3) Residual computation:
     - _generate_knots_impl calls an internal residual routine each iteration.
     - `_add_knots` does not compute residuals; the caller must supply:
         residuals (used by add_knot), fp, fpold.
  4) Return values:
     - _generate_knots_impl yields multiple t's and eventually returns None.
     - `_add_knots` returns:
         * (t_new, nplus) after inserting knots,
         * (not_a_knot_t, nplus) if n >= nmax,
         * (t, nplus) if n >= nest (storage cap).
r   r   r   )	TOLr!   intrM   r   rL   r   r   r   )r+   rW   r   r   r   r   r   r7   fpold	residualsnplusaccnr   deltanpl1js                    r3   
_add_knotsr     s    Z c'C	A6D 	y
,1Ks5<%'(U1WE!GSax34
 5\Q1( GGAJ 9 q$e++ 98O' * 8Or5   r   r   2   r<   r=   r   r   nestxnestyr   c                L   [        XX)5      u  pn  nU
R                  US-   :  d  UR                  US-   :  a8  [        SU SU SUS-    SUS-    SU
R                   SUR                   S35      e[        U	S   c  U
S   OU	S   5      n[        U	S   c  U
S
   OU	S   5      n[        U	S   c  US   OU	S   5      n[        U	S   c  US
   OU	S   5      nS
nUS:X  ab  Uc  Ub  [        S5      e[	        X5      n[	        X5      n[        XUUUX45      u  nnn[        UUUUUUXUX5      u  nnn[        UUUU4X445      $ [        U
R                  XX7S9u  nnnn[        UR                  UUXHS9u  nnnnS	nSn[        U 5      [        U5      -   n S	n!S	n"S	n#[        U 5       H  n[        XUUUX45      u  nnn[        UUUUUUU
UUUU5      u  nnn$[        U5      U:X  a  [        U5      U:X  a  Un!UU:  a    OUS:X  a.  [        XUUUUUUU[        R                  " U$S-  SS9U"S9u  nn"SnO-[        XUUUUUUU[        R                  " U$S-  SS9U#S9u  nn#Sn[        U5      U:  a  [        U5      U:  a    OUnM     [        U5      U:X  a   [        U5      U:X  a  [        WUUW4X445      $ Sn[        UU5      u  n%n&n'[        UU5      u  n(n)n*[        U%U&U'5      n%[        U(U)U*5      n([        XUUUX45      u  nnn[!        UU%UU(UUUXUXUU!UUS9u  nn+n,[        U,UUU+4X445      $ )a  
Core adaptive bivariate spline fitter using the 1/p-penalty convention.

Parameters
----------
x, y : array_like
    Strictly increasing coordinate vectors.
Z : array_like, shape (len(x), len(y))
    Data grid.
kx, ky : int, optional
    Spline degrees along x and y, default 3 (cubic).
s : float, optional
    Target residual (`fp` target). `s = 0` requests an interpolatory
    surface; `s > 0` triggers smoothing with penalty weight **1/p**.
maxit : int, optional
    Maximum iterations for the `p`-search when smoothing, default 50.
nestx, nesty : int or None
    Max coefficient counts per axis (nesting limits).
bbox : sequence of 4 scalars
    Optional domain limits `(xb, xe, yb, ye)`. Use `None` entries to skip.

Returns
-------
NdBSpline
    Fitted 2-D spline surface.

Notes
-----
The internal smoothing parameter `p` follows the **inverse**-penalty
rule: penalty term is 1/p.  Hence, larger `p` -> weaker smoothing
(approaching interpolation), while smaller `p` -> stronger smoothing.
A sentinel value `p == -1` is interpreted as *p = inf*, corresponding to
an exact (interpolatory) fit.

The iterative process adaptively grows knot vectors based on residual
energy and optionally performs a 1-D search over `p` to satisfy `fp ~ s`.
r   z/Not enough samples inside bbox for degrees (kx=z, ky=z ). Need at least k+1 per axis: (z, z). Got (z).r   Nr   r   r   r   zs == 0 is interpolation only)r   r,   r   )r   r   r   r7   r   r   r   r+   )r   r   )r   r!   r   ri   r   r   r   r>   r   r   rL   r   r   rx   r   r@   r   )-r+   r,   r   r<   r=   r   r   r   r   r   x_fity_fitZ_fitr   r   r   r   r   rm   r}   r   rz   r{   r|   C0r7   nminxnmaxxnminynmaxyr   	last_axismpmr   nplusxnplusyr   Drx	offset_dxnc_dxDry	offset_dync_dyC_smfp_sms-                                                r3   _regrid_fitpackr     s   R !1q ?E%AzzR!V

b1f 5=bTrd K,,.qD6BqD6 :JJ<r%**R1
 	
 
47?uQxQ	8B	DGOuRya	9B	47?uQxQ	8B	DGOuRya	9B
ACx 1;<< ##,1b"b.R%b"a%'U%*3	B  RRL2(;;/

BBSBue/

BBSBueEI
a&3q6/C
CFF 3Z,1b"b.R
 &b"a&("e&("e&+-	B r7eB5 0C6 #1bu5r&&AA.	JB
 I#1bu5r&&AA.	JB
 I r7eB5 0S V 2w%CGu,RRL2(;;	A RLCE RLCE
sIu
-C
sIu
-C(aR)KRQ$Rb#q%'U%'q%(aANAtU EBD>B8<<r5   )r   r<   r=   r   r   c                   Uc  S/S-  n[         R                  " U [        S9n [         R                  " U[        S9n[         R                  " U[        S9n[         R                  " U5      n[        U5      n[         R                  " [         R
                  " U 5      S:  5      (       d  [        S5      e[         R                  " [         R
                  " U5      S:  5      (       d  [        S5      eU R                  UR                  S   :w  a  [        S5      eUR                  UR                  S	   :w  a  [        S
5      eUS:  a  [        S5      eUR                  S:X  d  [        SUR                   35      e[        XX$XVUSSUS9
$ )ab  
Interface for 2-D smoothing B-spline fitting (1/p penalty form).

Parameters
----------
x, y : array_like
    Strictly increasing 1-D coordinate vectors.
z : array_like, shape (len(x), len(y))
    Data grid.
bbox : sequence of 4 scalars
    Optional bounding box ``(xb, xe, yb, ye)``; use ``None`` entries to disable.
kx, ky : int, optional
    Spline degrees along `x` and `y`, default cubic (3).
s : float, optional
    Target smoothing residual (`fp` target). Must satisfy ``s >= 0``.
    The underlying formulation uses a **1/p** penalty, meaning:
    - small `p` -> heavy smoothing,
    - large `p` -> light smoothing (approaching interpolation).
    Setting `p == -1` internally denotes *p = inf*, i.e. a pure interpolant.
maxit : int, optional
    Maximum iterations for `p`-search if invoked.

Returns
-------
NdBSpline
    Fitted bivariate spline surface.
N   r   r   zx must be strictly increasingzy must be strictly increasingr   z7x dimension of z must have same number of elements as xr   z7y dimension of z must have same number of elements as yzs should be s >= 0.0)r   z"bbox shape should be (4,), found: r   )
r   r    ri   r(   r#   r$   r   r!   r   r   )r+   r,   r   r   r<   r=   r   r   s           r3   _regridr   !  s=   8 |vax


1E"A


1E"A


1E"A88D>DaA66"''!*s"##89966"''!*s"##899vvRSSvvRSS	C/00::=djj\JKK	a2%$T+ +r5   )r   r   T)NNrB   )rc   numpyr   scipy.interpolate._ndbspliner    scipy.interpolate._fitpack_repror   r   r   r    r	   scipy.sparser
   scipy._lib._utilr   r4   r>   r@   rp   r   r   r   r   r   r   r   r   r   r   rf   r5   r3   <module>r      s   Tj  2, ,  " *G0T3<) )X&V #'JX? ?J BF#R%AP0f tp c
D	K=\ "aAB 4+r5   