diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh index 1b37ed020..61dabd67f 100755 --- a/ci/test_wheel_cuopt.sh +++ b/ci/test_wheel_cuopt.sh @@ -66,11 +66,14 @@ cd - RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)" export RAPIDS_DATASET_ROOT_DIR -# Please enable this once ISSUE https://github.com/NVIDIA/cuopt/issues/94 is fixed # Run CLI tests timeout 10m bash ./python/libcuopt/libcuopt/tests/test_cli.sh # Run Python tests + +# Due to race condition in certain cases UCX might not be able to cleanup properly, so we set the number of threads to 1 +export OMP_NUM_THREADS=1 + RAPIDS_DATASET_ROOT_DIR=./datasets timeout 30m python -m pytest --verbose --capture=no ./python/cuopt/cuopt/tests/ # run jump tests and cvxpy integration tests for only nightly builds diff --git a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst index 6d942bde6..e321e319d 100644 --- a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst +++ b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst @@ -16,6 +16,13 @@ You may use the following functions to determine the number of bytes used to rep .. doxygenfunction:: cuOptGetIntSize .. doxygenfunction:: cuOptGetFloatSize +Version Information +------------------- + +You may use the following function to get the version of the cuOpt library + +.. doxygenfunction:: cuOptGetVersion + Status Codes ------------ @@ -25,6 +32,9 @@ Every function in the C API returns a status code that indicates success or fail .. doxygendefine:: CUOPT_INVALID_ARGUMENT .. doxygendefine:: CUOPT_MPS_FILE_ERROR .. doxygendefine:: CUOPT_MPS_PARSE_ERROR +.. doxygendefine:: CUOPT_VALIDATION_ERROR +.. doxygendefine:: CUOPT_OUT_OF_MEMORY +.. doxygendefine:: CUOPT_RUNTIME_ERROR Optimization Problem -------------------- @@ -156,9 +166,22 @@ These constants are used as parameter names in the :c:func:`cuOptSetParameter`, .. doxygendefine:: CUOPT_MIP_ABSOLUTE_TOLERANCE .. doxygendefine:: CUOPT_MIP_RELATIVE_TOLERANCE .. doxygendefine:: CUOPT_MIP_INTEGRALITY_TOLERANCE +.. doxygendefine:: CUOPT_MIP_ABSOLUTE_GAP +.. doxygendefine:: CUOPT_MIP_RELATIVE_GAP .. doxygendefine:: CUOPT_MIP_SCALING .. doxygendefine:: CUOPT_MIP_HEURISTICS_ONLY +.. doxygendefine:: CUOPT_MIP_PRESOLVE .. doxygendefine:: CUOPT_PRESOLVE +.. doxygendefine:: CUOPT_LOG_TO_CONSOLE +.. doxygendefine:: CUOPT_CROSSOVER +.. doxygendefine:: CUOPT_FOLDING +.. doxygendefine:: CUOPT_AUGMENTED +.. doxygendefine:: CUOPT_DUALIZE +.. doxygendefine:: CUOPT_ORDERING +.. doxygendefine:: CUOPT_ELIMINATE_DENSE_COLUMNS +.. doxygendefine:: CUOPT_CUDSS_DETERMINISTIC +.. doxygendefine:: CUOPT_BARRIER_DUAL_INITIAL_POINT +.. doxygendefine:: CUOPT_DUAL_POSTSOLVE .. doxygendefine:: CUOPT_SOLUTION_FILE .. doxygendefine:: CUOPT_NUM_CPU_THREADS .. doxygendefine:: CUOPT_USER_PROBLEM_FILE @@ -186,6 +209,7 @@ These constants are used to configure `CUOPT_METHOD` via :c:func:`cuOptSetIntege .. doxygendefine:: CUOPT_METHOD_CONCURRENT .. doxygendefine:: CUOPT_METHOD_PDLP .. doxygendefine:: CUOPT_METHOD_DUAL_SIMPLEX +.. doxygendefine:: CUOPT_METHOD_BARRIER Solving an LP or MIP @@ -206,12 +230,15 @@ The output of a solve is a `cuOptSolution` object. The following functions may be used to access information from a `cuOptSolution` .. doxygenfunction:: cuOptGetTerminationStatus +.. doxygenfunction:: cuOptGetErrorStatus +.. doxygenfunction:: cuOptGetErrorString .. doxygenfunction:: cuOptGetPrimalSolution .. doxygenfunction:: cuOptGetObjectiveValue .. doxygenfunction:: cuOptGetSolveTime .. doxygenfunction:: cuOptGetMIPGap .. doxygenfunction:: cuOptGetSolutionBound .. doxygenfunction:: cuOptGetDualSolution +.. doxygenfunction:: cuOptGetDualObjectiveValue .. doxygenfunction:: cuOptGetReducedCosts When you are finished with a `cuOptSolution` object you should destory it with diff --git a/docs/cuopt/source/cuopt-python/routing/routing-example.ipynb b/docs/cuopt/source/cuopt-python/routing/routing-example.ipynb index 2cf903c46..b376ac8e4 100644 --- a/docs/cuopt/source/cuopt-python/routing/routing-example.ipynb +++ b/docs/cuopt/source/cuopt-python/routing/routing-example.ipynb @@ -12,10 +12,62 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 1, "id": "2cb694f7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/luffy/.local/lib/python3.12/site-packages/cudf/utils/_ptxcompiler.py:64: UserWarning: Error getting driver and runtime versions:\n", + "\n", + "stdout:\n", + "\n", + "\n", + "\n", + "stderr:\n", + "\n", + "Traceback (most recent call last):\n", + " File \"\", line 4, in \n", + " File \"/home/luffy/miniforge3/envs/cuopt/lib/python3.12/site-packages/numba_cuda/numba/cuda/cudadrv/driver.py\", line 393, in safe_cuda_api_call\n", + " return self._check_cuda_python_error(fname, libfn(*args))\n", + " ^^^^^^^^^^^^\n", + "TypeError: cuDriverGetVersion() takes no arguments (1 given)\n", + "\n", + "\n", + "Not patching Numba\n", + " warnings.warn(msg, UserWarning)\n", + "/home/luffy/.local/lib/python3.12/site-packages/cupy/_environment.py:596: UserWarning: \n", + "--------------------------------------------------------------------------------\n", + "\n", + " CuPy may not function correctly because multiple CuPy packages are installed\n", + " in your environment:\n", + "\n", + " cupy, cupy-cuda12x\n", + "\n", + " Follow these steps to resolve this issue:\n", + "\n", + " 1. For all packages listed above, run the following command to remove all\n", + " existing CuPy installations:\n", + "\n", + " $ pip uninstall \n", + "\n", + " If you previously installed CuPy via conda, also run the following:\n", + "\n", + " $ conda uninstall cupy\n", + "\n", + " 2. Install the appropriate CuPy package.\n", + " Refer to the Installation Guide for detailed instructions.\n", + "\n", + " https://docs.cupy.dev/en/stable/install.html\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\n", + " warnings.warn(f'''\n" + ] + } + ], "source": [ "from cuopt import routing\n", "from cuopt import distance_engine\n", @@ -61,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 2, "id": "5d12f05d", "metadata": {}, "outputs": [], @@ -100,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 3, "id": "2c824c99", "metadata": {}, "outputs": [], @@ -122,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 4, "id": "4e08f664", "metadata": {}, "outputs": [], @@ -152,22 +204,50 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 5, "id": "9975bf1a", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Waypoint graph node to time matrix index mapping \n", - "{np.int64(0): 0, np.int64(4): 1, np.int64(5): 2, np.int64(6): 3}\n", - "\n", - " 0 1 2 3\n", - "0 0.0 6.0 4.0 6.0\n", - "1 6.0 0.0 4.0 6.0\n", - "2 4.0 4.0 0.0 4.0\n", - "3 6.0 6.0 4.0 0.0\n" + "ename": "RuntimeError", + "evalue": "CuPy failed to load libnvrtc.so.12: OSError: libnvrtc.so.12: cannot open shared object file: No such file or directory", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mOSError\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32mcupy_backends/cuda/_softlink.pyx:25\u001b[39m, in \u001b[36mcupy_backends.cuda._softlink.SoftLink.__init__\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniforge3/envs/cuopt/lib/python3.12/ctypes/__init__.py:379\u001b[39m, in \u001b[36mCDLL.__init__\u001b[39m\u001b[34m(self, name, mode, handle, use_errno, use_last_error, winmode)\u001b[39m\n\u001b[32m 378\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m handle \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m379\u001b[39m \u001b[38;5;28mself\u001b[39m._handle = \u001b[43m_dlopen\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 380\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "\u001b[31mOSError\u001b[39m: libnvrtc.so.12: cannot open shared object file: No such file or directory", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[31mRuntimeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 6\u001b[39m\n\u001b[32m 1\u001b[39m waypoint_graph = distance_engine.WaypointMatrix(\n\u001b[32m 2\u001b[39m offsets,\n\u001b[32m 3\u001b[39m edges,\n\u001b[32m 4\u001b[39m weights\n\u001b[32m 5\u001b[39m )\n\u001b[32m----> \u001b[39m\u001b[32m6\u001b[39m cost_matrix = \u001b[43mwaypoint_graph\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcompute_cost_matrix\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtarget_locations\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 7\u001b[39m transit_time_matrix = cost_matrix.copy(deep=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 8\u001b[39m target_map = {v:k \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(target_locations)}\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniforge3/envs/cuopt/lib/python3.12/site-packages/cuopt/utilities/exception_handler.py:60\u001b[39m, in \u001b[36mcatch_cuopt_exception..func\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 58\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(err[\u001b[33m\"\u001b[39m\u001b[33mmsg\u001b[39m\u001b[33m\"\u001b[39m])\n\u001b[32m 59\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m60\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[32m 61\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 62\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m e\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniforge3/envs/cuopt/lib/python3.12/site-packages/cuopt/utilities/exception_handler.py:36\u001b[39m, in \u001b[36mcatch_cuopt_exception..func\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 33\u001b[39m \u001b[38;5;129m@functools\u001b[39m.wraps(f)\n\u001b[32m 34\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mfunc\u001b[39m(*args, **kwargs):\n\u001b[32m 35\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m36\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 37\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 38\u001b[39m err_msg = \u001b[38;5;28mstr\u001b[39m(e)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniforge3/envs/cuopt/lib/python3.12/site-packages/cuopt/distance_engine/waypoint_matrix.py:133\u001b[39m, in \u001b[36mWaypointMatrix.compute_cost_matrix\u001b[39m\u001b[34m(self, target_locations)\u001b[39m\n\u001b[32m 130\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m target_locations.shape[\u001b[32m0\u001b[39m] <= \u001b[32m0\u001b[39m:\n\u001b[32m 131\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33m\"\"\"\u001b[39m\u001b[33mTarget_locations length must be positive\u001b[39m\u001b[33m\"\"\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m133\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcompute_cost_matrix\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtarget_locations\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/miniforge3/envs/cuopt/lib/python3.12/site-packages/cuopt/distance_engine/waypoint_matrix_wrapper.pyx:81\u001b[39m, in \u001b[36mcuopt.distance_engine.waypoint_matrix_wrapper.WaypointMatrix.compute_cost_matrix\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/cudf/utils/performance_tracking.py:51\u001b[39m, in \u001b[36m_performance_tracking..wrapper\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 43\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m nvtx.enabled():\n\u001b[32m 44\u001b[39m stack.enter_context(\n\u001b[32m 45\u001b[39m nvtx.annotate(\n\u001b[32m 46\u001b[39m message=func.\u001b[34m__qualname__\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 49\u001b[39m )\n\u001b[32m 50\u001b[39m )\n\u001b[32m---> \u001b[39m\u001b[32m51\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/cudf/core/dataframe.py:810\u001b[39m, in \u001b[36mDataFrame.__init__\u001b[39m\u001b[34m(self, data, index, columns, dtype, copy, nan_as_null)\u001b[39m\n\u001b[32m 808\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mdescr\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m arr_interface:\n\u001b[32m 809\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(arr_interface[\u001b[33m\"\u001b[39m\u001b[33mdescr\u001b[39m\u001b[33m\"\u001b[39m]) == \u001b[32m1\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m810\u001b[39m new_df = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_from_arrays\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 811\u001b[39m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindex\u001b[49m\u001b[43m=\u001b[49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcolumns\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcolumns\u001b[49m\n\u001b[32m 812\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 813\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 814\u001b[39m new_df = \u001b[38;5;28mself\u001b[39m.from_records(\n\u001b[32m 815\u001b[39m data, index=index, columns=columns\n\u001b[32m 816\u001b[39m )\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/cudf/utils/performance_tracking.py:51\u001b[39m, in \u001b[36m_performance_tracking..wrapper\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 43\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m nvtx.enabled():\n\u001b[32m 44\u001b[39m stack.enter_context(\n\u001b[32m 45\u001b[39m nvtx.annotate(\n\u001b[32m 46\u001b[39m message=func.\u001b[34m__qualname__\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 49\u001b[39m )\n\u001b[32m 50\u001b[39m )\n\u001b[32m---> \u001b[39m\u001b[32m51\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/cudf/core/dataframe.py:5947\u001b[39m, in \u001b[36mDataFrame._from_arrays\u001b[39m\u001b[34m(cls, data, index, columns, nan_as_null)\u001b[39m\n\u001b[32m 5945\u001b[39m array_data: np.ndarray | cupy.ndarray\n\u001b[32m 5946\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(data, \u001b[33m\"\u001b[39m\u001b[33m__cuda_array_interface__\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m-> \u001b[39m\u001b[32m5947\u001b[39m array_data = \u001b[43mcupy\u001b[49m\u001b[43m.\u001b[49m\u001b[43masarray\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43morder\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mF\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 5948\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(data, \u001b[33m\"\u001b[39m\u001b[33m__array_interface__\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m 5949\u001b[39m array_data = np.asarray(data, order=\u001b[33m\"\u001b[39m\u001b[33mF\u001b[39m\u001b[33m\"\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/cupy/_creation/from_data.py:88\u001b[39m, in \u001b[36masarray\u001b[39m\u001b[34m(a, dtype, order, blocking)\u001b[39m\n\u001b[32m 56\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34masarray\u001b[39m(a, dtype=\u001b[38;5;28;01mNone\u001b[39;00m, order=\u001b[38;5;28;01mNone\u001b[39;00m, *, blocking=\u001b[38;5;28;01mFalse\u001b[39;00m):\n\u001b[32m 57\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Converts an object to array.\u001b[39;00m\n\u001b[32m 58\u001b[39m \n\u001b[32m 59\u001b[39m \u001b[33;03m This is equivalent to ``array(a, dtype, copy=False, order=order)``.\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 86\u001b[39m \n\u001b[32m 87\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m88\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_core\u001b[49m\u001b[43m.\u001b[49m\u001b[43marray\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43morder\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mblocking\u001b[49m\u001b[43m=\u001b[49m\u001b[43mblocking\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:2502\u001b[39m, in \u001b[36mcupy._core.core.array\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:2512\u001b[39m, in \u001b[36mcupy._core.core.array\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:2543\u001b[39m, in \u001b[36mcupy._core.core._array_from_cupy_ndarray\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:618\u001b[39m, in \u001b[36mcupy._core.core._ndarray_base.astype\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:686\u001b[39m, in \u001b[36mcupy._core.core._ndarray_base.astype\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/_kernel.pyx:1374\u001b[39m, in \u001b[36mcupy._core._kernel.ufunc.__call__\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/_kernel.pyx:1401\u001b[39m, in \u001b[36mcupy._core._kernel.ufunc._get_ufunc_kernel\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/_kernel.pyx:1082\u001b[39m, in \u001b[36mcupy._core._kernel._get_ufunc_kernel\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/_kernel.pyx:94\u001b[39m, in \u001b[36mcupy._core._kernel._get_simple_elementwise_kernel\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/_kernel.pyx:82\u001b[39m, in \u001b[36mcupy._core._kernel._get_simple_elementwise_kernel_from_code\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:2375\u001b[39m, in \u001b[36mcupy._core.core.compile_with_cache\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy/_core/core.pyx:2320\u001b[39m, in \u001b[36mcupy._core.core.assemble_cupy_compiler_options\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy_backends/cuda/libs/nvrtc.pyx:57\u001b[39m, in \u001b[36mcupy_backends.cuda.libs.nvrtc.getVersion\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy_backends/cuda/libs/_cnvrtc.pxi:72\u001b[39m, in \u001b[36mcupy_backends.cuda.libs.nvrtc.initialize\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy_backends/cuda/libs/_cnvrtc.pxi:75\u001b[39m, in \u001b[36mcupy_backends.cuda.libs.nvrtc._initialize\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy_backends/cuda/libs/_cnvrtc.pxi:153\u001b[39m, in \u001b[36mcupy_backends.cuda.libs.nvrtc._get_softlink\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mcupy_backends/cuda/_softlink.pyx:32\u001b[39m, in \u001b[36mcupy_backends.cuda._softlink.SoftLink.__init__\u001b[39m\u001b[34m()\u001b[39m\n", + "\u001b[31mRuntimeError\u001b[39m: CuPy failed to load libnvrtc.so.12: OSError: libnvrtc.so.12: cannot open shared object file: No such file or directory" ] } ], @@ -230,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "id": "72b715c7", "metadata": {}, "outputs": [ @@ -409,7 +489,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "id": "9e17e899", "metadata": {}, "outputs": [ @@ -496,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "id": "2e765325", "metadata": {}, "outputs": [], @@ -525,7 +605,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "id": "c936b137", "metadata": {}, "outputs": [ @@ -567,7 +647,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "id": "87c2d9f8", "metadata": {}, "outputs": [], @@ -596,7 +676,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "id": "1d325f4b", "metadata": {}, "outputs": [ @@ -642,7 +722,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "id": "064978ca", "metadata": {}, "outputs": [], @@ -666,7 +746,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "id": "b3f328e3", "metadata": {}, "outputs": [], @@ -708,7 +788,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "id": "a6babc11", "metadata": { "scrolled": true @@ -732,7 +812,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "id": "28a05ace", "metadata": {}, "outputs": [ @@ -792,7 +872,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "id": "e0d98709", "metadata": {}, "outputs": [ @@ -838,7 +918,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "id": "c13cfbf3", "metadata": { "scrolled": true @@ -945,7 +1025,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.10" + "version": "3.12.11" } }, "nbformat": 4, diff --git a/docs/cuopt/source/introduction.rst b/docs/cuopt/source/introduction.rst index 100282128..de24f4746 100644 --- a/docs/cuopt/source/introduction.rst +++ b/docs/cuopt/source/introduction.rst @@ -66,9 +66,15 @@ This is a linear program. How cuOpt Solves the Linear Programming Problem ------------------------------------------------ -cuOpt includes an LP solver based on `PDLP `__, a new First-Order Method (FOM) used to solve large-scale LPs. This solver implements gradient descent, enhanced by heuristics, and performing massively parallel operations efficiently by leveraging the latest NVIDIA GPUs. +cuOpt includes three LP solving methods: -In addition to PDLP, cuOpt includes a dual simplex solver that runs on the CPU. Both algorithms can be run concurrently on the GPU and CPU. +* **PDLP**: Based on `PDLP `__, a First-Order Method (FOM) for solving large-scale LPs. This solver implements primal-dual hybrid gradient enhanced by heuristics. Sparse matrix-vector products are perfomed efficiently on NVIDIA GPUs. + +* **Barrier (Interior-Point)**: A primal-dual interior-point method that uses GPU-accelerated sparse Cholesky and LDLT solves via cuDSS, and sparse matrix operations via cuSparse. + +* **Dual Simplex**: A CPU-based dual simplex solver for small to medium-sized problems. + +All three algorithms can be run concurrently on both GPU and CPU, with the fastest solution returned automatically. Mixed Integer Linear Programming (MILP) ========================================= @@ -121,6 +127,7 @@ cuOpt supports the following APIs: - `AMPL `_ - `GAMS `_ - `PuLP `_ + - `JuMP `_ ================================== diff --git a/docs/cuopt/source/lp-features.rst b/docs/cuopt/source/lp-features.rst index c5d589907..fc450736b 100644 --- a/docs/cuopt/source/lp-features.rst +++ b/docs/cuopt/source/lp-features.rst @@ -13,6 +13,7 @@ The LP solver can be accessed in the following ways: - AMPL - GAMS - PuLP + - JuMP - **C API**: A native C API that provides direct low-level access to cuOpt's LP capabilities, enabling integration into any application or system that can interface with C. @@ -65,9 +66,11 @@ Users can control how the solver will operate by specifying the PDLP solver mode Method ------ -**Concurrent**: The default method for solving linear programs. When concurrent is selected, cuOpt runs two algorithms at the same time: PDLP on the GPU and dual simplex on the CPU. A solution is returned from the algorithm that finishes first. +**Concurrent**: The default method for solving linear programs. When concurrent is selected, cuOpt runs three algorithms in parallel: PDLP on the GPU, barrier (interior-point) on the GPU, and dual simplex on the CPU. A solution is returned from the algorithm that finishes first. -**PDLP**: Primal-Dual Hybrid Gradient for Linear Program is an algorithm for solving large-scale linear programming problems on the GPU. PDLP does not attempt to any matrix factorizations during the course of the solve. Select this method if your LP is so large that factorization will not fit into memory. By default PDLP solves to low relative tolerance and the solutions it returns do not lie at a vertex of the feasible region. Enable crossover to obtain a highly accurate basic solution from a PDLP solution. +**PDLP**: Primal-Dual Hybrid Gradient for Linear Program is an algorithm for solving large-scale linear programming problems on the GPU. PDLP does not attempt any matrix factorizations during the course of the solve. Select this method if your LP is so large that factorization will not fit into memory. By default PDLP solves to low relative tolerance and the solutions it returns do not lie at a vertex of the feasible region. Enable crossover to obtain a highly accurate basic solution from a PDLP solution. + +**Barrier**: The barrier method (also known as interior-point method) solves linear programs using a primal-dual predictor-corrector algorithm. This method uses GPU-accelerated sparse Cholesky and sparse LDLT solves via cuDSS, and GPU-accelerated sparse matrix-vector and matrix-matrix operations via cuSparse. Barrier is particularly effective for large-scale problems and can automatically apply techniques like folding, dualization, and dense column elimination to improve performance. This method solves the linear systems at each iteration using the augmented system or the normal equations (ADAT). Enable crossover to obtain a highly accurate basic solution from a barrier solution. **Dual Simplex**: Dual simplex is the simplex method applied to the dual of the linear program. Dual simplex requires the basis factorization of linear program fit into memory. Select this method if your LP is small to medium sized, or if you require a high-quality basic solution. @@ -75,7 +78,7 @@ Method Crossover --------- -Crossover allows you to obtain a high-quality basic solution from the results of a PDLP solve. More details can be found :ref:`here `. +Crossover allows you to obtain a high-quality basic solution from the results of a PDLP or barrier solve. When enabled, crossover converts these solutions to a vertex solution (basic solution) with high accuracy. More details can be found :ref:`here `. Presolve diff --git a/docs/cuopt/source/lp-milp-settings.rst b/docs/cuopt/source/lp-milp-settings.rst index 258695419..d755d5a97 100644 --- a/docs/cuopt/source/lp-milp-settings.rst +++ b/docs/cuopt/source/lp-milp-settings.rst @@ -23,8 +23,8 @@ may run slightly over the limit. If set along with the iteration limit, cuOpt wi the first limit (iteration or time) is hit. -Note: by default there is no time limit. So cuOpt will run until it finds an optimal solution, -or proves the problem is infeasible or unbounded. +.. note:: by default there is no time limit. So cuOpt will run until it finds an optimal solution, + or proves the problem is infeasible or unbounded. @@ -33,25 +33,25 @@ Log to Console ``CUOPT_LOG_TO_CONSOLE`` controls whether cuOpt should log information to the console during a solve. If true, a logging info is written to the console, if false no logging info is written to the console (logs may still be written to a file.) -Note: the default value is true. +.. note:: the default value is true. Log File ^^^^^^^^ ``CUOPT_LOG_FILE`` controls the name of a log file where cuOpt should write information about the solve. -Note: the default value is ``""`` and no log file is written. This setting is ignored by the cuOpt service, use the log callback feature instead. +.. note:: the default value is ``""`` and no log file is written. This setting is ignored by the cuOpt service, use the log callback feature instead. Solution File ^^^^^^^^^^^^^ ``CUOPT_SOLUTION_FILE`` controls the name of a file where cuOpt should write the solution. -Note: the default value is ``""`` and no solution file is written. This setting is ignored by the cuOpt service. +.. note:: the default value is ``""`` and no solution file is written. This setting is ignored by the cuOpt service. User Problem File ^^^^^^^^^^^^^^^^^ ``CUOPT_USER_PROBLEM_FILE`` controls the name of a file where cuOpt should write the user problem. -Note: the default value is ``""`` and no user problem file is written. This setting is ignored by the cuOpt service. +.. note:: the default value is ``""`` and no user problem file is written. This setting is ignored by the cuOpt service. Num CPU Threads ^^^^^^^^^^^^^^^ @@ -59,7 +59,7 @@ Num CPU Threads the amount of CPU resources cuOpt uses. Set this to a large value to improve solve times for CPU parallel parts of the solvers. -Note: by default the number of CPU threads is automatically determined based on the number of CPU cores. +.. note:: by default the number of CPU threads is automatically determined based on the number of CPU cores. Presolve ^^^^^^^^ @@ -78,20 +78,19 @@ We now describe the parameter settings used to control cuOpt's Linear Programmin Method ^^^^^^ -``CUOPT_METHOD`` controls the method to solve the linear programming problem. Three methods are available: +``CUOPT_METHOD`` controls the method to solve the linear programming problem. Four methods are available: -* ``Concurrent``: Use both PDLP and dual simplex in parallel. +* ``Concurrent``: Use PDLP, dual simplex, and barrier in parallel (default). * ``PDLP``: Use the PDLP method. * ``Dual Simplex``: Use the dual simplex method. +* ``Barrier``: Use the barrier (interior-point) method. -Note: The default method is ``Concurrent``. +.. note:: The default method is ``Concurrent``. C API users should use the constants defined in :ref:`method-constants` for this parameter. Server Thin client users should use the :class:`cuopt_sh_client.SolverMethod` for this parameter. - - PDLP Solver Mode ^^^^^^^^^^^^^^^^ @@ -117,8 +116,8 @@ For performance reasons, cuOpt's does not constantly checks for iteration limit, the solver might run a few extra iterations over the limit. If set along with the time limit, cuOpt will stop at the first limit (iteration or time) reached. -Note: by default there is no iteration limit. So, cuOpt will run until it finds an optimal solution, -or proves the problem is infeasible or unbounded. +.. note:: by default there is no iteration limit. So, cuOpt will run until it finds an optimal solution, + or proves the problem is infeasible or unbounded. Infeasiblity Detection @@ -129,8 +128,8 @@ is not always accurate. Some problems detected as infeasible may converge under Detecting infeasibility consumes both more runtime and memory. The added runtime is between 3% and 7%, the added memory consumpution is between 10% and 20%. -Note: by default PDLP will not detect infeasibility. Dual simplex will always detect infeasibility -regardless of this setting. +.. note:: by default PDLP will not detect infeasibility. Dual simplex will always detect infeasibility + regardless of this setting. Strict Infeasibility ^^^^^^^^^^^^^^^^^^^^ @@ -139,21 +138,21 @@ Strict Infeasibility is detected as infeasible, PDLP will stop. When false both the current and average solution need to be detected as infeasible for PDLP to stop. -Note: the default value is false. +.. note:: the default value is false. .. _crossover: Crossover ^^^^^^^^^ -``CUOPT_CROSSOVER`` controls whether PDLP should crossover to a basic solution after a optimal solution is found. +``CUOPT_CROSSOVER`` controls whether PDLP or barrier should crossover to a basic solution after an optimal solution is found. Changing this value has a significant impact on accuracy and runtime. -By default the solutions provided by PDLP are low accuracy and may have many variables that lie +By default the solutions provided by PDLP and barrier do not lie at a vertex and thus may have many variables that lie between their bounds. Enabling crossover allows the user to obtain a high-quality basic solution that lies at a vertex of the feasible region. If n is the number of variables, and m is the number of constraints, n - m variables will be on their bounds in a basic solution. -Note: the default value is false. +.. note:: the default value is false. Save Best Primal So Far ^^^^^^^^^^^^^^^^^^^^^^^ @@ -164,21 +163,104 @@ With this parameter set to true, PDLP * If no primal feasible was found, the one with the lowest primal residual will be kept * If two have the same primal residual, the one with the best objective will be kept -Note: the default value is false. +.. note:: the default value is false. First Primal Feasible ^^^^^^^^^^^^^^^^^^^^^ ``CUOPT_FIRST_PRIMAL_FEASIBLE`` controls whether PDLP should stop when the first primal feasible solution is found. -Note: the default value is false. +.. note:: the default value is false. Per Constraint Residual ^^^^^^^^^^^^^^^^^^^^^^^ ``CUOPT_PER_CONSTRAINT_RESIDUAL`` controls whether PDLP should compute the primal & dual residual per constraint instead of globally. -Note: the default value is false. +.. note:: the default value is false. + +Barrier Solver Settings +^^^^^^^^^^^^^^^^^^^^^^^^ + +The following settings control the behavior of the barrier (interior-point) method: + +Folding +""""""" + +``CUOPT_FOLDING`` controls whether to fold the linear program. Folding can reduce problem size by exploiting symmetry in the problem. + +* ``-1``: Automatic (default) - cuOpt decides whether to fold based on problem characteristics +* ``0``: Disable folding +* ``1``: Force folding to run + +.. note:: the default value is ``-1`` (automatic). + +Dualize +""""""" + +``CUOPT_DUALIZE`` controls whether to dualize the linear program in presolve. Dualizing can improve solve time for problems, with inequality constraints, where there are more constraints than variables. + +* ``-1``: Automatic (default) - cuOpt decides whether to dualize based on problem characteristics +* ``0``: Don't attempt to dualize +* ``1``: Force dualize + +.. note:: the default value is ``-1`` (automatic). + +Ordering +"""""""" + +``CUOPT_ORDERING`` controls the ordering algorithm used by cuDSS for sparse factorizations. The ordering can significantly impact solver run time. + +* ``-1``: Automatic (default) - cuOpt selects the best ordering +* ``0``: cuDSS default ordering +* ``1``: AMD (Approximate Minimum Degree) ordering + +.. note:: the default value is ``-1`` (automatic). + +Augmented System +"""""""""""""""" + +``CUOPT_AUGMENTED`` controls which linear system to solve in the barrier method. + +* ``-1``: Automatic (default) - cuOpt selects the best linear system to solve +* ``0``: Solve the ADAT system (normal equations) +* ``1``: Solve the augmented system + +.. note:: the default value is ``-1`` (automatic). The augmented system may be more stable for some problems, while ADAT may be faster for others. + +Eliminate Dense Columns +"""""""""""""""""""""""" + +``CUOPT_ELIMINATE_DENSE_COLUMNS`` controls whether to eliminate dense columns from the constraint matrix before solving. Eliminating dense columns can improve performance by reducing fill-in during factorization. +However, extra solves must be performed at each iteration. + +* ``true``: Eliminate dense columns (default) +* ``false``: Don't eliminate dense columns + +This setting only has an effect when the ADAT (normal equation) system is solved. + +.. note:: the default value is ``true``. + +cuDSS Deterministic Mode +""""""""""""""""""""""""" + +``CUOPT_CUDSS_DETERMINISTIC`` controls whether cuDSS operates in deterministic mode. Deterministic mode ensures reproducible results across runs but may be slower. + +* ``true``: Use deterministic mode +* ``false``: Use non-deterministic mode (default) + +.. note:: the default value is ``false``. Enable deterministic mode if reproducibility is more important than performance. + +Dual Initial Point +"""""""""""""""""" + +``CUOPT_BARRIER_DUAL_INITIAL_POINT`` controls the method used to compute the dual initial point for the barrier solver. The choice of initial point will affect the number of iterations performed by barrier. + +* ``-1``: Automatic (default) - cuOpt selects the best method +* ``0``: Use an initial point from a heuristic approach based on the paper "On Implementing Mehrotra's Predictor–Corrector Interior-Point Method for Linear Programming" (SIAM J. Optimization, 1992) by Lustig, Martsten, Shanno. +* ``1``: Use an initial point from solving a least squares problem that minimizes the norms of the dual variables and reduced costs while statisfying the dual equality constraints. + +.. note:: the default value is ``-1`` (automatic). Absolute Primal Tolerance ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -204,7 +286,7 @@ The primal feasibility condition is computed as follows:: primal_feasiblity < absolute_primal_tolerance + relative_primal_tolerance * l2_norm(b) -Note: the default value is ``1e-4``. +.. note:: the default value is ``1e-4``. Absolute Dual Tolerance ^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +299,7 @@ The dual feasibility condition is computed as follows:: dual_feasiblity < absolute_dual_tolerance + relative_dual_tolerance * l2_norm(c) -Note: the default value is ``1e-4``. +.. note:: the default value is ``1e-4``. Relative Dual Tolerance ^^^^^^^^^^^^^^^^^^^^^^^ @@ -228,7 +310,7 @@ The dual feasibility condition is computed as follows:: dual_feasiblity < absolute_dual_tolerance + relative_dual_tolerance * l2_norm(c) -Note: the default value is ``1e-4``. +.. note:: the default value is ``1e-4``. Absolute Gap Tolerance @@ -241,7 +323,7 @@ The duality gap is computed as follows:: duality_gap < absolute_gap_tolerance + relative_gap_tolerance * (|primal_objective| + |dual_objective|) -Note: the default value is ``1e-4``. +.. note:: the default value is ``1e-4``. Relative Gap Tolerance @@ -253,7 +335,7 @@ The duality gap is computed as follows:: duality_gap < absolute_gap_tolerance + relative_gap_tolerance * (|primal_objective| + |dual_objective|) -Note: the default value is ``1e-4``. +.. note:: the default value is ``1e-4``. Mixed Integer Linear Programming @@ -269,7 +351,7 @@ Heuristics only bound is improved via the GPU. When set to false, both the GPU and CPU are used and the dual bound is improved on the CPU. -Note: the default value is false. +.. note:: the default value is false. Scaling ^^^^^^^ @@ -277,7 +359,7 @@ Scaling ``CUOPT_MIP_SCALING`` controls if scaling should be applied to the MIP problem. When true scaling is applied, when false, no scaling is applied. -Note: the defaulte value is true. +.. note:: the defaulte value is true. Absolute Tolerance @@ -285,14 +367,14 @@ Absolute Tolerance ``CUOPT_MIP_ABSOLUTE_TOLERANCE`` controls the MIP absolute tolerance. -Note: the default value is ``1e-6``. +.. note:: the default value is ``1e-6``. Relative Tolerance ^^^^^^^^^^^^^^^^^^ ``CUOPT_MIP_RELATIVE_TOLERANCE`` controls the MIP relative tolerance. -Note: the default value is ``1e-12``. +.. note:: the default value is ``1e-12``. Integrality Tolerance @@ -301,7 +383,7 @@ Integrality Tolerance ``CUOPT_INTEGRALITY_TOLERANCE`` controls the MIP integrality tolerance. A variable is considered to be integral, if it is within the integrality tolerance of an integer. -Note: the default value is ``1e-5``. +.. note:: the default value is ``1e-5``. Absolute MIP Gap ^^^^^^^^^^^^^^^^ @@ -316,7 +398,7 @@ when minimizing or when maximizing. -Note: the default value is ``1e-10``. +.. note:: the default value is ``1e-10``. Relative MIP Gap ^^^^^^^^^^^^^^^^ @@ -328,4 +410,4 @@ Relative MIP Gap If the Best Objective and the Dual Bound are both zero the gap is zero. If the best objective value is zero, the gap is infinity. -Note: the default value is ``1e-4``. +.. note:: the default value is ``1e-4``. diff --git a/docs/cuopt/source/milp-features.rst b/docs/cuopt/source/milp-features.rst index 40eba5c40..429bc0530 100644 --- a/docs/cuopt/source/milp-features.rst +++ b/docs/cuopt/source/milp-features.rst @@ -13,6 +13,7 @@ The MILP solver can be accessed in the following ways: - AMPL - GAMS - PuLP + - JuMP - **C API**: A native C API that provides direct low-level access to cuOpt's MILP solver, enabling integration into any application or system that can interface with C. diff --git a/docs/cuopt/source/system-requirements.rst b/docs/cuopt/source/system-requirements.rst index e7d963ae5..7ad702194 100644 --- a/docs/cuopt/source/system-requirements.rst +++ b/docs/cuopt/source/system-requirements.rst @@ -47,6 +47,7 @@ Dependencies are installed automatically when using the pip and Conda installati - CUDA 12.2 with Driver 535.86.10+ - CUDA 12.5 with Driver 555.42.06+ - CUDA 12.9 with Driver 570.42.01+ + - CUDA 13.0 with Driver 580.65.06+ .. dropdown:: Recommended Requirements for Best Performance diff --git a/docs/cuopt/source/thirdparty_modeling_languages/index.rst b/docs/cuopt/source/thirdparty_modeling_languages/index.rst index 3fa6c5466..0acda399a 100644 --- a/docs/cuopt/source/thirdparty_modeling_languages/index.rst +++ b/docs/cuopt/source/thirdparty_modeling_languages/index.rst @@ -21,3 +21,10 @@ PuLP Support PuLP can be used with near zero code changes: simply switch to cuOpt as a solver to solve linear and mixed-integer programming problems. Please refer to the `PuLP documentation `_ for more information. Also, see the example notebook in the `cuopt-examples `_ repository. + +-------------------------- +JuMP Support +-------------------------- + +JuMP can be used with near zero code changes: simply switch to cuOpt as a solver to solve linear and mixed-integer programming problems. +Please refer to the `JuMP documentation `_ for more information. diff --git a/python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx b/python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx index 50641d331..2c196751f 100644 --- a/python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx +++ b/python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx @@ -25,8 +25,6 @@ import warnings import numpy as np -import cudf - from libc.stdint cimport uintptr_t from libcpp.memory cimport unique_ptr from libcpp.utility cimport move @@ -46,13 +44,11 @@ def type_cast(np_obj, np_type, name): def get_data_ptr(array): - if isinstance(array, cudf.Series): - return array.__cuda_array_interface__['data'][0] - elif isinstance(array, np.ndarray): + if isinstance(array, np.ndarray): return array.__array_interface__['data'][0] else: raise Exception( - "get_data_ptr must be called with cudf.Series or np.ndarray" + "get_data_ptr must be called with np.ndarray" ) diff --git a/python/cuopt/cuopt/tests/linear_programming/test_python_API.py b/python/cuopt/cuopt/tests/linear_programming/test_python_API.py index 1f0ade10e..c7ef8b99b 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_python_API.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_python_API.py @@ -34,10 +34,21 @@ sense, ) from cuopt.linear_programming.solver.solver_parameters import ( + CUOPT_AUGMENTED, + CUOPT_BARRIER_DUAL_INITIAL_POINT, + CUOPT_CUDSS_DETERMINISTIC, + CUOPT_DUALIZE, + CUOPT_ELIMINATE_DENSE_COLUMNS, + CUOPT_FOLDING, CUOPT_INFEASIBILITY_DETECTION, + CUOPT_METHOD, + CUOPT_ORDERING, CUOPT_PDLP_SOLVER_MODE, ) -from cuopt.linear_programming.solver_settings import PDLPSolverMode +from cuopt.linear_programming.solver_settings import ( + PDLPSolverMode, + SolverMethod, +) RAPIDS_DATASET_ROOT_DIR = os.getenv("RAPIDS_DATASET_ROOT_DIR") if RAPIDS_DATASET_ROOT_DIR is None: @@ -449,3 +460,225 @@ def test_problem_update(): prob.updateObjective(constant=5, sense=MINIMIZE) prob.solve() assert prob.ObjValue == pytest.approx(5) + + +@pytest.mark.parametrize( + "test_name,settings_config", + [ + ( + "automatic", + { + CUOPT_FOLDING: -1, + CUOPT_DUALIZE: -1, + CUOPT_ORDERING: -1, + CUOPT_AUGMENTED: -1, + }, + ), + ( + "forced_on", + { + CUOPT_FOLDING: 1, + CUOPT_DUALIZE: 1, + CUOPT_ORDERING: 1, + CUOPT_AUGMENTED: 1, + CUOPT_ELIMINATE_DENSE_COLUMNS: True, + CUOPT_CUDSS_DETERMINISTIC: True, + }, + ), + ( + "disabled", + { + CUOPT_FOLDING: 0, + CUOPT_DUALIZE: 0, + CUOPT_ORDERING: 0, + CUOPT_AUGMENTED: 0, + CUOPT_ELIMINATE_DENSE_COLUMNS: False, + CUOPT_CUDSS_DETERMINISTIC: False, + }, + ), + ( + "mixed", + { + CUOPT_FOLDING: 1, + CUOPT_DUALIZE: 0, + CUOPT_ORDERING: -1, + CUOPT_AUGMENTED: 1, + }, + ), + ( + "folding_on", + { + CUOPT_FOLDING: 1, + }, + ), + ( + "folding_off", + { + CUOPT_FOLDING: 0, + }, + ), + ( + "dualize_on", + { + CUOPT_DUALIZE: 1, + }, + ), + ( + "dualize_off", + { + CUOPT_DUALIZE: 0, + }, + ), + ( + "amd_ordering", + { + CUOPT_ORDERING: 1, + }, + ), + ( + "cudss_ordering", + { + CUOPT_ORDERING: 0, + }, + ), + ( + "augmented_system", + { + CUOPT_AUGMENTED: 1, + }, + ), + ( + "adat_system", + { + CUOPT_AUGMENTED: 0, + }, + ), + ( + "no_dense_elim", + { + CUOPT_ELIMINATE_DENSE_COLUMNS: False, + }, + ), + ( + "cudss_deterministic", + { + CUOPT_CUDSS_DETERMINISTIC: True, + }, + ), + ( + "combo1", + { + CUOPT_FOLDING: 1, + CUOPT_DUALIZE: 1, + CUOPT_ORDERING: 1, + }, + ), + ( + "combo2", + { + CUOPT_FOLDING: 0, + CUOPT_AUGMENTED: 0, + CUOPT_ELIMINATE_DENSE_COLUMNS: False, + }, + ), + ( + "dual_initial_point_automatic", + { + CUOPT_BARRIER_DUAL_INITIAL_POINT: -1, + }, + ), + ( + "dual_initial_point_lustig", + { + CUOPT_BARRIER_DUAL_INITIAL_POINT: 0, + }, + ), + ( + "dual_initial_point_least_squares", + { + CUOPT_BARRIER_DUAL_INITIAL_POINT: 1, + }, + ), + ( + "combo3_with_dual_init", + { + CUOPT_AUGMENTED: 1, + CUOPT_BARRIER_DUAL_INITIAL_POINT: 1, + CUOPT_ELIMINATE_DENSE_COLUMNS: True, + }, + ), + ], +) +def test_barrier_solver_settings(test_name, settings_config): + """ + Parameterized test for barrier solver with different configurations. + + Tests the barrier solver across various settings combinations to ensure + correctness and robustness. Each configuration tests different aspects + of the barrier solver implementation. + + Problem: + maximize 5*xs + 20*xl + subject to 1*xs + 3*xl <= 200 + 3*xs + 2*xl <= 160 + xs, xl >= 0 + + Expected Solution: + Optimal objective: 1333.33 + xs = 0, xl = 66.67 (corner solution where constraint 1 is binding) + + Args + ---- + test_name: Descriptive name for the test configuration + settings_config: Dictionary of barrier solver parameters to set + """ + prob = Problem(f"Barrier Test - {test_name}") + + # Add variables + xs = prob.addVariable(lb=0, vtype=VType.CONTINUOUS, name="xs") + xl = prob.addVariable(lb=0, vtype=VType.CONTINUOUS, name="xl") + + # Add constraints + prob.addConstraint(xs + 3 * xl <= 200, name="constraint1") + prob.addConstraint(3 * xs + 2 * xl <= 160, name="constraint2") + + # Set objective: maximize 5*xs + 20*xl + prob.setObjective(5 * xs + 20 * xl, sense=MAXIMIZE) + + # Configure solver settings + settings = SolverSettings() + settings.set_parameter(CUOPT_METHOD, SolverMethod.Barrier) + settings.set_parameter("time_limit", 10) + + # Apply test-specific settings + for param_name, param_value in settings_config.items(): + settings.set_parameter(param_name, param_value) + + print(f"\nTesting configuration: {test_name}") + print(f"Settings: {settings_config}") + + # Solve the problem + prob.solve(settings) + + print(f"Status: {prob.Status.name}") + print(f"Objective: {prob.ObjValue}") + print(f"xs = {xs.Value}, xl = {xl.Value}") + + # Verify solution + assert prob.solved, f"Problem not solved for {test_name}" + assert prob.Status.name == "Optimal", f"Not optimal for {test_name}" + assert prob.ObjValue == pytest.approx( + 1333.33, rel=0.01 + ), f"Incorrect objective for {test_name}" + assert xs.Value == pytest.approx( + 0.0, abs=1e-4 + ), f"Incorrect xs value for {test_name}" + assert xl.Value == pytest.approx( + 66.67, rel=0.01 + ), f"Incorrect xl value for {test_name}" + + # Verify constraint slacks are non-negative + for c in prob.getConstraints(): + assert ( + c.Slack >= -1e-6 + ), f"Negative slack for {c.getConstraintName()} in {test_name}" diff --git a/python/cuopt_server/cuopt_server/tests/test_lp.py b/python/cuopt_server/cuopt_server/tests/test_lp.py index 8fc85aa3a..4a01daaca 100644 --- a/python/cuopt_server/cuopt_server/tests/test_lp.py +++ b/python/cuopt_server/cuopt_server/tests/test_lp.py @@ -146,3 +146,77 @@ def test_sample_milp( res.json()["response"]["solver_response"], expected_status, ) + + +# @pytest.mark.skip(reason="Skipping barrier solver options test") +@pytest.mark.parametrize( + "folding, dualize, ordering, augmented, eliminate_dense, cudss_determ, " + "dual_initial_point", + [ + # Test automatic settings (default) + (-1, -1, -1, -1, True, False, -1), + # Test folding off, no dualization, cuDSS default ordering, ADAT system + (0, 0, 0, 0, True, False, 0), + # Test folding on, force dualization, AMD ordering, augmented system + (1, 1, 1, 1, True, True, 1), + # Test mixed settings: automatic folding, no dualize, AMD, augmented + (-1, 0, 1, 1, False, False, 0), + # Test no folding, automatic dualize, cuDSS default, ADAT + (0, -1, 0, 0, True, True, -1), + # Test dual initial point with Lustig-Marsten-Shanno + (-1, -1, -1, -1, True, False, 0), + # Test dual initial point with least squares + (-1, -1, -1, 1, True, False, 1), + ], +) +def test_barrier_solver_options( + cuoptproc, # noqa + folding, + dualize, + ordering, + augmented, + eliminate_dense, + cudss_determ, + dual_initial_point, +): + """ + Test the barrier solver (method=3) with various configuration options: + - folding: (-1) automatic, (0) off, (1) on + - dualize: (-1) automatic, (0) don't dualize, (1) force dualize + - ordering: (-1) automatic, (0) cuDSS default, (1) AMD + - augmented: (-1) automatic, (0) ADAT, (1) augmented system + - eliminate_dense_columns: True to eliminate, False to not + - cudss_deterministic: True for deterministic, False for + nondeterministic + - barrier_dual_initial_point: (-1) automatic, (0) Lustig-Marsten-Shanno, + (1) dual least squares + """ + data = get_std_data_for_lp() + + # Use barrier solver (method=3) + data["solver_config"]["method"] = 3 + + # Configure barrier solver options + data["solver_config"]["folding"] = folding + data["solver_config"]["dualize"] = dualize + data["solver_config"]["ordering"] = ordering + data["solver_config"]["augmented"] = augmented + data["solver_config"]["eliminate_dense_columns"] = eliminate_dense + data["solver_config"]["cudss_deterministic"] = cudss_determ + data["solver_config"]["barrier_dual_initial_point"] = dual_initial_point + + res = get_lp(client, data) + + assert res.status_code == 200 + + print("\n=== Barrier Solver Test Configuration ===") + print(f"folding={folding}, dualize={dualize}, ordering={ordering}") + print(f"augmented={augmented}, eliminate_dense={eliminate_dense}") + print(f"cudss_deterministic={cudss_determ}") + print(f"barrier_dual_initial_point={dual_initial_point}") + print(res.json()) + + validate_lp_result( + res.json()["response"]["solver_response"], + LPTerminationStatus.Optimal.name, + ) diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py index 8eeca3645..b74d66b09 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py @@ -520,6 +520,8 @@ class SolverConfig(StrictModel): "
" "- Dual Simplex: 2, Dual Simplex method" "
" + "- Barrier: 3, Barrier method" + "
" "Note: Not supported for MILP. ", ) mip_scaling: Optional[bool] = Field( diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py index b05974217..1dbfaf51e 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py @@ -440,15 +440,15 @@ def is_mip(var_types): solver_settings.set_parameter( CUOPT_LOG_FILE, solver_config.log_file ) - if solver_config.augmented != "": + if solver_config.augmented is not None: solver_settings.set_parameter( CUOPT_AUGMENTED, solver_config.augmented ) - if solver_config.folding != "": + if solver_config.folding is not None: solver_settings.set_parameter(CUOPT_FOLDING, solver_config.folding) - if solver_config.dualize != "": + if solver_config.dualize is not None: solver_settings.set_parameter(CUOPT_DUALIZE, solver_config.dualize) - if solver_config.ordering != "": + if solver_config.ordering is not None: solver_settings.set_parameter( CUOPT_ORDERING, solver_config.ordering ) diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt index 175e501e4..b6fbb6b2b 100644 --- a/python/libcuopt/CMakeLists.txt +++ b/python/libcuopt/CMakeLists.txt @@ -86,6 +86,7 @@ set(rpaths "$ORIGIN/../../nvidia/curand/lib" "$ORIGIN/../../nvidia/cusolver/lib" "$ORIGIN/../../nvidia/cusparse/lib" + "$ORIGIN/../../nvidia/nvjitlink/lib" ) # Add CUDA version-specific paths based on CUDA compiler version