{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "798985b6-de4b-4c30-a935-85518daa8f1c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import os\n", "import sys\n", "\n", "parent_dir = os.path.abspath(\"..\")\n", "if parent_dir not in sys.path:\n", " sys.path.append(parent_dir)\n", "from magick_benchmark_utils import benchmark_mvg_save\n", "\n", "from functools import partial\n", "from IPython.display import Image\n", "\n", "import pandas as pd\n", "import numpy as np\n", "\n", "from lets_plot import *\n", "LetsPlot.setup_html()" ] }, { "cell_type": "code", "execution_count": 2, "id": "7a90957a-3445-4b5e-a594-02e65762d658", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "
\n", "
\n", " \n", "
\n", " \n", "
\n", "
\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def path_plot(n):\n", " # 1. Configuration\n", " POINTS_PER_OBJECT = 1000 # Fixed complexity per island\n", " \n", " # Calculate how many objects we need to reach roughly 'n' points\n", " num_objects = max(1, int(n / POINTS_PER_OBJECT))\n", " \n", " # Re-adjust actual N so arrays match perfectly (optional, but clean)\n", " actual_n_per_obj = POINTS_PER_OBJECT\n", " \n", " # 2. Create the Template (One Spiky Island)\n", " np.random.seed(42) # Consistent shape every time\n", " theta = np.linspace(0, 2 * np.pi, actual_n_per_obj)\n", " \n", " # Create a \"gear\" shape\n", " base_radius = 10.0\n", " spikes = 2.0 * np.sin(12 * theta) # 12 spikes per object\n", " jitter = np.random.normal(0, 0.2, actual_n_per_obj)\n", " r = base_radius + spikes + jitter\n", " \n", " template_x = r * np.cos(theta)\n", " template_y = r * np.sin(theta)\n", " \n", " # 3. Create the Grid Offsets\n", " # We want a roughly square grid (rows ~ cols)\n", " cols = int(np.ceil(np.sqrt(num_objects)))\n", " rows = int(np.ceil(num_objects / cols))\n", " \n", " # Generate X and Y offsets\n", " spacing = 30.0 # Enough space so they don't overlap\n", " \n", " all_x = []\n", " all_y = []\n", " all_ids = []\n", " \n", " obj_count = 0\n", " for row in range(rows):\n", " for col in range(cols):\n", " if obj_count >= num_objects:\n", " break\n", " \n", " # Shift the template to the new grid position\n", " center_x = col * spacing\n", " center_y = row * spacing\n", " \n", " all_x.append(template_x + center_x)\n", " all_y.append(template_y + center_y)\n", " \n", " # Create an ID array for this object (e.g., [0, 0, ...], [1, 1, ...])\n", " # This is vital for geom_path to know when to \"lift the pen\"\n", " all_ids.append(np.full(actual_n_per_obj, obj_count))\n", " \n", " obj_count += 1\n", " \n", " # 4. Flatten arrays for Lets-Plot\n", " x_final = np.concatenate(all_x)\n", " y_final = np.concatenate(all_y)\n", " ids_final = np.concatenate(all_ids)\n", " \n", " # 5. Plot\n", " # Note: group='id' tells geom_path these are separate islands\n", " return ggplot({'x': x_final, 'y': y_final, 'id': ids_final}, aes('x', 'y')) \\\n", " + geom_path(aes(group='id')) \\\n", " + coord_fixed() \\\n", " + theme_void() \\\n", " + guides(color='none')\n", "\n", "path_plot(50_000)" ] }, { "cell_type": "code", "execution_count": 3, "id": "e3995efc-e97c-41f8-888f-c7d24d13d961", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "
\n", "
\n", " \n", "
\n", " \n", "
\n", "
\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def polygon_plot(n):\n", " # 1. Configuration\n", " POINTS_PER_OBJECT = 1000 # Fixed complexity per island\n", " \n", " # Calculate how many objects we need to reach roughly 'n' points\n", " num_objects = max(1, int(n / POINTS_PER_OBJECT))\n", " \n", " # Re-adjust actual N so arrays match perfectly (optional, but clean)\n", " actual_n_per_obj = POINTS_PER_OBJECT\n", " \n", " # 2. Create the Template (One Spiky Island)\n", " np.random.seed(42) # Consistent shape every time\n", " theta = np.linspace(0, 2 * np.pi, actual_n_per_obj)\n", " \n", " # Create a \"gear\" shape\n", " base_radius = 10.0\n", " spikes = 2.0 * np.sin(12 * theta) # 12 spikes per object\n", " jitter = np.random.normal(0, 0.2, actual_n_per_obj)\n", " r = base_radius + spikes + jitter\n", " \n", " template_x = r * np.cos(theta)\n", " template_y = r * np.sin(theta)\n", " \n", " # 3. Create the Grid Offsets\n", " # We want a roughly square grid (rows ~ cols)\n", " cols = int(np.ceil(np.sqrt(num_objects)))\n", " rows = int(np.ceil(num_objects / cols))\n", " \n", " # Generate X and Y offsets\n", " spacing = 30.0 # Enough space so they don't overlap\n", " \n", " all_x = []\n", " all_y = []\n", " all_ids = []\n", " \n", " obj_count = 0\n", " for row in range(rows):\n", " for col in range(cols):\n", " if obj_count >= num_objects:\n", " break\n", " \n", " # Shift the template to the new grid position\n", " center_x = col * spacing\n", " center_y = row * spacing\n", " \n", " all_x.append(template_x + center_x)\n", " all_y.append(template_y + center_y)\n", " \n", " # Create an ID array for this object (e.g., [0, 0, ...], [1, 1, ...])\n", " # This is vital for geom_path to know when to \"lift the pen\"\n", " all_ids.append(np.full(actual_n_per_obj, obj_count))\n", " \n", " obj_count += 1\n", " \n", " # 4. Flatten arrays for Lets-Plot\n", " x_final = np.concatenate(all_x)\n", " y_final = np.concatenate(all_y)\n", " ids_final = np.concatenate(all_ids)\n", " \n", " # 5. Plot\n", " # Note: group='id' tells geom_path these are separate islands\n", " return ggplot({'x': x_final, 'y': y_final, 'id': ids_final}, aes('x', 'y')) \\\n", " + geom_polygon(aes(group='id')) \\\n", " + coord_fixed() \\\n", " + theme_void() \\\n", " + guides(color='none')\n", "\n", "polygon_plot(10_000)" ] }, { "cell_type": "code", "execution_count": 4, "id": "9b96a4e8-409d-43e5-be70-6dea153c0f02", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "
\n", "
\n", " \n", "
\n", " \n", "
\n", "
\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def random_path_plot(n):\n", " np.random.seed(42)\n", " \n", " x = np.random.uniform(size=n)\n", " y = np.random.uniform(size=n)\n", " v = np.random.normal(size=n)\n", "\n", " return ggplot({'x': x, 'y': y}, aes('x', 'y')) \\\n", " + geom_path()\n", "\n", "random_path_plot(1_000)" ] }, { "cell_type": "code", "execution_count": 5, "id": "99e19bb5-5bba-489f-8e42-3c2b9b1581e3", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "
\n", "
\n", " \n", "
\n", " \n", "
\n", "
\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def random_polygon_plot(n):\n", " np.random.seed(42)\n", " \n", " x = np.random.uniform(size=n)\n", " y = np.random.uniform(size=n)\n", " v = np.random.normal(size=n)\n", "\n", " return ggplot({'x': x, 'y': y}, aes('x', 'y')) \\\n", " + geom_polygon()\n", "\n", "random_polygon_plot(1_000)" ] }, { "cell_type": "code", "execution_count": 6, "id": "ab67f710-4aa2-4dea-be58-d1ce7e2a0faf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "path_regular@1x\n", "[1/6] n=1000 (0.3551s)\n", "[2/6] n=2000 (0.3623s)\n", "[3/6] n=5000 (0.5320s)\n", "[4/6] n=10000 (0.8325s)\n", "[5/6] n=25000 (1.2887s)\n", "[6/6] n=50000 (2.1470s)\n", "path_regular@2x\n", "[1/6] n=1000 (0.7315s)\n", "[2/6] n=2000 (0.8105s)\n", "[3/6] n=5000 (1.3096s)\n", "[4/6] n=10000 (1.8129s)\n", "[5/6] n=25000 (3.0816s)\n", "[6/6] n=50000 (5.2857s)\n", "polygon_regular@1x\n", "[1/6] n=1000 (0.3700s)\n", "[2/6] n=2000 (0.4211s)\n", "[3/6] n=5000 (0.7210s)\n", "[4/6] n=10000 (0.9169s)\n", "[5/6] n=25000 (1.8532s)\n", "[6/6] n=50000 (2.9746s)\n", "polygon_regular@2x\n", "[1/6] n=1000 (0.7561s)\n", "[2/6] n=2000 (0.8257s)\n", "[3/6] n=5000 (1.4840s)\n", "[4/6] n=10000 (2.5731s)\n", "[5/6] n=25000 (4.4706s)\n", "[6/6] n=50000 (9.0855s)\n", "path_random@1x\n", "[1/6] n=1000 (1.0514s)\n", "[2/6] n=2000 (2.2533s)\n", "[3/6] n=5000 (5.5864s)\n", "[4/6] n=10000 (10.6660s)\n", "[5/6] n=25000 (29.0151s)\n", "[6/6] n=50000 (TIMEOUT)\n", "path_random@2x\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Collecting stack traces from native extensions (`--native`) is not supported on your platform.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[1/6] n=1000 (4.7127s)\n", "[2/6] n=2000 (9.7572s)\n", "[3/6] n=5000 (29.4471s)\n", "[4/6] n=10000 (TIMEOUT)\n", "[5/6] n=25000 (SKIPPED - previous timeout)\n", "[6/6] n=50000 (SKIPPED - previous timeout)\n", "polygon_random@1x\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Collecting stack traces from native extensions (`--native`) is not supported on your platform.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[1/6] n=1000 (1.2951s)\n", "[2/6] n=2000 (2.5123s)\n", "[3/6] n=5000 (6.4129s)\n", "[4/6] n=10000 (13.5237s)\n", "[5/6] n=25000 (53.3059s)\n", "[6/6] n=50000 (TIMEOUT)\n", "polygon_random@2x\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Collecting stack traces from native extensions (`--native`) is not supported on your platform.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[1/6] n=1000 (6.1119s)\n", "[2/6] n=2000 (12.4378s)\n", "[3/6] n=5000 (37.4405s)\n", "[4/6] n=10000 (TIMEOUT)\n", "[5/6] n=25000 (SKIPPED - previous timeout)\n", "[6/6] n=50000 (SKIPPED - previous timeout)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Collecting stack traces from native extensions (`--native`) is not supported on your platform.\n" ] } ], "source": [ "ns = [1_000, 2_000, 5_000, 10_000, 25_000, 50_000]\n", "\n", "dfs = []\n", "\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(path_plot), file_prefix=\"path_regular\", scale=1, timeout=60)))\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(path_plot), file_prefix=\"path_regular\", scale=2, timeout=60)))\n", "\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(polygon_plot), file_prefix=\"polygon_regular\", scale=1, timeout=60)))\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(polygon_plot), file_prefix=\"polygon_regular\", scale=2, timeout=60)))\n", "\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(random_path_plot), file_prefix=\"path_random\", scale=1, timeout=60)))\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(random_path_plot), file_prefix=\"path_random\", scale=2, timeout=60)))\n", "\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(random_polygon_plot), file_prefix=\"polygon_random\", scale=1, timeout=60)))\n", "dfs.append(pd.DataFrame(benchmark_mvg_save(ns, partial(random_polygon_plot), file_prefix=\"polygon_random\", scale=2, timeout=60)))\n", "\n", "df = pd.concat(dfs, ignore_index=True)" ] }, { "cell_type": "code", "execution_count": 7, "id": "a594f912-468c-4f18-a55c-42e85aa66e38", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
nprefixscaletime_maptime_painttime_snapshottime_totalerror
01000path_regular10.0445950.0036440.3068160.355055NaN
12000path_regular10.0331220.0038010.3253640.362287NaN
25000path_regular10.0579720.0062070.4678510.532030NaN
310000path_regular10.1298370.0140630.6886410.832542NaN
425000path_regular10.2999580.0735700.9151531.288681NaN
\n", "
" ], "text/plain": [ " n prefix scale time_map time_paint time_snapshot \\\n", "0 1000 path_regular 1 0.044595 0.003644 0.306816 \n", "1 2000 path_regular 1 0.033122 0.003801 0.325364 \n", "2 5000 path_regular 1 0.057972 0.006207 0.467851 \n", "3 10000 path_regular 1 0.129837 0.014063 0.688641 \n", "4 25000 path_regular 1 0.299958 0.073570 0.915153 \n", "\n", " time_total error \n", "0 0.355055 NaN \n", "1 0.362287 NaN \n", "2 0.532030 NaN \n", "3 0.832542 NaN \n", "4 1.288681 NaN " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.head()" ] }, { "cell_type": "code", "execution_count": 8, "id": "1bce66f9-822d-4b22-864f-44011aaea309", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", "
\n", "
\n", " \n", "
\n", " \n", "
\n", "
\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ggplot(df) + geom_line(aes(x='n', y='time_snapshot', color=as_discrete('scale'))) + facet_grid(x='prefix', x_order=-1)" ] }, { "cell_type": "code", "execution_count": 9, "id": "8498337b-11c6-4848-a145-839bfa85ed1a", "metadata": {}, "outputs": [], "source": [ "df.to_csv(\"path_variant_optimized.csv\", index=False)" ] } ], "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.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }