{ "cells": [ { "cell_type": "markdown", "id": "feaca835", "metadata": {}, "source": [ "# Synthetic Trajectory Generation with Nomad\n", "\n", "This notebook demonstrates how to generate realistic synthetic human mobility trajectories." ] }, { "cell_type": "code", "execution_count": 1, "id": "c283fa66", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:28.574446Z", "iopub.status.busy": "2026-08-13T02:19:28.574446Z", "iopub.status.idle": "2026-08-13T02:19:30.744183Z", "shell.execute_reply": "2026-08-13T02:19:30.743183Z" } }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import time\n", "from pathlib import Path\n", "from joblib import Parallel, delayed\n", "\n", "import nomad.data as data_folder\n", "from nomad.city_gen import City\n", "from nomad.traj_gen import Agent, Population\n", "from nomad.stop_detection.viz import plot_pings, plot_time_barcode" ] }, { "cell_type": "code", "execution_count": 2, "id": "dbf680cf", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:30.746184Z", "iopub.status.busy": "2026-08-13T02:19:30.745183Z", "iopub.status.idle": "2026-08-13T02:19:31.444570Z", "shell.execute_reply": "2026-08-13T02:19:31.444570Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "City: Garden City\n", "Dimensions: (22, 22)\n", "Buildings: 106\n" ] } ], "source": [ "data_dir = Path(data_folder.__file__).parent\n", "city = City.from_geopackage(data_dir / \"garden-city.gpkg\")\n", "city._build_hub_network(hub_size=16)\n", "city.compute_gravity(exponent=2.0)\n", "city.compute_shortest_paths(callable_only=True)\n", "\n", "print(f\"City: {city.name}\")\n", "print(f\"Dimensions: {city.dimensions}\")\n", "print(f\"Buildings: {len(city.buildings_gdf)}\")" ] }, { "cell_type": "markdown", "id": "9534f6e1", "metadata": {}, "source": [ "## Part 1: Effect of Sampling Parameters on Sparsity\n", "\n", "Generate 3 agents with 2-day trajectories, varying beta_duration and beta_start \n", "to show their effect on sparsity (q = observed points / ground truth points)." ] }, { "cell_type": "code", "execution_count": 3, "id": "df0a983d", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:31.447572Z", "iopub.status.busy": "2026-08-13T02:19:31.446571Z", "iopub.status.idle": "2026-08-13T02:19:32.581034Z", "shell.execute_reply": "2026-08-13T02:19:32.581034Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Agent 0: q=0.051, beta_start=100, beta_dur=60\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Agent 1: q=0.031, beta_start=250, beta_dur=150\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Agent 2: q=0.135, beta_start=400, beta_dur=240\n" ] } ], "source": [ "population = Population(city)\n", "population.generate_agents(N=3, seed=42, name_count=2)\n", "\n", "# Vary beta_duration and beta_start to target different sparsity levels\n", "sampling_params = [\n", " {'beta_ping': 5, 'beta_start': 100, 'beta_durations': 60}, \n", " {'beta_ping': 5, 'beta_start': 250, 'beta_durations': 150}, \n", " {'beta_ping': 5, 'beta_start': 400, 'beta_durations': 240} \n", "]\n", "\n", "# Generate 2-day trajectories for quick visualization\n", "for i, (agent_id, agent) in enumerate(population.roster.items()):\n", " agent.generate_trajectory(\n", " datetime=pd.Timestamp(\"2024-01-01T07:00-04:00\"),\n", " end_time=pd.Timestamp(\"2024-01-03T07:00-04:00\"),\n", " seed=i\n", " )\n", "\n", " agent.set_beta_params(sampling_params[i])\n", " agent.sample_trajectory(\n", " replace_sparse_traj=True,\n", " seed=i\n", " )\n", " \n", " q = len(agent.sparse_traj) / len(agent.trajectory)\n", " print(f\"Agent {i}: q={q:.3f}, beta_start={sampling_params[i]['beta_start']}, \"\n", " f\"beta_dur={sampling_params[i]['beta_durations']}\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "2b39c915", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:32.583037Z", "iopub.status.busy": "2026-08-13T02:19:32.583037Z", "iopub.status.idle": "2026-08-13T02:19:34.735046Z", "shell.execute_reply": "2026-08-13T02:19:34.735046Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\franc\\AppData\\Local\\Temp\\ipykernel_22336\\3481434713.py:24: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", " plt.show()\n" ] } ], "source": [ "fig, axes = plt.subplots(2, 3, figsize=(15, 10), \n", " gridspec_kw={'height_ratios': [10, 1]})\n", "\n", "for i, (agent_id, agent) in enumerate(population.roster.items()):\n", " ax_map = axes[0, i]\n", " ax_barcode = axes[1, i]\n", " \n", " city.plot_city(ax=ax_map, doors=False, address=False)\n", " \n", " traj = agent.sparse_traj\n", " plot_pings(traj, ax=ax_map, s=15, point_color='red', \n", " x='x', y='y', timestamp='timestamp')\n", " \n", " plot_time_barcode(traj['timestamp'], ax=ax_barcode, set_xlim=True)\n", " \n", " q = len(traj) / len(agent.trajectory)\n", " ax_map.set_title(f\"Agent {i}: {len(traj)} obs (q={q:.2f})\\n\"\n", " f\"beta_start={sampling_params[i]['beta_start']}, \"\n", " f\"beta_dur={sampling_params[i]['beta_durations']}\")\n", " ax_map.set_axis_off()\n", "\n", "plt.tight_layout()\n", "plt.savefig('data/trajectories_visualization.png', dpi=150, bbox_inches='tight')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "4738e919", "metadata": {}, "source": [ "## Part 2: Parallel Generation at Scale\n", "\n", "Generate trajectories for 15 users using parallelization." ] }, { "cell_type": "code", "execution_count": 5, "id": "dae35ed1", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:34.737046Z", "iopub.status.busy": "2026-08-13T02:19:34.737046Z", "iopub.status.idle": "2026-08-13T02:19:34.750292Z", "shell.execute_reply": "2026-08-13T02:19:34.750292Z" } }, "outputs": [], "source": [ "def generate_agent_trajectory(args):\n", " \"\"\"Worker function for parallel generation.\"\"\"\n", " identifier, home, work, seed = args\n", " data_dir = Path(data_folder.__file__).parent\n", " city = City.from_geopackage(data_dir / \"garden-city.gpkg\")\n", " city._build_hub_network(hub_size=16)\n", " city.compute_gravity(exponent=2.0)\n", " city.compute_shortest_paths(callable_only=True)\n", " agent = Agent(identifier=identifier, city=city, home=home, workplace=work)\n", " \n", " agent.generate_trajectory(\n", " datetime=pd.Timestamp(\"2024-01-01T07:00-04:00\"),\n", " end_time=pd.Timestamp(\"2024-01-08T07:00-04:00\"),\n", " seed=seed\n", " )\n", " agent.set_beta_params(beta_start=None, beta_durations=None, beta_ping=5)\n", " agent.sample_trajectory(\n", " replace_sparse_traj=True,\n", " seed=seed\n", " )\n", " sparse_df = agent.sparse_traj.copy()\n", " sparse_df['user_id'] = identifier\n", " sparse_df['home'] = home\n", " sparse_df['workplace'] = work\n", " return sparse_df" ] }, { "cell_type": "code", "execution_count": 6, "id": "21bd0af5", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:34.752294Z", "iopub.status.busy": "2026-08-13T02:19:34.752294Z", "iopub.status.idle": "2026-08-13T02:19:34.765034Z", "shell.execute_reply": "2026-08-13T02:19:34.765034Z" } }, "outputs": [], "source": [ "n_agents = 15\n", "rng = np.random.default_rng(100)\n", "homes = city.buildings_gdf[city.buildings_gdf['building_type'] == 'home']['id'].to_numpy()\n", "workplaces = city.buildings_gdf[city.buildings_gdf['building_type'] == 'workplace']['id'].to_numpy()\n", "\n", "agent_params = [\n", " (f'agent_{i:04d}',\n", " rng.choice(homes),\n", " rng.choice(workplaces),\n", " i)\n", " for i in range(n_agents)\n", "]" ] }, { "cell_type": "code", "execution_count": 7, "id": "25602bf2", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:34.767036Z", "iopub.status.busy": "2026-08-13T02:19:34.767036Z", "iopub.status.idle": "2026-08-13T02:19:48.658913Z", "shell.execute_reply": "2026-08-13T02:19:48.658913Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[Parallel(n_jobs=-1)]: Using backend LokyBackend with 12 concurrent workers.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generating 15 agents in parallel...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[Parallel(n_jobs=-1)]: Done 2 out of 15 | elapsed: 11.9s remaining: 1.3min\n", "[Parallel(n_jobs=-1)]: Done 4 out of 15 | elapsed: 12.1s remaining: 33.4s\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[Parallel(n_jobs=-1)]: Done 6 out of 15 | elapsed: 12.3s remaining: 18.5s\n", "[Parallel(n_jobs=-1)]: Done 8 out of 15 | elapsed: 12.4s remaining: 10.9s\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[Parallel(n_jobs=-1)]: Done 10 out of 15 | elapsed: 12.6s remaining: 6.2s\n", "[Parallel(n_jobs=-1)]: Done 12 out of 15 | elapsed: 12.6s remaining: 3.1s\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generated 15 agents in 13.87s (0.92s per agent)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[Parallel(n_jobs=-1)]: Done 15 out of 15 | elapsed: 13.8s finished\n" ] } ], "source": [ "print(f\"Generating {n_agents} agents in parallel...\")\n", "start_time = time.time()\n", "\n", "results = Parallel(n_jobs=-1, verbose=10)(\n", " delayed(generate_agent_trajectory)(params) for params in agent_params\n", ")\n", "\n", "generation_time = time.time() - start_time\n", "print(f\"Generated {n_agents} agents in {generation_time:.2f}s ({generation_time/n_agents:.2f}s per agent)\")" ] }, { "cell_type": "code", "execution_count": 8, "id": "4682ebc3", "metadata": { "execution": { "iopub.execute_input": "2026-08-13T02:19:48.660915Z", "iopub.status.busy": "2026-08-13T02:19:48.660915Z", "iopub.status.idle": "2026-08-13T02:19:49.158483Z", "shell.execute_reply": "2026-08-13T02:19:49.158483Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saved sparse trajectories to data/trajectories_15_users\n" ] } ], "source": [ "parallel_population = Population(city)\n", "for df, params in zip(results, agent_params):\n", " identifier, home, work, seed = params\n", " agent = Agent(identifier=identifier, city=city, home=home, workplace=work, seed=seed)\n", " agent.sparse_traj = df.drop(columns=['home', 'workplace'])\n", " parallel_population.add_agent(agent, verbose=False)\n", "\n", "parallel_population.reproject_to_mercator(sparse_traj=True)\n", "\n", "output_path = 'data/trajectories_15_users'\n", "parallel_population.save_pop(\n", " sparse_path=str(output_path),\n", " fmt='parquet'\n", ")\n", "print(f\"Saved sparse trajectories to {output_path}\")" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "all", "formats": "ipynb,py:percent" }, "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.10.0" } }, "nbformat": 4, "nbformat_minor": 5 }