Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .lp_harness
Binary file not shown.
53 changes: 53 additions & 0 deletions .lp_harness.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <cuopt/mathematical_optimization/io/lp_writer.hpp>
#include <cuopt/mathematical_optimization/io/parser.hpp>
#include <cuopt/mathematical_optimization/io/data_model.hpp>

#include <cmath>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>

using namespace cuopt::mathematical_optimization::io;

static int failures = 0;
#define CHECK(cond, msg) do { if(!(cond)){ std::cout << "FAIL: " << msg << "\n"; ++failures; } } while(0)
static void near(double a, double b, const std::string& m){ if(std::fabs(a-b) > 1e-9 && !(std::isinf(a)&&std::isinf(b)&&((a>0)==(b>0)))){ std::cout<<"FAIL(near): "<<m<<" got "<<b<<" want "<<a<<"\n"; ++failures; } }
static std::unordered_map<std::string,int> idx(const std::vector<std::string>& n){ std::unordered_map<std::string,int> m; for(size_t i=0;i<n.size();++i)m[n[i]]=(int)i; return m; }

static void round(const std::string& tag, const std::string& lp){
data_model_t<int,double> a = read_lp_from_string<int,double>(lp);
std::string path = std::string("/home/nfs/iroy/lp_writer/.lp_out_")+tag+".lp";
lp_writer_t<int,double> w(a);
w.write(path);
data_model_t<int,double> b = read_lp<int,double>(path);
CHECK(a.get_sense()==b.get_sense(), tag+" sense");
near(a.get_objective_offset(), b.get_objective_offset(), tag+" offset");
const auto& an=a.get_variable_names(); const auto& bn=b.get_variable_names();
CHECK(an.size()==bn.size(), tag+" nvars");
auto bi=idx(bn);
const auto& ac=a.get_objective_coefficients(); const auto& bc=b.get_objective_coefficients();
const auto& alb=a.get_variable_lower_bounds(); const auto& blb=b.get_variable_lower_bounds();
const auto& aub=a.get_variable_upper_bounds(); const auto& bub=b.get_variable_upper_bounds();
const auto& at=a.get_variable_types(); const auto& bt=b.get_variable_types();
for(size_t i=0;i<an.size();++i){ if(!bi.count(an[i])){ std::cout<<"FAIL missing var "<<an[i]<<"\n"; ++failures; continue;} int j=bi[an[i]];
near(ac[i],bc[j],tag+" c "+an[i]); near(alb[i],blb[j],tag+" lb "+an[i]); near(aub[i],bub[j],tag+" ub "+an[i]);
CHECK(at[i]==bt[j], tag+" type "+an[i]); }
CHECK(a.get_row_names().size()==b.get_row_names().size(), tag+" nrows");
CHECK(a.has_quadratic_objective()==b.has_quadratic_objective(), tag+" qobj");
CHECK(a.get_quadratic_constraints().size()==b.get_quadratic_constraints().size(), tag+" nqc");
std::remove(path.c_str());
std::cout << tag << " done\n";
}

int main(){
round("simple", "Minimize\n obj: 3 x + 2 y - z\nSubject To\n c1: x + y <= 10\n c2: x - z >= -4\n c3: 2 x + y = 6\nBounds\n 0 <= x <= 8\n y >= 1\n -5 <= z <= 5\nEnd\n");
round("mip", "Minimize\n obj: x + y + 2 z\nSubject To\n c1: x + y + z <= 5\nBounds\n 0 <= y <= 10\nGenerals\n y\nBinaries\n x\n z\nEnd\n");
round("qpobj", "Minimize\n obj: x + [ 2 x ^ 2 + 4 x * y + 6 y ^ 2 ] / 2\nSubject To\n c1: x + y >= 1\nEnd\n");
round("qcqp", "Minimize\n obj: x + y\nSubject To\n lin: x + y <= 10\n qc: x + [ x ^ 2 + y ^ 2 ] <= 4\nEnd\n");
std::cout << "\nTOTAL FAILURES: " << failures << "\n";
return failures?1:0;
}
1 change: 1 addition & 0 deletions cpp/include/cuopt/mathematical_optimization/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@

