From 05c9696c94ffb4ee839c30f7cbcd481f6a3eaf47 Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Mon, 17 Jun 2024 22:13:06 +0800 Subject: [PATCH 01/13] Update --- docs/apis/brainunit.math.rst | 796 ++--- docs/index.rst | 2 +- docs/tutorials.rst | 7 +- docs/tutorials/mathematical_functions.ipynb | 95 + docs/tutorials/old_physical_units.ipynb | 3396 +++++++++++++++++++ docs/tutorials/physical_units.ipynb | 3299 +----------------- 6 files changed, 3914 insertions(+), 3681 deletions(-) create mode 100644 docs/tutorials/mathematical_functions.ipynb create mode 100644 docs/tutorials/old_physical_units.ipynb diff --git a/docs/apis/brainunit.math.rst b/docs/apis/brainunit.math.rst index 60ec0a1..2a05c7d 100644 --- a/docs/apis/brainunit.math.rst +++ b/docs/apis/brainunit.math.rst @@ -1,398 +1,398 @@ -``brainunit.math`` module -========================= - -.. currentmodule:: brainunit.math -.. automodule:: brainunit.math - -Array Creation --------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - full - full_like - eye - identity - diag - tri - tril - triu - empty - empty_like - ones - ones_like - zeros - zeros_like - array - asarray - arange - linspace - logspace - fill_diagonal - array_split - meshgrid - vander - - -Array Manipulation ------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - reshape - moveaxis - transpose - swapaxes - row_stack - concatenate - stack - vstack - hstack - dstack - column_stack - split - dsplit - hsplit - vsplit - tile - repeat - unique - append - flip - fliplr - flipud - roll - atleast_1d - atleast_2d - atleast_3d - expand_dims - squeeze - sort - argsort - argmax - argmin - argwhere - nonzero - flatnonzero - searchsorted - extract - count_nonzero - max - min - amax - amin - block - compress - diagflat - diagonal - choose - ravel - - -Functions Accepting Unitless ----------------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - exp - exp2 - expm1 - log - log10 - log1p - log2 - arccos - arccosh - arcsin - arcsinh - arctan - arctanh - cos - cosh - sin - sinc - sinh - tan - tanh - deg2rad - rad2deg - degrees - radians - angle - percentile - nanpercentile - quantile - nanquantile - hypot - arctan2 - logaddexp - logaddexp2 - - -Functions with Bitwise Operations ---------------------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - bitwise_not - invert - bitwise_and - bitwise_or - bitwise_xor - left_shift - right_shift - - -Functions Changing Unit ------------------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - reciprocal - prod - product - nancumprod - nanprod - cumprod - cumproduct - var - nanvar - cbrt - square - sqrt - multiply - divide - power - cross - ldexp - true_divide - floor_divide - float_power - divmod - remainder - convolve - - -Indexing Functions ------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - where - tril_indices - tril_indices_from - triu_indices - triu_indices_from - take - select - - -Functions Keeping Unit ----------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - real - imag - conj - conjugate - negative - positive - abs - round - around - round_ - rint - floor - ceil - trunc - fix - sum - nancumsum - nansum - cumsum - ediff1d - absolute - fabs - median - nanmin - nanmax - ptp - average - mean - std - nanmedian - nanmean - nanstd - diff - modf - fmod - mod - copysign - heaviside - maximum - minimum - fmax - fmin - lcm - gcd - interp - clip - - -Logical Functions ------------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - all - any - logical_not - equal - not_equal - greater - greater_equal - less - less_equal - array_equal - isclose - allclose - logical_and - logical_or - logical_xor - alltrue - sometrue - - -Functions Matching Unit ------------------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - add - subtract - nextafter - - -Functions Removing Unit ------------------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - signbit - sign - histogram - bincount - corrcoef - correlate - cov - digitize - - -Window Functions ----------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - bartlett - blackman - hamming - hanning - kaiser - - -Get Attribute Functions ------------------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - ndim - isreal - isscalar - isfinite - isinf - isnan - shape - size - - -Linear Algebra Functions ------------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - dot - vdot - inner - outer - kron - matmul - trace - - -More Functions --------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - :template: classtemplate.rst - - finfo - iinfo - broadcast_arrays - broadcast_shapes - einsum - gradient - intersect1d - nan_to_num - nanargmax - nanargmin - rot90 - tensordot - frexp - dtype - e - pi - inf - - +``brainunit.math`` module +========================= + +.. currentmodule:: brainunit.math +.. automodule:: brainunit.math + +Array Creation +-------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + full + full_like + eye + identity + diag + tri + tril + triu + empty + empty_like + ones + ones_like + zeros + zeros_like + array + asarray + arange + linspace + logspace + fill_diagonal + array_split + meshgrid + vander + + +Array Manipulation +------------------ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + reshape + moveaxis + transpose + swapaxes + row_stack + concatenate + stack + vstack + hstack + dstack + column_stack + split + dsplit + hsplit + vsplit + tile + repeat + unique + append + flip + fliplr + flipud + roll + atleast_1d + atleast_2d + atleast_3d + expand_dims + squeeze + sort + argsort + argmax + argmin + argwhere + nonzero + flatnonzero + searchsorted + extract + count_nonzero + max + min + amax + amin + block + compress + diagflat + diagonal + choose + ravel + + +Functions Accepting Unitless +---------------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + exp + exp2 + expm1 + log + log10 + log1p + log2 + arccos + arccosh + arcsin + arcsinh + arctan + arctanh + cos + cosh + sin + sinc + sinh + tan + tanh + deg2rad + rad2deg + degrees + radians + angle + percentile + nanpercentile + quantile + nanquantile + hypot + arctan2 + logaddexp + logaddexp2 + round + around + round_ + rint + floor + ceil + trunc + fix + corrcoef + correlate + cov + + +Functions with Bitwise Operations +--------------------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + bitwise_not + invert + bitwise_and + bitwise_or + bitwise_xor + left_shift + right_shift + + +Functions Changing Unit +----------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + reciprocal + prod + product + nancumprod + nanprod + cumprod + cumproduct + var + nanvar + cbrt + square + sqrt + multiply + divide + power + cross + ldexp + true_divide + floor_divide + float_power + divmod + remainder + convolve + + +Indexing Functions +------------------ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + where + tril_indices + tril_indices_from + triu_indices + triu_indices_from + take + select + + +Functions Keeping Unit +---------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + real + imag + conj + conjugate + negative + positive + abs + sum + nancumsum + nansum + cumsum + ediff1d + absolute + fabs + median + nanmin + nanmax + ptp + average + mean + std + nanmedian + nanmean + nanstd + diff + modf + fmod + mod + copysign + heaviside + maximum + minimum + fmax + fmin + lcm + gcd + interp + clip + histogram + + +Logical Functions +----------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + all + any + logical_not + equal + not_equal + greater + greater_equal + less + less_equal + array_equal + isclose + allclose + logical_and + logical_or + logical_xor + alltrue + sometrue + + +Functions Matching Unit +----------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + add + subtract + nextafter + + +Functions Removing Unit +----------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + signbit + sign + bincount + digitize + + +Window Functions +---------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + bartlett + blackman + hamming + hanning + kaiser + + +Get Attribute Functions +----------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + ndim + isreal + isscalar + isfinite + isinf + isnan + shape + size + + +Linear Algebra Functions +------------------------ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + dot + vdot + inner + outer + kron + matmul + trace + + +More Functions +-------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + :template: classtemplate.rst + + finfo + iinfo + broadcast_arrays + broadcast_shapes + einsum + gradient + intersect1d + nan_to_num + nanargmax + nanargmin + rot90 + tensordot + frexp + dtype + e + pi + inf + + diff --git a/docs/index.rst b/docs/index.rst index 67cb379..8b09d23 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,6 @@ See also the BDP ecosystem :hidden: :maxdepth: 2 - tutorials/physical_units.rst + tutorials.rst api.rst diff --git a/docs/tutorials.rst b/docs/tutorials.rst index ed16e77..25ca43c 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -1,7 +1,8 @@ -Tutorials Documentation -======================= +Tutorials +========= .. toctree:: :maxdepth: 2 - tutorials/physical_units \ No newline at end of file + tutorials/physical_units + tutorials/mathematical_functions \ No newline at end of file diff --git a/docs/tutorials/mathematical_functions.ipynb b/docs/tutorials/mathematical_functions.ipynb new file mode 100644 index 0000000..8edb093 --- /dev/null +++ b/docs/tutorials/mathematical_functions.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NumPy Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic Operations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Universal Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Indexing, Slicing and Iterating" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Shape Manipulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Changing the shape of a Quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Stacking together different Quantities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Splitting one Quantity into several smaller ones" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparing Quantities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Customize Functions that Accept Quantities" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials/old_physical_units.ipynb b/docs/tutorials/old_physical_units.ipynb new file mode 100644 index 0000000..f6ebb36 --- /dev/null +++ b/docs/tutorials/old_physical_units.ipynb @@ -0,0 +1,3396 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Physical Units" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Braincore includes a system for physical units. The base units are defined by their standard SI unit names:\n", + "`amp`/`ampere`, `kilogram`/`kilogramme`, `second`, `metre`/`meter`, `kilogram`, `mole`/`mol`, `kelvin`, and `candela`. In addition to these base units, braincore defines a set of derived units: `coulomb`, `farad`, `gram`/`gramme`, `hertz`, `joule`, `liter`/\n", + "`litre`, `molar`, `pascal`, `ohm`, `siemens`, `volt`, `watt`,\n", + "together with prefixed versions (e.g. `msiemens = 0.001*siemens`) using the\n", + "prefixes `p, n, u, m, k, M, G, T` (two exceptions to this rule: `kilogram`\n", + "is not defined with any additional prefixes, and `metre` and `meter` are\n", + "additionaly defined with the \"centi\" prefix, i.e. `cmetre`/`cmeter`).\n", + "For convenience, a couple of additional useful standard abbreviations such as\n", + "`cm` (instead of `cmetre`/`cmeter`), `nS` (instead of `nsiemens`),\n", + "`ms` (instead of `msecond`), `Hz` (instead of `hertz`), `mM`\n", + "(instead of `mmolar`) are included. To avoid clashes with common variable\n", + "names, no one-letter abbreviations are provided (e.g. you can use `mV` or\n", + "`nS`, but *not* `V` or `S`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Importing units\n", + "Braincore generates standard names for units, combining the unit name (e.g. “siemens”) with a prefixes (e.g. “m”), and also generates squared and cubed versions by appending a number. For example, the units “msiemens”, “siemens2”, “usiemens3” are all predefined. You can import these units from the package `brianunit` – accordingly, an `from brainunit import *` will result in everything being imported.\n", + "\n", + "We recommend importing only the units you need, to have a cleaner namespace. For example, `import brainunit as bu` and then using `bu.msiemens` instead of `msiemens`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:18:06.720762Z", + "start_time": "2024-06-08T08:18:06.566328Z" + } + }, + "outputs": [], + "source": [ + "import brainunit as bu" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using units\n", + "You can generate a physical quantity by multiplying a scalar or ndarray with its physical unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20. * msecond" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tau = 20 * bu.ms\n", + "tau" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([10., 20., 30.], dtype=float32) * hertz" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rates = [10, 20, 30] * bu.Hz\n", + "rates" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[10., 20., 30.],\n", + " [20., 30., 40.]], dtype=float32) * hertz" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rates = [[10, 20, 30], [20, 30, 40]] * bu.Hz\n", + "rates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Braincore will check the consistency of operations on units and raise an error for dimensionality mismatches:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cannot calculate ... += 1, units do not match (units are s and 1).\n" + ] + } + ], + "source": [ + "try:\n", + " tau += 1 # ms? second?\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cannot calculate 3.0 + 3.0, units do not match (units are kg and A).\n" + ] + } + ], + "source": [ + "try:\n", + " 3 * bu.kgram + 3 * bu.amp\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basics\n", + "Numpy functions have been overwritten to correctly work with units.\n", + "\n", + "The important attributes of a `Quantity` object are:\n", + "- `value`: the numerical value of the quantity\n", + "- `unit`: the unit of the quantity\n", + "- `ndim`: the number of dimensions of quantity's value\n", + "- `shape`: the shape of the quantity's value\n", + "- `size`: the size of the quantity's value\n", + "- `dtype`: the dtype of the quantity's value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### An example" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[10., 20., 30.],\n", + " [20., 30., 40.]], dtype=float32) * hertz" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rates" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array([[10., 20., 30.],\n", + " [20., 30., 40.]], dtype=float32)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rates.value" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "second ** -1" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rates.unit" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(2, (2, 3), 6, dtype('float32'))" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rates.ndim, rates.shape, rates.size, rates.dtype" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Quantity Creation\n", + "Creating a Quantity object can be accomplished in several ways, categorized based on the type of input used. Here, we present the methods grouped by their input types and characteristics for better clarity." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "import jax.numpy as jnp\n", + "import brainstate as bst\n", + "bst.environ.set(precision=64) # we recommend using 64-bit precision for better numerical stability" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Scalar and Array Multiplication\n", + "- Multiplying a Scalar with a Unit\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5. * msecond" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "5 * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Multiplying a Jax nunmpy value type with a Unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5. * msecond" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jnp.float64(5) * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Multiplying a Jax numpy array with a Unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jnp.array([1, 2, 3]) * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Multiplying a List with a Unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[1, 2, 3] * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Direct Quantity Creation\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity Directly with a Value" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(5.)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity Directly with a Value and Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5. * msecond" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(5, unit=bu.ms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity with a Jax numpy Array of Values and a Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(jnp.array([1, 2, 3]), unit=bu.ms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity with a List of Values and a Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([1, 2, 3], unit=bu.ms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity with a List of Quantities" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([0.5, 1. ]) * second" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([500 * bu.ms, 1 * bu.second])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Using the with_units Method" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([0.5, 1. ]) * second" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity.with_units(jnp.array([0.5, 1]), second=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Unitless Quantity\n", + "Quantities can be unitless, which means they have no units. If there is no unit provided, the quantity is assumed to be unitless. The following are examples of creating unitless quantities:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(ArrayImpl([1., 2., 3.]))" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(ArrayImpl([1., 2., 3.]))" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(jnp.array([1, 2, 3]))" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(ArrayImpl([], dtype=float64))" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Illegal Quantity Creation\n", + "The following are examples of illegal quantity creation:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All elements must have the same unit\n" + ] + } + ], + "source": [ + "try:\n", + " bu.Quantity([500 * bu.ms, 1])\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Value 'some' with dtype , >=) are supported:" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.350568Z", + "start_time": "2024-06-08T08:17:18.335369Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(ArrayImpl([10., 12., 14., 16., 18.]) * mvolt,\n", + " ArrayImpl([ 8., 12., 16., 20., 24.]) * mvolt)" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 = jnp.arange(10, 20, 2) * bu.mV\n", + "q2 = jnp.arange(8, 27, 4) * bu.mV\n", + "q1, q2" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.463131Z", + "start_time": "2024-06-08T08:17:18.448183Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(Array([False, True, False, False, False], dtype=bool),\n", + " Array([ True, False, True, True, True], dtype=bool))" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 == q2, q1 != q2" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.536630Z", + "start_time": "2024-06-08T08:17:18.523328Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(Array([False, False, True, True, True], dtype=bool),\n", + " Array([False, True, True, True, True], dtype=bool))" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 < q2, q1 <= q2" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.658605Z", + "start_time": "2024-06-08T08:17:18.644318Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(Array([ True, False, False, False, False], dtype=bool),\n", + " Array([ True, True, False, False, False], dtype=bool))" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 > q2, q1 >= q2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Binary Operations\n", + "The binary operations add (+), subtract (-), multiply (*), divide (/), floor divide (//), remainder (%), divmod (divmod), power (**), matmul (@), shift (<<, >>), round(round) are supported:" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.750090Z", + "start_time": "2024-06-08T08:17:18.733415Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(ArrayImpl([1., 2., 3.]) * mvolt, ArrayImpl([2., 3., 4.]) * mvolt)" + ] + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 = jnp.array([1, 2, 3]) * bu.mV\n", + "q2 = jnp.array([2, 3, 4]) * bu.mV\n", + "q1, q2" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.855866Z", + "start_time": "2024-06-08T08:17:18.840386Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(ArrayImpl([3., 5., 7.]) * mvolt, ArrayImpl([-1., -1., -1.]) * mvolt)" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 + q2, q1 - q2" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:18.941214Z", + "start_time": "2024-06-08T08:17:18.927264Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([ 2., 6., 12.]) * mvolt2" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 * q2" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.058063Z", + "start_time": "2024-06-08T08:17:19.045255Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(Array([0.5 , 0.66666667, 0.75 ], dtype=float64),\n", + " Array([0., 0., 0.], dtype=float64),\n", + " ArrayImpl([1., 2., 3.]) * mvolt)" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 / q2, q1 // q2, q1 % q2" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.137905Z", + "start_time": "2024-06-08T08:17:19.125338Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(Array([0., 0., 0.], dtype=float64), ArrayImpl([1., 2., 3.]) * mvolt)" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "divmod(q1, q2)" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.289789Z", + "start_time": "2024-06-08T08:17:19.275896Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 4., 9.]) * mvolt2" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 ** 2" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.347106Z", + "start_time": "2024-06-08T08:17:19.334827Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "20. * mvolt2" + ] + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 @ q2" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.472101Z", + "start_time": "2024-06-08T08:17:19.456793Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(ArrayImpl([ 0., 4., 8., 12., 16.]) * volt,\n", + " ArrayImpl([0., 0., 0., 0., 1.]) * volt)" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 = bu.Quantity(jnp.arange(5, dtype=jnp.int32), unit=bu.mV.unit)\n", + "q1 << 2, q1 >> 2" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.531264Z", + "start_time": "2024-06-08T08:17:19.514038Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "round(80.23456, 2) : 80.23 mV\n", + "round(100.000056, 3) : 100. mV\n", + "round(-100.000056, 3) : -100. mV\n" + ] + } + ], + "source": [ + "q1 = 80.23456 * bu.mV\n", + "q2 = 100.000056 * bu.mV\n", + "q3 = -100.000056 * bu.mV\n", + "print(\"round(80.23456, 2) : \", q1.round(5))\n", + "print(\"round(100.000056, 3) : \", q2.round(6))\n", + "print(\"round(-100.000056, 3) : \", q3.round(6))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Shape Manipulation\n", + "The shape of an array can be changed with various commands. Note that the following three commands all return a modified array, but do not change the original array:" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.688835Z", + "start_time": "2024-06-08T08:17:19.670457Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[1., 2.],\n", + " [3., 4.]]) * mvolt" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q = [[1, 2], [3, 4]] * bu.mV\n", + "q" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.754912Z", + "start_time": "2024-06-08T08:17:19.743837Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3., 4.]) * mvolt" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.flatten()" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.867999Z", + "start_time": "2024-06-08T08:17:19.854522Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[1., 3.],\n", + " [2., 4.]]) * mvolt" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.swapaxes(0, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:19.923531Z", + "start_time": "2024-06-08T08:17:19.907391Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 3.]) * mvolt" + ] + }, + "execution_count": 111, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.take(jnp.array([0, 2]))" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.036298Z", + "start_time": "2024-06-08T08:17:20.023150Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[1., 3.],\n", + " [2., 4.]]) * mvolt" + ] + }, + "execution_count": 112, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.transpose()" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.122048Z", + "start_time": "2024-06-08T08:17:20.109833Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[1., 2., 1., 2.],\n", + " [3., 4., 3., 4.]]) * mvolt" + ] + }, + "execution_count": 113, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.tile(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.228759Z", + "start_time": "2024-06-08T08:17:20.213853Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[[1., 2.],\n", + " [3., 4.]]]) * mvolt" + ] + }, + "execution_count": 114, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.unsqueeze(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.294326Z", + "start_time": "2024-06-08T08:17:20.279806Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[[1., 2.],\n", + " [3., 4.]]]) * mvolt" + ] + }, + "execution_count": 115, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.expand_dims(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.410465Z", + "start_time": "2024-06-08T08:17:20.397348Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[[1., 2.],\n", + " [3., 4.]]]) * mvolt" + ] + }, + "execution_count": 116, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "expand_as_shape = (1, 2, 2)\n", + "q.expand_as(jnp.zeros(expand_as_shape).shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.473612Z", + "start_time": "2024-06-08T08:17:20.454902Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[ 1., 30.],\n", + " [10., 4.]]) * mvolt" + ] + }, + "execution_count": 117, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q_put = [[1, 2], [3, 4]] * bu.mV\n", + "q_put.put([[1, 0], [0, 1]], [10, 30] * bu.mV)\n", + "q_put" + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.586577Z", + "start_time": "2024-06-08T08:17:20.573505Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[1., 2.],\n", + " [3., 4.]]) * mvolt" + ] + }, + "execution_count": 118, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q_squeeze = [[1, 2], [3, 4]] * bu.mV\n", + "q_squeeze.squeeze()" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-08T08:17:20.667592Z", + "start_time": "2024-06-08T08:17:20.652077Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[ArrayImpl([[1., 2.]]) * mvolt, ArrayImpl([[3., 4.]]) * mvolt]" + ] + }, + "execution_count": 119, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q_spilt = [[1, 2], [3, 4]] * bu.mV\n", + "q_spilt.split(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Numpy Methods\n", + "All methods that make sense on quantities should work, i.e. they check for the correct units of their arguments and return quantities with units were appropriate.\n", + "\n", + "These methods defined at `braincore.math`, so you can use them by importing `import braincore.math as bm` and then using `bm.method_name`.\n", + "#### Functions that remove unit\n", + "- all\n", + "- any\n", + "- nonzero\n", + "- argmax\n", + "- argmin\n", + "- argsort\n", + "- ones_like\n", + "- zeros_like\n", + "\n", + "#### Functions that keep unit\n", + "- round\n", + "- std\n", + "- sum\n", + "- trace\n", + "- cumsum\n", + "- diagonal\n", + "- max\n", + "- mean\n", + "- min\n", + "- ptp\n", + "- ravel\n", + "- absolute\n", + "- rint\n", + "- negative\n", + "- positive\n", + "- conj\n", + "- conjugate\n", + "- floor\n", + "- ceil\n", + "- trunc\n", + "\n", + "#### Functions that change unit\n", + "- var\n", + "- multiply\n", + "- divide\n", + "- true_divide\n", + "- floor_divide\n", + "- dot\n", + "- matmul\n", + "- sqrt\n", + "- square\n", + "- reciprocal\n", + "\n", + "#### Functions that need to match unit\n", + "- add\n", + "- subtract\n", + "- maximum\n", + "- minimum\n", + "- remainder\n", + "- mod\n", + "- fmod\n", + "\n", + "#### Functions that only work with unitless quantities\n", + "- sin\n", + "- sinh\n", + "- arcsinh\n", + "- cos\n", + "- cosh\n", + "- arccos\n", + "- arccosh\n", + "- tan\n", + "- tanh\n", + "- arctan\n", + "- arctanh\n", + "- log\n", + "- log10\n", + "- exp\n", + "- expm1\n", + "- log1p\n", + "\n", + "#### Functions that compare quantities\n", + "- less\n", + "- less_equal\n", + "- greater\n", + "- greater_equal\n", + "- equal\n", + "- not_equal\n", + "\n", + "#### Functions that work on all quantities and return boolean arrays(Logical operations)\n", + "- logical_and\n", + "- logical_or\n", + "- logical_xor\n", + "- logical_not\n", + "- isreal\n", + "- iscomplex\n", + "- isfinite\n", + "- isinf\n", + "- isnan" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/tutorials/physical_units.ipynb b/docs/tutorials/physical_units.ipynb index f6ebb36..88f3c83 100644 --- a/docs/tutorials/physical_units.ipynb +++ b/docs/tutorials/physical_units.ipynb @@ -4,3393 +4,134 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Physical Units" + "# Quantity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Braincore includes a system for physical units. The base units are defined by their standard SI unit names:\n", - "`amp`/`ampere`, `kilogram`/`kilogramme`, `second`, `metre`/`meter`, `kilogram`, `mole`/`mol`, `kelvin`, and `candela`. In addition to these base units, braincore defines a set of derived units: `coulomb`, `farad`, `gram`/`gramme`, `hertz`, `joule`, `liter`/\n", - "`litre`, `molar`, `pascal`, `ohm`, `siemens`, `volt`, `watt`,\n", - "together with prefixed versions (e.g. `msiemens = 0.001*siemens`) using the\n", - "prefixes `p, n, u, m, k, M, G, T` (two exceptions to this rule: `kilogram`\n", - "is not defined with any additional prefixes, and `metre` and `meter` are\n", - "additionaly defined with the \"centi\" prefix, i.e. `cmetre`/`cmeter`).\n", - "For convenience, a couple of additional useful standard abbreviations such as\n", - "`cm` (instead of `cmetre`/`cmeter`), `nS` (instead of `nsiemens`),\n", - "`ms` (instead of `msecond`), `Hz` (instead of `hertz`), `mM`\n", - "(instead of `mmolar`) are included. To avoid clashes with common variable\n", - "names, no one-letter abbreviations are provided (e.g. you can use `mV` or\n", - "`nS`, but *not* `V` or `S`)." + "## Creating Quantity Instances" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Importing units\n", - "Braincore generates standard names for units, combining the unit name (e.g. “siemens”) with a prefixes (e.g. “m”), and also generates squared and cubed versions by appending a number. For example, the units “msiemens”, “siemens2”, “usiemens3” are all predefined. You can import these units from the package `brianunit` – accordingly, an `from brainunit import *` will result in everything being imported.\n", - "\n", - "We recommend importing only the units you need, to have a cleaner namespace. For example, `import brainunit as bu` and then using `bu.msiemens` instead of `msiemens`." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:18:06.720762Z", - "start_time": "2024-06-08T08:18:06.566328Z" - } - }, - "outputs": [], - "source": [ - "import brainunit as bu" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using units\n", - "You can generate a physical quantity by multiplying a scalar or ndarray with its physical unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "20. * msecond" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tau = 20 * bu.ms\n", - "tau" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([10., 20., 30.], dtype=float32) * hertz" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates = [10, 20, 30] * bu.Hz\n", - "rates" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[10., 20., 30.],\n", - " [20., 30., 40.]], dtype=float32) * hertz" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates = [[10, 20, 30], [20, 30, 40]] * bu.Hz\n", - "rates" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Braincore will check the consistency of operations on units and raise an error for dimensionality mismatches:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cannot calculate ... += 1, units do not match (units are s and 1).\n" - ] - } - ], - "source": [ - "try:\n", - " tau += 1 # ms? second?\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cannot calculate 3.0 + 3.0, units do not match (units are kg and A).\n" - ] - } - ], - "source": [ - "try:\n", - " 3 * bu.kgram + 3 * bu.amp\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Basics\n", - "Numpy functions have been overwritten to correctly work with units.\n", - "\n", - "The important attributes of a `Quantity` object are:\n", - "- `value`: the numerical value of the quantity\n", - "- `unit`: the unit of the quantity\n", - "- `ndim`: the number of dimensions of quantity's value\n", - "- `shape`: the shape of the quantity's value\n", - "- `size`: the size of the quantity's value\n", - "- `dtype`: the dtype of the quantity's value" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### An example" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[10., 20., 30.],\n", - " [20., 30., 40.]], dtype=float32) * hertz" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Array([[10., 20., 30.],\n", - " [20., 30., 40.]], dtype=float32)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates.value" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "second ** -1" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates.unit" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(2, (2, 3), 6, dtype('float32'))" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates.ndim, rates.shape, rates.size, rates.dtype" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Quantity Creation\n", - "Creating a Quantity object can be accomplished in several ways, categorized based on the type of input used. Here, we present the methods grouped by their input types and characteristics for better clarity." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "import jax.numpy as jnp\n", - "import brainstate as bst\n", - "bst.environ.set(precision=64) # we recommend using 64-bit precision for better numerical stability" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "#### Scalar and Array Multiplication\n", - "- Multiplying a Scalar with a Unit\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5. * msecond" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "5 * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Multiplying a Jax nunmpy value type with a Unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5. * msecond" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "jnp.float64(5) * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Multiplying a Jax numpy array with a Unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "jnp.array([1, 2, 3]) * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Multiplying a List with a Unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "[1, 2, 3] * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Direct Quantity Creation\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity Directly with a Value" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(5.)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity Directly with a Value and Unit" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5. * msecond" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(5, unit=bu.ms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity with a Jax numpy Array of Values and a Unit" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(jnp.array([1, 2, 3]), unit=bu.ms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity with a List of Values and a Unit" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([1, 2, 3], unit=bu.ms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity with a List of Quantities" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([0.5, 1. ]) * second" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([500 * bu.ms, 1 * bu.second])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Using the with_units Method" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([0.5, 1. ]) * second" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity.with_units(jnp.array([0.5, 1]), second=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Unitless Quantity\n", - "Quantities can be unitless, which means they have no units. If there is no unit provided, the quantity is assumed to be unitless. The following are examples of creating unitless quantities:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(ArrayImpl([1., 2., 3.]))" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([1, 2, 3])" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(ArrayImpl([1., 2., 3.]))" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(jnp.array([1, 2, 3]))" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(ArrayImpl([], dtype=float64))" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Illegal Quantity Creation\n", - "The following are examples of illegal quantity creation:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "All elements must have the same unit\n" - ] - } - ], - "source": [ - "try:\n", - " bu.Quantity([500 * bu.ms, 1])\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Value 'some' with dtype , >=) are supported:" - ] - }, - { - "cell_type": "code", - "execution_count": 95, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.350568Z", - "start_time": "2024-06-08T08:17:18.335369Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([10., 12., 14., 16., 18.]) * mvolt,\n", - " ArrayImpl([ 8., 12., 16., 20., 24.]) * mvolt)" - ] - }, - "execution_count": 95, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 = jnp.arange(10, 20, 2) * bu.mV\n", - "q2 = jnp.arange(8, 27, 4) * bu.mV\n", - "q1, q2" - ] - }, - { - "cell_type": "code", - "execution_count": 96, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.463131Z", - "start_time": "2024-06-08T08:17:18.448183Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([False, True, False, False, False], dtype=bool),\n", - " Array([ True, False, True, True, True], dtype=bool))" - ] - }, - "execution_count": 96, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 == q2, q1 != q2" - ] - }, - { - "cell_type": "code", - "execution_count": 97, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.536630Z", - "start_time": "2024-06-08T08:17:18.523328Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([False, False, True, True, True], dtype=bool),\n", - " Array([False, True, True, True, True], dtype=bool))" - ] - }, - "execution_count": 97, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 < q2, q1 <= q2" - ] - }, - { - "cell_type": "code", - "execution_count": 98, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.658605Z", - "start_time": "2024-06-08T08:17:18.644318Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([ True, False, False, False, False], dtype=bool),\n", - " Array([ True, True, False, False, False], dtype=bool))" - ] - }, - "execution_count": 98, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 > q2, q1 >= q2" + "# Combining, Defining, and Displaying Units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Binary Operations\n", - "The binary operations add (+), subtract (-), multiply (*), divide (/), floor divide (//), remainder (%), divmod (divmod), power (**), matmul (@), shift (<<, >>), round(round) are supported:" - ] - }, - { - "cell_type": "code", - "execution_count": 99, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.750090Z", - "start_time": "2024-06-08T08:17:18.733415Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([1., 2., 3.]) * mvolt, ArrayImpl([2., 3., 4.]) * mvolt)" - ] - }, - "execution_count": 99, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 = jnp.array([1, 2, 3]) * bu.mV\n", - "q2 = jnp.array([2, 3, 4]) * bu.mV\n", - "q1, q2" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.855866Z", - "start_time": "2024-06-08T08:17:18.840386Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([3., 5., 7.]) * mvolt, ArrayImpl([-1., -1., -1.]) * mvolt)" - ] - }, - "execution_count": 100, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 + q2, q1 - q2" - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.941214Z", - "start_time": "2024-06-08T08:17:18.927264Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([ 2., 6., 12.]) * mvolt2" - ] - }, - "execution_count": 101, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 * q2" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.058063Z", - "start_time": "2024-06-08T08:17:19.045255Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([0.5 , 0.66666667, 0.75 ], dtype=float64),\n", - " Array([0., 0., 0.], dtype=float64),\n", - " ArrayImpl([1., 2., 3.]) * mvolt)" - ] - }, - "execution_count": 102, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 / q2, q1 // q2, q1 % q2" - ] - }, - { - "cell_type": "code", - "execution_count": 103, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.137905Z", - "start_time": "2024-06-08T08:17:19.125338Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([0., 0., 0.], dtype=float64), ArrayImpl([1., 2., 3.]) * mvolt)" - ] - }, - "execution_count": 103, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "divmod(q1, q2)" - ] - }, - { - "cell_type": "code", - "execution_count": 104, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.289789Z", - "start_time": "2024-06-08T08:17:19.275896Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 4., 9.]) * mvolt2" - ] - }, - "execution_count": 104, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 ** 2" - ] - }, - { - "cell_type": "code", - "execution_count": 105, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.347106Z", - "start_time": "2024-06-08T08:17:19.334827Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "20. * mvolt2" - ] - }, - "execution_count": 105, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 @ q2" - ] - }, - { - "cell_type": "code", - "execution_count": 106, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.472101Z", - "start_time": "2024-06-08T08:17:19.456793Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([ 0., 4., 8., 12., 16.]) * volt,\n", - " ArrayImpl([0., 0., 0., 0., 1.]) * volt)" - ] - }, - "execution_count": 106, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 = bu.Quantity(jnp.arange(5, dtype=jnp.int32), unit=bu.mV.unit)\n", - "q1 << 2, q1 >> 2" - ] - }, - { - "cell_type": "code", - "execution_count": 107, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.531264Z", - "start_time": "2024-06-08T08:17:19.514038Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "round(80.23456, 2) : 80.23 mV\n", - "round(100.000056, 3) : 100. mV\n", - "round(-100.000056, 3) : -100. mV\n" - ] - } - ], - "source": [ - "q1 = 80.23456 * bu.mV\n", - "q2 = 100.000056 * bu.mV\n", - "q3 = -100.000056 * bu.mV\n", - "print(\"round(80.23456, 2) : \", q1.round(5))\n", - "print(\"round(100.000056, 3) : \", q2.round(6))\n", - "print(\"round(-100.000056, 3) : \", q3.round(6))" + "## Basic example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Shape Manipulation\n", - "The shape of an array can be changed with various commands. Note that the following three commands all return a modified array, but do not change the original array:" - ] - }, - { - "cell_type": "code", - "execution_count": 108, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.688835Z", - "start_time": "2024-06-08T08:17:19.670457Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 2.],\n", - " [3., 4.]]) * mvolt" - ] - }, - "execution_count": 108, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q = [[1, 2], [3, 4]] * bu.mV\n", - "q" - ] - }, - { - "cell_type": "code", - "execution_count": 109, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.754912Z", - "start_time": "2024-06-08T08:17:19.743837Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3., 4.]) * mvolt" - ] - }, - "execution_count": 109, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.flatten()" - ] - }, - { - "cell_type": "code", - "execution_count": 110, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.867999Z", - "start_time": "2024-06-08T08:17:19.854522Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 3.],\n", - " [2., 4.]]) * mvolt" - ] - }, - "execution_count": 110, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.swapaxes(0, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": 111, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.923531Z", - "start_time": "2024-06-08T08:17:19.907391Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 3.]) * mvolt" - ] - }, - "execution_count": 111, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.take(jnp.array([0, 2]))" - ] - }, - { - "cell_type": "code", - "execution_count": 112, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.036298Z", - "start_time": "2024-06-08T08:17:20.023150Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 3.],\n", - " [2., 4.]]) * mvolt" - ] - }, - "execution_count": 112, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.transpose()" - ] - }, - { - "cell_type": "code", - "execution_count": 113, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.122048Z", - "start_time": "2024-06-08T08:17:20.109833Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 2., 1., 2.],\n", - " [3., 4., 3., 4.]]) * mvolt" - ] - }, - "execution_count": 113, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.tile(2)" - ] - }, - { - "cell_type": "code", - "execution_count": 114, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.228759Z", - "start_time": "2024-06-08T08:17:20.213853Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[[1., 2.],\n", - " [3., 4.]]]) * mvolt" - ] - }, - "execution_count": 114, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.unsqueeze(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 115, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.294326Z", - "start_time": "2024-06-08T08:17:20.279806Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[[1., 2.],\n", - " [3., 4.]]]) * mvolt" - ] - }, - "execution_count": 115, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.expand_dims(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 116, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.410465Z", - "start_time": "2024-06-08T08:17:20.397348Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[[1., 2.],\n", - " [3., 4.]]]) * mvolt" - ] - }, - "execution_count": 116, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expand_as_shape = (1, 2, 2)\n", - "q.expand_as(jnp.zeros(expand_as_shape).shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 117, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.473612Z", - "start_time": "2024-06-08T08:17:20.454902Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[ 1., 30.],\n", - " [10., 4.]]) * mvolt" - ] - }, - "execution_count": 117, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q_put = [[1, 2], [3, 4]] * bu.mV\n", - "q_put.put([[1, 0], [0, 1]], [10, 30] * bu.mV)\n", - "q_put" - ] - }, - { - "cell_type": "code", - "execution_count": 118, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.586577Z", - "start_time": "2024-06-08T08:17:20.573505Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 2.],\n", - " [3., 4.]]) * mvolt" - ] - }, - "execution_count": 118, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q_squeeze = [[1, 2], [3, 4]] * bu.mV\n", - "q_squeeze.squeeze()" - ] - }, - { - "cell_type": "code", - "execution_count": 119, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.667592Z", - "start_time": "2024-06-08T08:17:20.652077Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[ArrayImpl([[1., 2.]]) * mvolt, ArrayImpl([[3., 4.]]) * mvolt]" - ] - }, - "execution_count": 119, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q_spilt = [[1, 2], [3, 4]] * bu.mV\n", - "q_spilt.split(2)" + "## Defining units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Numpy Methods\n", - "All methods that make sense on quantities should work, i.e. they check for the correct units of their arguments and return quantities with units were appropriate.\n", - "\n", - "These methods defined at `braincore.math`, so you can use them by importing `import braincore.math as bm` and then using `bm.method_name`.\n", - "#### Functions that remove unit\n", - "- all\n", - "- any\n", - "- nonzero\n", - "- argmax\n", - "- argmin\n", - "- argsort\n", - "- ones_like\n", - "- zeros_like\n", - "\n", - "#### Functions that keep unit\n", - "- round\n", - "- std\n", - "- sum\n", - "- trace\n", - "- cumsum\n", - "- diagonal\n", - "- max\n", - "- mean\n", - "- min\n", - "- ptp\n", - "- ravel\n", - "- absolute\n", - "- rint\n", - "- negative\n", - "- positive\n", - "- conj\n", - "- conjugate\n", - "- floor\n", - "- ceil\n", - "- trunc\n", - "\n", - "#### Functions that change unit\n", - "- var\n", - "- multiply\n", - "- divide\n", - "- true_divide\n", - "- floor_divide\n", - "- dot\n", - "- matmul\n", - "- sqrt\n", - "- square\n", - "- reciprocal\n", - "\n", - "#### Functions that need to match unit\n", - "- add\n", - "- subtract\n", - "- maximum\n", - "- minimum\n", - "- remainder\n", - "- mod\n", - "- fmod\n", - "\n", - "#### Functions that only work with unitless quantities\n", - "- sin\n", - "- sinh\n", - "- arcsinh\n", - "- cos\n", - "- cosh\n", - "- arccos\n", - "- arccosh\n", - "- tan\n", - "- tanh\n", - "- arctan\n", - "- arctanh\n", - "- log\n", - "- log10\n", - "- exp\n", - "- expm1\n", - "- log1p\n", - "\n", - "#### Functions that compare quantities\n", - "- less\n", - "- less_equal\n", - "- greater\n", - "- greater_equal\n", - "- equal\n", - "- not_equal\n", - "\n", - "#### Functions that work on all quantities and return boolean arrays(Logical operations)\n", - "- logical_and\n", - "- logical_or\n", - "- logical_xor\n", - "- logical_not\n", - "- isreal\n", - "- iscomplex\n", - "- isfinite\n", - "- isinf\n", - "- isnan" + "## Displaying in JIT / grad / ... transformations" ] } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.5" + "name": "python" } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 2 } From 090c3505941d2fa9158664867d52681df620b807 Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 13:42:02 +0800 Subject: [PATCH 02/13] Restruct docs --- docs/index.rst | 3 +- docs/mathematical_functions.ipynb | 36 + .../customize_functions.ipynb | 18 + .../numpy_functions.ipynb} | 14 - docs/physical_units.ipynb | 341 ++ .../combining_defining_displaying.ipynb | 39 + docs/physical_units/constants.ipynb | 18 + docs/physical_units/conversion.ipynb | 39 + docs/physical_units/mechanism.ipynb | 18 + docs/physical_units/quantity.ipynb | 60 + docs/physical_units/standard_units.ipynb | 18 + docs/tutorials.rst | 8 - docs/tutorials/old_physical_units.ipynb | 3396 ----------------- docs/tutorials/physical_units.ipynb | 137 - 14 files changed, 589 insertions(+), 3556 deletions(-) create mode 100644 docs/mathematical_functions.ipynb create mode 100644 docs/mathematical_functions/customize_functions.ipynb rename docs/{tutorials/mathematical_functions.ipynb => mathematical_functions/numpy_functions.ipynb} (83%) create mode 100644 docs/physical_units.ipynb create mode 100644 docs/physical_units/combining_defining_displaying.ipynb create mode 100644 docs/physical_units/constants.ipynb create mode 100644 docs/physical_units/conversion.ipynb create mode 100644 docs/physical_units/mechanism.ipynb create mode 100644 docs/physical_units/quantity.ipynb create mode 100644 docs/physical_units/standard_units.ipynb delete mode 100644 docs/tutorials.rst delete mode 100644 docs/tutorials/old_physical_units.ipynb delete mode 100644 docs/tutorials/physical_units.ipynb diff --git a/docs/index.rst b/docs/index.rst index 8b09d23..18b4e95 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,7 @@ See also the BDP ecosystem :hidden: :maxdepth: 2 - tutorials.rst + physical_units + mathematical_functions api.rst diff --git a/docs/mathematical_functions.ipynb b/docs/mathematical_functions.ipynb new file mode 100644 index 0000000..69dbfbd --- /dev/null +++ b/docs/mathematical_functions.ipynb @@ -0,0 +1,36 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Mathematical Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{toctree}\n", + ":maxdepth: 2\n", + "\n", + "mathematical_functions/numpy_functions\n", + "mathematical_functions/customize_functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/mathematical_functions/customize_functions.ipynb b/docs/mathematical_functions/customize_functions.ipynb new file mode 100644 index 0000000..b72d77b --- /dev/null +++ b/docs/mathematical_functions/customize_functions.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Customize Functions that Accept Quantities" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials/mathematical_functions.ipynb b/docs/mathematical_functions/numpy_functions.ipynb similarity index 83% rename from docs/tutorials/mathematical_functions.ipynb rename to docs/mathematical_functions/numpy_functions.ipynb index 8edb093..3d70fe9 100644 --- a/docs/tutorials/mathematical_functions.ipynb +++ b/docs/mathematical_functions/numpy_functions.ipynb @@ -69,20 +69,6 @@ "source": [ "## Comparing Quantities" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Customize Functions that Accept Quantities" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/physical_units.ipynb b/docs/physical_units.ipynb new file mode 100644 index 0000000..72790a8 --- /dev/null +++ b/docs/physical_units.ipynb @@ -0,0 +1,341 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Units and Quantities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`brainunit` includes a system for physical units. The base units are defined by their standard SI unit names:\n", + "`amp`/`ampere`, `kilogram`/`kilogramme`, `second`, `metre`/`meter`, `kilogram`, `mole`/`mol`, `kelvin`, and `candela`. In addition to these base units, braincore defines a set of derived units: `coulomb`, `farad`, `gram`/`gramme`, `hertz`, `joule`, `liter`/\n", + "`litre`, `molar`, `pascal`, `ohm`, `siemens`, `volt`, `watt`,\n", + "together with prefixed versions (e.g. `msiemens = 0.001*siemens`) using the\n", + "prefixes `p, n, u, m, k, M, G, T` (two exceptions to this rule: `kilogram`\n", + "is not defined with any additional prefixes, and `metre` and `meter` are\n", + "additionaly defined with the \"centi\" prefix, i.e. `cmetre`/`cmeter`).\n", + "For convenience, a couple of additional useful standard abbreviations such as\n", + "`cm` (instead of `cmetre`/`cmeter`), `nS` (instead of `nsiemens`),\n", + "`ms` (instead of `msecond`), `Hz` (instead of `hertz`), `mM`\n", + "(instead of `mmolar`) are included. To avoid clashes with common variable\n", + "names, no one-letter abbreviations are provided (e.g. you can use `mV` or\n", + "`nS`, but *not* `V` or `S`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Getting Started" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Most users of the `brainunit` package will work with `Quantity`: the combination of a value and a unit. The most convenient way to\n", + "create a `Quantity` is to multiply or divide a value by one of the built-in\n", + "units. It works with scalars, sequences, and `numpy` or `jax.numpy` arrays." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Examples\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "61.8 * second" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import brainunit as bu\n", + "61.8 * bu.second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "we recommend using 64-bit precision for better numerical stability" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "61.8 * second" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import brainstate as bst\n", + "bst.environ.set(precision=64)\n", + "61.8 * bu.second" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * second" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[1., 2., 3.] * bu.second" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * second" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "np.array([1., 2., 3.]) * bu.second" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * second" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import jax.numpy as jnp\n", + "jnp.array([1., 2., 3.]) * bu.second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can get the dim and value from a `Quantity` using the unit and value members:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "61.8" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q = 61.8 * bu.second\n", + "q.value" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "second" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q.dim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From this basic building block, it is possible to start combining quantities with different units:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.471875 * metre * second ** -1" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "15.1 * bu.meter / (32.0 * bu.second)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "22.98674431 * second" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "3.0 * bu.kmeter / (130.51 * bu.meter / bu.second)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create a dimensionless quantity, directly use the `Quantity` constructor:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dimension()" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from brainunit import Quantity\n", + "\n", + "q = Quantity(61.8)\n", + "q.dim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using `brainunit`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{toctree}\n", + ":maxdepth: 2\n", + "\n", + "physical_units/quantity\n", + "physical_units/standard_units\n", + "physical_units/constants\n", + "physical_units/mechanism\n", + "physical_units/conversion\n", + "physical_units/combining_defining_displaying\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "brainpy-dev", + "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.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/physical_units/combining_defining_displaying.ipynb b/docs/physical_units/combining_defining_displaying.ipynb new file mode 100644 index 0000000..75bf345 --- /dev/null +++ b/docs/physical_units/combining_defining_displaying.ipynb @@ -0,0 +1,39 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Combining, Defining, and Displaying Units" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining units" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Displaying in JIT / grad / ... transformations" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/physical_units/constants.ipynb b/docs/physical_units/constants.ipynb new file mode 100644 index 0000000..840e217 --- /dev/null +++ b/docs/physical_units/constants.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Constants" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/physical_units/conversion.ipynb b/docs/physical_units/conversion.ipynb new file mode 100644 index 0000000..7ecbec4 --- /dev/null +++ b/docs/physical_units/conversion.ipynb @@ -0,0 +1,39 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantity Conversion for Other Utilities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dimensionless Quantities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting Quantities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Converting to Plain Python Scalars" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/physical_units/mechanism.ipynb b/docs/physical_units/mechanism.ipynb new file mode 100644 index 0000000..606c895 --- /dev/null +++ b/docs/physical_units/mechanism.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Mechanisms of Quantity / Dimension / Unit" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/physical_units/quantity.ipynb b/docs/physical_units/quantity.ipynb new file mode 100644 index 0000000..52432e9 --- /dev/null +++ b/docs/physical_units/quantity.ipynb @@ -0,0 +1,60 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating Quantity Instances" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Converting to Different Units" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attributes of a Quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Arithmetic Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Addition and Subtraction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Multiplication and Division" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/physical_units/standard_units.ipynb b/docs/physical_units/standard_units.ipynb new file mode 100644 index 0000000..66bdd7c --- /dev/null +++ b/docs/physical_units/standard_units.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Standard Units" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials.rst b/docs/tutorials.rst deleted file mode 100644 index 25ca43c..0000000 --- a/docs/tutorials.rst +++ /dev/null @@ -1,8 +0,0 @@ -Tutorials -========= - -.. toctree:: - :maxdepth: 2 - - tutorials/physical_units - tutorials/mathematical_functions \ No newline at end of file diff --git a/docs/tutorials/old_physical_units.ipynb b/docs/tutorials/old_physical_units.ipynb deleted file mode 100644 index f6ebb36..0000000 --- a/docs/tutorials/old_physical_units.ipynb +++ /dev/null @@ -1,3396 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Physical Units" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Braincore includes a system for physical units. The base units are defined by their standard SI unit names:\n", - "`amp`/`ampere`, `kilogram`/`kilogramme`, `second`, `metre`/`meter`, `kilogram`, `mole`/`mol`, `kelvin`, and `candela`. In addition to these base units, braincore defines a set of derived units: `coulomb`, `farad`, `gram`/`gramme`, `hertz`, `joule`, `liter`/\n", - "`litre`, `molar`, `pascal`, `ohm`, `siemens`, `volt`, `watt`,\n", - "together with prefixed versions (e.g. `msiemens = 0.001*siemens`) using the\n", - "prefixes `p, n, u, m, k, M, G, T` (two exceptions to this rule: `kilogram`\n", - "is not defined with any additional prefixes, and `metre` and `meter` are\n", - "additionaly defined with the \"centi\" prefix, i.e. `cmetre`/`cmeter`).\n", - "For convenience, a couple of additional useful standard abbreviations such as\n", - "`cm` (instead of `cmetre`/`cmeter`), `nS` (instead of `nsiemens`),\n", - "`ms` (instead of `msecond`), `Hz` (instead of `hertz`), `mM`\n", - "(instead of `mmolar`) are included. To avoid clashes with common variable\n", - "names, no one-letter abbreviations are provided (e.g. you can use `mV` or\n", - "`nS`, but *not* `V` or `S`)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing units\n", - "Braincore generates standard names for units, combining the unit name (e.g. “siemens”) with a prefixes (e.g. “m”), and also generates squared and cubed versions by appending a number. For example, the units “msiemens”, “siemens2”, “usiemens3” are all predefined. You can import these units from the package `brianunit` – accordingly, an `from brainunit import *` will result in everything being imported.\n", - "\n", - "We recommend importing only the units you need, to have a cleaner namespace. For example, `import brainunit as bu` and then using `bu.msiemens` instead of `msiemens`." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:18:06.720762Z", - "start_time": "2024-06-08T08:18:06.566328Z" - } - }, - "outputs": [], - "source": [ - "import brainunit as bu" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using units\n", - "You can generate a physical quantity by multiplying a scalar or ndarray with its physical unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "20. * msecond" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tau = 20 * bu.ms\n", - "tau" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([10., 20., 30.], dtype=float32) * hertz" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates = [10, 20, 30] * bu.Hz\n", - "rates" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[10., 20., 30.],\n", - " [20., 30., 40.]], dtype=float32) * hertz" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates = [[10, 20, 30], [20, 30, 40]] * bu.Hz\n", - "rates" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Braincore will check the consistency of operations on units and raise an error for dimensionality mismatches:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cannot calculate ... += 1, units do not match (units are s and 1).\n" - ] - } - ], - "source": [ - "try:\n", - " tau += 1 # ms? second?\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cannot calculate 3.0 + 3.0, units do not match (units are kg and A).\n" - ] - } - ], - "source": [ - "try:\n", - " 3 * bu.kgram + 3 * bu.amp\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Basics\n", - "Numpy functions have been overwritten to correctly work with units.\n", - "\n", - "The important attributes of a `Quantity` object are:\n", - "- `value`: the numerical value of the quantity\n", - "- `unit`: the unit of the quantity\n", - "- `ndim`: the number of dimensions of quantity's value\n", - "- `shape`: the shape of the quantity's value\n", - "- `size`: the size of the quantity's value\n", - "- `dtype`: the dtype of the quantity's value" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### An example" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[10., 20., 30.],\n", - " [20., 30., 40.]], dtype=float32) * hertz" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Array([[10., 20., 30.],\n", - " [20., 30., 40.]], dtype=float32)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates.value" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "second ** -1" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates.unit" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(2, (2, 3), 6, dtype('float32'))" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rates.ndim, rates.shape, rates.size, rates.dtype" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Quantity Creation\n", - "Creating a Quantity object can be accomplished in several ways, categorized based on the type of input used. Here, we present the methods grouped by their input types and characteristics for better clarity." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "import jax.numpy as jnp\n", - "import brainstate as bst\n", - "bst.environ.set(precision=64) # we recommend using 64-bit precision for better numerical stability" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "#### Scalar and Array Multiplication\n", - "- Multiplying a Scalar with a Unit\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5. * msecond" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "5 * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Multiplying a Jax nunmpy value type with a Unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5. * msecond" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "jnp.float64(5) * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Multiplying a Jax numpy array with a Unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "jnp.array([1, 2, 3]) * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Multiplying a List with a Unit:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "[1, 2, 3] * bu.ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Direct Quantity Creation\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity Directly with a Value" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(5.)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity Directly with a Value and Unit" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5. * msecond" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(5, unit=bu.ms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity with a Jax numpy Array of Values and a Unit" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(jnp.array([1, 2, 3]), unit=bu.ms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity with a List of Values and a Unit" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3.]) * msecond" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([1, 2, 3], unit=bu.ms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Creating a Quantity with a List of Quantities" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([0.5, 1. ]) * second" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([500 * bu.ms, 1 * bu.second])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Using the with_units Method" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([0.5, 1. ]) * second" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity.with_units(jnp.array([0.5, 1]), second=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Unitless Quantity\n", - "Quantities can be unitless, which means they have no units. If there is no unit provided, the quantity is assumed to be unitless. The following are examples of creating unitless quantities:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(ArrayImpl([1., 2., 3.]))" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([1, 2, 3])" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(ArrayImpl([1., 2., 3.]))" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity(jnp.array([1, 2, 3]))" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantity(ArrayImpl([], dtype=float64))" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bu.Quantity([])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Illegal Quantity Creation\n", - "The following are examples of illegal quantity creation:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "All elements must have the same unit\n" - ] - } - ], - "source": [ - "try:\n", - " bu.Quantity([500 * bu.ms, 1])\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Value 'some' with dtype , >=) are supported:" - ] - }, - { - "cell_type": "code", - "execution_count": 95, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.350568Z", - "start_time": "2024-06-08T08:17:18.335369Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([10., 12., 14., 16., 18.]) * mvolt,\n", - " ArrayImpl([ 8., 12., 16., 20., 24.]) * mvolt)" - ] - }, - "execution_count": 95, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 = jnp.arange(10, 20, 2) * bu.mV\n", - "q2 = jnp.arange(8, 27, 4) * bu.mV\n", - "q1, q2" - ] - }, - { - "cell_type": "code", - "execution_count": 96, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.463131Z", - "start_time": "2024-06-08T08:17:18.448183Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([False, True, False, False, False], dtype=bool),\n", - " Array([ True, False, True, True, True], dtype=bool))" - ] - }, - "execution_count": 96, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 == q2, q1 != q2" - ] - }, - { - "cell_type": "code", - "execution_count": 97, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.536630Z", - "start_time": "2024-06-08T08:17:18.523328Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([False, False, True, True, True], dtype=bool),\n", - " Array([False, True, True, True, True], dtype=bool))" - ] - }, - "execution_count": 97, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 < q2, q1 <= q2" - ] - }, - { - "cell_type": "code", - "execution_count": 98, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.658605Z", - "start_time": "2024-06-08T08:17:18.644318Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([ True, False, False, False, False], dtype=bool),\n", - " Array([ True, True, False, False, False], dtype=bool))" - ] - }, - "execution_count": 98, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 > q2, q1 >= q2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Binary Operations\n", - "The binary operations add (+), subtract (-), multiply (*), divide (/), floor divide (//), remainder (%), divmod (divmod), power (**), matmul (@), shift (<<, >>), round(round) are supported:" - ] - }, - { - "cell_type": "code", - "execution_count": 99, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.750090Z", - "start_time": "2024-06-08T08:17:18.733415Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([1., 2., 3.]) * mvolt, ArrayImpl([2., 3., 4.]) * mvolt)" - ] - }, - "execution_count": 99, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 = jnp.array([1, 2, 3]) * bu.mV\n", - "q2 = jnp.array([2, 3, 4]) * bu.mV\n", - "q1, q2" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.855866Z", - "start_time": "2024-06-08T08:17:18.840386Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([3., 5., 7.]) * mvolt, ArrayImpl([-1., -1., -1.]) * mvolt)" - ] - }, - "execution_count": 100, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 + q2, q1 - q2" - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:18.941214Z", - "start_time": "2024-06-08T08:17:18.927264Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([ 2., 6., 12.]) * mvolt2" - ] - }, - "execution_count": 101, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 * q2" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.058063Z", - "start_time": "2024-06-08T08:17:19.045255Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([0.5 , 0.66666667, 0.75 ], dtype=float64),\n", - " Array([0., 0., 0.], dtype=float64),\n", - " ArrayImpl([1., 2., 3.]) * mvolt)" - ] - }, - "execution_count": 102, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 / q2, q1 // q2, q1 % q2" - ] - }, - { - "cell_type": "code", - "execution_count": 103, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.137905Z", - "start_time": "2024-06-08T08:17:19.125338Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(Array([0., 0., 0.], dtype=float64), ArrayImpl([1., 2., 3.]) * mvolt)" - ] - }, - "execution_count": 103, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "divmod(q1, q2)" - ] - }, - { - "cell_type": "code", - "execution_count": 104, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.289789Z", - "start_time": "2024-06-08T08:17:19.275896Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 4., 9.]) * mvolt2" - ] - }, - "execution_count": 104, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 ** 2" - ] - }, - { - "cell_type": "code", - "execution_count": 105, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.347106Z", - "start_time": "2024-06-08T08:17:19.334827Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "20. * mvolt2" - ] - }, - "execution_count": 105, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 @ q2" - ] - }, - { - "cell_type": "code", - "execution_count": 106, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.472101Z", - "start_time": "2024-06-08T08:17:19.456793Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(ArrayImpl([ 0., 4., 8., 12., 16.]) * volt,\n", - " ArrayImpl([0., 0., 0., 0., 1.]) * volt)" - ] - }, - "execution_count": 106, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q1 = bu.Quantity(jnp.arange(5, dtype=jnp.int32), unit=bu.mV.unit)\n", - "q1 << 2, q1 >> 2" - ] - }, - { - "cell_type": "code", - "execution_count": 107, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.531264Z", - "start_time": "2024-06-08T08:17:19.514038Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "round(80.23456, 2) : 80.23 mV\n", - "round(100.000056, 3) : 100. mV\n", - "round(-100.000056, 3) : -100. mV\n" - ] - } - ], - "source": [ - "q1 = 80.23456 * bu.mV\n", - "q2 = 100.000056 * bu.mV\n", - "q3 = -100.000056 * bu.mV\n", - "print(\"round(80.23456, 2) : \", q1.round(5))\n", - "print(\"round(100.000056, 3) : \", q2.round(6))\n", - "print(\"round(-100.000056, 3) : \", q3.round(6))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Shape Manipulation\n", - "The shape of an array can be changed with various commands. Note that the following three commands all return a modified array, but do not change the original array:" - ] - }, - { - "cell_type": "code", - "execution_count": 108, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.688835Z", - "start_time": "2024-06-08T08:17:19.670457Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 2.],\n", - " [3., 4.]]) * mvolt" - ] - }, - "execution_count": 108, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q = [[1, 2], [3, 4]] * bu.mV\n", - "q" - ] - }, - { - "cell_type": "code", - "execution_count": 109, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.754912Z", - "start_time": "2024-06-08T08:17:19.743837Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 2., 3., 4.]) * mvolt" - ] - }, - "execution_count": 109, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.flatten()" - ] - }, - { - "cell_type": "code", - "execution_count": 110, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.867999Z", - "start_time": "2024-06-08T08:17:19.854522Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 3.],\n", - " [2., 4.]]) * mvolt" - ] - }, - "execution_count": 110, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.swapaxes(0, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": 111, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:19.923531Z", - "start_time": "2024-06-08T08:17:19.907391Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([1., 3.]) * mvolt" - ] - }, - "execution_count": 111, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.take(jnp.array([0, 2]))" - ] - }, - { - "cell_type": "code", - "execution_count": 112, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.036298Z", - "start_time": "2024-06-08T08:17:20.023150Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 3.],\n", - " [2., 4.]]) * mvolt" - ] - }, - "execution_count": 112, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.transpose()" - ] - }, - { - "cell_type": "code", - "execution_count": 113, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.122048Z", - "start_time": "2024-06-08T08:17:20.109833Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 2., 1., 2.],\n", - " [3., 4., 3., 4.]]) * mvolt" - ] - }, - "execution_count": 113, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.tile(2)" - ] - }, - { - "cell_type": "code", - "execution_count": 114, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.228759Z", - "start_time": "2024-06-08T08:17:20.213853Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[[1., 2.],\n", - " [3., 4.]]]) * mvolt" - ] - }, - "execution_count": 114, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.unsqueeze(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 115, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.294326Z", - "start_time": "2024-06-08T08:17:20.279806Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[[1., 2.],\n", - " [3., 4.]]]) * mvolt" - ] - }, - "execution_count": 115, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q.expand_dims(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 116, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.410465Z", - "start_time": "2024-06-08T08:17:20.397348Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[[1., 2.],\n", - " [3., 4.]]]) * mvolt" - ] - }, - "execution_count": 116, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expand_as_shape = (1, 2, 2)\n", - "q.expand_as(jnp.zeros(expand_as_shape).shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 117, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.473612Z", - "start_time": "2024-06-08T08:17:20.454902Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[ 1., 30.],\n", - " [10., 4.]]) * mvolt" - ] - }, - "execution_count": 117, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q_put = [[1, 2], [3, 4]] * bu.mV\n", - "q_put.put([[1, 0], [0, 1]], [10, 30] * bu.mV)\n", - "q_put" - ] - }, - { - "cell_type": "code", - "execution_count": 118, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.586577Z", - "start_time": "2024-06-08T08:17:20.573505Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "ArrayImpl([[1., 2.],\n", - " [3., 4.]]) * mvolt" - ] - }, - "execution_count": 118, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q_squeeze = [[1, 2], [3, 4]] * bu.mV\n", - "q_squeeze.squeeze()" - ] - }, - { - "cell_type": "code", - "execution_count": 119, - "metadata": { - "ExecuteTime": { - "end_time": "2024-06-08T08:17:20.667592Z", - "start_time": "2024-06-08T08:17:20.652077Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[ArrayImpl([[1., 2.]]) * mvolt, ArrayImpl([[3., 4.]]) * mvolt]" - ] - }, - "execution_count": 119, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "q_spilt = [[1, 2], [3, 4]] * bu.mV\n", - "q_spilt.split(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Numpy Methods\n", - "All methods that make sense on quantities should work, i.e. they check for the correct units of their arguments and return quantities with units were appropriate.\n", - "\n", - "These methods defined at `braincore.math`, so you can use them by importing `import braincore.math as bm` and then using `bm.method_name`.\n", - "#### Functions that remove unit\n", - "- all\n", - "- any\n", - "- nonzero\n", - "- argmax\n", - "- argmin\n", - "- argsort\n", - "- ones_like\n", - "- zeros_like\n", - "\n", - "#### Functions that keep unit\n", - "- round\n", - "- std\n", - "- sum\n", - "- trace\n", - "- cumsum\n", - "- diagonal\n", - "- max\n", - "- mean\n", - "- min\n", - "- ptp\n", - "- ravel\n", - "- absolute\n", - "- rint\n", - "- negative\n", - "- positive\n", - "- conj\n", - "- conjugate\n", - "- floor\n", - "- ceil\n", - "- trunc\n", - "\n", - "#### Functions that change unit\n", - "- var\n", - "- multiply\n", - "- divide\n", - "- true_divide\n", - "- floor_divide\n", - "- dot\n", - "- matmul\n", - "- sqrt\n", - "- square\n", - "- reciprocal\n", - "\n", - "#### Functions that need to match unit\n", - "- add\n", - "- subtract\n", - "- maximum\n", - "- minimum\n", - "- remainder\n", - "- mod\n", - "- fmod\n", - "\n", - "#### Functions that only work with unitless quantities\n", - "- sin\n", - "- sinh\n", - "- arcsinh\n", - "- cos\n", - "- cosh\n", - "- arccos\n", - "- arccosh\n", - "- tan\n", - "- tanh\n", - "- arctan\n", - "- arctanh\n", - "- log\n", - "- log10\n", - "- exp\n", - "- expm1\n", - "- log1p\n", - "\n", - "#### Functions that compare quantities\n", - "- less\n", - "- less_equal\n", - "- greater\n", - "- greater_equal\n", - "- equal\n", - "- not_equal\n", - "\n", - "#### Functions that work on all quantities and return boolean arrays(Logical operations)\n", - "- logical_and\n", - "- logical_or\n", - "- logical_xor\n", - "- logical_not\n", - "- isreal\n", - "- iscomplex\n", - "- isfinite\n", - "- isinf\n", - "- isnan" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/tutorials/physical_units.ipynb b/docs/tutorials/physical_units.ipynb deleted file mode 100644 index 88f3c83..0000000 --- a/docs/tutorials/physical_units.ipynb +++ /dev/null @@ -1,137 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Quantity" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating Quantity Instances" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Converting to Different Units" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Attributes of a Quantity" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Arithmetic Functions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Addition and Subtraction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Multiplication and Division" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Standard Units" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Constants" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Mechanisms of Quantity / Dimension / Unit" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Quantity Conversion for Other Utilities" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dimensionless Quantities" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plotting Quantities" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Converting to Plain Python Scalars" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Combining, Defining, and Displaying Units" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Basic example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Defining units" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Displaying in JIT / grad / ... transformations" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 4b6d9140614dac24becc8ad3ac50365aa2f9d322 Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 15:42:23 +0800 Subject: [PATCH 03/13] Update quantity.ipynb --- docs/physical_units/quantity.ipynb | 1450 +++++++++++++++++++++++++++- 1 file changed, 1449 insertions(+), 1 deletion(-) diff --git a/docs/physical_units/quantity.ipynb b/docs/physical_units/quantity.ipynb index 52432e9..04bfa74 100644 --- a/docs/physical_units/quantity.ipynb +++ b/docs/physical_units/quantity.ipynb @@ -7,6 +7,139 @@ "# Quantity" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Braincore generates standard names for units, combining the unit name (e.g. “siemens”) with a prefixes (e.g. “m”), and also generates squared and cubed versions by appending a number. For example, the units “msiemens”, “siemens2”, “usiemens3” are all predefined. You can import these units from the package `brianunit` – accordingly, an `from brainunit import *` will result in everything being imported.\n", + "\n", + "We recommend importing only the units you need, to have a cleaner namespace. For example, `import brainunit as bu` and then using `bu.msiemens` instead of `msiemens`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import brainunit as bu" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can generate a physical quantity by multiplying a scalar or ndarray with its physical unit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20. * msecond" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tau = 20 * bu.ms\n", + "tau" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([10., 20., 30.], dtype=float32) * hertz" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "rates = [10, 20, 30] * bu.Hz\n", + "rates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[10., 20., 30.],\n", + " [20., 30., 40.]], dtype=float32) * hertz" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "rates = [[10, 20, 30], [20, 30, 40]] * bu.Hz\n", + "rates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Braincore will check the consistency of operations on units and raise an error for dimensionality mismatches:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cannot calculate ... += 1, units do not match (units are s and 1).\n" + ] + } + ], + "source": [ + "try:\n", + " tau += 1 # ms? second?\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cannot calculate 3.0 + 3.0, units do not match (units are kg and A).\n" + ] + } + ], + "source": [ + "try:\n", + " 3 * bu.kgram + 3 * bu.amp\n", + "except Exception as e:\n", + " print(e)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -14,6 +147,438 @@ "## Creating Quantity Instances" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Creating a Quantity object can be accomplished in several ways, categorized based on the type of input used. Here, we present the methods grouped by their input types and characteristics for better clarity." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import jax.numpy as jnp\n", + "import brainstate as bst\n", + "bst.environ.set(precision=64) # we recommend using 64-bit precision for better numerical stability" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Scalar and Array Multiplication\n", + "- Multiplying a Scalar with a Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5. * msecond" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "5 * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Multiplying a Jax nunmpy value type with a Unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5. * msecond" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jnp.float64(5) * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Multiplying a Jax numpy array with a Unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jnp.array([1, 2, 3]) * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Multiplying a List with a Unit:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[1, 2, 3] * bu.ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Direct Quantity Creation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity Directly with a Value" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(5.)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity Directly with a Value and Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5. * msecond" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(5, unit=bu.ms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity with a Jax numpy Array of Values and a Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(jnp.array([1, 2, 3]), unit=bu.ms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity with a List of Values and a Unit" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.]) * msecond" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([1, 2, 3], unit=bu.ms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Creating a Quantity with a List of Quantities" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([0.5, 1. ]) * second" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([500 * bu.ms, 1 * bu.second])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Using the with_units Method" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([0.5, 1. ]) * second" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity.with_units(jnp.array([0.5, 1]), second=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Unitless Quantity\n", + "Quantities can be unitless, which means they have no units. If there is no unit provided, the quantity is assumed to be unitless. The following are examples of creating unitless quantities:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(ArrayImpl([1., 2., 3.]))" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(ArrayImpl([1., 2., 3.]))" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity(jnp.array([1, 2, 3]))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(ArrayImpl([], dtype=float64))" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bu.Quantity([])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Illegal Quantity Creation\n", + "The following are examples of illegal quantity creation:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All elements must have the same unit\n" + ] + } + ], + "source": [ + "try:\n", + " bu.Quantity([500 * bu.ms, 1])\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Value 'some' with dtype Date: Tue, 18 Jun 2024 16:00:17 +0800 Subject: [PATCH 04/13] Update standard_units.ipynb --- docs/physical_units/standard_units.ipynb | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/physical_units/standard_units.ipynb b/docs/physical_units/standard_units.ipynb index 66bdd7c..e5dd5eb 100644 --- a/docs/physical_units/standard_units.ipynb +++ b/docs/physical_units/standard_units.ipynb @@ -6,6 +6,70 @@ "source": [ "# Standard Units" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Standard units are defined in the `brainunit` package as object instances.\n", + "\n", + "All units are defined in terms of basic \"irreducible\" units. The irreducible units include:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Length (meter)\n", + "- Time (second)\n", + "- Mass (kilogram)\n", + "- Current (ampere)\n", + "- Temperature (Kelvin)\n", + "- Luminous intensity (candela)\n", + "- Amount of substance (mole)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prefixs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Most units can be used with prefixes, with both the standard [SI](https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf) prefixes supported" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "| Symbol | Prefix | Value |\n", + "|--------|-------------|-------|\n", + "| Y | yotta- | 1e24 |\n", + "| Z | zetta- | 1e21 |\n", + "| E | exa- | 1e18 |\n", + "| P | peta- | 1e15 |\n", + "| T | tera- | 1e12 |\n", + "| G | giga- | 1e9 |\n", + "| M | mega- | 1e6 |\n", + "| k | kilo- | 1e3 |\n", + "| h | hecto- | 1e2 |\n", + "| da | deka-, deca | 1e1 |\n", + "| d | deci- | 1e-1 |\n", + "| c | centi- | 1e-2 |\n", + "| m | milli- | 1e-3 |\n", + "| u | micro- | 1e-6 |\n", + "| n | nano- | 1e-9 |\n", + "| p | pico- | 1e-12 |\n", + "| f | femto- | 1e-15 |\n", + "| a | atto- | 1e-18 |\n", + "| z | zepto- | 1e-21 |\n", + "| y | yocto- | 1e-24 |" + ] } ], "metadata": { From 81fd659bea750cb37d1d74e8b5d059f58a6008ca Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 16:06:32 +0800 Subject: [PATCH 05/13] Update constants.ipynb --- docs/physical_units/constants.ipynb | 56 ++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/physical_units/constants.ipynb b/docs/physical_units/constants.ipynb index 840e217..65af00b 100644 --- a/docs/physical_units/constants.ipynb +++ b/docs/physical_units/constants.ipynb @@ -6,11 +6,65 @@ "source": [ "# Constants" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`brainunit` contains a number of physical constants." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Getting Started" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can import a Constant directly from the `brainunit` package:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from brainunit import (\n", + " avogadro_constant,\n", + " boltzmann_constant,\n", + " electric_constant,\n", + " electron_mass,\n", + " elementary_charge,\n", + " faraday_constant,\n", + " gas_constant,\n", + " magnetic_constant,\n", + " molar_mass_constant,\n", + " zero_celsius,\n", + ")" + ] } ], "metadata": { + "kernelspec": { + "display_name": "brainpy-dev", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "name": "python", + "version": "3.10.13" } }, "nbformat": 4, From 979753228e11b3af361067f69528d5a1fdf2b790 Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 16:52:57 +0800 Subject: [PATCH 06/13] Update mechanism.ipynb --- docs/_static/mechanism.png | Bin 0 -> 124191 bytes docs/physical_units/mechanism.ipynb | 261 +++++++++++++++++++++++++++- 2 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 docs/_static/mechanism.png diff --git a/docs/_static/mechanism.png b/docs/_static/mechanism.png new file mode 100644 index 0000000000000000000000000000000000000000..b65dd83a3de9b329924cb48ce501e94c8614bd32 GIT binary patch literal 124191 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

dZTO16FT%HPHJb8S9xX$4Xq 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" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dimension" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Dimension class represents the fundamental dimensions of physical quantities, such as length, mass, time, etc. It is an immutable object, ensuring that each combination of dimensions is unique.\n", + "\n", + "**Key Features**:\n", + "- Storage of Dimension Exponents: Uses a tuple _dims to store the exponents for each base SI unit.\n", + "- Immutability: Ensures immutability by disabling increment operators (e.g., __imul__, __idiv__, etc.).\n", + "- Dimension Arithmetic: Supports multiplication (__mul__), division (__div__), and power (__pow__) operations, creating new dimension objects.\n", + "- Comparison: Supports dimension comparison through __eq__ and __ne__ methods.\n", + "- Singleton Pattern: Utilizes the get_or_create_dimension function to ensure that instances of the same dimension combination are the same object." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Quantity class represents a physical quantity with a numerical value and a unit. It is the core class for handling physical quantities in brainunit.\n", + "\n", + "**Key Features**:\n", + "- Value and Dimension: Stores the numerical value (_value) and the dimension (_dim).\n", + "- Unit Handling: Works in conjunction with the Unit class to handle conversions between different units.\n", + "- Arithmetic Operations: Supports all basic arithmetic operations, ensuring dimensional consistency.\n", + "- Dimension Checking: Automatically checks for dimensional consistency during operations, throwing a DimensionMismatchError if inconsistencies are found.\n", + "- Unit Conversion: Provides methods like in_unit and in_best_unit to convert the numerical representation to different units.\n", + "- Integration with NumPy and JAX: Supports interoperability with NumPy and JAX arrays, allowing the use of these libraries' functionalities on physical quantities." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Units" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Unit` class in the provided Python code is designed to handle physical units in a way that maintains dimensional consistency and allows for easy scaling and conversion between different units. Here's a detailed explanation of how the Unit class is implemented and how it handles scaling:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Class Structure and Initialization\n", + "The `Unit` class is derived from `Quantity` and includes several attributes to manage the unit's properties:\n", + "- `_value`: The numeric value of the unit.\n", + "- `_unit`: The dimensions of the unit.\n", + "- `scale`: The scale of the unit, represented as an exponent of 10.\n", + "- `_dispname`: The display name of the unit.\n", + "- `_name`: The full name of the unit.\n", + "- `iscompound`: A flag indicating whether the unit is a compound unit (composed of other units).\n", + "\n", + "The `__init__` method initializes these attributes based on the provided parameters. The `scale` attribute is crucial for scaling the unit, as it determines the prefix (like kilo, milli, etc.) that should be applied to the base unit." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scaling Mechanism\n", + "Scaling in the `Unit` class is handled through the `scale` attribute and the use of standard SI prefixes. The `create_scaled_unit` method is used to create a new unit that is a scaled version of an existing base unit. This method takes a `baseunit` and a `scalefactor` (the prefix like \"m\" for milli) and adjusts the `scale` attribute accordingly. For example, if the `baseunit` is metre and the `scalefactor` is \"k\" (for kilo), the `scale` would be increased by 3 (since kilo represents 10^3)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example\n", + "There is a series of examples to illustrate how to use the Unit class to create basic units, compound units, and units with different scales." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Creating Basic Units\n", + "First, we create some basic units, such as meters (metre) and seconds (second):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(metre, second)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from brainunit import Unit, get_or_create_dimension\n", + "\n", + "# Creating a basic unit: metre\n", + "metre = Unit.create(get_or_create_dimension(m=1), \"metre\", \"m\")\n", + "\n", + "# Creating a basic unit: second\n", + "second = Unit.create(get_or_create_dimension(s=1), \"second\", \"s\")\n", + "\n", + "metre, second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, `get_or_create_dimension(m=1)` creates a dimension object representing length (meters), and `Unit.create` uses this dimension to create a unit named \"metre\" with a display name \"m\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Creating Compound Units\n", + "Next, we create a compound unit, such as volt(metre ^ 2 * kilogram / (second ^ 3 * ampere)):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "volt" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "volt = Unit.create(get_or_create_dimension(m=2, kg=1, s=-3, A=-1), \"volt\", \"V\")\n", + "\n", + "volt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, we define the dimensions for the compound unit and create a new unit named \"volt\" with the specified dimensions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Creating Scaled Units\n", + "Finally, we create a scaled version of a basic unit, such as kilometers (kilometre):" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "kmetre" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kilometre = Unit.create_scaled_unit(metre, \"k\")\n", + "\n", + "kilometre" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1000.0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 * kilometre / (1 * metre)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, `create_scaled_unit` creates a new unit named \"kilometre\" by scaling the base unit \"metre\" with a scale factor of \"k\" (kilo).\n", + "\n", + "The scale factor determines the prefix used for the unit, allowing for easy conversion between different scales of the same unit." + ] } ], "metadata": { + "kernelspec": { + "display_name": "brainpy-dev", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" } }, "nbformat": 4, From 6e333563f44ca126a699088721ac57070cf7ce0c Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 21:35:43 +0800 Subject: [PATCH 07/13] Update conversion.ipynb --- docs/physical_units/conversion.ipynb | 240 ++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 1 deletion(-) diff --git a/docs/physical_units/conversion.ipynb b/docs/physical_units/conversion.ipynb index 7ecbec4..4c87d5f 100644 --- a/docs/physical_units/conversion.ipynb +++ b/docs/physical_units/conversion.ipynb @@ -14,6 +14,62 @@ "## Dimensionless Quantities" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dimensionless Quantities are useful in many scenarios and some mathematical functions only accept dimensionless quantities. `Quantity` provides `to_value` method to convert a quantity to a dimensionless quantity." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([1., 2., 3.], dtype=float32) * mvolt" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import brainunit as bu\n", + "a = [1, 2, 3] * bu.mV\n", + "a" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array([0.001, 0.002, 0.003], dtype=float32, weak_type=True)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.to_value(bu.volt)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`to_value` method accepts a `unit` parameter and returns the value of the `Quantity` in the scale of the given unit." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -21,17 +77,199 @@ "## Plotting Quantities" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dimensionless `Quantity` can be convieniently plotted using [matplotlib](https://matplotlib.org/). \n", + "\n", + "The Dimensionless `Quantity` can be passed to matplotlib plotting functions." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "

" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "plt.figure(figsize=(5, 3))\n", + "plt.plot(a.to_value(bu.volt))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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IYG+PD62P9f162h2LJELpvIhIuImausdPPb+bl7+/CAASTFhLJmYSufwViYnE2kca2OfcOzYMX7zm98QHh5OUFAQ/fr149tvv2243zAM5syZQ8eOHQkKCmLEiBHs27evWUOLiHibgtNVjHv+y4bicefwOFb/9zAVD3FLjSofZ86cYfjw4fj5+bFu3Tp27tzJU089Rdu2bRse8/jjj7N48WKef/55vv76a4KDgxk5ciTV1dXNHl5ExBu8n3+c6xdvZOvRMkIDfXnxtwOYc2Nf/H0b/f+PIi7BYhiGcaEPnjlzJl988QUbN2487/2GYRAdHc0999zDjBkzACgrKyMyMpK//e1vTJgw4Rdfw2azERYWRllZGaGhusyziHivmno789/dxaubDgOQ0rkNSyamENNWezvE9TRm+92o2vz2228zcOBAxo0bR0REBCkpKbz00ksN9x88eJCioiJGjBjRsCwsLIwhQ4awadOm866zpqYGm812zk1ExNsdOlnJ2Oe+bCgef7iiG6v+MFTFQzxCo8rHgQMHeO655+jZsycffPABd999N1OmTOHVV18FoKioCIDIyMhznhcZGdlw3w8tWLCAsLCwhltsbGxTfg4REY+xdmsho5Z8Tv4xG21b+fHK7wYx67o++PlozCKeoVHfdnE4HAwcOJD58+cDkJKSQn5+Ps8//zy33357kwLMmjWLzMzMhn/bbDYVEBHxStV1dh58ZyfZXx8BYFDXtiyemELHsCCTk4k0r0aVj44dO9K3b99zlvXp04fXX38dgKioKACKi4vp2PH/f+e8uLiY5OTk864zICCAgICAxsQQEfE4352oIH1ZDruLyrFY4I9Xdmf6iF74am+HeKBG/VYPHz6cPXv2nLNs7969dOnSBYC4uDiioqJYv359w/02m42vv/6aoUOHNkNcERHPsyb3KDcu+ZzdReWEB/vz6h2DuXdkvIqHeKxG7fmYPn06w4YNY/78+YwfP57Nmzfz4osv8uKLLwJgsViYNm0aDz/8MD179iQuLo7Zs2cTHR3N6NGjWyK/iIjbOltrZ+7b+az69igAl3Rrx+IJKUSEBpqcTKRlNap8DBo0iDVr1jBr1iwefPBB4uLiWLRoEZMmTWp4zH333UdlZSW///3vKS0t5dJLL+X9998nMFB/TCIi/7KvuJw/LsthX0kFFgtMuaonU67uiY/V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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import brainunit.math as bm\n", + "a = bm.arange(10 * bu.meter, step=1 * bu.meter)\n", + "b = bm.arange(100 * bu.second, step=10 * bu.second)\n", + "plt.plot(a.to_value(bu.meter), b.to_value(bu.second))" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Converting to Plain Python Scalars" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Converting `Quantity` objects does not work for non-dimensionless quantities." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "only dimensionless scalar quantities can be converted to Python scalars. But got 3. mV\n" + ] + } + ], + "source": [ + "try:\n", + " float(3. * bu.mV)\n", + "except TypeError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Only dimensionless quantities can be converted to plain Python scalars." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.003" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "float(3. * bu.mV.to_value(bu.volt))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "750.0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "float(3. * bu.kmeter / (4. * bu.meter))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1500" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "int(6. * bu.kmeter / (4. * bu.meter))" + ] } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" } }, "nbformat": 4, From 4fdd9b6afa20c7d0e5f978dad2f55f9c2b30e8ee Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 21:51:56 +0800 Subject: [PATCH 08/13] Update combining_defining_displaying.ipynb --- .../combining_defining_displaying.ipynb | 187 +++++++++++++++++- 1 file changed, 186 insertions(+), 1 deletion(-) diff --git a/docs/physical_units/combining_defining_displaying.ipynb b/docs/physical_units/combining_defining_displaying.ipynb index 75bf345..67a5da7 100644 --- a/docs/physical_units/combining_defining_displaying.ipynb +++ b/docs/physical_units/combining_defining_displaying.ipynb @@ -14,6 +14,35 @@ "## Basic example" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Units and quantities can be combined together using the regular Python numeric operators:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import brainunit as bu\n", + "volt = bu.meter2 * bu.kilogram / (bu.second3 * bu.ampere)\n", + "volt == bu.volt" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -21,6 +50,148 @@ "## Defining units" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Users are free to define new units, either fundamental or compound, using the `Unit.create` and `Unit.create_scaled_unit` function:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Creating Basic Units\n", + "First, we create some basic units, such as meters (metre) and seconds (second):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(metre, second)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from brainunit import Unit, get_or_create_dimension\n", + "\n", + "# Creating a basic unit: metre\n", + "metre = Unit.create(get_or_create_dimension(m=1), \"metre\", \"m\")\n", + "\n", + "# Creating a basic unit: second\n", + "second = Unit.create(get_or_create_dimension(s=1), \"second\", \"s\")\n", + "\n", + "metre, second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, `get_or_create_dimension(m=1)` creates a dimension object representing length (meters), and `Unit.create` uses this dimension to create a unit named \"metre\" with a display name \"m\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Creating Compound Units\n", + "Next, we create a compound unit, such as volt(metre ^ 2 * kilogram / (second ^ 3 * ampere)):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "volt" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "volt = Unit.create(get_or_create_dimension(m=2, kg=1, s=-3, A=-1), \"volt\", \"V\")\n", + "\n", + "volt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, we define the dimensions for the compound unit and create a new unit named \"volt\" with the specified dimensions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Creating Scaled Units\n", + "Finally, we create a scaled version of a basic unit, such as kilometers (kilometre):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "kmetre" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "kilometre = Unit.create_scaled_unit(metre, \"k\")\n", + "\n", + "kilometre" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1000.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "1 * kilometre / (1 * metre)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, `create_scaled_unit` creates a new unit named \"kilometre\" by scaling the base unit \"metre\" with a scale factor of \"k\" (kilo).\n", + "\n", + "The scale factor determines the prefix used for the unit, allowing for easy conversion between different scales of the same unit." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -30,8 +201,22 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" } }, "nbformat": 4, From b7dbcd40c115cde6484f65d27fe0271d7c170a0e Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 22:29:23 +0800 Subject: [PATCH 09/13] Update numpy_functions.ipynb --- .../numpy_functions.ipynb | 645 +++++++++++++++++- 1 file changed, 638 insertions(+), 7 deletions(-) diff --git a/docs/mathematical_functions/numpy_functions.ipynb b/docs/mathematical_functions/numpy_functions.ipynb index 3d70fe9..347682d 100644 --- a/docs/mathematical_functions/numpy_functions.ipynb +++ b/docs/mathematical_functions/numpy_functions.ipynb @@ -11,42 +11,255 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Basic" + "In `brainunit.math` we reimplemented almost all important NumPy functions compatible with both `Quantity` and arrays.\n", + "For compatible with `Quantity`, we categorized the functions into serveral groups.\n", + "\n", + "- [Array Creation](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#array-creation)\n", + "- [Array Manipulation](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#array-manipulation)\n", + "- [Functions Accept Unitless](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#functions-accept-unitless)\n", + "- [Functions with Bitwise Operations](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#functions-with-bitwise-operations)\n", + "- [Functions Changing Unit](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#functions-changing-unit)\n", + "- [Indexing Functions](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#indexing-functions)\n", + "- [Functions Keeping Unit](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#functions-keeping-unit)\n", + "- [Logical Functions](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#logical-functions)\n", + "- [Functions Matching Unit](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#functions-matching-unit)\n", + "- [Functions Removing Unit](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#functions-removing-unit)\n", + "- [Window Functions](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#window-functions)\n", + "- [Get Attribute Functions](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#get-attribute-functions)\n", + "- [Linear Algebra Functions](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#linear-algebra-functions)\n", + "- [More Functions](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html#more-functions)\n", + "\n", + "Detailed information can be found in the [API documentation](https://brainunit.readthedocs.io/en/latest/apis/brainunit.math.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Basic Operations" + "## Shape Manipulation" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "import brainunit as bu\n", + "import brainunit.math as bm\n", + "import jax.numpy as jnp\n", + "import brainstate as bst\n", + "bst.environ.set(precision=64)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Universal Functions" + "### Changing the shape of a Quantity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Indexing, Slicing and Iterating" + "A Quantity has a shape given by the number of elements along each axis:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[7., 9., 4., 8.],\n", + " [3., 0., 3., 6.],\n", + " [9., 4., 1., 9.]]) * mvolt" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = jnp.floor(10 * bst.random.random((3, 4))) * bu.mV\n", + "a" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 4)" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Shape Manipulation" + "The shape of a Quantity can be changed with various commands. Note that the following three commands all return a modified array, but do not change the original array:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([7., 9., 4., 8., 3., 0., 3., 6., 9., 4., 1., 9.]) * mvolt" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.ravel() # returns the array, flattened" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[7., 9.],\n", + " [4., 8.],\n", + " [3., 0.],\n", + " [3., 6.],\n", + " [9., 4.],\n", + " [1., 9.]]) * mvolt" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.reshape(6, 2) # returns the array with a modified shape" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[7., 3., 9.],\n", + " [9., 0., 4.],\n", + " [4., 3., 1.],\n", + " [8., 6., 9.]]) * mvolt" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.T # returns the array, transposed" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(4, 3)" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.T.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 4)" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Changing the shape of a Quantity" + "The order of the elements in the Quantity resulting from ravel is normally “C-style”, that is, the rightmost index “changes the fastest”, so the element after a[0, 0] is a[0, 1]. If the Quantity is reshaped to some other shape, again the Quantity is treated as “C-style”. NumPy normally creates arrays stored in this order, so ravel will usually not need to copy its argument, but if the Quantity was made by taking slices of another Quantity or created with unusual options, it may need to be copied. The functions ravel and reshape can also be instructed, using an optional argument, to use FORTRAN-style arrays, in which the leftmost index changes the fastest.\n", + "\n", + "The reshape function only returns its argument with a modified shape, due to Jax's immutability. The resize method is not available in braincore." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If a dimension is given as -1 in a reshaping operation, the other dimensions are automatically calculated:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[7., 9., 4., 8.],\n", + " [3., 0., 3., 6.],\n", + " [9., 4., 1., 9.]]) * mvolt" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.reshape(3, -1)" ] }, { @@ -56,6 +269,235 @@ "### Stacking together different Quantities" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Several arrays can be stacked together along different axes:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[8., 4.],\n", + " [4., 4.]]) * mvolt" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = jnp.floor(10 * bst.random.random((2, 2))) * bu.mV\n", + "a" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[3., 6.],\n", + " [1., 9.]]) * mvolt" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b = jnp.floor(10 * bst.random.random((2, 2))) * bu.mV\n", + "b" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[8., 4.],\n", + " [4., 4.],\n", + " [3., 6.],\n", + " [1., 9.]]) * mvolt" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.vstack((a, b))" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[8., 4., 3., 6.],\n", + " [4., 4., 1., 9.]]) * mvolt" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.hstack((a, b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function column_stack stacks 1D Quantities as columns into a 2D Quantities. It is equivalent to hstack only for 2D Quantities:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[8., 4., 3., 6.],\n", + " [4., 4., 1., 9.]]) * mvolt" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.column_stack((a, b)) # with 2D arrays" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[4., 2.],\n", + " [2., 8.]]) * mvolt" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = jnp.array([4., 2.]) * bu.mV\n", + "b = jnp.array([2., 8.]) * bu.mV\n", + "bm.column_stack((a, b)) # returns a 2D array" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([4., 2., 2., 8.]) * mvolt" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.hstack((a, b)) # the result is different" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[4.],\n", + " [2.]]) * mvolt" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a[:, jnp.newaxis] # view `a` as a 2D column vector" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[4., 2.],\n", + " [2., 8.]]) * mvolt" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.column_stack((a[:, jnp.newaxis], b[:, jnp.newaxis]))" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[4., 2.],\n", + " [2., 8.]]) * mvolt" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.hstack((a[:, jnp.newaxis], b[:, jnp.newaxis])) # the result is the same" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -63,17 +505,206 @@ "### Splitting one Quantity into several smaller ones" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using hsplit, you can split an array along its horizontal axis, either by specifying the number of equally shaped Quantities to return, or by specifying the columns after which the division should occur:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ArrayImpl([[2., 2., 2., 7., 7., 8., 6., 4., 7., 2., 7., 5.],\n", + " [5., 9., 7., 8., 3., 7., 4., 5., 3., 5., 4., 1.]]) * mvolt" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = jnp.floor(10 * bst.random.random((2, 12))) * bu.mV\n", + "a" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[ArrayImpl([[2., 2., 2., 7.],\n", + " [5., 9., 7., 8.]]) * mvolt,\n", + " ArrayImpl([[7., 8., 6., 4.],\n", + " [3., 7., 4., 5.]]) * mvolt,\n", + " ArrayImpl([[7., 2., 7., 5.],\n", + " [3., 5., 4., 1.]]) * mvolt]" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.hsplit(a, 3) # Split a into 3" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[ArrayImpl([[2., 2., 2.],\n", + " [5., 9., 7.]]) * mvolt,\n", + " ArrayImpl([[7.],\n", + " [8.]]) * mvolt,\n", + " ArrayImpl([[7., 8., 6., 4., 7., 2., 7., 5.],\n", + " [3., 7., 4., 5., 3., 5., 4., 1.]]) * mvolt]" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.hsplit(a, (3, 4)) # Split `a` after the third and the fourth column" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comparing Quantities" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The equality of `Quantity` objects is best tested using the `brainunit.math.allclose()` and `brainunit.math.isclose()` functions, which are unit-aware analogues of the numpy functions with the same name:" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array(True, dtype=bool)" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.allclose([1, 2] * bu.meter, [100, 200] * bu.cm)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array([ True, False], dtype=bool)" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bm.isclose([1, 2] * bu.meter, [100, 20] * bu.cm)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The use of Python comparison operators is also supported(>, <, >=, <=, ==, !=) if the two `Quantity` objects have the same unit. If the two `Quantity` objects have different units, a `DimensionMismatchError` will be raised." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 * bu.meter < 50 * bu.cm" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cannot perform comparison 1.0 < 50.0, units do not match (units are m and s).\n" + ] + } + ], + "source": [ + "from brainunit import DimensionMismatchError\n", + "\n", + "try:\n", + " 1 * bu.meter < 50 * bu.second\n", + "except DimensionMismatchError as e:\n", + " print(e)" + ] } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" } }, "nbformat": 4, From 8d95ee6df97e8924e24924fe6066aefd86f98e6b Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 22:43:45 +0800 Subject: [PATCH 10/13] Update customize_functions.ipynb --- .../customize_functions.ipynb | 142 +++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/docs/mathematical_functions/customize_functions.ipynb b/docs/mathematical_functions/customize_functions.ipynb index b72d77b..b11c190 100644 --- a/docs/mathematical_functions/customize_functions.ipynb +++ b/docs/mathematical_functions/customize_functions.ipynb @@ -6,11 +6,151 @@ "source": [ "# Customize Functions that Accept Quantities" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In scientific computing, it is crucial to ensure that function parameters and return values have the correct units. To streamline this process, we can use the `brainunit.check_units` decorator to validate the units of function parameters and return values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `check_units` Decorator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `check_units` decorator allows us to specify the units that function parameters and return values should have. If the provided units are incorrect, it raises a `DimensionMismatchError`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example\n", + "Let's demonstrate the usage of the `check_units` decorator through a set of test cases.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import brainunit as bu\n", + "from brainunit import check_units\n", + "import jax.numpy as jnp\n", + "\n", + "@check_units(v=bu.volt)\n", + "def f(v, x):\n", + " '''\n", + " v must have units of volt, x can have any (or no) units\n", + " '''\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Correct units\n", + "f(3 * bu.mV, 5 * bu.second)\n", + "f(5 * bu.volt, \"something\")\n", + "f([1, 2, 3] * bu.volt, None)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Lists that can be converted should also work\n", + "f([1 * bu.volt, 2 * bu.volt, 3 * bu.volt], None)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Strings and None are also allowed to pass\n", + "f(\"a string\", None)\n", + "f(None, None)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Function 'f' expected a array with unit volt for argument 'v' but got '5. s' (unit is s).\n", + "Function 'f' expected a array with unit volt for argument 'v' but got '5.' (unit is 1).\n", + "unsupported operand type(s) for /: 'object' and 'int'\n", + "Argument 'v' is not a array, expected a array with dimensions V\n" + ] + } + ], + "source": [ + "# Incorrect units\n", + "try:\n", + " f(5 * bu.second, None)\n", + "except Exception as e:\n", + " print(e)\n", + "\n", + "try:\n", + " f(5, None)\n", + "except Exception as e:\n", + " print(e)\n", + "\n", + "try:\n", + " f(object(), None)\n", + "except Exception as e:\n", + " print(e)\n", + "\n", + "try:\n", + " f([1, 2 * bu.volt, 3], None)\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Through these test cases, we can ensure that our functions behave correctly when handling quantities and can handle unit mismatches." + ] } ], "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" } }, "nbformat": 4, From 32086d8d0cf8a3d2f2419396f511dd2847797c5a Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Tue, 18 Jun 2024 23:07:37 +0800 Subject: [PATCH 11/13] Update --- brainunit/_unit_test.py | 22 ++- .../combining_defining_displaying.ipynb | 169 ++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) diff --git a/brainunit/_unit_test.py b/brainunit/_unit_test.py index 992cd91..5c6c2c4 100644 --- a/brainunit/_unit_test.py +++ b/brainunit/_unit_test.py @@ -40,7 +40,7 @@ get_basic_unit, have_same_unit, in_unit, - is_scalar_type, + is_scalar_type, in_best_unit, ) # from braincore.math import ufuncs_integers from brainunit._unit_shortcuts import Hz, cm, kHz, ms, mV, nS @@ -1456,6 +1456,26 @@ def f2(a): r = f2(val) bu.math.allclose(val + 1 * bu.siemens / bu.cm ** 2, r) + @jax.jit + def f3(a): + b = a * bu.siemens / bu.cm ** 2 + print(in_unit(b, bu.siemens / bu.meter ** 2)) + return b + + val = np.random.rand(3) + r = f3(val) + bu.math.allclose(val * bu.siemens / bu.cm ** 2, r) + + @jax.jit + def f4(a): + b = a * bu.siemens / bu.cm ** 2 + print(in_best_unit(b)) + return b + + val = np.random.rand(3) + r = f4(val) + bu.math.allclose(val * bu.siemens / bu.cm ** 2, r) + def test_jit_array2(): a = 2.0 * (bu.farad / bu.metre ** 2) diff --git a/docs/physical_units/combining_defining_displaying.ipynb b/docs/physical_units/combining_defining_displaying.ipynb index 67a5da7..db76b39 100644 --- a/docs/physical_units/combining_defining_displaying.ipynb +++ b/docs/physical_units/combining_defining_displaying.ipynb @@ -198,6 +198,175 @@ "source": [ "## Displaying in JIT / grad / ... transformations" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic Display methods" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Except directly using the `str` and `print` functions to display a `Quantity`, `brainunit` also provides `in_unit` and `in_best_unit` functions to display a `Quantity` in a specific unit or the best unit(the value is not too large or too small) respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3. V\n" + ] + }, + { + "data": { + "text/plain": [ + "('3000. mV', '3. V')" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from brainunit import in_unit, in_best_unit\n", + "a = 3 * bu.volt\n", + "\n", + "print(a) # print is same as `in_best_unit(a)`\n", + "\n", + "in_unit(a, bu.mV), in_best_unit(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Displaying in JIT transformations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`brainunit` support use the display methods above in JIT transformations." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tracedwith m^-4 kg^-1 s^3 A^2\n" + ] + }, + { + "data": { + "text/plain": [ + "Array(True, dtype=bool)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "import jax\n", + "import jax.numpy as jnp\n", + "\n", + "@jax.jit\n", + "def f1(a):\n", + " b = a * bu.siemens / bu.cm ** 2\n", + " print(b)\n", + " return b\n", + "\n", + "val = np.random.rand(3)\n", + "r = f1(val)\n", + "bu.math.allclose(val * bu.siemens / bu.cm ** 2, r)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tracedwith S/(m^2)\n" + ] + }, + { + "data": { + "text/plain": [ + "Array(True, dtype=bool)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@jax.jit\n", + "def f2(a):\n", + " b = a * bu.siemens / bu.cm ** 2\n", + " print(in_unit(b, bu.siemens / bu.meter ** 2))\n", + " return b\n", + "\n", + "val = np.random.rand(3)\n", + "r = f2(val)\n", + "bu.math.allclose(val * bu.siemens / bu.cm ** 2, r)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tracedwith S/(m^2)\n" + ] + }, + { + "data": { + "text/plain": [ + "Array(True, dtype=bool)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@jax.jit\n", + "def f3(a):\n", + " b = a * bu.siemens / bu.cm ** 2\n", + " print(in_best_unit(b))\n", + " return b\n", + "\n", + "val = np.random.rand(3)\n", + "r = f3(val)\n", + "bu.math.allclose(val * bu.siemens / bu.cm ** 2, r)" + ] } ], "metadata": { From e374a75ec654748ac5c5e376744c52346774138b Mon Sep 17 00:00:00 2001 From: He Sichao <1310722434@qq.com> Date: Wed, 19 Jun 2024 15:26:26 +0800 Subject: [PATCH 12/13] Update constants.ipynb --- docs/physical_units/constants.ipynb | 42 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/physical_units/constants.ipynb b/docs/physical_units/constants.ipynb index 65af00b..c34d3c2 100644 --- a/docs/physical_units/constants.ipynb +++ b/docs/physical_units/constants.ipynb @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -52,7 +52,37 @@ " magnetic_constant,\n", " molar_mass_constant,\n", " zero_celsius,\n", - ")" + ")\n", + "\n", + "constants = [avogadro_constant, boltzmann_constant, electric_constant, electron_mass, elementary_charge,\n", + " faraday_constant, gas_constant, magnetic_constant, molar_mass_constant, zero_celsius]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.022141e+23 mol^-1\n", + "1.3806486e-23 J/K\n", + "8.854188e-12 F/m\n", + "0.00091094 yg\n", + "160.21766663 zC\n", + "96485.3359375 s A mol^-1\n", + "8.3144598 J/(mol * K)\n", + "1.256637e-06 H/m\n", + "0.001 kg mol^-1\n", + "273.1499939 K\n" + ] + } + ], + "source": [ + "for constant in constants:\n", + " print(constant)" ] } ], @@ -63,7 +93,15 @@ "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.10.13" } }, From 097831568d9b93cc76beeb915470912de5ebdbea Mon Sep 17 00:00:00 2001 From: Chaoming Wang Date: Thu, 20 Jun 2024 09:55:14 +0800 Subject: [PATCH 13/13] update doc index --- docs/index.rst | 97 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 18b4e95..b27f4a3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,6 +36,72 @@ Installation pip install -U brainunit[tpu] -f https://storage.googleapis.com/jax-releases/libtpu_releases.html + + +---- + +Learn more +^^^^^^^^^^ + +.. grid:: + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`rocket_launch;2em` Installation + :class-card: sd-text-black sd-bg-light + :link: quickstart/installation.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`library_books;2em` Core Concepts + :class-card: sd-text-black sd-bg-light + :link: core_concepts.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`science;2em` BDP Tutorials + :class-card: sd-text-black sd-bg-light + :link: tutorials.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`token;2em` Advanced Tutorials + :class-card: sd-text-black sd-bg-light + :link: advanced_tutorials.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`settings;2em` BDP Toolboxes + :class-card: sd-text-black sd-bg-light + :link: toolboxes.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`rocket_launch;2em` FAQ + :class-card: sd-text-black sd-bg-light + :link: FAQ.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`data_exploration;2em` API documentation + :class-card: sd-text-black sd-bg-light + :link: api.html + + .. grid-item:: + :columns: 6 6 6 4 + + .. card:: :material-regular:`settings;2em` Examples + :class-card: sd-text-black sd-bg-light + :link: https://brainpy-examples.readthedocs.io/en/latest/index.html + + ---- @@ -57,9 +123,34 @@ See also the BDP ecosystem .. toctree:: :hidden: - :maxdepth: 2 + :maxdepth: 1 + :caption: Physical Units + + physical_units/quantity.ipynb + physical_units/standard_units.ipynb + physical_units/constants.ipynb + physical_units/conversion.ipynb + physical_units/combining_defining_displaying.ipynb + physical_units/mechanism.ipynb + + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Unit-aware Math Functions + + mathematical_functions/numpy_functions.ipynb + mathematical_functions/customize_functions.ipynb + + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: API Documentation - physical_units - mathematical_functions api.rst + +