{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Aggregation: Sum\n", "\n", "This notebook is a deep-dive on transformations for computing the sum with bounded stability.\n", "\n", "---\n", "Any functions that have not completed the proof-writing and vetting process may still be accessed if you opt-in to \"contrib\".\n", "Please contact us if you are interested in proof-writing. Thank you!" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:01.851670Z", "iopub.status.busy": "2025-06-04T18:19:01.851331Z", "iopub.status.idle": "2025-06-04T18:19:02.502355Z", "shell.execute_reply": "2025-06-04T18:19:02.502076Z" } }, "outputs": [], "source": [ "import opendp.prelude as dp\n", "dp.enable_features(\"contrib\")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Known/Unknown Dataset Size\n", "Constructing a sum transformation is easy!\n", "First, describe the metric space you are working in. \n", "This can also be filled in from the previous transformation." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.503932Z", "iopub.status.busy": "2025-06-04T18:19:02.503810Z", "iopub.status.idle": "2025-06-04T18:19:02.507107Z", "shell.execute_reply": "2025-06-04T18:19:02.506886Z" } }, "outputs": [], "source": [ "# the space of all integer vectors,\n", "# where distances between vectors are measured by the symmetric distance\n", "input_space = dp.vector_domain(dp.atom_domain(bounds=(0, 10))), dp.symmetric_distance()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Then construct the sum transformation:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.508382Z", "iopub.status.busy": "2025-06-04T18:19:02.508297Z", "iopub.status.idle": "2025-06-04T18:19:02.511007Z", "shell.execute_reply": "2025-06-04T18:19:02.510802Z" } }, "outputs": [], "source": [ "\n", "sum_trans = input_space >> dp.t.then_sum()\n", "\n", "# compute the sum\n", "assert sum_trans([1, 2, 4]) == 7 # 1 + 2 + 4\n", "\n", "# compute the sensitivity\n", "assert sum_trans.map(d_in=1) == 10 # d_in * max(|L|, U)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "In the integer case, since the sensitivity is scaled by $max(|L|, U)$, the sensitivity won't increase if you were to widen $L$ to $min(L, -U)$, or widen $U$ to $max(-L, U)$.\n", "This doesn't hold for floating-point datasets with unknown size, for reasons we'll cover in the next section.\n", "\n", "If the dataset size is public information, then restrict the input space:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.512182Z", "iopub.status.busy": "2025-06-04T18:19:02.512107Z", "iopub.status.idle": "2025-06-04T18:19:02.514482Z", "shell.execute_reply": "2025-06-04T18:19:02.514290Z" } }, "outputs": [], "source": [ "# the space of all length-3 integer vectors,\n", "# where distances between vectors are measured by the symmetric distance\n", "input_space = dp.vector_domain(dp.atom_domain(bounds=(-10, 10)), size=3), dp.symmetric_distance()\n", "sum_trans = input_space >> dp.t.then_sum()\n", "\n", "# compute the sum\n", "assert sum_trans([1, 2, 4]) == 7 # 1 + 2 + 4\n", "\n", "# compute the sensitivity\n", "assert sum_trans.map(d_in=2) == 20 # (d_in // 2) * (U - L)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Since the sensitivity is scaled by $U - L$, the sensitivity won't increase if you shift both bounds by the same fixed constant. Therefore, the sensitivity remains small in situations where $L$ and $U$ share the same sign and are large in magnitude.\n", "\n", "_All_ sum transformations expect a `d_in` in terms of the `SymmetricDistance`.\n", "However, when the dataset size is known, we are operating in the bounded-DP regime where adjacent datasets have the same size.\n", "This means it takes both an addition and a removal to change any single record, to reach any adjacent dataset.\n", "This results in a stair-step relationship between $d_{in}$ and $d_{out}$:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.515658Z", "iopub.status.busy": "2025-06-04T18:19:02.515582Z", "iopub.status.idle": "2025-06-04T18:19:02.833312Z", "shell.execute_reply": "2025-06-04T18:19:02.833047Z" } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAHDCAYAAACESXgYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQw9JREFUeJzt3Qm8TWX///8PGY7IWKYyNRpKg4RypyQnt0RUSN0qpQGFRk2igeqW0m1oEE1SGpQK6VSazJoHUYoyNHJQDmX9H+/r+1v7v/d2ZudcZ+9zXs/HY3H2tPa1hr2uz7rGUkEQBAYAAOBJaV9fBAAAIAQfAADAK4IPAADgFcEHAADwiuADAAB4RfABAAC8IvgAAABeEXwAAACvCD4AAIBXBB8AAMArgg8AAOAVwQcSwtSpU61UqVL2/fffe/3e2267zX1vfj7z66+/WrLJz/YWxTqTkabJqlSpkl1//fVWHBX37YNfBB8AUAAUOG/bts2OOOIIK46K+/bBL4IPJITzzz/f/vrrL2vQoEFRJwXIly+++ML9X1wz5+K+ffCrjOfvAzK11157uQVI5sy5TJky1qRJEyuOivv2wS9KPuDFli1bbPDgwdawYUMrX7681axZ00499VRbvnx5pm0+9L8eZ7VE++mnn+yiiy6yWrVquXU3a9bMHnvssd3S8P7771vLli0tJSXFDjroIHvooYf2aJvU5uOcc86xypUrW40aNeyqq66y7du3x7zno48+sk6dOrn3qL78lFNOsYULF8a854ILLnD7JTdtKcLnVq1a5T5XtWpVq1Klil144YX2559/5nt7fe/DnM6HvOyX8PE333xj5513ntsf++23n91yyy2uncLatWuta9eu7hjUrl3bxowZs9s6v/76a1uzZk2u0//ss8/aUUcd5fZDixYtbPHixS5zPvTQQ61cuXK2p55//nm3TfPnz9/tNe1zvfb555/nel/mVWFvH0DJB7y47LLL3AV14MCB1rRpU/vtt99cRvbVV1/ZMcccs9v7lXk8+eSTMc/t3LnThgwZEnPx27hxo7Vu3dpdjLVufW727NnWr18/S09Pdxdl+eyzz6xjx47udWVWf//9tw0fPtxltvmlwEMX/FGjRrmAYty4cfbHH3/YE0884V7Xxfpf//qXy/Suu+46K1u2rMs4TjrpJJeptGrVao++u1GjRu67lck8+uijLtO5++6787y9RbEP83o+5EbPnj3dXfno0aPttddeszvuuMOqV6/u9nn79u3dvnn66aftmmuucQHUiSeeGPmsPteuXTt75513cvyesWPH2tChQ61bt252xRVX2Keffmqnn366CwTzm/Z4nTt3dsHqc88959IVHxgoODz88MMLZV/62D5AdwZAoatSpUowYMCALF+fMmVKoNNx9erVWb7niiuuCPbaa6/grbfeijzXr1+/oE6dOsGvv/4a895evXq57/zzzz/d427dugUpKSnBDz/8EHnPl19+6daX15/B8OHD3WfOOOOM3dKn5z/55JPId5YrVy749ttvI+9Zt25dsM8++wQnnnhi5Lm+ffsGDRo0yPJ7Mnvuoosuinn+zDPPDGrUqBF5nJftLYp9mNP5kJf9Ej7u379/5Lm///47OOCAA4JSpUoFo0ePjjz/xx9/BBUqVHDrjqbPt2vXLsd0f/TRR0GZMmWCG2+8Meb5yy+/3K3j9ttvDwpK7969g5o1a7ptCa1fvz4oXbp0MHLkyDzty9zyuX0o2ah2gRe6a1q0aJGtW7cuX59XacKECRPsnnvusZNPPtk9pzzjhRdesC5duri/VQ0SLqmpqbZ582ZXKvDPP//Y3Llz3Z1c/fr1Y+529b78GjBgQMzjQYMGuf9ff/11951vvPGG+84DDzww8p46derYueee6+5MVaqQX7rbjaYSFt3xap152d6i2od7ej5k5uKLL478rfZDxx57rNsmleBEf+9hhx1m3333Xcxn9b7clHrceeedrlrnpptuink+LJ2IboyZkZHhSoXye5xVkvPzzz/HpEslHLt27XKvFca+zMv2AXuC4ANeKGhQHXW9evXsuOOOc8X28RlAVj7++GOX2fbu3dsVB4d++eUX27Rpkz388MOuKiB6URsI0cVb71NPmkMOOWS3dSsjyq/49akNROnSpV17FX2n2mBktn5l2MpA1BYhv6IDAKlWrZr7X9U+edneotqHe3I+5HafKBNVm4V99913t+e1n/JKwYQCS/XM2nvvvWNeUxVUfOas9heq0lK1W36cdtppLq2qZolvi6G2FwW9L/O6fXs6XojOK5RcBB/wQm0UdEF88MEHrW7dunbvvfe6emu1LciOMokePXq4i63aNURTBi5qZDhv3rxMlxNOOMF8ye9AW1l9TqUNWcmqZ9D/1SDk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Q6wwNbzhNfPp+9GdLVkcf/zxtn79eqtSpYp7PHXqVBc0xGdSS5YssYoVK+Z6vS+++KIbcyE6eNF6CyIgufPOO61r165unclMv5eZM2d6CVjij3N2FKicfPLJ9scff7huq4mkadOm1rp1a7vvvvvslltuKerkACjqko+7777bBRL/+9//7KuvvnKP77nnHnvwwQcj79HjcePG2aRJk2zRokUuM0tNTbXt27cXRvqRC+XKlcvVmAj77bdfnhrzVq9ePVezF+aVulJOnjzZ+vXrV+DrLs5ye5yTwYUXXuiuNX///XdRJwVAUQcfH374obsb7dy5s7sjPeuss6xjx462ePHiSKmHiu1VEqL3NW/e3J544glbt26du/sryZ5//nk74ogjXDdBdRPu0KFDTLHyo48+ak2aNLGUlBRr3LixTZgwYbfidZU06G5VAcKRRx5pCxYsiLznhx9+sC5duli1atVcwNesWTN7/fXXdyuO19+6sG/evNk9p0V35/HVLueee6717NkzZhvUs2Dfffd1xzS+2kV/Kw1DhgyJrFfbp5mJte3RdC4ojVu2bMl0Xynd5cuXd3e/0aVuCkYaNWrk9uFhhx1mDzzwQKafHzFihAuk9N2XXXaZ7dixI1fHYdeuXTZy5EhX5K/vP+qoo2zOnDlZHlOVIMWXGmjbwsxfrystn3zySWSf6DnRsbj44osj6Wzfvr17X3b0Ozv66KPdOXLsscfaRx99FPN6fLVLVueEziedR6LX9BmVYIq2t23btm67tH9OP/10+/bbb/N0LsoHH3zgzgm9ru/QDYhKWcL9PGrUqMix1Ofjz5FTTz3Vfv/9d5s/f362+wRACQg+VKyblpZm33zzjXusi+X7779vnTp1co9Xr17tqmN0QQ+pCLhVq1a7XZxCGRkZlp6eHrMUNyoK7927t1100UWuxEiZRPfu3V2wJk8//bTdeuutrqpBr991112uuPnxxx+PWc9NN91k11xzjSvCP/TQQ906wzvDAQMGuH357rvv2meffeZKpSpVqpTpMVSAoQxP6dKidcbr06ePzZo1y7Zu3Rp5bu7cua5U4swzz9zt/cqMlGkr8w7XqwyvV69eNmXKlJj36rEC16xKTd577z1r0aJFzHPKsLT+GTNmuDYB2l833nijPffcczHv0/kZ7uNnnnnGpUsBQG6Og4KZMWPG2H//+1/7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cKEbwbdx48b29ddf2wMPPOBmaz7vvPOsffv2loii51SKprQr3ZpfSe677z5LBhrIUOfKqlWrrE6dOta7d+/INiSi5cuXW7Vq1axRo0bu8ZNPPmmTJk2yNWvWWIMGDWzgwIHWq1cvS1SDBg2yc845x/71r39ZMvrf//5nixcvtn//+99uP2v/jxo1yo3I3b17dxs5cqSVKZOY9+dLly51edXBBx9sFSpUcDfN5557rptVXnmVrqMaW2ufffaxhFWAE9omrVatWgX9+/cPdu3atdtrek6vtW7dOkhWH3/8cVC6dOkgEa1YsSJo0KBBUKpUKZfGE088MVi3bl3k9Q0bNiRs2mX27NlBuXLlgurVqwcpKSnu8X777Rd06NAhaN++fbDXXnsFaWlpQSLSPj/qqKOCk046KWbR8y1btnR/n3zyyUGiatKkSfDbb7+5v9esWRM0bNgwqFKliku7jkfNmjWD7777LkhUzZs3D+bNm+f+fuSRR4IKFSoEV155ZTBx4sRg8ODBQaVKlYLJkycHiSr8zR5yyCHB6NGjg/Xr1wfJ4vbbbw/22WefoEePHkHt2rVd+mvUqBHccccdwV133eV+w7feemuQqE444YTgtttuizx+8sknXT4mv//+u/td61xKZAQfQeAyja+++irL1/Wa3pOoXn755WyXsWPHJmwG3q1bt6Bz587BL7/8EqxcudL93ahRo+CHH35IiuCjTZs2wU033eT+fuaZZ4Jq1aoFN954Y+T1G264ITj11FODRDRq1Ci3r+ODozJlygRffPFFkOiU+W3cuNH93adPn+D4448PNm3a5B5v2bLFBYC9e/cOEpWCje+//979ffTRRwcPP/xwzOtPP/100LRp0yCR9/+bb74ZXHXVVcG+++4blC1bNjjjjDOCWbNmBf/880+QyA466KDghRdeiNyc6Sbhqaeeirz+4osvBgcffHCQyOfOt99+G3ms/a39r+ulvPHGG0HdunWDREbwEQTujunxxx/P8nW9prvzRL8D0f9ZLYmagevu9NNPP40pabrsssuC+vXrux9XogcflStXdkFTeAFQxr18+fLI65999llQq1atIFEtXrw4OPTQQ4Orr7462LFjR9IGHwceeKC74Eb74IMPgnr16gWJSnfaS5cujfwOlAlGW7VqlctkkmH/69x59tlng9TUVJeRK+NTEB7+NhKN9mt4gyPKuD///PPIYwWFe++9d5CoGjRoELz//vuRxyot1vH4888/3ePVq1cn9A2z0NvFzK655hrr37+/XXXVVfbKK6/YokWL3KK/9dxll11m1113nSUq1W+/+OKLrq4ys0V1y4lK7T2i61VLlSrlGlF16dLF2rVrZ998840lOqVZ1OArJSUlZkpp1blu3rzZElXLli1dexvNxaQ2TZ9//nlke5JBmFa1DdLvINr+++/vtitRderUyZ3ronP9+eefj3ld7VdUp58MypYt69p/qJ3Bd999Z5dccok9/fTTdthhh1kiUmeCL7/80v29cuVK++effyKP5YsvvrCaNWtaourWrZvLl7S/3377bevTp487h9T+Q1asWOHO/4RW1NFPopg+fbqrM9NdX1haoL/1nCL6RNalS5fglltuyfJ13VFpexKR6uefeOKJTF8bMGBAULVq1YQu+VC9vdp5RJd07Ny5M/L43XffdVUbyUDVRiql0f5OlpKPI444wlVZqH3E888/H/P6/Pnzg/333z9IVD/99JMrdVU7p6FDh7q78bZt2waXXHKJe05tiV577bUgGUo+MqNSzPjSqERx8803u3YdF198sft9qnpUpa1qbzNp0iRXYjZkyJAgUW3ZsiU455xzIvmVqhyj2zfNnTs3eO6554JERm+XODt37nTdbmXfffd1EX2ie++991xL/9NOOy3T1/WaWkcrMk40al2u9L/++uuZvn7FFVe4HgAqwUlESlu9evWsc+fOmb5+44032s8//2yPPvqoJYMff/zRlYSoJX3FihUtkY0YMSLmcevWrV2vtdC1117rtueZZ56xRLVp0yYbPXq0zZo1y5UY6DxXCc4JJ5xgQ4YMSegeduqlo+tKIvcoyor2s/a7eoloOIUbbrjBnn32WVfCrd6NKnlVb5hE/w1s377d/v77b6tUqZIlG4IPAADgFW0+AACAVwQfAADAK4IPAADgFcEH4EHDhg3t/vvvj+kiOnPmTC/fVVRuueUW14W9pNM8G+oaWZBOOukkGzx4cMId8z2lRsMvvPBCUScDHhB8APnw/fffuwDi448/zvX8QQWdEU+dOtWqVq3q5bvySpMzao6Ym266yYqjvGT22g86VoUpL8c8kQOVm2++2fU8SdTebSg4BB9AIdJET6JZkTVxoQ8+vysr6lqsLoyaIK2k0sBVykQ16Fxm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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import pandas as pd\n", "pd.DataFrame(\n", " [(d_in, sum_trans.map(d_in)) for d_in in range(10)], \n", " columns=[\"max contributions (symmetric distance)\", \"sensitivity (absolute distance)\"]\n", ").plot.bar(0, title=\"sized_bounded_sum: $d_{in}$ vs. $d_{out}$\", width=0.9);" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Intuitively, if we say the symmetric distance between adjacent datasets is at most one, and all adjacent datasets differ by an even number of additions and removals, then the sensitivity is zero." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Floating-Point\n", "\n", "Floating-point addition is not closed, that is, \n", "adding two floating point numbers doesn't necessarily result in another floating-point number.\n", "To resolve this, the IEEE-754 floating-point standard requires a rounding to the nearest floating-point number.\n", "Unfortunately, this influences the sensitivity of the summation.\n", "\n", "In the OpenDP Library, stability maps account for the increased sensitivity due to floating-point rounding in intermediate operations by adding an additional constant term that scales with the dataset size." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.851248Z", "iopub.status.busy": "2025-06-04T18:19:02.851062Z", "iopub.status.idle": "2025-06-04T18:19:02.853745Z", "shell.execute_reply": "2025-06-04T18:19:02.853528Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "20.00000000004426\n" ] } ], "source": [ "input_space = dp.vector_domain(dp.atom_domain(bounds=(-10., 10.)), size=1000), dp.symmetric_distance()\n", "sum_trans = input_space >> dp.t.then_sum()\n", "\n", "# The sensitivity is now slightly larger than 20 because of the floating-point constant term\n", "print(sum_trans.map(d_in=2))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Unfortunately, the worst-case sensitivity analysis hits a snag when the dataset size is unknown, as the rounding error becomes unbounded.\n", "\n", "To keep the sensitivity finite, a dataset truncation operation is applied first:\n", "The dataset size is reduced to no greater than $2^{20}$ elements (a little over 1 million records), if necessary, via a simple random sample.\n", "\n", "The dataset truncation also causes a regression in the sensitivity, as it it is now scaled by $max(|L|, U, U - L)$. \n", "This accounts for the case where an adjacent dataset with one additional row needs to drop a random row to preserve the dataset size.\n", "In practice, the worst-case penalty on the sensitivity is when $L = -U$. " ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.855045Z", "iopub.status.busy": "2025-06-04T18:19:02.854941Z", "iopub.status.idle": "2025-06-04T18:19:02.857236Z", "shell.execute_reply": "2025-06-04T18:19:02.857021Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "20.000000093132268\n" ] } ], "source": [ "# show the worst-case degradation\n", "input_space = dp.vector_domain(dp.atom_domain(bounds=(-10., 10.))), dp.symmetric_distance()\n", "sum_trans = input_space >> dp.t.then_sum()\n", "\n", "# the sensitivity is now scaled by max(|L|, U, U - L) = max(|-10.|, 10., 10. - -10.) = 20.\n", "print(sum_trans.map(d_in=1))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "If the bounds share the same sign, then the sensitivity remains unchanged, save for the constant term to account for float rounding." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.858394Z", "iopub.status.busy": "2025-06-04T18:19:02.858308Z", "iopub.status.idle": "2025-06-04T18:19:02.860377Z", "shell.execute_reply": "2025-06-04T18:19:02.860172Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.000000093132268\n" ] } ], "source": [ "# if the bounds share sign, the sensitivity is unaffected\n", "input_space = dp.vector_domain(dp.atom_domain(bounds=(-10., 0.))), dp.symmetric_distance()\n", "sum_trans = input_space >> dp.t.then_sum()\n", "\n", "# the sensitivity is now scaled by max(|L|, U, U - L) = max(|-10.|, 0., 0. - -10.) = 10.\n", "print(sum_trans.map(d_in=1))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Specialized Floating-Point Constructors\n", "\n", "In the previous section an arbitrary limit of ($2^{20}$) on dataset size was baked into the constructor,\n", "to help simplify the library interface.\n", "This limit be manipulated by calling the appropriate constructor:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.861525Z", "iopub.status.busy": "2025-06-04T18:19:02.861439Z", "iopub.status.idle": "2025-06-04T18:19:02.863785Z", "shell.execute_reply": "2025-06-04T18:19:02.863585Z" } }, "outputs": [ { "data": { "text/plain": [ "10.00000000000295" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dp.t.make_bounded_float_checked_sum(size_limit=100, bounds=(-10., 0.)).map(d_in=1)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The size of the relaxation term varies according to the dataset size, magnitude of the bounds, summation algorithm and floating-point bit depth.\n", "The following visualization shows the effect of dataset size and choice of algorithm.\n", "\n", "To isolate the relaxation term for this visualization, the sensitivity is calculated for the case when datasets differ by zero additions or removals.\n", "A dataset with the same rows but a different row ordering will result in a different answer for the same sum query." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:02.865011Z", "iopub.status.busy": "2025-06-04T18:19:02.864925Z", "iopub.status.idle": "2025-06-04T18:19:03.088297Z", "shell.execute_reply": "2025-06-04T18:19:03.088055Z" } }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "bounds = (0., 10.)\n", "sizes = list(range(1, 10_000_000, 10_000))\n", "\n", "pd.DataFrame({\n", " \"dataset size\": sizes,\n", " \"sequential\": [dp.t.make_sized_bounded_float_checked_sum(size, bounds, S=\"Sequential\").map(0) for size in sizes],\n", " \"pairwise\": [dp.t.make_sized_bounded_float_checked_sum(size, bounds, S=\"Pairwise\").map(0) for size in sizes],\n", "}).plot(0, ylabel=\"sensitivity\"); # type: ignore" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The increase to sensitivity when using a sequential summation algorithm grows on the order of $O(n^2)$, while the pairwise algorithm grows on the order of $O(n \\log_2(n))$.\n", "\n", "OpenDP defaults to the pairwise algorithm, but the ability to configure the algorithm can be useful to calculate the sensitivity in situations where you don't have control over how the summation is computed.\n", "For example, floating-point aggregations in SQLite and MySQL both exhibit increases in sensitivity akin to the sequential algorithm.\n", "\n", "Beware, these relaxation terms grow far more quickly when the data type is adjusted to single-precision floats (`f32`)!\n", "\n", "\n", "\n", "\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Specialized Integer Constructors\n", "\n", "Just as in the case for floating-point types, there are specialized constructors for summation over integral types.\n", "\n", "The integral transformations from `make_sum` use properties of the bounds, data types and input metric to determine which strategy to use to compute the sum in a way that protects the sensitivity from the effects of numerical overflow.\n", "\n", "The following strategies are ordered by computational efficiency:\n", "\n", "* ``checked`` can be used when the dataset size multiplied by the bounds doesn't overflow.\n", "* ``monotonic`` can be used when the bounds share the same sign.\n", "* ``ordered`` can be used when the input metric is ``InsertDeleteDistance``.\n", "* ``split`` separately sums positive and negative numbers, and then adds these sums together.\n", "\n", "``monotonic``, ``ordered`` and ``split`` are implemented with saturation arithmetic. \n", "``checked``, ``monotonic`` and ``split`` protect against underestimating sensitivity by preserving associativity.\n", "\n", "These each have their own uses.\n", "For example, if the dataset is considered ordered-- that is, the dataset distance metric is sensitive to changes in row ordering (`InsertDeleteDistance`), then neighboring datasets share the same row ordering, and it becomes safe to use arithmetic that saturates at the minimum and maximum representable values of the data type." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:03.089585Z", "iopub.status.busy": "2025-06-04T18:19:03.089506Z", "iopub.status.idle": "2025-06-04T18:19:03.092503Z", "shell.execute_reply": "2025-06-04T18:19:03.092296Z" } }, "outputs": [ { "data": { "text/plain": [ "Transformation(\n", " input_domain = VectorDomain(AtomDomain(bounds=[1, 20], T=i32)),\n", " output_domain = AtomDomain(T=i32),\n", " input_metric = InsertDeleteDistance(),\n", " output_metric = AbsoluteDistance(i32))" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# because of the way this constructor is called...\n", "dp.t.make_sum(dp.vector_domain(dp.atom_domain(bounds=(1, 20))), dp.insert_delete_distance())\n", "# ...it internally uses this constructor to build the transformation:\n", "dp.t.make_bounded_int_ordered_sum(bounds=(1, 20))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "If you are trying to model a computation performed outside of OpenDP, you may not have access to saturation arithmetic.\n", "If this is the case, you can use `make_sized_bounded_int_checked_sum` to perform an overflow check at the moment the constructor is called." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2025-06-04T18:19:03.093686Z", "iopub.status.busy": "2025-06-04T18:19:03.093607Z", "iopub.status.idle": "2025-06-04T18:19:03.095838Z", "shell.execute_reply": "2025-06-04T18:19:03.095621Z" } }, "outputs": [ { "data": { "text/plain": [ "Transformation(\n", " input_domain = VectorDomain(AtomDomain(bounds=[-2, 4], T=i32), size=1234),\n", " output_domain = AtomDomain(T=i32),\n", " input_metric = SymmetricDistance(),\n", " output_metric = AbsoluteDistance(i32))" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Configure the type argument \"T\" to determine the necessary bit depth to avoid overflow\n", "dp.t.make_sized_bounded_int_checked_sum(1234, (-2, 4), T=\"i32\")" ] } ], "metadata": { "kernelspec": { "display_name": "psi", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.1" }, "vscode": { "interpreter": { "hash": "3220da548452ac41acb293d0d6efded0f046fab635503eb911c05f743e930f34" } } }, "nbformat": 4, "nbformat_minor": 2 }