diff --git a/docs/src/awkward.ipynb b/docs/src/awkward.ipynb index a4a99237..3f5da595 100644 --- a/docs/src/awkward.ipynb +++ b/docs/src/awkward.ipynb @@ -13,7 +13,7 @@ "id": "26894013-8d55-45b5-9592-6a82c6e7440a", "metadata": {}, "source": [ - "First, [install](../index.md#installation) and import Vector." + "First, [install](../index.md#installation) and import Vector and [Awkward Array](https://awkward-array.org/)." ] }, { @@ -22,7 +22,1004 @@ "id": "8f504534-1fa5-4dfb-b0c9-f7a2b2d12eae", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "import vector\n", + "import awkward as ak" + ] + }, + { + "cell_type": "markdown", + "id": "b119a69a-19e6-4568-bdde-fca92ec23f2f", + "metadata": {}, + "source": [ + "## Making an Awkward Array of vectors" + ] + }, + { + "cell_type": "markdown", + "id": "bb1e0f46-627e-463a-840b-bac3bf0588eb", + "metadata": {}, + "source": [ + "Awkward Arrays are arrays with more complex data structures than NumPy allows, such as variable-length lists, nested records, missing and even heterogeneous data (different data types in the same array).\n", + "\n", + "Vectors can be included among those data structures. In this context, vectors are Awkward \"records,\" objects with named fields, that can be nested inside of other structures. The vector properties and methods are implemented through Awkward Array's [behavior](https://awkward-array.org/doc/main/reference/ak.behavior.html) mechanism. Unlike [vector objects](object.md) and [NumPy subclasses](numpy.md), the vectors can't be ordinary Python classes because they might be nested within other data structures, such as variable-length lists, and these lists are implemented in a columnar way that isn't open to Python's introspection.\n", + "\n", + "Let's start with an example. Below, we create an Awkward Array using its [ak.Array](https://awkward-array.org/doc/main/reference/generated/ak.Array.html) constructor, but include `with_name` and `behavior` arguments:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "42b68d0a-d867-4fed-84ad-59d4732e112b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{x: 1.1, y: 2.2}, {x: 3.3, y: 4.4}],\n",
+       " [],\n",
+       " [{x: 5.5, y: 6.6}]]\n",
+       "----------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 80 B\n",
+       "type: 3 * var * Vector2D[\n",
+       "    x: float64,\n",
+       "    y: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "arr = ak.Array(\n", + " [\n", + " [{\"x\": 1.1, \"y\": 2.2}, {\"x\": 3.3, \"y\": 4.4}],\n", + " [],\n", + " [{\"x\": 5.5, \"y\": 6.6}],\n", + " ],\n", + " with_name=\"Vector2D\",\n", + " behavior=vector.backends.awkward.behavior,\n", + ")\n", + "arr" + ] + }, + { + "cell_type": "markdown", + "id": "e674ffdd-a42e-4707-911d-def1a0010858", + "metadata": {}, + "source": [ + "The above array contains 3 lists, the first has length 2, the second has length 0, and the third has length 1. The lists contain records with field names `\"x\"` and `\"y\"`, and the record type is named `\"Vector2D\"`. In addition, this array has `behavior` from `vector.backends.awkward.behavior`, which is a large dict containing classes and functions to implement vector operations.\n", + "\n", + "For instance, we can compute `rho` and `phi` coordinates in the same way as with the [NumPy subclasses](numpy.md), an array at a time:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1d5484f9-07e1-4192-bf88-3b32f1028bf5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[2.46, 5.5],\n",
+       " [],\n",
+       " [8.59]]\n",
+       "-----------------------\n",
+       "backend: cpu\n",
+       "nbytes: 56 B\n",
+       "type: 3 * var * float64
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "arr.rho" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "61e61f29-015d-4f53-94cd-07444811c9ec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[1.11, 0.927],\n",
+       " [],\n",
+       " [0.876]]\n",
+       "-----------------------\n",
+       "backend: cpu\n",
+       "nbytes: 56 B\n",
+       "type: 3 * var * float64
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "arr.phi" + ] + }, + { + "cell_type": "markdown", + "id": "66d1c600-568a-41cc-b31a-f7eaedef1c68", + "metadata": {}, + "source": [ + "As with NumPy, performing operations an array at a time is usually much faster than writing Python for loops. What Awkward Array provides on top of that is the ability to do these operations _through_ variable-length lists and other structures.\n", + "\n", + "An Awkward Array needs all of the following for its records to be interpreted as vectors:\n", + "\n", + "1. the record name, which can be assigned using [ak.with_name](https://awkward-array.org/doc/main/reference/generated/ak.with_name.html) or as a constructor argument, must be one of `\"Vector2D\"`, `\"Momentum2D\"`, `\"Vector3D\"`, `\"Momentum3D\"`, `\"Vector4D\"`, and `\"Momentum4D\"`\n", + "2. the field names must be recognized coordinate names, following the same conventions as [vector objects](object.md)\n", + "3. the array must have `vector.backends.awkward.behavior` as its `behavior`.\n", + "\n", + "When Awkward Arrays are saved in files, such as with [ak.to_parquet](https://awkward-array.org/doc/main/reference/generated/ak.to_parquet.html), they retain their record names and field names, so conditions 1 and 2 above are persistent. They don't preserve condition 3, the behaviors, since these are Python classes and functions.\n", + "\n", + "To make sure that Vector behaviors are always available, you can call [vector.register_awkward](make_awkward.md#vector.register_awkward) at the beginning of every script, like this:\n", + "\n", + "```python\n", + "import awkward as ak\n", + "import vector\n", + "vector.register_awkward()\n", + "```\n", + "\n", + "This function copies Vector's behaviors into Awkward's global [ak.behavior](https://awkward-array.org/doc/main/reference/ak.behavior.html) so that any array with the right record and field names (such as one read from a file) automatically have Vector behaviors.\n", + "\n", + "Vector also has a [vector.Array](make_awkward.md#vector.Array) constructor, which works like [ak.Array](https://awkward-array.org/doc/main/reference/generated/ak.Array.html) but sets `with_name` automatically, as well as [vector.zip](make_awkward.md#vector.zip), which works like [ak.zip](https://awkward-array.org/doc/main/reference/generated/ak.zip.html) and sets `with_name` automatically. However, these functions still require you to set field names appropriately and if you need to do something complex, it's easier to use Awkward Array's own functions and assign the record name after the array is built, using [ak.with_name](https://awkward-array.org/doc/main/reference/generated/ak.with_name.html)." + ] + }, + { + "cell_type": "markdown", + "id": "467f6d6d-dd6a-4c6e-8ff2-cc7b61e6b949", + "metadata": {}, + "source": [ + "## Using an Awkward array of vectors" + ] + }, + { + "cell_type": "markdown", + "id": "8677dc3e-1482-42c4-9157-75c0cea61f45", + "metadata": {}, + "source": [ + "First, let's make some arrays to use in examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "025fcf28-c946-4c52-9c06-333a217ead52", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import awkward as ak\n", + "import vector\n", + "\n", + "vector.register_awkward()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "15050418-eab0-4da4-a1bd-c8ceef2a47c2", + "metadata": {}, + "outputs": [], + "source": [ + "def array_of_momentum3d(num_vectors):\n", + " return ak.zip(\n", + " {\n", + " \"px\": np.random.normal(0, 1, num_vectors),\n", + " \"py\": np.random.normal(0, 1, num_vectors),\n", + " \"pz\": np.random.normal(0, 1, num_vectors),\n", + " },\n", + " with_name=\"Momentum3D\",\n", + " )\n", + "\n", + "\n", + "def array_of_lists_of_momentum3d(mean_num_per_list, num_lists):\n", + " num_per_list = np.random.poisson(mean_num_per_list, num_lists)\n", + " return ak.unflatten(\n", + " array_of_momentum3d(np.sum(num_per_list)),\n", + " num_per_list,\n", + " )\n", + "\n", + "\n", + "a = array_of_momentum3d(10)\n", + "b = array_of_lists_of_momentum3d(1.5, 10)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "43fbd738-8243-45bf-af35-2b09ef196bff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[{px: -0.541, py: 0.714, pz: -0.983},\n",
+       " {px: 0.61, py: -0.48, pz: -0.0845},\n",
+       " {px: 0.929, py: 0.718, pz: 0.0935},\n",
+       " {px: 1.52, py: -1.39, pz: -0.0187},\n",
+       " {px: 0.675, py: 0.0216, pz: -1.12},\n",
+       " {px: 0.542, py: -0.524, pz: 0.586},\n",
+       " {px: 0.927, py: 0.476, pz: 0.602},\n",
+       " {px: 0.615, py: 1.23, pz: -0.59},\n",
+       " {px: -0.626, py: 0.072, pz: 0.136},\n",
+       " {px: 2.23, py: 1.05, pz: -0.355}]\n",
+       "--------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 240 B\n",
+       "type: 10 * Momentum3D[\n",
+       "    px: float64,\n",
+       "    py: float64,\n",
+       "    pz: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c347edb1-78b3-4963-a503-b877597c2ee5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{px: 0.73, py: 0.862, pz: -0.129}, {px: 0.746, py: 0.614, pz: ..., ...}],\n",
+       " [],\n",
+       " [{px: 0.493, py: -0.633, pz: -0.413}, {px: 0.836, py: -1.77, ...}],\n",
+       " [{px: -0.58, py: -0.586, pz: -0.513}, {...}, ..., {px: -0.201, py: 1.52, ...}],\n",
+       " [{px: -0.474, py: 1.6, pz: -0.1}, {px: 0.218, py: 0.155, pz: 0.834}],\n",
+       " [{px: -0.881, py: -0.183, pz: -0.725}, {px: -0.108, py: -0.889, ...}],\n",
+       " [{px: -1.77, py: 2.04, pz: 0.896}],\n",
+       " [{px: -1.08, py: -0.591, pz: -0.0674}, {px: -0.183, py: -0.289, ...}],\n",
+       " [{px: -1.08, py: -1.15, pz: 2.6}],\n",
+       " [{px: -0.419, py: -0.363, pz: -0.563}, {px: -2.48, py: -0.228, ...}]]\n",
+       "--------------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 544 B\n",
+       "type: 10 * var * Momentum3D[\n",
+       "    px: float64,\n",
+       "    py: float64,\n",
+       "    pz: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b" + ] + }, + { + "cell_type": "markdown", + "id": "7a6e73c4-cc2c-4cb5-9807-13c1bc970f78", + "metadata": {}, + "source": [ + "Awkward Array uses array-at-a-time functions like NumPy, so if we want to compute dot products of each vector in `a` with every vector of each list in `b`, we'd say:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "144ee86e-2b47-4526-9d6b-3d490be69ba8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[0.347, -1.32],\n",
+       " [],\n",
+       " [-0.035, -0.385],\n",
+       " [-0.0551, 0.194, 2.28, -2.86, -2.39],\n",
+       " [-0.173, -0.782],\n",
+       " [-0.807, 0.704],\n",
+       " [-0.128],\n",
+       " [-1.35, -0.446],\n",
+       " [0.945],\n",
+       " [-1.11, -5.58]]\n",
+       "--------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 240 B\n",
+       "type: 10 * var * float64
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.dot(b)" + ] + }, + { + "cell_type": "markdown", + "id": "1088c2a6-e744-4308-bd74-4ed9471d3bd1", + "metadata": {}, + "source": [ + "Note that `a` and `b` have different numbers of vectors, but the same array lengths. The operation above [broadcasts](https://awkward-array.org/doc/main/user-guide/how-to-math-broadcasting.html) array `a` into `b`, like the following code:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9edab13c-5b8e-47f2-afac-c0dd7241a8ca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.3470024410105361 -1.32262374856204 ]\n", + "[]\n", + "[-0.0350058354646221 -0.3845727581312185 ]\n", + "[-0.05513156034267678 0.1939866533163302 2.2806023784977056 -2.861051860111839 -2.3860250251022475 ]\n", + "[-0.17338156209593328 -0.7816799910864897 ]\n", + "[-0.8071770038569903 0.7044860439866077 ]\n", + "[-0.1279745100114199 ]\n", + "[-1.3483450919978617 -0.44626327125953613 ]\n", + "[0.9449043237160631 ]\n", + "[-1.1124522691465186 -5.579496184145224 ]\n" + ] + } + ], + "source": [ + "for i in range(len(a)):\n", + " print(\"[\", end=\"\")\n", + "\n", + " for j in range(len(b[i])):\n", + " out = a[i].dot(b[i, j])\n", + "\n", + " print(out, end=\" \")\n", + "\n", + " print(\"]\")" + ] + }, + { + "cell_type": "markdown", + "id": "0e17784d-a838-4c87-a708-f530bd0fbe59", + "metadata": {}, + "source": [ + "Like NumPy, the array-at-a-time expression is more concise and faster:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1e3ffd74-469d-4ccd-984d-706413442700", + "metadata": {}, + "outputs": [], + "source": [ + "a = array_of_momentum3d(10000)\n", + "b = array_of_lists_of_momentum3d(1.5, 10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "135e44f1-7e25-49ef-8805-8638b3d0e9be", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "9.08 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit -n1 -r1\n", + "\n", + "out = np.zeros(10000)\n", + "\n", + "for i in range(len(a)):\n", + " for j in range(len(b[i])):\n", + " out[i] += a[i].dot(b[i, j])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6b4d2c04-9034-4558-905a-af4539be1d40", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.44 ms ± 20.2 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "out = np.sum(a.dot(b), axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "cd509b13-8e87-40b0-b478-bb40154dd5c1", + "metadata": {}, + "source": [ + "(Note the units.)\n", + "\n", + "Just as with NumPy, all of the coordinate transformations and vector operations are implemented for Awkward Arrays of vectors." + ] + }, + { + "cell_type": "markdown", + "id": "716cd51c-88ec-42e7-89ae-719cef7e4368", + "metadata": {}, + "source": [ + "## Some troubleshooting hints" + ] + }, + { + "cell_type": "markdown", + "id": "904561c9-b998-4e4a-8ce1-e799931d9285", + "metadata": {}, + "source": [ + "Make sure that the Vector behaviors are actually installed and applied to your data. In the data type, the record type should appear as `\"Vector2D\"`, `\"Momentum2D\"`, `\"Vector3D\"`, `\"Momentum3D\"`, `\"Vector4D\"`, or `\"Momentum4D\"`, rather than the generic curly brackets `{` and `}`, and if you extract one record from the array, can you perform a vector operation on it?\n", + "\n", + "Make sure that your arrays broadcast the way that you want them to. If the vector behaviors are clouding the picture, make simpler arrays with numbers in place of records. Can you add them with `+`? (Addition uses the same broadcasting rules as all other operations.)\n", + "\n", + "If your code runs but doesn't give the results you expect, try slicing the arrays to just the first two items with `arr[:2]`. Step through the calculation on just two elements, observing the results of each operation. Are they what you expect?" + ] + }, + { + "cell_type": "markdown", + "id": "60db1ccb-8d0a-4bc8-9050-6f68b03e2ee4", + "metadata": {}, + "source": [ + "## Advanced: subclassing Awkward-Vector behaviors" + ] + }, + { + "cell_type": "markdown", + "id": "00d4f231-46f3-41a0-8573-9564aad8b29b", + "metadata": {}, + "source": [ + "It is possible to write subclasses for Awkward-Vector behaviors as mixins to extend the vector functionalities. For instance, the `MomentumAwkward` classes can be extended in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "967444cc-a4f1-4e2b-8289-3224333218f1", + "metadata": {}, + "outputs": [], + "source": [ + "behavior = vector.backends.awkward.behavior\n", + "\n", + "\n", + "@ak.mixin_class(behavior)\n", + "class TwoVector(vector.backends.awkward.MomentumAwkward2D):\n", + " pass\n", + "\n", + "\n", + "@ak.mixin_class(behavior)\n", + "class ThreeVector(vector.backends.awkward.MomentumAwkward3D):\n", + " pass\n", + "\n", + "\n", + "# required for transforming vectors\n", + "# the class names must always end with \"Array\"\n", + "TwoVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821\n", + "TwoVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821\n", + "TwoVectorArray.MomentumClass = TwoVectorArray # noqa: F821\n", + "\n", + "ThreeVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821\n", + "ThreeVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821\n", + "ThreeVectorArray.MomentumClass = ThreeVectorArray # noqa: F821" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "42ea1b53-6003-4294-ac52-3e014eab92b7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{pt: 1, phi: 1.2}, {pt: 2, phi: 1.4}],\n",
+       " [],\n",
+       " [{pt: 3, phi: 1.6}],\n",
+       " [{pt: 4, phi: 3.4}]]\n",
+       "------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 104 B\n",
+       "type: 4 * var * TwoVector[\n",
+       "    pt: int64,\n",
+       "    phi: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec = ak.zip(\n", + " {\n", + " \"pt\": [[1, 2], [], [3], [4]],\n", + " \"phi\": [[1.2, 1.4], [], [1.6], [3.4]],\n", + " },\n", + " with_name=\"TwoVector\",\n", + " behavior=behavior,\n", + ")\n", + "vec" + ] + }, + { + "cell_type": "markdown", + "id": "c2889f3f-5fb3-42ea-bd5d-8c0135aa7c81", + "metadata": {}, + "source": [ + "The binary operators are not automatically registered by Awkward, but Vector methods can be used to perform operations on subclassed vectors." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b4ecc08c-253b-46b7-a78a-1aca4558d863", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 2, phi: 1.2}, {rho: 4, phi: 1.4}],\n",
+       " [],\n",
+       " [{rho: 6, phi: 1.6}],\n",
+       " [{rho: 8, phi: -2.88}]]\n",
+       "---------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 104 B\n",
+       "type: 4 * var * TwoVector[\n",
+       "    rho: float64,\n",
+       "    phi: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec.add(vec)" + ] + }, + { + "cell_type": "markdown", + "id": "41426373-4492-4ff6-9496-3e5d88ef630e", + "metadata": {}, + "source": [ + "Similarly, other vector methods can be used by the new methods internally." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2c05135c-ba70-4b24-9d9f-2b04f72bf1ce", + "metadata": {}, + "outputs": [], + "source": [ + "import numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8e24d10e-a1ab-4614-9e8e-62a6378c0864", + "metadata": {}, + "outputs": [], + "source": [ + "@ak.mixin_class(behavior)\n", + "class LorentzVector(vector.backends.awkward.MomentumAwkward4D):\n", + " @ak.mixin_class_method(np.divide, {numbers.Number})\n", + " def divide(self, factor):\n", + " return self.scale(1 / factor)\n", + "\n", + "\n", + "# required for transforming vectors\n", + "# the class names must always end with \"Array\"\n", + "LorentzVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821\n", + "LorentzVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821\n", + "LorentzVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821\n", + "LorentzVectorArray.MomentumClass = LorentzVectorArray # noqa: F821" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "203112e0-217e-4dc5-8145-881d48c9cf08", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{pt: 1, eta: 1.2, phi: 0.3, energy: 50}, {pt: 2, eta: 1.4, ...}],\n",
+       " [],\n",
+       " [{pt: 3, eta: 1.6, phi: 0.5, energy: 52}],\n",
+       " [{pt: 4, eta: 3.4, phi: 0.6, energy: 60}]]\n",
+       "-----------------------------------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 168 B\n",
+       "type: 4 * var * LorentzVector[\n",
+       "    pt: int64,\n",
+       "    eta: float64,\n",
+       "    phi: float64,\n",
+       "    energy: int64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec = ak.zip(\n", + " {\n", + " \"pt\": [[1, 2], [], [3], [4]],\n", + " \"eta\": [[1.2, 1.4], [], [1.6], [3.4]],\n", + " \"phi\": [[0.3, 0.4], [], [0.5], [0.6]],\n", + " \"energy\": [[50, 51], [], [52], [60]],\n", + " },\n", + " with_name=\"LorentzVector\",\n", + " behavior=behavior,\n", + ")\n", + "vec" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "6bf279a8-fca7-4c16-aca8-90565818a646", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 0.5, phi: 0.3, eta: 1.2, t: 25}, {rho: 1, phi: 0.4, ...}],\n",
+       " [],\n",
+       " [{rho: 1.5, phi: 0.5, eta: 1.6, t: 26}],\n",
+       " [{rho: 2, phi: 0.6, eta: 3.4, t: 30}]]\n",
+       "-----------------------------------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 168 B\n",
+       "type: 4 * var * LorentzVector[\n",
+       "    rho: float64,\n",
+       "    phi: float64,\n",
+       "    eta: float64,\n",
+       "    t: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec / 2" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "6648d398-60e4-4a7d-bd88-a7dd4a35e8d0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 1, phi: 0.3}, {rho: 2, phi: 0.4}],\n",
+       " [],\n",
+       " [{rho: 3, phi: 0.5}],\n",
+       " [{rho: 4, phi: 0.6}]]\n",
+       "-------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 104 B\n",
+       "type: 4 * var * TwoVector[\n",
+       "    rho: int64,\n",
+       "    phi: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec.like(vector.obj(x=1, y=2))" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "09854ba6-f571-42f8-95a9-b0ac52553cda", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 1, phi: 0.3, eta: 1.2}, {rho: 2, phi: 0.4, eta: 1.4}],\n",
+       " [],\n",
+       " [{rho: 3, phi: 0.5, eta: 1.6}],\n",
+       " [{rho: 4, phi: 0.6, eta: 3.4}]]\n",
+       "---------------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 136 B\n",
+       "type: 4 * var * ThreeVector[\n",
+       "    rho: int64,\n",
+       "    phi: float64,\n",
+       "    eta: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec.like(vector.obj(x=1, y=2, z=3))" + ] + }, + { + "cell_type": "markdown", + "id": "59cf3641-e227-4f0b-a1fa-99b0d196f02f", + "metadata": {}, + "source": [ + "It is also possible to manually add binary operations in vector's behavior dict to enable binary operations." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "11778dd0-4726-43cc-817b-3326528dd144", + "metadata": {}, + "outputs": [], + "source": [ + "_binary_dispatch_cls = {\n", + " \"TwoVector\": TwoVector,\n", + " \"ThreeVector\": ThreeVector,\n", + " \"LorentzVector\": LorentzVector,\n", + "}\n", + "_rank = [TwoVector, ThreeVector, LorentzVector]\n", + "\n", + "for lhs, lhs_to in _binary_dispatch_cls.items():\n", + " for rhs, rhs_to in _binary_dispatch_cls.items():\n", + " out_to = min(lhs_to, rhs_to, key=_rank.index)\n", + " behavior[(np.add, lhs, rhs)] = out_to.add\n", + " behavior[(np.subtract, lhs, rhs)] = out_to.subtract" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "3088485c-70e4-461d-86b3-5db81148389f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 2, phi: 0.3, eta: 1.2, t: 100}, {rho: 4, phi: 0.4, eta: 1.4, ...}],\n",
+       " [],\n",
+       " [{rho: 6, phi: 0.5, eta: 1.6, t: 104}],\n",
+       " [{rho: 8, phi: 0.6, eta: 3.4, t: 120}]]\n",
+       "---------------------------------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 168 B\n",
+       "type: 4 * var * LorentzVector[\n",
+       "    rho: float64,\n",
+       "    phi: float64,\n",
+       "    eta: float64,\n",
+       "    t: int64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec + vec" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "040e47cc-013d-423e-b5df-b0982a2279aa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 2, phi: 0.3}, {rho: 4, phi: 0.4}],\n",
+       " [],\n",
+       " [{rho: 6, phi: 0.5}],\n",
+       " [{rho: 8, phi: 0.6}]]\n",
+       "---------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 104 B\n",
+       "type: 4 * var * TwoVector[\n",
+       "    rho: float64,\n",
+       "    phi: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec.to_2D() + vec.to_2D()" + ] + }, + { + "cell_type": "markdown", + "id": "e2259fbe-3eea-4fde-bedb-b5631f639ca3", + "metadata": {}, + "source": [ + "Finally, instead of manually registering the superclass ufuncs, one can use the utility `copy_behaviors` function to copy behavior items for a new subclass -" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "4af071da-8612-49a2-9a56-7e49487cf866", + "metadata": {}, + "outputs": [], + "source": [ + "behavior.update(ak._util.copy_behaviors(\"Vector2D\", \"TwoVector\", behavior))\n", + "behavior.update(ak._util.copy_behaviors(\"Vector3D\", \"ThreeVector\", behavior))\n", + "behavior.update(ak._util.copy_behaviors(\"Momentum4D\", \"LorentzVector\", behavior))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "1c434751-a932-4694-92fc-9e48b51905b0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 2, phi: 0.3, eta: 1.2, t: 100}, {rho: 4, phi: 0.4, eta: 1.4, ...}],\n",
+       " [],\n",
+       " [{rho: 6, phi: 0.5, eta: 1.6, t: 104}],\n",
+       " [{rho: 8, phi: 0.6, eta: 3.4, t: 120}]]\n",
+       "------------------------------------------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 168 B\n",
+       "type: 4 * var * Momentum4D[\n",
+       "    rho: float64,\n",
+       "    phi: float64,\n",
+       "    eta: float64,\n",
+       "    t: int64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec + vec" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "09e2bc41-340d-4ad6-82d8-f08cf869644a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
[[{rho: 2, phi: 0.3}, {rho: 4, phi: 0.4}],\n",
+       " [],\n",
+       " [{rho: 6, phi: 0.5}],\n",
+       " [{rho: 8, phi: 0.6}]]\n",
+       "--------------------------------------------------------------\n",
+       "backend: cpu\n",
+       "nbytes: 104 B\n",
+       "type: 4 * var * Vector2D[\n",
+       "    rho: float64,\n",
+       "    phi: float64\n",
+       "]
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vec.to_2D() + vec.to_2D()" + ] } ], "metadata": { diff --git a/docs/src/make_awkward.md b/docs/src/make_awkward.md index 44b10a2b..fae35119 100644 --- a/docs/src/make_awkward.md +++ b/docs/src/make_awkward.md @@ -1,6 +1,6 @@ # Making Awkward Arrays of vectors -An Awkward Array of vectors is an Awkward Array containing appropriately named records, appropriately named fields, and the Vector behaviors registered in the array. Here's a complete example for illustration: +An Awkward Array of vectors is an Awkward Array containing appropriately named records, appropriately named fields, and the Vector [behaviors](https://awkward-array.org/doc/main/reference/ak.behavior.html) registered in the array. Here's a complete example for illustration: ```python >>> import awkward as ak diff --git a/docs/src/numpy.ipynb b/docs/src/numpy.ipynb index 2694368e..d830eb60 100644 --- a/docs/src/numpy.ipynb +++ b/docs/src/numpy.ipynb @@ -13,7 +13,7 @@ "id": "1144533f-0290-43f2-a9f2-adc9d042ed1a", "metadata": {}, "source": [ - "First, [install](../index.md#installation) and import Vector and NumPy. (Vector requires NumPy, so if you can use Vector, you've already installed NumPy.)" + "First, [install](../index.md#installation) and import Vector and [NumPy](https://numpy.org/). (Vector requires NumPy, so if you can use Vector, you've already installed NumPy.)" ] }, { diff --git a/docs/src/sympy.ipynb b/docs/src/sympy.ipynb index afc0b10d..df5e10ac 100644 --- a/docs/src/sympy.ipynb +++ b/docs/src/sympy.ipynb @@ -40,7 +40,7 @@ "id": "07e49af1-a2e4-43e9-b265-38d917456ce0", "metadata": {}, "source": [ - "[SymPy](https://www.sympy.org/) is a computer algebra system like Mathematica and Maple. It primarily deals with algebraic expressions, rather than concrete numbers. However, all of the coordinate transformations and vector manipulations can be applied symbolically through Vector's SymPy backend.\n", + "SymPy is a computer algebra system like Mathematica and Maple. It primarily deals with algebraic expressions, rather than concrete numbers. However, all of the coordinate transformations and vector manipulations can be applied symbolically through Vector's SymPy backend.\n", "\n", "When comparing SymPy to the other backends, note that SymPy vector expressions have a different sign convention for operations on space-like and negative time-like 4D vectors. For all other backends, Vector's conventions were chosen to agree with popular HEP libraries, particularly [ROOT](https://root.cern), but for the SymPy backend, those conventions would insert piecewise if-then branches, which would complicate symbolic expressions.\n", "\n",