/* @brief File format constants for problem I/O */
#define CUOPT_FILE_FORMAT_MPS 0
#define CUOPT_FILE_FORMAT_LP 1

/* @brief Status codes constants */
#define CUOPT_SUCCESS 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,11 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
raft::handle_t const* handle_ptr = nullptr) override;

/**
* @brief Write the optimization problem to an MPS file.
* @param[in] mps_file_path Path to the output MPS file
* @brief Write the optimization problem to a file.
* @param[in] file_path Path to the output file
* @param[in] format File format to write (MPS or LP)
*/
void write_to_mps(const std::string& mps_file_path) override;
void write_to_file(const std::string& file_path, file_format_t format) override;

/**
* @brief Check if this problem is equivalent to another problem.
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/cuopt/mathematical_optimization/cuopt_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ cuopt_int_t cuOptReadProblem(const char* filename, cuOptOptimizationProblem* pro
*
* @param[in] problem - The optimization problem to write.
* @param[in] filename - The path to the output file.
* @param[in] format - The file format to use. Currently only CUOPT_FILE_FORMAT_MPS is supported.
* @param[in] format - The file format to use. Supported values are
* CUOPT_FILE_FORMAT_MPS and CUOPT_FILE_FORMAT_LP.
*
* @return A status code indicating success or failure. Returns CUOPT_INVALID_ARGUMENT
* if an unsupported format is specified.
Expand Down
73 changes: 73 additions & 0 deletions cpp/include/cuopt/mathematical_optimization/io/lp_writer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

#include <cuopt/mathematical_optimization/io/data_model_view.hpp>
#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>

#include <memory>
#include <string>

namespace cuopt::mathematical_optimization::io {

/**
* @brief Main writer class for LP files
*
* Writes an optimization problem to a file in the LP format understood by
* read_lp(). The emitted dialect is a superset of what a typical solver
* expects and round-trips through read_lp():
* - Minimize / Maximize objective (with optional quadratic '[ ... ] / 2' term)
* - Subject To constraints (linear, plus optional quadratic '[ ... ]' term)
* - Bounds
* - Generals / Binaries / Semi-Continuous variable sections
*
* Notes / limitations that mirror the LP format itself:
* - Range rows (a linear row with two distinct finite bounds) are emitted as
* two constraints ('_lo' with '>=' and '_up' with '<=') because the LP
* format has no single-line ranged-row syntax.
* - The objective scaling factor is not representable in LP format and is
* therefore not written (identical to the MPS writer).
*
* @tparam i_t data type of the indices
* @tparam f_t data type of the weights and variables
*/
template <typename i_t, typename f_t>
class lp_writer_t {
public:
/**
* @brief Ctor. Takes a data model view as input and writes it out as an LP formatted file
*
* @param[in] problem Data model view to write
*/
lp_writer_t(const data_model_view_t<i_t, f_t>& problem);

/**
* @brief Ctor. Takes a data model as input and writes it out as an LP formatted file
*
* @param[in] problem Data model to write
*/
lp_writer_t(const mps_data_model_t<i_t, f_t>& problem);

/**
* @brief Writes the problem to an LP formatted file
*
* @param[in] lp_file_path Path to the LP file to write
*/
void write(const std::string& lp_file_path);

private:
// Owned view (created when constructing from mps_data_model_t)
std::unique_ptr<data_model_view_t<i_t, f_t>> owned_view_;
// Reference to the view (either external or owned)
const data_model_view_t<i_t, f_t>& problem_;

// Helper to create view from data model
static data_model_view_t<i_t, f_t> create_view(const mps_data_model_t<i_t, f_t>& model);
}; // class lp_writer_t

} // namespace cuopt::mathematical_optimization::io
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class mps_data_model_t {
bool has_quadratic_constraints() const noexcept;

/** whether to maximize or minimize the objective function */
bool maximize_;
bool maximize_{false};
/**
* the constraint matrix itself in the CSR format
* @{
Expand Down
12 changes: 12 additions & 0 deletions cpp/include/cuopt/mathematical_optimization/io/writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,16 @@ namespace cuopt::mathematical_optimization::io {
template <typename i_t, typename f_t>
void write_mps(const data_model_view_t<i_t, f_t>& problem, const std::string& mps_file_path);

/**
* @brief Writes the problem to an LP formatted file
*
* Emits the LP format understood by read_lp().
* Supports LP, MIP, and QP/QCQP problems, plus semi-continuous variables.
*
* @param[in] problem The problem data model view to write
* @param[in] lp_file_path Path to the LP file to write
*/
template <typename i_t, typename f_t>
void write_lp(const data_model_view_t<i_t, f_t>& problem, const std::string& lp_file_path);

} // namespace cuopt::mathematical_optimization::io
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,11 @@ class optimization_problem_t : public optimization_problem_interface_t<i_t, f_t>
// ============================================================================

