--- name: newop description: Create a new PyLops linear operator following docs/source/adding.rst - class file, docstring, tests, docs entry and example. Use when the user asks to add/implement/port a new operator into PyLops, including porting an existing non-PyLops forward/adjoint implementation from a URL or a local file (e.g. "add a Foo operator", "turn this script into a PyLops operator", "port the operator at "). --- Goal: add a new, PyLops-compliant `LinearOperator` to the library, complete with docstring, registration, tests, docs entry and a gallery example, following `docs/source/adding.rst` (the authoritative guide - read it if unsure). The operator may be written from scratch (from a mathematical description) or **ported** from an existing non-PyLops implementation supplied as a **web link** or a **local file**. Ask for the operator name and the source only if neither is inferable from the invocation. ## 0. Get the source material - **Web link**: fetch it with `WebFetch` (or the browser tools if the page needs JS). Extract the actual forward/adjoint code, not the prose. - **Local file**: read it in full. - **Neither**: work from the user's mathematical description, and state the assumed definition of the operator before writing code. Then write down explicitly, before touching `pylops/`: - what the forward map does, and its input/output shapes; - whether the source's "adjoint" is a true adjoint (\(\mathbf{A}^H\)) or merely an inverse/transpose/approximation - **this is the most common porting bug**; - which source parameters become `__init__` arguments, which become derived members, and which are irrelevant (e.g. plotting, I/O, CLI args); - whether the operator is real- or complex-linear, and whether it is `explicit`. If the source adjoint is not the true adjoint, say so and implement the correct adjoint - the dot-test in step 4 will fail otherwise. Never relax the dot-test tolerance to make a wrong adjoint pass. ## 1. Place the file - One class per file; file named after the class but **lowercase** (`pylops/basicoperators/diagonal.py` holds `Diagonal`). Choose the subpackage by theme: `basicoperators`, `signalprocessing`, `waveeqprocessing`, `optimization`, etc. Create a new subpackage only if nothing fits. - If the operator is just a composition of existing operators, write a **function** returning the composed operator instead of a class (see `pylops.Laplacian`). - Start the file with `__all__ = [""]`. - Register it: add the import/`__all__` entry in the subpackage `__init__.py` (and its module-level summary table), plus the top-level `pylops/__init__.py` if the operator is meant to be user-facing as `pylops.`. ## 2. Write the class Use `reference/operator_template.py` as the skeleton. Key rules: - Inherit from `pylops.LinearOperator` and initialize via `super().__init__(dtype=np.dtype(dtype), dims=dims, dimsd=dimsd, name=name)`. Prefer `dims`/`dimsd` over setting `shape` directly; `shape` is derived. Set `explicit=True` only when the operator also exposes a dense matrix `A`. - Decorate `_matvec`/`_rmatvec` with `@reshaped` when the operator is n-dimensional, so `x` arrives shaped as `dims` (`dimsd` for `_rmatvec`) and the return value is flattened for you. - Use the backend helpers rather than raw NumPy so CuPy/JAX work: `pylops.utils.backend.get_array_module`, `to_cupy_conditional`, and friends. Do not `import numpy` for array creation inside `_matvec`/`_rmatvec`. - Type-annotate with `pylops.utils.typing` (`NDArray`, `DTypeLike`, `InputDimsLike`). - Keep a `name` argument (default a short string) for `pylops.utils.describe`. - Write the `numpydoc` docstring with, at minimum: one-line summary, expanded description, `Parameters`, `Attributes` (when non-obvious), `Raises` (when the `__init__` validates inputs), and a `Notes` section giving the maths of forward and adjoint in `.. math::` blocks. Match the level of detail of neighbouring operators. ## 3. Add tests Add to the existing `pytests/test_*.py` matching the subpackage, or create a new one following the same header (the `TEST_CUPY_PYLOPS` / `backend` guard block). Follow `reference/test_template.py`: - module-level `par*` dicts, parametrized with `@pytest.mark.parametrize("par", [...])` covering real/complex and, where relevant, square/over-/under-determined; - an `assert dottest(Op, nr, nc, rtol=..., complexflag=0 if par["imag"] == 0 else 3, backend=backend)` in every test of a new configuration; - a forward check against an independently computed expected result (e.g. `Op.todense() @ x`, or the original source implementation's output); - an inversion round-trip with `lsqr` / `Op / y` and `assert_array_almost_equal` when the operator is invertible; - error-path tests for anything the `__init__` raises. ## 4. Run Always use `uv`: ```bash uv run pytest pytests/test_.py -k -q make lint_uv ``` Iterate until the dot-test and all assertions pass cleanly. ## 5. Document - Add the operator name to the right `autosummary` block in `docs/source/api/index.rst`. - Add a gallery example `examples/plot_.py` (or a tutorial in `tutorials/` for a heavier workflow), following the sphinx-gallery format of `examples/plot_diagonal.py`: `r"""` title/underline/description `"""` header, then `###...` comment blocks separating narrative from code, and matplotlib figures showing forward and adjoint (and inversion, if relevant). ## 6. Final checklist (from `docs/source/adding.rst`) Report back confirming each item: - [ ] single class (or function) in its own file, in a suitable `pylops` subpackage - [ ] `__init__`, `_matvec`, `_rmatvec` implemented (plus `todense`/`matrix` if cheap) - [ ] operator exported from the subpackage and top-level `__init__.py` - [ ] numpydoc docstring with `Parameters` and a mathematical `Notes` section - [ ] test added, `dottest` passes, forward/inverse checked - [ ] listed in `docs/source/api/index.rst` - [ ] used in at least one `examples/` script or `tutorials/` script - [ ] `make lint_uv` clean When porting, close with a short note on what differed between the source implementation and the PyLops version (adjoint correction, shape/flattening conventions, dtype handling, removed I/O).