{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Arithmetic and NumPy Ufunc Support\n\n:class:`~pyvisual.core.mesh3d.CartesianMesh` (and\n:class:`~pyvisual.core.mesh3d.SphericalMesh`) inherit a full arithmetic suite\nfrom :class:`~pyvisual.core.mesh3d._BaseFrameMesh`.  Standard Python operators\n(``+``, ``-``, ``*``, ``/``, ``**``, etc.) and NumPy ufuncs such as\n:obj:`numpy.log10` and :obj:`numpy.sqrt` operate element-wise on the active\nscalar field and return a new mesh of the same type with the result as the\nactive scalar.  The coordinate arrays are never modified \u2014 only the data\nchanges.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import numpy as np\nfrom pyvisual import Plot3d\nfrom pyvisual.core.mesh3d import CartesianMesh"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Build a Mesh\n\nConstruct a :class:`~pyvisual.core.mesh3d.CartesianMesh` over a regular\nCartesian grid.  The scalar data is the Euclidean distance\n$r = \\sqrt{x^2 + y^2 + z^2}$ from the origin, providing a smooth,\nsign-definite field on which to demonstrate arithmetic operations.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "x = np.linspace(-5, 5, 20)\ny = np.linspace(-5, 5, 20)\nz = np.linspace(-5, 5, 20)\nX, Y, Z = np.meshgrid(x, y, z, indexing='ij')\ndist = np.sqrt(X ** 2 + Y ** 2 + Z ** 2)\n\nmesh = CartesianMesh(X, Y, Z, data=dist, dataid='r')\nprint(f\"data range : [{mesh.data.min():.2f}, {mesh.data.max():.2f}]\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Scalar Arithmetic\n\nStandard Python arithmetic operators act element-wise on the active scalar\nfield and return a new :class:`~pyvisual.core.mesh3d.CartesianMesh` \u2014 the\npoint coordinates are untouched.  Here we subtract the field minimum to\nshift the distribution to zero, then divide by the resulting maximum to\nnormalize to the range $[0, 1]$.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "mesh_shifted = mesh - mesh.data.min()\nmesh_norm = mesh_shifted / mesh_shifted.data.max()\nprint(f\"normalised range : [{mesh_norm.data.min():.2f}, {mesh_norm.data.max():.2f}]\")\n\nplotter = Plot3d()\nplotter.show_axes()\nplotter.add_sun()\nplotter.add_mesh(mesh_norm, cmap='plasma', clim=(0, 1), opacity=0.3, show_scalar_bar=False)\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## NumPy Ufunc: ``np.log10``\n\nThe :meth:`~pyvisual.core.mesh3d._BaseFrameMesh.__array_ufunc__` hook lets\nany single-output NumPy ufunc act directly on the mesh.\n:obj:`numpy.log10` applied to the normalized distance converts the field to\na logarithmic scale that compresses the large dynamic range near the outer\nboundary and reveals structure close to the origin.  Points at or below zero\n(here, the grid corner where $r = 0$) are masked by the log.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "mesh_log = np.log10(mesh_norm + 1e-6)\nprint(f\"log10 range : [{mesh_log.data.min():.2f}, {mesh_log.data.max():.2f}]\")\n\nplotter = Plot3d()\nplotter.show_axes()\nplotter.add_sun()\nplotter.add_mesh(mesh_log, cmap='rainbow', clim=(-3, 0), opacity=0.3, show_scalar_bar=False)\nplotter.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.13.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}