/**
* @brief Write the optimization problem to an MPS file.
* @param[in] mps_file_path Path to the output MPS file
* @brief Write the optimization problem to a file.
* @param[in] file_path Path to the output file
* @param[in] format File format to write (MPS or LP)
*/
void write_to_mps(const std::string& mps_file_path) override;
void write_to_file(const std::string& file_path, file_format_t format) override;

/* Print scaling information */
void print_scaling_information() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
#include <raft/core/handle.hpp>
#include <rmm/device_uvector.hpp>

#include <algorithm>
#include <cctype>
#include <cstdint>
#include <memory>
#include <span>
#include <stdexcept>
#include <string>
#include <vector>

Expand All @@ -24,6 +27,32 @@ namespace cuopt::mathematical_optimization {
enum class var_t { CONTINUOUS = 0, INTEGER, SEMI_CONTINUOUS };
enum class problem_category_t : int8_t { LP = 0, MIP = 1, IP = 2 };

/** @brief File format used when serializing an optimization problem. */
enum class file_format_t { mps = 0, lp = 1 };

/**
* @brief Pick the output file format from a path's extension.
*
* A `.lp` suffix selects LP; `.mps` and `.qps` select MPS/QPS. Compressed
* output is not supported, so `.gz` and `.bz2` suffixes are rejected.
*
* @param[in] path Output file path.
* @return The inferred file format.
*/
inline file_format_t file_format_from_path(const std::string& path)
{
std::string lower(path);
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
if (lower.ends_with(".lp")) { return file_format_t::lp; }
if (lower.ends_with(".mps") || lower.ends_with(".qps")) { return file_format_t::mps; }
throw std::invalid_argument(
"write: unrecognized output file extension. Supported (case-insensitive): "
".mps, .qps, .lp. Compressed output is not supported. Given path: " +
path);
}

template <typename i_t, typename f_t>
class optimization_problem_t;
template <typename i_t, typename f_t>
Expand Down Expand Up @@ -380,10 +409,11 @@ class optimization_problem_interface_t {
// ============================================================================

/**
* @brief Write the optimization problem to an MPS file.
* @param[in] mps_file_path Path to the output MPS file
* @brief Write the optimization problem to a file.
* @param[in] file_path Path to the output file
* @param[in] format File format to write (MPS or LP)
*/
virtual void write_to_mps(const std::string& mps_file_path) = 0;
virtual void write_to_file(const std::string& file_path, file_format_t format) = 0;

// ============================================================================
// Comparison
Expand Down
1 change: 1 addition & 0 deletions cpp/src/io/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set(PARSERS_SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/data_model_view.cpp
${CMAKE_CURRENT_SOURCE_DIR}/file_to_string.cpp
${CMAKE_CURRENT_SOURCE_DIR}/lp_parser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/lp_writer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mps_data_model.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mps_parser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mps_writer.cpp
Expand Down
Loading