{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# `ws3` Parallel Model Building + HiGHS (`highspy`) \u2014 Hands-On\n", "\n", "This notebook shows how to:\n", "1) Build a `ws3` optimization problem model in both **serial** and **parallel** modes.\n", "2) Solve the problem with **HiGHS** via the `highspy` bindings.\n", "3) Measure **wall time** to find the parallel parameter **sweet spot**.\n", "\n", "Note that we test building and solving a `ws3` optimization on up to 64 cores in this notebook. Obviously this will not work as intended if you have fewer than 64 cores available in the environment in which you run this code. Use common sense in interpreting output." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set up environment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below we (optionally) install the `ws3` package from the local source code in this repository using the `-e` flag to ensure that the package is installed in editable mode (i.e., any changes you make to the source code immediately affect `ws3` behaviour the next time you run the notebook). This is is not necessary if you have installed `ws3` using pip or another method.\n", "\n", "Note that the code in this notebook has been tested with the version of the `ws3` package that is included in this repository. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Set auto-reload to reload modules when they are changed." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "clobber_ws3 = False\n", "if clobber_ws3:\n", " %pip uninstall -y ws3\n", " %pip install -e .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 0: Setup\n", "\n", "On Linux, `fork` is the default (fastest). For Windows/macOS (and portable scripts), set `spawn`.\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import os, sys, time, platform, cProfile, pstats, io\n", "import ws3\n", "from ws3 import opt, forest\n", "import highspy\n", "\n", "\n", "# (Optional) force a portable start method in your launcher if needed:\n", "# ws3.forest.MP_CONTEXT = \"spawn\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Load data and configure model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Set some reasonable model parameters." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "base_year = 2020 # base year for the problem\n", "horizon = 20 # number of periods in the simulation horizon\n", "period_length = 10 # period length (in years)\n", "max_age = 1000 # maximum age of a stand (in years)\n", "tvy_name = \"totvol\" # name for total volume yield component\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create a new `ForestModel` object and import a model dataset from the `data` directory." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "fm = ws3.forest.ForestModel(model_name=\"tsa24\",\n", " model_path=\"data/woodstock_model_files_tsa24\",\n", " base_year=base_year,\n", " horizon=horizon,\n", " period_length=period_length,\n", " max_age=max_age)\n", "fm.import_landscape_section()\n", "fm.import_areas_section(convert_periods_to_years=period_length)\n", "fm.import_yields_section(convert_periods_to_years=period_length)\n", "fm.import_actions_section(convert_periods_to_years=period_length)\n", "fm.import_transitions_section(convert_periods_to_years=period_length)\n", "fm.initialize_areas()\n", "fm.add_null_action()\n", "fm.reset_actions()\n", "\n", "fm.actions[\"harvest\"].is_harvest = True # set harvest action to be a harvest action (needed for `cmp_c_z` to work correctly)\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "import time\n", "import functools\n", "from util import cmp_c_z, cmp_c_caa, cmp_c_ci\n", "from util import compile_scenario, plot_scenario" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "acodes = (\"null\", \"harvest\")\n", "expr = \"0.85 * totvol\"\n", "\n", "# Row builders / coefficient functions (project-specific)\n", "coeff_funcs = {\n", " \"z\": functools.partial(cmp_c_z, expr=expr), \n", " \"cflw_hv\": functools.partial(cmp_c_caa, expr=expr, acodes=[\"harvest\"]), \n", " \"cflw_ha\": functools.partial(cmp_c_caa, expr=\"1.\", acodes=[\"harvest\"]),\n", " \"cgen_gs\" : functools.partial(cmp_c_ci, yname=tvy_name, mask=None)\n", "}\n", "\n", "# Flow-constraint epsilons and reference period\n", "cflw_e = {\n", " \"cflw_hv\": ({p:0.05 for p in fm.periods}, 1),\n", " \"cflw_ha\": ({p:0.05 for p in fm.periods}, 1),\n", "}\n", "\n", "# General bounds (per period)\n", "gs_lb_rhs = fm.inventory(0, \"totvol\") * 0.90 # 90% of initial growing stock level\n", "cgen_data = {\n", " \"cgen_gs\": {\"lb\":{10:gs_lb_rhs}, \"ub\":{10:999999999.}}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Build and solve the problem with 1 core. You might want to open an interactive system terminal shell before running the rest of this notebook and keep an eye on memory allocation and CPU core activity while the model builds and solves (to get a realtime view of system resource utilization under different parameter values). " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "workers = 1" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 1 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 205.91s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have a reference time for building the problem in serial mode. \n", "\n", "We will also solve the problem with 1 core and compile and display the solution just to make sure the model is working correctly before proceeding to test various parallel model parameter values." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 3s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "WARNING: Number of threads available = 1 < 8 = Simplex concurrency to be used: Parallel performance may be less than anticipated\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 72 -1.0611783165e+10 Pr: 4434(1.52937e+09); Du: 0(1.72505e-07) 133s\n", " 144 -8.3945478108e+09 Pr: 4386(2.55624e+08) 160s\n", " 447 -7.5791507202e+09 Pr: 4186(1.16792e+08) 165s\n", " 1374 -6.4218954439e+09 Pr: 3702(1.01576e+08) 170s\n", " 2441 -5.3558475700e+09 Pr: 3528(6.53618e+07) 176s\n", " 3299 -4.7823649957e+09 Pr: 3415(1.09355e+08) 181s\n", " 4183 -3.9937973149e+09 Pr: 3586(9.95246e+07) 186s\n", " 5945 -3.3607201147e+09 Pr: 3442(1.1896e+08) 191s\n", " 8392 -2.4218065109e+09 Pr: 2861(6.12309e+07); Du: 0(3.2306e-08) 197s\n", " 10886 -1.9272268074e+09 Pr: 2453(3.86569e+07) 202s\n", " 12740 -1.7360223742e+09 Pr: 2455(2.60278e+07) 208s\n", " 15022 -1.7049224331e+09 Pr: 2251(6.11247e+07); Du: 0(8.4082e-08) 213s\n", " 16937 -1.6728486671e+09 Pr: 2153(3.21198e+07) 218s\n", " 18965 -1.6112238062e+09 Pr: 2182(2.64841e+07) 224s\n", " 21024 -1.5777114067e+09 Pr: 1900(2.30809e+07) 229s\n", " 23368 -1.5647453176e+09 Pr: 1867(8.2494e+07); Du: 0(2.13932e-08) 235s\n", " 25893 -1.5499435701e+09 Pr: 1790(1.33966e+07) 240s\n", " 28123 -1.4933861118e+09 Pr: 1601(5.46795e+06); Du: 0(4.15042e-09) 246s\n", " 29835 -1.4659928811e+09 Pr: 1221(3.05762e+06); Du: 0(7.24469e-09) 252s\n", " 31567 -1.4461292456e+09 Pr: 506(1.81901e+06); Du: 0(1.28772e-08) 259s\n", " 32297 -1.4441396268e+09 Pr: 174(36813.7); Du: 0(8.41078e-08) 264s\n", " 32478 -1.4440246751e+09 Pr: 0(0); Du: 0(5.81036e-07) 265s\n", "WARNING: Using concurrency of 1 for parallel strategy rather than minimum number (2) specified in options\n", "Using EKK primal simplex solver\n", " Iteration Objective Infeasibilities num(sum)\n", " 32478 -1.4440293920e+09 Pr: 0(0); Du: 3663(0.00175359) 265s\n", " 32507 -1.4440246751e+09 Pr: 0(0); Du: 0(8.1066e-06) 266s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 32507\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 1.6510699791e-15\n", "HiGHS run time : 266.48\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " period oha ohv ogs\n", "0 1 323917.431871 7.202118e+07 6.927929e+08\n", "1 2 307721.560278 6.842012e+07 6.798218e+08\n", "2 3 307721.560278 6.842012e+07 6.760743e+08\n", "3 4 307721.560278 6.842012e+07 6.789138e+08\n", "4 5 340113.303465 6.842012e+07 6.827610e+08\n", "5 6 340113.303465 6.842012e+07 6.854458e+08\n", "6 7 340113.303465 6.842012e+07 6.901670e+08\n", "7 8 333083.385036 6.842012e+07 6.975081e+08\n", "8 9 307721.560278 6.842012e+07 7.018079e+08\n", "9 10 307721.560278 6.842012e+07 6.997713e+08\n", "10 11 307721.560278 7.562224e+07 6.761588e+08\n", "11 12 340113.303465 7.562224e+07 6.450464e+08\n", "12 13 340113.303465 7.562224e+07 6.144736e+08\n", "13 14 340113.303465 7.562224e+07 5.893272e+08\n", "14 15 340113.303465 7.562224e+07 5.739232e+08\n", "15 16 340113.303465 7.562224e+07 5.680156e+08\n", "16 17 340113.303465 7.562224e+07 5.648146e+08\n", "17 18 340113.303465 7.562224e+07 5.603040e+08\n", "18 19 340113.303465 7.562224e+07 5.530622e+08\n", "19 20 340113.303465 7.562224e+07 5.426653e+08\n" ] }, { "data": { "image/png": "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zQ8KCpKXlxf1FYViypQpSklJUUxMTJZmXpKKFSum5557Tv7+/ua8fv36yc3NTUePHtUDDzygUqVKqW/fvpKuN/YvvPCC/P39ZbFYVLNmTf3nP/+x+ht45JFH1LhxY6v9dO3aVQ4ODlY1bevWrXJwcNDXX38tKft76DNvuTpw4IDatm2rEiVKqEKFCpoyZUqWY/nll1/00EMPqWTJkvLx8dGIESO0evXqPN2Xf/ToUdWpUydLMy9d/wyUycHBQSkpKZo/f75Zx/v162cu37Vrlzp37ix3d3e5ubmpffv2io+Pz7LNCxcuaMSIEapSpYosFosqVqyoJ598Ur///nuOOaampurBBx+Uh4eHtmzZkuvxLF++XO3atcsyxknm/4kbN240/0+sV6+e+f58/vnnqlevnlxcXBQUFKRdu3bluh9JKlGihLy9vbPUNknq0KGDvvvuO/3555+33E5RwBl6KCkpKds/9GvXrmWZN3v2bNWpU0cPPfSQihUrpq+++krPPPOMMjIyFBERYRV76NAh9enTR0899ZQGDx6smjVrqnfv3powYYISExPl6+trxn733Xc6deqUHnvsMXPeU089pXnz5ql///567rnndOzYMc2cOVO7du3S999/r+LFi+vs2bPq2LGjvL299eKLL8rT01PHjx/X559/Lkny9vbW7NmzNXToUD388MN65JFHJEn169eXdP0DbObZ6BdffFElS5bUkiVL1L17d3322Wd6+OGHJUmJiYlq27at/vrrLzPunXfeyfPZuLVr1+rnn39W//795evrq/379+udd97R/v37FR8fn6Uw9uzZU9WrV9frr79u/mf22muv6aWXXlKvXr00aNAgnTt3Tm+99ZZatWqlXbt2mf9ZLF26VJcvX9bQoUNVpkwZbdu2TW+99ZZ+/fVXLV26NNc8ExISlJaWluU/zBt/Tp9//rmeeeYZlSpVSv/973/Vo0cPnThxQmXKlJF0/ZLQLVu26LHHHlPFihV1/PhxzZ49W23atNGBAwdUokQJq20GBQXd9ZeYwb5R4wq3xvXq1UujR4/WkiVLNGrUKKtlS5YsUceOHVW6dGlJMo+3adOmio6O1pkzZzRjxgx9//33VnWsIPTu3Vu1atXSpEmTtHLlSr366qvy8vLS3Llz1a5dO02ePFkLFizQyJEj1bRpU/Ny3IyMDD300EP67rvv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AmjO7z78THC7HG2dAAAAAAAAyD8aegAAAAAA7BCX3BcCLp0BgOxRHwEAAAoOZ+gBAAAAALBDNPQAAAAAANghGnoAKIKqVKkiBweHLFNERIStUwMAAEAecQ89ABRB27dvV3p6uvl637596tChg3r27GnDrAAAAJAfNPQAUAR5e3tbvZ40aZKqVq2q1q1b2ygjAAAA5BeX3ANAEZeWlqaPP/5YAwYMkIODg63TAQAAQB5xhh4Airjly5frwoUL6tevX65xqampSk1NNV8nJycXcmYAAADIDWfoAaCIe//999W5c2f5+fnlGhcdHS0PDw9z8vf3v0MZAgAAIDs09ABQhP3yyy9at26dBg0adMvYqKgoJSUlmdPJkyfvQIYAAADICZfcA0ARFhMTIx8fH3Xp0uWWsRaLRRaL5Q5kBQAAslPlxZV5ijs+6db/r+PewBl6ACiiMjIyFBMTo/DwcBUrxve7AAAA9oaGHgCKqHXr1unEiRMaMGCArVMBAADA38ApGQAoojp27CjDMGydBgAAAP4mGvq7wN+5Fya/6+Q1/ub93Avupvf3XntvgTvhTvw9so/CzQsAABQOLrkHAAAAAMAO5auhnz17turXry93d3e5u7srJCREX3/9tbn86tWrioiIUJkyZeTm5qYePXrozJkzVts4ceKEunTpohIlSsjHx0ejRo3SX3/9ZRWzceNGNW7cWBaLRdWqVdO8efOy5DJr1ixVqVJFLi4uCg4O1rZt26yW5yUXAAAAAADsVb4a+ooVK2rSpElKSEjQjh071K5dO3Xr1k379++XJI0YMUJfffWVli5dqk2bNunUqVN65JFHzPXT09PVpUsXpaWlacuWLZo/f77mzZuncePGmTHHjh1Tly5d1LZtW+3evVvDhw/XoEGDtHr1ajNm8eLFioyM1Pjx47Vz5041aNBAYWFhOnv2rBlzq1wAAAAAALBn+Wrou3btqgceeEDVq1dXjRo19Nprr8nNzU3x8fFKSkrS+++/r6lTp6pdu3YKCgpSTEyMtmzZovj4eEnSmjVrdODAAX388cdq2LChOnfurFdeeUWzZs1SWlqaJGnOnDkKCAjQm2++qVq1amnYsGF69NFHNW3aNDOPqVOnavDgwerfv79q166tOXPmqESJEvrggw8kKU+5AAAAAABgz/72PfTp6elatGiRUlJSFBISooSEBF27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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "if problem.status() != ws3.opt.STATUS_OPTIMAL:\n", " print('Model not optimal.')\n", " df = None \n", "else:\n", " sch = fm.compile_schedule(problem)\n", " fm.apply_schedule(sch, \n", " force_integral_area=False, \n", " override_operability=False,\n", " fuzzy_age=False,\n", " recourse_enabled=False,\n", " verbose=False,\n", " compile_c_ycomps=True\n", " )\n", " df = compile_scenario(fm)\n", " print(df)\n", " fig, ax = plot_scenario(df)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild and solve the same problem, but with 2 cores." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "workers = 2" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 2 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 224.74s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 3s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "WARNING: Number of threads available = 2 < 8 = Simplex concurrency to be used: Parallel performance may be less than anticipated\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 62 -9.3778657921e+09 Pr: 4412(6.28471e+08) 146s\n", " 127 -8.1932871410e+09 Pr: 4203(4.61045e+08); Du: 0(9.41877e-08) 158s\n", " 489 -7.4080343917e+09 Pr: 4081(7.61578e+07); Du: 0(2.55574e-09) 164s\n", " 1788 -5.9517248380e+09 Pr: 3633(8.25822e+07); Du: 0(2.98644e-08) 169s\n", " 2754 -5.1174280373e+09 Pr: 3403(9.00023e+07); Du: 0(3.30517e-08) 175s\n", " 3935 -4.0599762397e+09 Pr: 3524(9.52876e+07) 181s\n", " 6048 -3.1846461666e+09 Pr: 3531(1.18075e+08) 186s\n", " 8802 -2.2111406738e+09 Pr: 2864(3.54672e+07) 191s\n", " 11645 -1.8548556807e+09 Pr: 2452(6.9844e+07) 196s\n", " 14859 -1.6961273901e+09 Pr: 2289(2.0375e+07) 202s\n", " 17684 -1.6239667123e+09 Pr: 2082(1.86446e+07) 207s\n", " 20715 -1.5689207037e+09 Pr: 1775(1.85489e+07); Du: 0(8.79974e-09) 212s\n", " 24110 -1.5624119647e+09 Pr: 1935(3.77035e+07) 218s\n", " 27254 -1.4958470606e+09 Pr: 1742(7.29301e+06); Du: 0(3.48364e-08) 224s\n", " 29916 -1.4647766851e+09 Pr: 1188(5.49377e+06); Du: 0(3.24601e-08) 229s\n", " 31827 -1.4461284711e+09 Pr: 510(337654); Du: 0(6.59284e-08) 236s\n", " 32675 -1.4440246751e+09 Pr: 0(0); Du: 0(4.25428e-09) 240s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 32675\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 2.0638374739e-15\n", "HiGHS run time : 240.43\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild and solve the same problem, but with 4 cores." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "workers = 4" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 4 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 180.17s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 3s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "WARNING: Number of threads available = 4 < 8 = Simplex concurrency to be used: Parallel performance may be less than anticipated\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 76 -1.0090968799e+10 Pr: 4435(5.65204e+08) 132s\n", " 134 -8.3735976761e+09 Pr: 4198(2.35676e+08) 142s\n", " 551 -7.3666232510e+09 Pr: 4085(7.74913e+07) 148s\n", " 1881 -5.9582324420e+09 Pr: 3658(7.05966e+07) 153s\n", " 3044 -5.0283147476e+09 Pr: 3442(9.61591e+07) 159s\n", " 3997 -4.0718214698e+09 Pr: 3463(8.85617e+07) 164s\n", " 6680 -3.0293157251e+09 Pr: 3469(1.21311e+08) 169s\n", " 10081 -2.0157907527e+09 Pr: 2749(7.67172e+07) 175s\n", " 13365 -1.7283759175e+09 Pr: 2391(4.09505e+07) 180s\n", " 16623 -1.6795285974e+09 Pr: 2133(3.02162e+07) 185s\n", " 19761 -1.5877275648e+09 Pr: 2154(1.78827e+07) 191s\n", " 22848 -1.5680189680e+09 Pr: 1940(5.94837e+07); Du: 0(7.01371e-08) 196s\n", " 26728 -1.4953484146e+09 Pr: 1641(4.0448e+06); Du: 0(6.26156e-08) 202s\n", " 29335 -1.4647734459e+09 Pr: 1212(2.73558e+06); Du: 0(2.48074e-07) 207s\n", " 31201 -1.4461285715e+09 Pr: 528(675780) 213s\n", " 32064 -1.4440246751e+09 Pr: 0(0); Du: 0(3.44383e-09) 216s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 32064\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 3.7974609520e-15\n", "HiGHS run time : 217.26\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild and solve the same problem, but with 8 cores." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "workers = 8" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 8 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 199.71s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 4s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 71 -9.1403711680e+09 Pr: 4398(7.6073e+08) 147s\n", " 129 -8.1701817935e+09 Pr: 4203(2.90735e+08) 155s\n", " 1144 -6.5908877337e+09 Pr: 3836(1.60856e+08) 161s\n", " 2370 -5.3544632983e+09 Pr: 3576(6.67971e+07) 166s\n", " 3529 -4.3716887834e+09 Pr: 3356(1.09251e+08) 173s\n", " 5293 -3.3461327758e+09 Pr: 3669(1.11313e+08) 178s\n", " 8607 -2.1856485784e+09 Pr: 2881(4.00959e+07); Du: 0(9.98316e-08) 183s\n", " 11756 -1.7688183088e+09 Pr: 2475(7.34996e+07) 188s\n", " 15545 -1.6829120005e+09 Pr: 2175(5.19987e+07); Du: 0(3.77496e-08) 194s\n", " 19078 -1.5857819348e+09 Pr: 2031(2.38788e+07) 199s\n", " 22510 -1.5685961999e+09 Pr: 1697(1.11435e+07) 204s\n", " 26509 -1.5164456066e+09 Pr: 1814(9.75099e+06); Du: 0(3.98685e-08) 209s\n", " 29621 -1.4613754431e+09 Pr: 930(1.98947e+06); Du: 0(1.65603e-09) 215s\n", " 31540 -1.4441720306e+09 Pr: 205(119988); Du: 0(1.24444e-07) 220s\n", " 31783 -1.4440246751e+09 Pr: 0(0); Du: 0(1.28472e-07) 221s\n", "WARNING: Using concurrency of 1 for parallel strategy rather than minimum number (2) specified in options\n", "Using EKK primal simplex solver\n", " Iteration Objective Infeasibilities num(sum)\n", " 31783 -1.4440246751e+09 Pr: 0(0); Du: 715(0.000372316) 221s\n", " 31797 -1.4440246751e+09 Pr: 0(0); Du: 0(1.04817e-05) 222s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 31797\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 7.4298149061e-16\n", "HiGHS run time : 222.54\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild and solve the same problem, but with 16 cores." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "workers = 16" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 16 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 227.73s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 3s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 66 -1.1251912865e+10 Pr: 4375(1.73568e+09) 151s\n", " 121 -8.4309021230e+09 Pr: 4410(3.91691e+08) 179s\n", " 448 -7.5477651357e+09 Pr: 4106(1.65751e+08) 185s\n", " 1667 -6.0596936456e+09 Pr: 3561(9.06301e+07) 191s\n", " 2653 -5.1788871593e+09 Pr: 3488(1.07611e+08) 196s\n", " 3810 -4.3729156030e+09 Pr: 3536(7.98353e+07) 202s\n", " 5526 -3.5430741540e+09 Pr: 3520(6.83297e+07) 207s\n", " 8576 -2.2154244405e+09 Pr: 2872(5.07237e+07) 213s\n", " 11998 -1.7614115077e+09 Pr: 2395(4.49786e+07) 218s\n", " 15479 -1.6936998494e+09 Pr: 2020(1.84433e+07) 224s\n", " 19102 -1.6008342410e+09 Pr: 2075(2.84657e+07) 229s\n", " 22465 -1.5684003360e+09 Pr: 1740(1.46312e+07) 234s\n", " 26609 -1.5087149887e+09 Pr: 1828(5.55282e+06) 240s\n", " 29126 -1.4634396103e+09 Pr: 1130(1.56525e+06); Du: 0(5.75094e-08) 245s\n", " 30982 -1.4444263286e+09 Pr: 332(943478); Du: 0(2.00021e-08) 250s\n", " 31453 -1.4440246751e+09 Pr: 0(0); Du: 0(3.40625e-08) 252s\n", "WARNING: Using concurrency of 1 for parallel strategy rather than minimum number (2) specified in options\n", "Using EKK primal simplex solver\n", " Iteration Objective Infeasibilities num(sum)\n", " 31453 -1.4440246751e+09 Pr: 0(0); Du: 132(0.000116388) 252s\n", " 31454 -1.4440246751e+09 Pr: 0(0); Du: 0(7.28502e-06) 252s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 31454\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 2.2289444718e-15\n", "HiGHS run time : 253.24\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild and solve the same problem, but with 32 cores." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "workers = 32" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 32 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 254.56s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 3s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 68 -9.8287125279e+09 Pr: 4436(4.32668e+08) 154s\n", " 128 -8.2487637044e+09 Pr: 4122(2.29633e+08) 173s\n", " 574 -7.2733102617e+09 Pr: 4108(8.02629e+07) 178s\n", " 1944 -5.8241401830e+09 Pr: 3519(7.09098e+07) 184s\n", " 3189 -4.8691855008e+09 Pr: 3283(8.24689e+07) 189s\n", " 4115 -4.0208477673e+09 Pr: 3543(1.4801e+08) 195s\n", " 6467 -3.0020163843e+09 Pr: 3480(1.08353e+08) 200s\n", " 9930 -1.9947083169e+09 Pr: 2704(2.31275e+08) 206s\n", " 13367 -1.7211583486e+09 Pr: 2322(4.99249e+07) 211s\n", " 16730 -1.6626995865e+09 Pr: 2198(2.38206e+07) 216s\n", " 20489 -1.5753482098e+09 Pr: 1964(6.11287e+07) 221s\n", " 24818 -1.5617086064e+09 Pr: 1933(1.5556e+07) 227s\n", " 29155 -1.4822839970e+09 Pr: 1387(4.64393e+06); Du: 0(4.05335e-08) 232s\n", " 31526 -1.4509350067e+09 Pr: 763(2.60451e+06); Du: 0(5.62414e-08) 238s\n", " 32923 -1.4440246751e+09 Pr: 0(0); Du: 0(3.45811e-08) 242s\n", "WARNING: Using concurrency of 1 for parallel strategy rather than minimum number (2) specified in options\n", "Using EKK primal simplex solver\n", " Iteration Objective Infeasibilities num(sum)\n", " 32923 -1.4440246751e+09 Pr: 0(0); Du: 83(8.98855e-05) 242s\n", " 32929 -1.4440246751e+09 Pr: 0(0); Du: 0(5.31874e-06) 242s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 32929\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 1.3208559833e-15\n", "HiGHS run time : 243.22\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild and solve the same problem, but with 64 cores." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "workers = 64" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "add_problem: build problem\n", "generate trees using 64 workers\n", "process trees\n", "_bld_p_m1: build problem\n", "_bld_p_m1: done building problem\n", "add_problem: compile flow constraints\n", "_cmp_cflw_m1: phase 1\n", "_cmp_cflw_m1: phase 2\n", "_cmp_cflw_m1: phase 3\n", "add_problem: compile general constraints\n", "Build time: 284.35s\n" ] } ], "source": [ "t0 = time.perf_counter()\n", "problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=True\n", ")\n", "t1 = time.perf_counter()\n", "print(f\"Build time: {t1 - t0:.2f}s\")" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running HiGHS 1.11.0 (git hash: 364c83a): Copyright (c) 2025 HiGHS under MIT licence terms\n", "WARNING: LP matrix packed vector contains 3 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 4.54747e-13] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [7.27596e-12, 7.27596e-12] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 1 |values| in [1.45519e-11, 1.45519e-11] less than or equal to 1e-09: ignored\n", "WARNING: LP matrix packed vector contains 2 |values| in [4.54747e-13, 1.45519e-11] less than or equal to 1e-09: ignored\n", "LP has 7778 rows; 461956 cols; 10014103 nonzeros\n", "Coefficient ranges:\n", " Matrix [1e-03, 1e+07]\n", " Cost [3e+00, 2e+07]\n", " Bound [1e+00, 1e+00]\n", " RHS [1e+00, 1e+09]\n", "Presolving model\n", "4476 rows, 458658 cols, 9697481 nonzeros 4s\n", "Dependent equations search running on 4398 equations with time limit of 1000.00s\n", "Dependent equations search removed 0 rows and 0 nonzeros in 0.02s (limit = 1000.00s)\n", "4475 rows, 458658 cols, 9413474 nonzeros 5s\n", "Presolve : Reductions: rows 4475(-3303); columns 458658(-3298); elements 9413474(-600629)\n", "Solving the presolved LP\n", "Using EKK parallel dual simplex solver - SIP with concurrency of 8\n", " Iteration Objective Infeasibilities num(sum)\n", " 0 0.0000000000e+00 Ph1: 0(0) 7s\n", " 67 -9.4437714651e+09 Pr: 4442(2.68216e+09) 173s\n", " 125 -8.2555413978e+09 Pr: 4145(2.33438e+08) 186s\n", " 545 -7.3750742902e+09 Pr: 4206(7.90811e+07) 192s\n", " 1851 -5.9849268130e+09 Pr: 3481(1.21812e+08); Du: 0(6.81872e-08) 197s\n", " 3023 -5.0439835211e+09 Pr: 3465(1.49353e+08) 202s\n", " 4045 -4.2461134672e+09 Pr: 3484(8.90032e+07) 207s\n", " 6646 -3.0152325529e+09 Pr: 3535(9.47888e+07) 212s\n", " 10192 -1.9960665728e+09 Pr: 2720(4.36531e+07) 218s\n", " 13704 -1.7268481182e+09 Pr: 2362(2.45244e+07) 223s\n", " 17530 -1.6369445728e+09 Pr: 2097(6.80344e+07) 228s\n", " 20812 -1.5764268782e+09 Pr: 1864(1.90361e+07) 233s\n", " 25475 -1.5336011536e+09 Pr: 1873(2.71848e+07); Du: 0(2.59463e-08) 239s\n", " 28624 -1.4757911322e+09 Pr: 1288(4.43103e+06) 244s\n", " 30905 -1.4506586473e+09 Pr: 719(788199); Du: 0(1.41957e-07) 250s\n", " 32465 -1.4440342403e+09 Pr: 0(0); Du: 0(4.43213e-07) 255s\n", " 32465 -1.4440246751e+09 Pr: 0(0); Du: 0(4.51458e-08) 255s\n", "WARNING: Using concurrency of 1 for parallel strategy rather than minimum number (2) specified in options\n", "Using EKK primal simplex solver\n", " Iteration Objective Infeasibilities num(sum)\n", " 32465 -1.4440246751e+09 Pr: 0(0); Du: 191(0.000131132) 256s\n", " 32499 -1.4440246751e+09 Pr: 0(0); Du: 0(5.60054e-06) 256s\n", "Solving the original LP from the solution after postsolve\n", "Model status : Optimal\n", "Simplex iterations: 32499\n", "Objective value : -1.4440246751e+09\n", "P-D objective error : 6.6042799166e-16\n", "HiGHS run time : 256.79\n" ] } ], "source": [ "problem.solve(verbose=True, threads=workers)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we can see from these results that throwing more cores at this problem improves performance, we do not get a benefit past 8 cores. As with many parallel processing problems, performance benefits of adding more parallel cores eventually gets overpowered by the fixed cost of worker process initialization (i.e., IPC cost from serialization of data that needs to be sent to and from worker threads at the start and end of work batches). The current parallel implementation in `ws3` requires a substantial amount of data to be sent back and forth, so this equilibrium maxes out relatively quickly. Hopefully in a future release we can find a way to reduce IPC cost, and maybe squeeze benefits from adding more cores if they are available." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we run profiling on 4, 8, and 16 core model building runs to get some more insight into what exactly is dominating runtime for these three cases." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Building problem with 1 workers\n", " 687767462 function calls (685741537 primitive calls) in 407.821 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 277 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 81 5.150 0.064 1050.072 12.964 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 1 0.016 0.016 268.823 268.823 /home/gep/projects/ws3/ws3/forest.py:968(_bld_p_m1)\n", "2033458/7696 18.879 0.000 236.965 0.031 /home/gep/projects/ws3/ws3/forest.py:1103(_bld_tree_m1)\n", " 80 31.158 0.389 104.831 1.310 /usr/lib/python3.12/selectors.py:451(select)\n", " 2682351 11.983 0.000 74.048 0.000 /home/gep/projects/ws3/ws3/forest.py:1495(compile_product)\n", " 375 34.520 0.092 72.136 0.192 {built-in method time.sleep}\n", " 2/1 4.492 2.246 65.126 65.126 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 2487718 28.985 0.000 53.620 0.000 /home/gep/projects/ws3/ws3/forest.py:1664(apply_action)\n", " 923912 6.263 0.000 51.945 0.000 /home/gep/projects/ws3/examples/util.py:128(cmp_c_caa)\n", " 30784 0.461 0.000 39.164 0.001 /home/gep/projects/ws3/ws3/common.py:1109(paths)\n", " 461956 5.348 0.000 37.198 0.000 /home/gep/projects/ws3/examples/util.py:83(cmp_c_z)\n", " 2309780 23.139 0.000 35.454 0.000 /home/gep/projects/ws3/ws3/common.py:1093(path)\n", " 461956 6.659 0.000 31.380 0.000 /home/gep/projects/ws3/examples/util.py:152(cmp_c_ci)\n", " 2682351 27.895 0.000 29.770 0.000 {built-in method builtins.eval}\n", " 9239120 10.397 0.000 23.955 0.000 /home/gep/projects/ws3/ws3/forest.py:1420(inventory)\n", " 20 5.205 0.260 23.415 1.171 /home/gep/projects/ws3/ws3/forest_helper.py:285(worker_cmp_cgen_phase3)\n", " 9708772 5.224 0.000 23.274 0.000 /home/gep/projects/ws3/ws3/common.py:82(hex_id)\n", " 40 20.450 0.511 20.450 0.511 /home/gep/projects/ws3/ws3/forest_helper.py:220(worker_cmp_cflw_phase3)\n", " 19189992 7.743 0.000 20.175 0.000 /home/gep/projects/ws3/ws3/core.py:324(__getitem__)\n", " 4502450 16.137 0.000 20.152 0.000 /home/gep/projects/ws3/ws3/forest.py:503(grow)\n", " 4 1.201 0.300 13.670 3.417 /home/gep/projects/ws3/ws3/forest_helper.py:105(worker_summarize_tree_batch)\n", " 7778 0.006 0.000 12.702 0.002 /home/gep/projects/ws3/ws3/opt.py:176(add_constraint)\n", " 1 0.027 0.027 12.623 12.623 /home/gep/projects/ws3/ws3/forest.py:1173(_cmp_cflw_m1)\n", " 7778 0.009 0.000 12.589 0.002 /home/gep/projects/ws3/ws3/opt.py:69(__init__)\n", " 7778 2.695 0.000 12.577 0.002 {built-in method builtins.all}\n", " 19189992 7.848 0.000 12.432 0.000 /home/gep/projects/ws3/ws3/core.py:45(__call__)\n", " 9708772 10.735 0.000 10.735 0.000 {built-in method _pickle.dumps}\n", " 38350126 6.527 0.000 9.963 0.000 /home/gep/projects/ws3/ws3/opt.py:72()\n", " 71675351 9.344 0.000 9.344 0.000 {method 'append' of 'list' objects}\n", " 86847728 9.214 0.000 9.214 0.000 /home/gep/projects/ws3/ws3/common.py:996(data)\n", "\n", "\n", "\n", "Building problem with 2 workers\n", " 199808762 function calls (199680982 primitive calls) in 390.364 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 545 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 83 1.429 0.017 1217.433 14.668 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 83 47.774 0.576 938.323 11.305 /usr/lib/python3.12/selectors.py:451(select)\n", " 83 11.567 0.139 414.717 4.997 {method 'poll' of 'select.epoll' objects}\n", " 2/1 0.000 0.000 390.363 390.363 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 1 0.000 0.000 390.042 390.042 /home/gep/projects/ws3/ws3/forest_helper.py:347(__exit__)\n", " 1 0.003 0.003 390.042 390.042 /usr/lib/python3.12/concurrent/futures/process.py:864(shutdown)\n", " 159/158 0.001 0.000 390.027 2.469 /usr/lib/python3.12/threading.py:1153(_wait_for_tstate_lock)\n", " 2/1 1.311 0.656 390.027 390.027 /usr/lib/python3.12/threading.py:1115(join)\n", " 474/73 0.002 0.000 388.935 5.328 {method 'acquire' of '_thread.lock' objects}\n", " 2/1 0.000 0.000 388.934 388.934 /usr/lib/python3.12/threading.py:1016(_bootstrap)\n", " 2/1 0.000 0.000 388.934 388.934 /usr/lib/python3.12/threading.py:1056(_bootstrap_inner)\n", " 1 0.004 0.004 388.934 388.934 /usr/lib/python3.12/concurrent/futures/process.py:340(run)\n", " 1 0.000 0.000 388.929 388.929 /usr/lib/python3.12/concurrent/futures/process.py:574(join_executor_internals)\n", " 1 0.000 0.000 388.929 388.929 /usr/lib/python3.12/concurrent/futures/process.py:578(_join_executor_internals)\n", " 4 0.000 0.000 387.904 96.976 /usr/lib/python3.12/multiprocessing/util.py:208(__call__)\n", " 1 0.000 0.000 387.904 387.904 /usr/lib/python3.12/multiprocessing/queues.py:147(join_thread)\n", " 298 194.424 0.652 301.104 1.010 {built-in method time.sleep}\n", " 1 0.015 0.015 185.773 185.773 /home/gep/projects/ws3/ws3/forest.py:968(_bld_p_m1)\n", " 32 0.029 0.001 117.171 3.662 /usr/lib/python3.12/concurrent/futures/process.py:415(wait_result_broken_or_wakeup)\n", " 91 0.002 0.000 98.245 1.080 /usr/lib/python3.12/multiprocessing/connection.py:1122(wait)\n", " 91 0.036 0.000 97.502 1.071 /usr/lib/python3.12/selectors.py:402(select)\n", " 1 0.000 0.000 55.185 55.185 /usr/lib/python3.12/multiprocessing/queues.py:214(_finalize_join)\n", " 28 43.702 1.561 43.702 1.561 {method 'dump' of '_pickle.Pickler' objects}\n", " 55 0.001 0.000 26.860 0.488 /usr/lib/python3.12/multiprocessing/connection.py:182(send_bytes)\n", " 55 0.014 0.000 26.591 0.483 /usr/lib/python3.12/multiprocessing/connection.py:406(_send_bytes)\n", " 1 0.075 0.075 24.116 24.116 /home/gep/projects/ws3/ws3/forest.py:1279(_cmp_cgen_m1)\n", " 15392 0.260 0.000 23.805 0.002 /home/gep/projects/ws3/ws3/common.py:1109(paths)\n", " 73 0.001 0.000 21.663 0.297 /usr/lib/python3.12/multiprocessing/connection.py:381(_send)\n", " 923912 13.205 0.000 21.373 0.000 /home/gep/projects/ws3/ws3/common.py:1093(path)\n", " 26 18.830 0.724 18.830 0.724 {built-in method _pickle.loads}\n", "\n", "\n", "\n", "Building problem with 4 workers\n", " 199820085 function calls (199692674 primitive calls) in 294.212 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 543 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 68 0.992 0.015 687.612 10.112 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 1 0.000 0.000 294.211 294.211 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 1 0.000 0.000 293.884 293.884 /home/gep/projects/ws3/ws3/forest_helper.py:347(__exit__)\n", " 1 0.004 0.004 293.884 293.884 /usr/lib/python3.12/concurrent/futures/process.py:864(shutdown)\n", " 127/122 0.000 0.000 293.865 2.409 /usr/lib/python3.12/threading.py:1153(_wait_for_tstate_lock)\n", " 2/1 0.000 0.000 293.865 293.865 /usr/lib/python3.12/threading.py:1115(join)\n", " 789/169 0.004 0.000 291.478 1.725 {method 'acquire' of '_thread.lock' objects}\n", " 2/1 0.000 0.000 291.474 291.474 /usr/lib/python3.12/threading.py:1016(_bootstrap)\n", " 2/1 0.000 0.000 291.474 291.474 /usr/lib/python3.12/threading.py:1056(_bootstrap_inner)\n", " 1 0.001 0.001 291.474 291.474 /usr/lib/python3.12/concurrent/futures/process.py:340(run)\n", " 1 0.000 0.000 291.474 291.474 /usr/lib/python3.12/concurrent/futures/process.py:574(join_executor_internals)\n", " 1 0.000 0.000 291.474 291.474 /usr/lib/python3.12/concurrent/futures/process.py:578(_join_executor_internals)\n", " 6 0.000 0.000 290.265 48.378 /usr/lib/python3.12/multiprocessing/util.py:208(__call__)\n", " 1 0.000 0.000 290.265 290.265 /usr/lib/python3.12/multiprocessing/queues.py:147(join_thread)\n", " 68 46.245 0.680 277.255 4.077 /usr/lib/python3.12/selectors.py:451(select)\n", " 66 0.695 0.011 152.453 2.310 /usr/lib/python3.12/concurrent/futures/process.py:415(wait_result_broken_or_wakeup)\n", " 214 99.358 0.464 142.185 0.664 {built-in method time.sleep}\n", " 1 0.019 0.019 112.891 112.891 /home/gep/projects/ws3/ws3/forest.py:968(_bld_p_m1)\n", " 193 0.003 0.000 58.819 0.305 /usr/lib/python3.12/multiprocessing/connection.py:1122(wait)\n", " 193 0.087 0.000 58.634 0.304 /usr/lib/python3.12/selectors.py:402(select)\n", " 64 50.125 0.783 50.125 0.783 {method 'dump' of '_pickle.Pickler' objects}\n", " 1 0.000 0.000 24.129 24.129 /home/gep/projects/ws3/ws3/forest.py:1173(_cmp_cflw_m1)\n", " 15392 0.254 0.000 23.324 0.002 /home/gep/projects/ws3/ws3/common.py:1109(paths)\n", " 60 8.006 0.133 22.857 0.381 /usr/lib/python3.12/multiprocessing/connection.py:246(recv)\n", " 923912 13.283 0.000 21.281 0.000 /home/gep/projects/ws3/ws3/common.py:1093(path)\n", " 60 17.061 0.284 17.061 0.284 {built-in method _pickle.loads}\n", " 68 6.393 0.094 16.267 0.239 {method 'poll' of 'select.epoll' objects}\n", " 125 0.001 0.000 14.102 0.113 /usr/lib/python3.12/multiprocessing/connection.py:182(send_bytes)\n", " 125 0.001 0.000 14.101 0.113 /usr/lib/python3.12/multiprocessing/connection.py:406(_send_bytes)\n", " 169 0.002 0.000 14.100 0.083 /usr/lib/python3.12/multiprocessing/connection.py:381(_send)\n", "\n", "\n", "\n", "Building problem with 8 workers\n", " 199851201 function calls (199723359 primitive calls) in 301.239 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 545 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 86 7.032 0.082 681.010 7.919 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 1 0.000 0.000 301.236 301.236 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 1 0.000 0.000 300.902 300.902 /home/gep/projects/ws3/ws3/forest_helper.py:347(__exit__)\n", " 1 0.004 0.004 300.902 300.902 /usr/lib/python3.12/concurrent/futures/process.py:864(shutdown)\n", " 155/154 0.001 0.000 300.881 1.954 /usr/lib/python3.12/threading.py:1153(_wait_for_tstate_lock)\n", " 2/1 0.000 0.000 300.881 300.881 /usr/lib/python3.12/threading.py:1115(join)\n", " 1330/334 0.028 0.000 295.999 0.886 {method 'acquire' of '_thread.lock' objects}\n", " 2/1 0.000 0.000 295.447 295.447 /usr/lib/python3.12/threading.py:1016(_bootstrap)\n", " 2/1 0.000 0.000 295.447 295.447 /usr/lib/python3.12/threading.py:1056(_bootstrap_inner)\n", " 1 0.001 0.001 295.446 295.446 /usr/lib/python3.12/concurrent/futures/process.py:340(run)\n", " 1 0.000 0.000 295.446 295.446 /usr/lib/python3.12/concurrent/futures/process.py:574(join_executor_internals)\n", " 1 0.000 0.000 295.446 295.446 /usr/lib/python3.12/concurrent/futures/process.py:578(_join_executor_internals)\n", " 10 0.000 0.000 293.868 29.387 /usr/lib/python3.12/multiprocessing/util.py:208(__call__)\n", " 1 0.000 0.000 293.868 293.868 /usr/lib/python3.12/multiprocessing/queues.py:147(join_thread)\n", " 86 57.895 0.673 191.861 2.231 /usr/lib/python3.12/selectors.py:451(select)\n", " 193 65.939 0.342 144.224 0.747 {built-in method time.sleep}\n", " 115 1.552 0.013 140.632 1.223 /usr/lib/python3.12/concurrent/futures/process.py:415(wait_result_broken_or_wakeup)\n", " 339 0.010 0.000 87.351 0.258 /usr/lib/python3.12/multiprocessing/connection.py:1122(wait)\n", " 339 0.016 0.000 86.429 0.255 /usr/lib/python3.12/selectors.py:402(select)\n", " 116 64.641 0.557 64.641 0.557 {method 'dump' of '_pickle.Pickler' objects}\n", " 1 0.001 0.001 38.528 38.528 /home/gep/projects/ws3/ws3/forest.py:1173(_cmp_cflw_m1)\n", " 108 8.978 0.083 31.152 0.288 /usr/lib/python3.12/multiprocessing/connection.py:246(recv)\n", " 339 8.134 0.024 24.589 0.073 {method 'poll' of 'select.poll' objects}\n", " 15392 0.255 0.000 23.410 0.002 /home/gep/projects/ws3/ws3/common.py:1109(paths)\n", " 923912 12.962 0.000 20.943 0.000 /home/gep/projects/ws3/ws3/common.py:1093(path)\n", " 108 20.139 0.186 20.139 0.186 {built-in method _pickle.loads}\n", " 225 0.002 0.000 16.733 0.074 /usr/lib/python3.12/multiprocessing/connection.py:182(send_bytes)\n", " 1 0.000 0.000 16.463 16.463 /home/gep/projects/ws3/ws3/forest.py:1279(_cmp_cgen_m1)\n", " 225 0.002 0.000 16.425 0.073 /usr/lib/python3.12/multiprocessing/connection.py:406(_send_bytes)\n", " 301 0.003 0.000 16.418 0.055 /usr/lib/python3.12/multiprocessing/connection.py:381(_send)\n", "\n", "\n", "\n", "Building problem with 16 workers\n", " 199920526 function calls (199791902 primitive calls) in 276.475 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 543 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 114 0.012 0.000 475.528 4.171 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 114 59.178 0.519 279.821 2.455 /usr/lib/python3.12/selectors.py:451(select)\n", " 1 0.000 0.000 276.474 276.474 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 1 0.000 0.000 276.143 276.143 /home/gep/projects/ws3/ws3/forest_helper.py:347(__exit__)\n", " 1 0.004 0.004 276.143 276.143 /usr/lib/python3.12/concurrent/futures/process.py:864(shutdown)\n", " 195/194 0.001 0.000 276.118 1.423 /usr/lib/python3.12/threading.py:1153(_wait_for_tstate_lock)\n", " 2/1 0.000 0.000 276.117 276.117 /usr/lib/python3.12/threading.py:1115(join)\n", " 2347/681 0.191 0.000 267.994 0.394 {method 'acquire' of '_thread.lock' objects}\n", " 2/1 0.000 0.000 265.797 265.797 /usr/lib/python3.12/threading.py:1016(_bootstrap)\n", " 2/1 0.000 0.000 265.797 265.797 /usr/lib/python3.12/threading.py:1056(_bootstrap_inner)\n", " 1 0.004 0.004 265.797 265.797 /usr/lib/python3.12/concurrent/futures/process.py:340(run)\n", " 1 0.000 0.000 265.793 265.793 /usr/lib/python3.12/concurrent/futures/process.py:574(join_executor_internals)\n", " 1 0.000 0.000 265.793 265.793 /usr/lib/python3.12/concurrent/futures/process.py:578(_join_executor_internals)\n", " 18 0.001 0.000 263.906 14.661 /usr/lib/python3.12/multiprocessing/util.py:208(__call__)\n", " 1 0.000 0.000 263.906 263.906 /usr/lib/python3.12/multiprocessing/queues.py:147(join_thread)\n", " 168 25.576 0.152 129.085 0.768 {built-in method time.sleep}\n", " 202 1.900 0.009 92.160 0.456 /usr/lib/python3.12/concurrent/futures/process.py:415(wait_result_broken_or_wakeup)\n", " 601 0.009 0.000 83.874 0.140 /usr/lib/python3.12/multiprocessing/connection.py:1122(wait)\n", " 212 80.261 0.379 80.261 0.379 {method 'dump' of '_pickle.Pickler' objects}\n", " 601 0.012 0.000 80.223 0.133 /usr/lib/python3.12/selectors.py:402(select)\n", " 1 0.001 0.001 27.911 27.911 /home/gep/projects/ws3/ws3/forest.py:1173(_cmp_cflw_m1)\n", " 196 9.684 0.049 26.920 0.137 /usr/lib/python3.12/multiprocessing/connection.py:246(recv)\n", " 601 6.826 0.011 24.492 0.041 {method 'poll' of 'select.poll' objects}\n", " 15392 0.263 0.000 23.368 0.002 /home/gep/projects/ws3/ws3/common.py:1109(paths)\n", " 409 0.003 0.000 23.187 0.057 /usr/lib/python3.12/multiprocessing/connection.py:182(send_bytes)\n", " 409 0.029 0.000 22.783 0.056 /usr/lib/python3.12/multiprocessing/connection.py:406(_send_bytes)\n", " 923912 12.893 0.000 20.852 0.000 /home/gep/projects/ws3/ws3/common.py:1093(path)\n", " 541 0.340 0.001 19.535 0.036 /usr/lib/python3.12/multiprocessing/connection.py:381(_send)\n", " 196 19.088 0.097 19.088 0.097 {built-in method _pickle.loads}\n", " 541 9.699 0.018 12.911 0.024 {built-in method posix.write}\n", "\n", "\n", "\n", "Building problem with 32 workers\n", " 200065847 function calls (199936173 primitive calls) in 324.315 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 545 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 184 0.036 0.000 551.675 2.998 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 1 0.000 0.000 324.311 324.311 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 1 0.000 0.000 323.968 323.968 /home/gep/projects/ws3/ws3/forest_helper.py:347(__exit__)\n", " 1 0.004 0.004 323.968 323.968 /usr/lib/python3.12/concurrent/futures/process.py:864(shutdown)\n", " 303/302 0.001 0.000 323.933 1.073 /usr/lib/python3.12/threading.py:1153(_wait_for_tstate_lock)\n", " 2/1 1.824 0.912 323.932 323.932 /usr/lib/python3.12/threading.py:1115(join)\n", "3966/1404 0.037 0.000 304.071 0.217 {method 'acquire' of '_thread.lock' objects}\n", " 2/1 0.000 0.000 303.528 303.528 /usr/lib/python3.12/threading.py:1016(_bootstrap)\n", " 2/1 0.000 0.000 303.528 303.528 /usr/lib/python3.12/threading.py:1056(_bootstrap_inner)\n", " 1 0.002 0.002 303.528 303.528 /usr/lib/python3.12/concurrent/futures/process.py:340(run)\n", " 1 0.000 0.000 303.526 303.526 /usr/lib/python3.12/concurrent/futures/process.py:574(join_executor_internals)\n", " 1 0.000 0.000 303.526 303.526 /usr/lib/python3.12/concurrent/futures/process.py:578(_join_executor_internals)\n", " 34 0.001 0.000 300.907 8.850 /usr/lib/python3.12/multiprocessing/util.py:208(__call__)\n", " 1 0.000 0.000 300.906 300.906 /usr/lib/python3.12/multiprocessing/queues.py:147(join_thread)\n", " 354 1.587 0.004 188.595 0.533 /usr/lib/python3.12/concurrent/futures/process.py:415(wait_result_broken_or_wakeup)\n", " 184 62.645 0.340 128.705 0.699 /usr/lib/python3.12/selectors.py:451(select)\n", " 1057 0.017 0.000 110.747 0.105 /usr/lib/python3.12/multiprocessing/connection.py:1122(wait)\n", " 1057 0.025 0.000 108.046 0.102 /usr/lib/python3.12/selectors.py:402(select)\n", " 380 97.451 0.256 97.451 0.256 {method 'dump' of '_pickle.Pickler' objects}\n", " 190 25.519 0.134 87.420 0.460 {built-in method time.sleep}\n", " 353 0.044 0.000 77.885 0.221 /usr/lib/python3.12/concurrent/futures/_base.py:199(as_completed)\n", " 1 1.872 1.872 58.850 58.850 /home/gep/projects/ws3/ws3/forest.py:1029(_gen_vars_m1)\n", " 348 6.866 0.020 46.064 0.132 /usr/lib/python3.12/multiprocessing/connection.py:246(recv)\n", " 729 0.009 0.000 38.524 0.053 /usr/lib/python3.12/multiprocessing/connection.py:182(send_bytes)\n", " 1057 6.164 0.006 36.126 0.034 {method 'poll' of 'select.poll' objects}\n", " 949 4.569 0.005 31.493 0.033 /usr/lib/python3.12/multiprocessing/connection.py:381(_send)\n", " 729 0.006 0.000 31.489 0.043 /usr/lib/python3.12/multiprocessing/connection.py:406(_send_bytes)\n", " 1 0.000 0.000 24.081 24.081 /usr/lib/python3.12/multiprocessing/queues.py:214(_finalize_join)\n", " 15392 0.262 0.000 23.443 0.002 /home/gep/projects/ws3/ws3/common.py:1109(paths)\n", " 348 22.085 0.063 22.085 0.063 {built-in method _pickle.loads}\n", "\n", "\n", "\n", "Building problem with 64 workers\n", " 200475142 function calls (200343762 primitive calls) in 385.793 seconds\n", "\n", " Ordered by: cumulative time\n", " List reduced from 543 to 30 due to restriction <30>\n", "\n", " ncalls tottime percall cumtime percall filename:lineno(function)\n", " 323 1.237 0.004 556.204 1.722 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)\n", " 1 0.000 0.000 385.776 385.776 /home/gep/projects/ws3/ws3/forest.py:872(add_problem)\n", " 1 0.000 0.000 385.438 385.438 /home/gep/projects/ws3/ws3/forest_helper.py:347(__exit__)\n", " 1 0.019 0.019 385.438 385.438 /usr/lib/python3.12/concurrent/futures/process.py:864(shutdown)\n", " 519/514 0.002 0.000 385.385 0.750 /usr/lib/python3.12/threading.py:1153(_wait_for_tstate_lock)\n", " 2/1 1.965 0.982 385.383 385.383 /usr/lib/python3.12/threading.py:1115(join)\n", "6975/2952 0.064 0.000 342.594 0.116 {method 'acquire' of '_thread.lock' objects}\n", " 2/1 0.000 0.000 341.896 341.896 /usr/lib/python3.12/threading.py:1016(_bootstrap)\n", " 2/1 0.001 0.000 341.896 341.896 /usr/lib/python3.12/threading.py:1056(_bootstrap_inner)\n", " 1 0.001 0.001 341.895 341.895 /usr/lib/python3.12/concurrent/futures/process.py:340(run)\n", " 1 0.000 0.000 341.895 341.895 /usr/lib/python3.12/concurrent/futures/process.py:574(join_executor_internals)\n", " 1 0.000 0.000 341.894 341.894 /usr/lib/python3.12/concurrent/futures/process.py:578(_join_executor_internals)\n", " 66 0.002 0.000 338.380 5.127 /usr/lib/python3.12/multiprocessing/util.py:208(__call__)\n", " 1 0.000 0.000 338.378 338.378 /usr/lib/python3.12/multiprocessing/queues.py:147(join_thread)\n", " 323 65.398 0.202 268.061 0.830 /usr/lib/python3.12/selectors.py:451(select)\n", " 1921 0.031 0.000 133.928 0.070 /usr/lib/python3.12/multiprocessing/connection.py:1122(wait)\n", " 1921 0.045 0.000 131.868 0.069 /usr/lib/python3.12/selectors.py:402(select)\n", " 1 1.052 1.052 126.511 126.511 /home/gep/projects/ws3/ws3/forest.py:1173(_cmp_cflw_m1)\n", " 642 0.585 0.001 116.736 0.182 /usr/lib/python3.12/concurrent/futures/process.py:415(wait_result_broken_or_wakeup)\n", " 700 104.076 0.149 104.076 0.149 {method 'dump' of '_pickle.Pickler' objects}\n", " 223 30.212 0.135 90.084 0.404 {built-in method time.sleep}\n", " 1337 0.022 0.000 61.920 0.046 /usr/lib/python3.12/multiprocessing/connection.py:182(send_bytes)\n", " 636 25.215 0.040 55.017 0.087 /usr/lib/python3.12/multiprocessing/connection.py:246(recv)\n", " 1 0.000 0.000 50.587 50.587 /usr/lib/python3.12/multiprocessing/queues.py:214(_finalize_join)\n", " 1921 8.294 0.004 49.576 0.026 {method 'poll' of 'select.poll' objects}\n", " 1 0.001 0.001 43.486 43.486 /usr/lib/python3.12/concurrent/futures/process.py:791(_launch_processes)\n", " 64 0.004 0.000 43.485 0.679 /usr/lib/python3.12/concurrent/futures/process.py:799(_spawn_process)\n", " 64 0.005 0.000 43.471 0.679 /usr/lib/python3.12/multiprocessing/process.py:110(start)\n", " 64 0.004 0.000 43.452 0.679 /usr/lib/python3.12/multiprocessing/context.py:279(_Popen)\n", " 64 0.002 0.000 43.447 0.679 /usr/lib/python3.12/multiprocessing/popen_fork.py:15(__init__)\n", "\n", "\n", "\n" ] } ], "source": [ "for workers in [1, 2, 4, 8, 16, 32, 64]:\n", " print(\"Building problem with\", workers, \"workers\")\n", " pr = cProfile.Profile()\n", " pr.enable()\n", " problem = fm.add_problem(\n", " name=\"test\",\n", " coeff_funcs=coeff_funcs, \n", " cflw_e=cflw_e, \n", " cgen_data=cgen_data,\n", " acodes=acodes,\n", " sense=ws3.opt.SENSE_MAXIMIZE, \n", " mask=None,\n", " workers=workers,\n", " verbose=False\n", " )\n", " pr.disable()\n", " \n", " s = io.StringIO()\n", " pstats.Stats(pr, stream=s).sort_stats(\"cumtime\").print_stats(30)\n", " print(s.getvalue())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Picking `max_workers` in `ws3`: quick guidance + deeper dive\n", "\n", "This note summarizes what we learned from running the build-phase under `cProfile` at 1, 2, 4, 8, 16, 32, 64 workers. It has two parts:\n", "\n", "- **Part A (quick start)**: practical advice for folks who just want a good `max_workers` value.\n", "- **Part B (deep dive)**: how to read the `cProfile` output, what\u2019s actually fast vs slow, and what to tweak if you\u2019re hacking on the parallel code.\n", "\n", "---\n", "\n", "## Part A \u2014 Quick start (what number should I use?)\n", "\n", "**TL;DR for this dataset/machine** \n", "Our wall-time results (build phase only):\n", "\n", "| workers | build wall time (s) |\n", "|---:|---:|\n", "| 1 | 407.8 |\n", "| 2 | 390.4 |\n", "| 4 | 294.2 |\n", "| 8 | 301.2 |\n", "| **16** | **276.5** \u2190 best here |\n", "| 32 | 324.3 |\n", "| 64 | 385.8 |\n", "\n", "**Recommendation:** \n", "- Start with **`max_workers=16`** on similar problem sizes/hardware. \n", "- If you have fewer physical cores, try **half your cores** (e.g., 8 on a 16-core box). \n", "- If you have many more cores (72\u2013150), don\u2019t jump straight to huge worker counts\u2014**diminishing returns** and **overhead** kick in quickly.\n", "\n", "**When to reduce workers:** \n", "- If you see *more* time spent with many workers than with fewer ones, drop to the smallest `N` near the best time (here, 16). \n", "- If the model is small, **stick with serial** (`max_workers=1`)\u2014parallel overhead can dominate.\n", "\n", "**HiGHS threads:** \n", "If you build **and** solve in the same session, don\u2019t give both the builder and HiGHS all cores. Example: **16 build workers + 8 solver threads** is often a good split.\n", "\n", "---\n", "\n", "## Part B \u2014 Deep dive (reading the profile, tuning, and hacking)\n", "\n", "### 1) Interpreting the profiles\n", "\n", "In good scaling regions (e.g., 4\u201316 workers above), the top cumulative-time frames are **productive compute**:\n", "\n", "- `forest._bld_tree_m1` (DFS tree construction)\n", "- `forest.compile_product`, `forest.apply_action`\n", "- `common.paths` / `common.path`\n", "- Your coefficient functions (e.g., `examples.util.cmp_c_*`)\n", "\n", "As you **overshoot** the sweet spot (32\u201364 workers above), **overhead** climbs into the top slots:\n", "\n", "- `selectors.select`, `connection.wait`, `poll` (multiprocessing pipes/queues)\n", "- `Pickler.dump` / `_pickle.loads` (task/result serialization)\n", "- `threading._wait_for_tstate_lock` / mass `acquire` calls (contention)\n", "- `concurrent.futures.process` join/shutdown\n", "- `time.sleep` backoffs in the executor\n", "- `asyncio.base_events._run_once` (Jupyter event loop noise)\n", "\n", "When overhead frames rival or exceed your compute frames in the **top 10 cumulative** list, you\u2019ve passed the knee of the curve.\n", "\n", "### 2) Choosing `max_workers` methodically\n", "\n", "1. Run a small sweep: `1, 2, 4, 8, 16, 32` (and 64 only if you must). \n", "2. Plot wall time vs workers (or just eyeball the printed numbers). \n", "3. Pick the **smallest** worker count near the **lowest** wall time where compute still dominates the top of the profile. \n", " - In our run: **16**.\n", "\n", "A simple heuristic for first tries:\n", "- **Small models**: 1 (serial).\n", "- **Medium**: 4\u20138.\n", "- **Large**: 8\u201316.\n", "- **Very large**: try 16 first; 32 only if you see clear gains (rare on single machine unless tasks are very chunky and memory is ample).\n", "\n", "### 3) Batch sizing\n", "\n", "We batch work per process to amortize scheduling and pickling costs. A practical rule:\n", "\n", "```text\n", "batch_size \u2248 len(tasks) // (2\u20134 * max_workers) + 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.3" } }, "nbformat": 4, "nbformat_minor": 4 }