diff --git a/.github/AGENTS.md b/.github/AGENTS.md new file mode 100644 index 000000000..51f799e19 --- /dev/null +++ b/.github/AGENTS.md @@ -0,0 +1,156 @@ +# AGENTS.md - AI Coding Agent Guidelines for cuOpt + +> This file provides essential context for AI coding assistants (Codex, Cursor, GitHub Copilot, etc.) working with the NVIDIA cuOpt codebase. + +> **For setup, building, testing, and contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).** + +--- + +## Project Overview + +**cuOpt** is NVIDIA's GPU-accelerated optimization engine for: +- **Mixed Integer Linear Programming (MILP)** +- **Linear Programming (LP)** +- **Quadratic Programming (QP)** +- **Vehicle Routing Problems (VRP)** including TSP and PDP + +### Architecture + +``` +cuopt/ +├── cpp/ # Core C++ engine (libcuopt, libmps_parser) +│ ├── include/cuopt/ # Public C/C++ headers +│ ├── src/ # Implementation (CUDA kernels, algorithms) +│ └── tests/ # C++ unit tests (gtest) +├── python/ +│ ├── cuopt/ # Python bindings and routing API +│ ├── cuopt_server/ # REST API server +│ ├── cuopt_self_hosted/ # Self-hosted deployment utilities +│ └── libcuopt/ # Python wrapper for C library +├── ci/ # CI/CD scripts and Docker configurations +├── conda/ # Conda recipes and environment files +├── docs/ # Documentation source +├── datasets/ # Test datasets for LP, MIP, routing +└── notebooks/ # Example Jupyter notebooks +``` + +### Supported APIs + +| API Type | LP | MILP | QP | Routing | +|----------|:--:|:----:|:--:|:-------:| +| C API | ✓ | ✓ | ✓ | ✗ | +| C++ API | ✓ | ✓ | ✓ | ✓ | +| Python | ✓ | ✓ | ✓ | ✓ | +| Server | ✓ | ✓ | ✗ | ✓ | + +--- + +## Coding Style and Conventions + +### C++ Naming Conventions + +- **Base style**: `snake_case` for all names (except test cases: PascalCase) +- **Prefixes/Suffixes**: + - `d_` → device data variables (e.g., `d_locations_`) + - `h_` → host data variables (e.g., `h_data_`) + - `_t` → template type parameters (e.g., `i_t`, `value_t`) + - `_` → private member variables (e.g., `n_locations_`) + +```cpp +// Example naming pattern +template +class locations_t { + private: + i_t n_locations_{}; + i_t* d_locations_{}; // device pointer + i_t* h_locations_{}; // host pointer +}; +``` + +### File Extensions + +| Extension | Usage | +|-----------|-------| +| `.hpp` | C++ headers | +| `.cpp` | C++ source | +| `.cu` | CUDA C++ source (nvcc required) | +| `.cuh` | CUDA headers with device code | + +### Include Order + +1. Local headers +2. RAPIDS headers +3. Related libraries +4. Dependencies +5. STL + +### Python Style + +- Follow PEP 8 +- Use type hints where applicable +- Tests use `pytest` framework + +### Formatting + +- **C++**: Enforced by `clang-format` (config: `cpp/.clang-format`) +- **Python**: Enforced via pre-commit hooks +- See [CONTRIBUTING.md](../CONTRIBUTING.md) for pre-commit setup + +--- + +## Error Handling Patterns + +### Runtime Assertions + +```cpp +// Use CUOPT_EXPECTS for runtime checks +CUOPT_EXPECTS(lhs.type() == rhs.type(), "Column type mismatch"); + +// Use CUOPT_FAIL for unreachable code paths +CUOPT_FAIL("This code path should not be reached."); +``` + +### CUDA Error Checking + +```cpp +// Always wrap CUDA calls +RAFT_CUDA_TRY(cudaMemcpy(&dst, &src, num_bytes)); +``` + +--- + +## Memory Management Guidelines + +- **Never use raw `new`/`delete`** - Use RMM allocators +- **Prefer `rmm::device_uvector`** for device memory +- **All operations should be stream-ordered** - Accept `cuda_stream_view` +- **Views (`*_view` suffix) are non-owning** - Don't manage their lifetime + +--- + +## Key Files Reference + +| Purpose | Location | +|---------|----------| +| Main build script | `build.sh` | +| Dependencies | `dependencies.yaml` | +| C++ formatting | `cpp/.clang-format` | +| Conda environments | `conda/environments/` | +| Test data download | `datasets/get_test_data.sh` | +| CI configuration | `ci/` | +| Version info | `VERSION` | + +--- + +## Common Pitfalls + +| Problem | Solution | +|---------|----------| +| Cython changes not reflected | Rerun: `./build.sh cuopt` | +| Missing `nvcc` | Set `$CUDACXX` or add CUDA to `$PATH` | +| CUDA out of memory | Reduce problem size or use streaming | +| Slow debug library loading | Device symbols cause delay; use selectively | + +--- + +*For detailed setup, build instructions, testing workflows, debugging, and contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).* diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..563581d27 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +This project has adopted the [Contributor Covenant Code of Conduct](https://docs.rapids.ai/resources/conduct/). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9e493f77b..452117b53 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,5 +1,5 @@ --- -name: Bug report +name: 🐛 Bug report about: Create a bug report to help us improve cuOpt title: "[BUG]" labels: "? - Needs Triage, bug" diff --git a/.github/ISSUE_TEMPLATE/documentation-request.md b/.github/ISSUE_TEMPLATE/documentation-request.md index 89a026f34..140bef44d 100644 --- a/.github/ISSUE_TEMPLATE/documentation-request.md +++ b/.github/ISSUE_TEMPLATE/documentation-request.md @@ -1,5 +1,5 @@ --- -name: Documentation request +name: 📚 Documentation request about: Report incorrect or needed documentation title: "[DOC]" labels: "? - Needs Triage, doc" diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9bf7d8890..1a5d4e3f3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,5 +1,5 @@ --- -name: Feature request +name: 🚀 Feature request about: Suggest an idea for cuOpt title: "[FEA]" labels: "? - Needs Triage, feature request" diff --git a/.github/ISSUE_TEMPLATE/submit-question.md b/.github/ISSUE_TEMPLATE/submit-question.md index 13271f601..973d9033d 100644 --- a/.github/ISSUE_TEMPLATE/submit-question.md +++ b/.github/ISSUE_TEMPLATE/submit-question.md @@ -1,5 +1,5 @@ --- -name: Submit question +name: ❓ Submit question about: Ask a general question about cuOpt title: "[QST]" labels: "? - Needs Triage, question" diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..2e6ba9ff7 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,15 @@ +Security +--------- +NVIDIA is dedicated to the security and trust of our software products and services, including all source code repositories managed through our organization. + +If you need to report a security issue, please use the appropriate contact points outlined below. Please do not report security vulnerabilities through GitHub/GitLab. + +Reporting Potential Security Vulnerability in NVIDIA cuOpt +---------------------------------------------------------- +To report a potential security vulnerability in NVIDIA cuOpt: + +- Web: [Security Vulnerability Submission Form](https://www.nvidia.com/object/submit-security-vulnerability.html) +- E-Mail: [psirt@nvidia.com](mailto:psirt@nvidia.com) +- We encourage you to use the following PGP key for secure email communication: [NVIDIA public PGP Key for communication](https://www.nvidia.com/en-us/security/pgp-key) +- Please include the following information: + - Product/Driver name and version/branch that contains the vulnerability diff --git a/README.md b/README.md index 7924c7b82..b300e6604 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ # cuOpt - GPU accelerated Optimization Engine [![Build Status](https://github.com/NVIDIA/cuopt/actions/workflows/build.yaml/badge.svg)](https://github.com/NVIDIA/cuopt/actions/workflows/build.yaml) +[![Version](https://img.shields.io/badge/version-26.02.00-blue)](https://github.com/NVIDIA/cuopt/releases) +[![Documentation](https://img.shields.io/badge/docs-latest-brightgreen)](https://docs.nvidia.com/cuopt/user-guide/latest/introduction.html) +[![Docker Hub](https://img.shields.io/badge/docker-nvidia%2Fcuopt-blue?logo=docker)](https://hub.docker.com/r/nvidia/cuopt) +[![Examples](https://img.shields.io/badge/examples-cuopt--examples-orange)](https://github.com/NVIDIA/cuopt-examples) +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/NVIDIA/cuopt-examples/blob/cuopt_examples_launcher/cuopt_examples_launcher.ipynb) +[![NVIDIA Launchable](https://img.shields.io/badge/NVIDIA-Launchable-76b900?logo=nvidia)](https://brev.nvidia.com/launchable/deploy?launchableID=env-2qIG6yjGKDtdMSjXHcuZX12mDNJ) +[![Videos and Tutorials](https://img.shields.io/badge/Videos_and_Tutorials-red?logo=youtube)](https://docs.nvidia.com/cuopt/user-guide/latest/resources.html#cuopt-examples-and-tutorials-videos) + + NVIDIA® cuOpt™ is a GPU-accelerated optimization engine that excels in mixed integer linear programming (MILP), linear programming (LP), and vehicle routing problems (VRP). It enables near real-time solutions for large-scale challenges with millions of variables and constraints, offering easy integration into existing solvers and seamless deployment across hybrid and multi-cloud environments. @@ -146,13 +155,3 @@ For current release timelines and dates, refer to the [RAPIDS Maintainers Docs]( ## Contributing Guide Review the [CONTRIBUTING.md](CONTRIBUTING.md) file for information on how to contribute code and issues to the project. - -## Resources - -- [libcuopt (C) documentation](https://docs.nvidia.com/cuopt/user-guide/latest/cuopt-c/index.html) -- [cuopt (Python) documentation](https://docs.nvidia.com/cuopt/user-guide/latest/cuopt-python/index.html) -- [cuopt (Server) documentation](https://docs.nvidia.com/cuopt/user-guide/latest/cuopt-server/index.html) -- [Examples and Notebooks](https://github.com/NVIDIA/cuopt-examples) -- [Test cuopt with NVIDIA Launchable](https://brev.nvidia.com/launchable/deploy?launchableID=env-2qIG6yjGKDtdMSjXHcuZX12mDNJ): Examples notebooks are pulled and hosted on [NVIDIA Launchable](https://docs.nvidia.com/brev/latest/). -- [Test cuopt on Google Colab](https://colab.research.google.com/github/nvidia/cuopt-examples/): Examples notebooks can be opened in Google Colab. Please note that you need to choose a `Runtime` as `GPU` in order to run the notebooks. -- [cuOpt Examples and Tutorial Videos](https://docs.nvidia.com/cuopt/user-guide/latest/resources.html#cuopt-examples-and-tutorials-videos) diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index 24124ad56..966725ef8 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -1,6 +1,6 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 ## Usage @@ -131,6 +131,9 @@ done PROJECT_FILE="docs/cuopt/source/project.json" sed_runner 's/\("version": "\)[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]"/\1'${NEXT_FULL_TAG}'"/g' "${PROJECT_FILE}" +# Update README.md version badge +sed_runner 's/badge\/version-[0-9]\+\.[0-9]\+\.[0-9]\+-blue/badge\/version-'${NEXT_FULL_TAG}'-blue/g' README.md + # Update nightly sed_runner 's/'"cuopt_version: \"[0-9][0-9].[0-9][0-9]\""'/'"cuopt_version: \"${NEXT_SHORT_TAG}\""'/g' .github/workflows/nightly.yaml diff --git a/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp b/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp index e1a75747d..18470e795 100644 --- a/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp +++ b/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -25,19 +25,19 @@ namespace cython { // aggregate for call_solve() return type // to be exposed to cython: struct linear_programming_ret_t { - std::unique_ptr primal_solution_; - std::unique_ptr dual_solution_; - std::unique_ptr reduced_cost_; + std::vector primal_solution_; + std::vector dual_solution_; + std::vector reduced_cost_; /* -- PDLP Warm Start Data -- */ - std::unique_ptr current_primal_solution_; - std::unique_ptr current_dual_solution_; - std::unique_ptr initial_primal_average_; - std::unique_ptr initial_dual_average_; - std::unique_ptr current_ATY_; - std::unique_ptr sum_primal_solutions_; - std::unique_ptr sum_dual_solutions_; - std::unique_ptr last_restart_duality_gap_primal_solution_; - std::unique_ptr last_restart_duality_gap_dual_solution_; + std::vector current_primal_solution_; + std::vector current_dual_solution_; + std::vector initial_primal_average_; + std::vector initial_dual_average_; + std::vector current_ATY_; + std::vector sum_primal_solutions_; + std::vector sum_dual_solutions_; + std::vector last_restart_duality_gap_primal_solution_; + std::vector last_restart_duality_gap_dual_solution_; double initial_primal_weight_; double initial_step_size_; int total_pdlp_iterations_; @@ -64,7 +64,7 @@ struct linear_programming_ret_t { }; struct mip_ret_t { - std::unique_ptr solution_; + std::vector solution_; linear_programming::mip_termination_status_t termination_status_; error_type_t error_status_; diff --git a/cpp/src/linear_programming/utilities/cython_solve.cu b/cpp/src/linear_programming/utilities/cython_solve.cu index 2f7a37df8..3623a8f2c 100644 --- a/cpp/src/linear_programming/utilities/cython_solve.cu +++ b/cpp/src/linear_programming/utilities/cython_solve.cu @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -142,28 +142,21 @@ linear_programming_ret_t call_solve_lp( const bool use_pdlp_solver_mode = true; auto solution = cuopt::linear_programming::solve_lp( op_problem, solver_settings, problem_checking, use_pdlp_solver_mode, is_batch_mode); + + // Convert device vectors to host vectors for LP solution linear_programming_ret_t lp_ret{ - std::make_unique(solution.get_primal_solution().release()), - std::make_unique(solution.get_dual_solution().release()), - std::make_unique(solution.get_reduced_cost().release()), - std::make_unique( - solution.get_pdlp_warm_start_data().current_primal_solution_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().current_dual_solution_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().initial_primal_average_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().initial_dual_average_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().current_ATY_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().sum_primal_solutions_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().sum_dual_solutions_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution_.release()), - std::make_unique( - solution.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_.release()), + cuopt::host_copy(solution.get_primal_solution()), + cuopt::host_copy(solution.get_dual_solution()), + cuopt::host_copy(solution.get_reduced_cost()), + cuopt::host_copy(solution.get_pdlp_warm_start_data().current_primal_solution_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().current_dual_solution_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().initial_primal_average_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().initial_dual_average_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().current_ATY_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().sum_primal_solutions_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().sum_dual_solutions_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution_), + cuopt::host_copy(solution.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_), solution.get_pdlp_warm_start_data().initial_primal_weight_, solution.get_pdlp_warm_start_data().initial_step_size_, solution.get_pdlp_warm_start_data().total_pdlp_iterations_, @@ -205,7 +198,9 @@ mip_ret_t call_solve_mip( error_type_t::ValidationError, "MIP solve cannot be called on an LP problem!"); auto solution = cuopt::linear_programming::solve_mip(op_problem, solver_settings); - mip_ret_t mip_ret{std::make_unique(solution.get_solution().release()), + + // Convert device vector to host vector for MILP solution + mip_ret_t mip_ret{cuopt::host_copy(solution.get_solution()), solution.get_termination_status(), solution.get_error_status().get_error_type(), solution.get_error_status().what(), diff --git a/python/cuopt/cuopt/distance_engine/waypoint_matrix_wrapper.pyx b/python/cuopt/cuopt/distance_engine/waypoint_matrix_wrapper.pyx index 1c959a593..770dda1a4 100644 --- a/python/cuopt/cuopt/distance_engine/waypoint_matrix_wrapper.pyx +++ b/python/cuopt/cuopt/distance_engine/waypoint_matrix_wrapper.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa +# SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa # SPDX-License-Identifier: Apache-2.0 # cython: profile=False @@ -26,6 +26,8 @@ from cuopt.utilities import series_from_buf import pyarrow as pa +import pyarrow as pa + cdef class WaypointMatrix: diff --git a/python/cuopt/cuopt/linear_programming/solver/solver.pxd b/python/cuopt/cuopt/linear_programming/solver/solver.pxd index c140e3d0c..d98676fc3 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver.pxd +++ b/python/cuopt/cuopt/linear_programming/solver/solver.pxd @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa # SPDX-License-Identifier: Apache-2.0 @@ -120,19 +120,19 @@ cdef extern from "cuopt/linear_programming/pdlp/solver_solution.hpp" namespace " cdef extern from "cuopt/linear_programming/utilities/cython_solve.hpp" namespace "cuopt::cython": # noqa cdef cppclass linear_programming_ret_t: - unique_ptr[device_buffer] primal_solution_ - unique_ptr[device_buffer] dual_solution_ - unique_ptr[device_buffer] reduced_cost_ + vector[double] primal_solution_ + vector[double] dual_solution_ + vector[double] reduced_cost_ # PDLP warm start data - unique_ptr[device_buffer] current_primal_solution_ - unique_ptr[device_buffer] current_dual_solution_ - unique_ptr[device_buffer] initial_primal_average_ - unique_ptr[device_buffer] initial_dual_average_ - unique_ptr[device_buffer] current_ATY_ - unique_ptr[device_buffer] sum_primal_solutions_ - unique_ptr[device_buffer] sum_dual_solutions_ - unique_ptr[device_buffer] last_restart_duality_gap_primal_solution_ - unique_ptr[device_buffer] last_restart_duality_gap_dual_solution_ + vector[double] current_primal_solution_ + vector[double] current_dual_solution_ + vector[double] initial_primal_average_ + vector[double] initial_dual_average_ + vector[double] current_ATY_ + vector[double] sum_primal_solutions_ + vector[double] sum_dual_solutions_ + vector[double] last_restart_duality_gap_primal_solution_ + vector[double] last_restart_duality_gap_dual_solution_ double initial_primal_weight_ double initial_step_size_ int total_pdlp_iterations_ @@ -155,7 +155,7 @@ cdef extern from "cuopt/linear_programming/utilities/cython_solve.hpp" namespace bool solved_by_pdlp_ cdef cppclass mip_ret_t: - unique_ptr[device_buffer] solution_ + vector[double] solution_ mip_termination_status_t termination_status_ error_type_t error_status_ string error_message_ diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx index 1991af0d6..6ddb81f27 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa # SPDX-License-Identifier: Apache-2.0 @@ -7,8 +7,6 @@ # cython: embedsignature = True # cython: language_level = 3 -from pylibraft.common.handle cimport * - from datetime import date, datetime from dateutil.relativedelta import relativedelta @@ -25,7 +23,7 @@ from libcpp.string cimport string from libcpp.utility cimport move from libcpp.vector cimport vector -from rmm.pylibrmm.device_buffer cimport DeviceBuffer +# DeviceBuffer not needed - using host vectors from C++ from cuopt.linear_programming.data_model.data_model cimport data_model_view_t from cuopt.linear_programming.data_model.data_model_wrapper cimport DataModel @@ -46,7 +44,6 @@ import sys import warnings from enum import IntEnum -import cupy as cp import numpy as np from numba import cuda @@ -56,7 +53,7 @@ from cuopt.linear_programming.solver_settings.solver_settings import ( PDLPSolverMode, SolverSettings, ) -from cuopt.utilities import InputValidationError, series_from_buf +from cuopt.utilities import InputValidationError import pyarrow as pa @@ -300,9 +297,14 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr, sol_ret = move(sol_ret_ptr.get()[0]) if sol_ret.problem_type == ProblemCategory.MIP or sol_ret.problem_type == ProblemCategory.IP: # noqa - solution = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.mip_ret.solution_) - ) + # Extract host solution vector from C++ and copy to NumPy + solution_data = sol_ret.mip_ret.solution_.data() + solution_size = sol_ret.mip_ret.solution_.size() + if solution_size > 0: + solution = np.asarray(solution_data).copy() + else: + solution = np.array([], dtype=np.float64) + termination_status = sol_ret.mip_ret.termination_status_ error_status = sol_ret.mip_ret.error_status_ error_message = sol_ret.mip_ret.error_message_ @@ -317,8 +319,6 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr, num_nodes = sol_ret.mip_ret.nodes_ num_simplex_iterations = sol_ret.mip_ret.simplex_iterations_ - solution = series_from_buf(solution, pa.float64()).to_numpy() - return Solution( ProblemCategory(sol_ret.problem_type), dict(zip(data_model_obj.get_variable_names(), solution)), @@ -339,15 +339,33 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr, ) else: - primal_solution = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.primal_solution_) - ) - dual_solution = DeviceBuffer.c_from_unique_ptr(move(sol_ret.lp_ret.dual_solution_)) # noqa - reduced_cost = DeviceBuffer.c_from_unique_ptr(move(sol_ret.lp_ret.reduced_cost_)) # noqa + # Extract host solution vectors from C++ and copy to NumPy for LP + primal_data = sol_ret.lp_ret.primal_solution_.data() + primal_size = sol_ret.lp_ret.primal_solution_.size() + dual_data = sol_ret.lp_ret.dual_solution_.data() + dual_size = sol_ret.lp_ret.dual_solution_.size() + reduced_data = sol_ret.lp_ret.reduced_cost_.data() + reduced_size = sol_ret.lp_ret.reduced_cost_.size() + + # Handle potentially empty vectors + if primal_size > 0: + primal_solution = np.asarray( + primal_data + ).copy() + else: + primal_solution = np.array([], dtype=np.float64) - primal_solution = series_from_buf(primal_solution, pa.float64()).to_numpy() - dual_solution = series_from_buf(dual_solution, pa.float64()).to_numpy() - reduced_cost = series_from_buf(reduced_cost, pa.float64()).to_numpy() + if dual_size > 0: + dual_solution = np.asarray(dual_data).copy() + else: + dual_solution = np.array([], dtype=np.float64) + + if reduced_size > 0: + reduced_cost = np.asarray( + reduced_data + ).copy() + else: + reduced_cost = np.array([], dtype=np.float64) termination_status = sol_ret.lp_ret.termination_status_ error_status = sol_ret.lp_ret.error_status_ @@ -363,33 +381,94 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr, # In BatchSolve, we don't get the warm start data if not is_batch: - current_primal_solution = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.current_primal_solution_) - ) - current_dual_solution = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.current_dual_solution_) - ) - initial_primal_average = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.initial_primal_average_) - ) - initial_dual_average = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.initial_dual_average_) - ) - current_ATY = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.current_ATY_) - ) - sum_primal_solutions = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.sum_primal_solutions_) - ) - sum_dual_solutions = DeviceBuffer.c_from_unique_ptr( - move(sol_ret.lp_ret.sum_dual_solutions_) - ) - last_restart_duality_gap_primal_solution = DeviceBuffer.c_from_unique_ptr( # noqa - move(sol_ret.lp_ret.last_restart_duality_gap_primal_solution_) - ) - last_restart_duality_gap_dual_solution = DeviceBuffer.c_from_unique_ptr( # noqa - move(sol_ret.lp_ret.last_restart_duality_gap_dual_solution_) - ) + # Extract host warm start vectors from C++ and copy to NumPy + curr_primal_data = sol_ret.lp_ret.current_primal_solution_.data() + curr_primal_size = sol_ret.lp_ret.current_primal_solution_.size() + curr_dual_data = sol_ret.lp_ret.current_dual_solution_.data() + curr_dual_size = sol_ret.lp_ret.current_dual_solution_.size() + init_primal_avg_data = sol_ret.lp_ret.initial_primal_average_.data() + init_primal_avg_size = sol_ret.lp_ret.initial_primal_average_.size() + init_dual_avg_data = sol_ret.lp_ret.initial_dual_average_.data() + init_dual_avg_size = sol_ret.lp_ret.initial_dual_average_.size() + curr_aty_data = sol_ret.lp_ret.current_ATY_.data() + curr_aty_size = sol_ret.lp_ret.current_ATY_.size() + sum_primal_data = sol_ret.lp_ret.sum_primal_solutions_.data() + sum_primal_size = sol_ret.lp_ret.sum_primal_solutions_.size() + sum_dual_data = sol_ret.lp_ret.sum_dual_solutions_.data() + sum_dual_size = sol_ret.lp_ret.sum_dual_solutions_.size() + last_rst_primal_data = sol_ret.lp_ret.last_restart_duality_gap_primal_solution_.data() # noqa + last_rst_primal_size = sol_ret.lp_ret.last_restart_duality_gap_primal_solution_.size() # noqa + last_rst_dual_data = sol_ret.lp_ret.last_restart_duality_gap_dual_solution_.data() # noqa + last_rst_dual_size = sol_ret.lp_ret.last_restart_duality_gap_dual_solution_.size() # noqa + + # Handle potentially empty vectors (barrier solver may have empty warm start data) + if curr_primal_size > 0: + current_primal_solution = np.asarray( + curr_primal_data + ).copy() + else: + current_primal_solution = np.array([], dtype=np.float64) + + if curr_dual_size > 0: + current_dual_solution = np.asarray( + curr_dual_data + ).copy() + else: + current_dual_solution = np.array([], dtype=np.float64) + + if init_primal_avg_size > 0: + initial_primal_average = np.asarray( + init_primal_avg_data + ).copy() + else: + initial_primal_average = np.array([], dtype=np.float64) + + if init_dual_avg_size > 0: + initial_dual_average = np.asarray( + init_dual_avg_data + ).copy() + else: + initial_dual_average = np.array([], dtype=np.float64) + + if curr_aty_size > 0: + current_ATY = np.asarray( + curr_aty_data + ).copy() + else: + current_ATY = np.array([], dtype=np.float64) + + if sum_primal_size > 0: + sum_primal_solutions = np.asarray( + sum_primal_data + ).copy() + else: + sum_primal_solutions = np.array([], dtype=np.float64) + + if sum_dual_size > 0: + sum_dual_solutions = np.asarray( + sum_dual_data + ).copy() + else: + sum_dual_solutions = np.array([], dtype=np.float64) + + if last_rst_primal_size > 0: + last_restart_duality_gap_primal_solution = np.asarray( + last_rst_primal_data + ).copy() + else: + last_restart_duality_gap_primal_solution = np.array( + [], dtype=np.float64 + ) + + if last_rst_dual_size > 0: + last_restart_duality_gap_dual_solution = np.asarray( + last_rst_dual_data + ).copy() + else: + last_restart_duality_gap_dual_solution = np.array( + [], dtype=np.float64 + ) + initial_primal_weight = sol_ret.lp_ret.initial_primal_weight_ initial_step_size = sol_ret.lp_ret.initial_step_size_ total_pdlp_iterations = sol_ret.lp_ret.total_pdlp_iterations_ @@ -399,36 +478,6 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr, sum_solution_weight = sol_ret.lp_ret.sum_solution_weight_ iterations_since_last_restart = sol_ret.lp_ret.iterations_since_last_restart_ # noqa - current_primal_solution = series_from_buf( - current_primal_solution, pa.float64() - ).to_numpy() - current_dual_solution = series_from_buf( - current_dual_solution, pa.float64() - ).to_numpy() - initial_primal_average = series_from_buf( - initial_primal_average, pa.float64() - ).to_numpy() - initial_dual_average = series_from_buf( - initial_dual_average, pa.float64() - ).to_numpy() - current_ATY = series_from_buf( - current_ATY, pa.float64() - ).to_numpy() - sum_primal_solutions = series_from_buf( - sum_primal_solutions, pa.float64() - ).to_numpy() - sum_dual_solutions = series_from_buf( - sum_dual_solutions, pa.float64() - ).to_numpy() - last_restart_duality_gap_primal_solution = series_from_buf( - last_restart_duality_gap_primal_solution, - pa.float64() - ).to_numpy() - last_restart_duality_gap_dual_solution = series_from_buf( - last_restart_duality_gap_dual_solution, - pa.float64() - ).to_numpy() - return Solution( ProblemCategory(sol_ret.problem_type), dict(zip(data_model_obj.get_variable_names(), primal_solution)), # noqa