Repository: metab0t/PyOptInterface Branch: master Commit: 37d4e67baec4 Files: 831 Total size: 7.4 MB Directory structure: gitextract_81kqktr_/ ├── .clang-format ├── .github/ │ ├── actions/ │ │ ├── setup_optimizers_linux/ │ │ │ └── action.yml │ │ ├── setup_optimizers_macos/ │ │ │ └── action.yml │ │ └── setup_optimizers_windows/ │ │ └── action.yml │ ├── dependabot.yml │ └── workflows/ │ ├── doc-build.yml │ ├── linux-build.yml │ ├── macos-build.yml │ ├── wheel.yml │ └── windows-build.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── GEMINI.md ├── LICENSE.md ├── README.md ├── bench/ │ ├── bench_linopy_cvxpy.py │ ├── bench_modify.jl │ ├── bench_modify.mod │ ├── bench_modify.py │ ├── bench_modify.run │ ├── bench_static.jl │ ├── bench_static.py │ ├── nqueens/ │ │ ├── .gitignore │ │ ├── nqueens.jl │ │ ├── nqueens_gurobipy.py │ │ ├── nqueens_poi.py │ │ └── nqueens_pythonmip.py │ └── test_delete.py ├── docs/ │ ├── Makefile │ ├── make.bat │ ├── requirements.txt │ └── source/ │ ├── api/ │ │ ├── pyoptinterface.copt.rst │ │ ├── pyoptinterface.gurobi.rst │ │ ├── pyoptinterface.highs.rst │ │ ├── pyoptinterface.knitro.rst │ │ ├── pyoptinterface.mosek.rst │ │ ├── pyoptinterface.rst │ │ └── pyoptinterface.xpress.rst │ ├── attribute/ │ │ ├── copt.md │ │ ├── gurobi.md │ │ ├── highs.md │ │ ├── ipopt.md │ │ ├── knitro.md │ │ ├── mosek.md │ │ └── xpress.md │ ├── benchmark.md │ ├── callback.md │ ├── changelog.md │ ├── common_model_interface.md │ ├── conf.py │ ├── constraint.md │ ├── container.md │ ├── copt.md │ ├── develop.md │ ├── examples/ │ │ ├── economic_dispatch.md │ │ ├── optimal_control_rocket.md │ │ └── optimal_power_flow.md │ ├── expression.md │ ├── faq.md │ ├── getting_started.md │ ├── gurobi.md │ ├── highs.md │ ├── index.md │ ├── infeasibility.md │ ├── ipopt.md │ ├── knitro.md │ ├── model.md │ ├── mosek.md │ ├── nonlinear.md │ ├── numpy.md │ ├── objective.md │ ├── roadmap.md │ ├── structure.md │ ├── variable.md │ └── xpress.md ├── include/ │ └── pyoptinterface/ │ ├── cache_model.hpp │ ├── container.hpp │ ├── copt_model.hpp │ ├── core.hpp │ ├── cppad_interface.hpp │ ├── dylib.hpp │ ├── gurobi_model.hpp │ ├── highs_model.hpp │ ├── ipopt_model.hpp │ ├── knitro_model.hpp │ ├── mosek_model.hpp │ ├── nleval.hpp │ ├── nlexpr.hpp │ ├── solver_common.hpp │ ├── tcc_interface.hpp │ └── xpress_model.hpp ├── lib/ │ ├── cache_model.cpp │ ├── copt_model.cpp │ ├── copt_model_ext.cpp │ ├── copt_model_ext_constants.cpp │ ├── core.cpp │ ├── core_ext.cpp │ ├── cppad_interface.cpp │ ├── cppad_interface_ext.cpp │ ├── gurobi_model.cpp │ ├── gurobi_model_ext.cpp │ ├── gurobi_model_ext_constants.cpp │ ├── highs_model.cpp │ ├── highs_model_ext.cpp │ ├── highs_model_ext_constants.cpp │ ├── ipopt_model.cpp │ ├── ipopt_model_ext.cpp │ ├── knitro_model.cpp │ ├── knitro_model_ext.cpp │ ├── knitro_model_ext_constants.cpp │ ├── main.cpp │ ├── mosek_model.cpp │ ├── mosek_model_ext.cpp │ ├── mosek_model_ext_constants.cpp │ ├── nleval.cpp │ ├── nleval_ext.cpp │ ├── nlexpr.cpp │ ├── nlexpr_ext.cpp │ ├── tcc_interface.cpp │ ├── tcc_interface_ext.cpp │ ├── xpress_model.cpp │ ├── xpress_model_ext.cpp │ └── xpress_model_ext_constants.cpp ├── optimizer_version.toml ├── pyproject.toml ├── scripts/ │ ├── generate_attribute_table.py │ └── generate_solver_constants.py ├── skills/ │ └── pyoptinterface-expert/ │ ├── SKILL.md │ └── references/ │ ├── api.md │ └── examples.md ├── src/ │ └── pyoptinterface/ │ ├── __init__.py │ ├── _src/ │ │ ├── __init__.py │ │ ├── aml.py │ │ ├── attributes.py │ │ ├── codegen_c.py │ │ ├── codegen_llvm.py │ │ ├── comparison_constraint.py │ │ ├── constraint_bridge.py │ │ ├── copt.py │ │ ├── cpp_graph_iter.py │ │ ├── dylib.py │ │ ├── gurobi.py │ │ ├── highs.py │ │ ├── ipopt.py │ │ ├── jit_c.py │ │ ├── jit_llvm.py │ │ ├── knitro.py │ │ ├── matrix.py │ │ ├── monkeypatch.py │ │ ├── mosek.py │ │ ├── nlfunc.py │ │ ├── solver_common.py │ │ ├── tupledict.py │ │ └── xpress.py │ ├── copt.py │ ├── gurobi.py │ ├── highs.py │ ├── ipopt.py │ ├── knitro.py │ ├── mosek.py │ ├── nl.py │ └── xpress.py ├── tests/ │ ├── conftest.py │ ├── simple_cb.py │ ├── test_basic.py │ ├── test_close.py │ ├── test_compare_constraint.py │ ├── test_exp_cone.py │ ├── test_iis.py │ ├── test_in_constraint.py │ ├── test_ipopt.py │ ├── test_knitro.py │ ├── test_lukvle10.py │ ├── test_matrix_api.py │ ├── test_nlp.py │ ├── test_nlp_bilinear.py │ ├── test_nlp_clnlbeam.py │ ├── test_nlp_expression.py │ ├── test_nlp_hs071.py │ ├── test_nlp_multiple_run.py │ ├── test_nlp_opf.py │ ├── test_nlp_rocket.py │ ├── test_operator.py │ ├── test_preopt.py │ ├── test_qp.py │ ├── test_reducedcost.py │ ├── test_simple_opt.py │ ├── test_soc.py │ ├── test_sos.py │ ├── test_tupledict.py │ ├── test_update.py │ ├── tsp_cb.py │ └── tsp_xpress.py └── thirdparty/ ├── ankerl/ │ ├── stl.h │ └── unordered_dense.h ├── cppad/ │ ├── CMakeLists.txt │ ├── include/ │ │ └── cppad/ │ │ ├── base_require.hpp │ │ ├── configure.hpp │ │ ├── core/ │ │ │ ├── abort_recording.hpp │ │ │ ├── abs.hpp │ │ │ ├── abs_normal_fun.hpp │ │ │ ├── ad.hpp │ │ │ ├── ad_assign.hpp │ │ │ ├── ad_binary.hpp │ │ │ ├── ad_ctor.hpp │ │ │ ├── ad_fun.hpp │ │ │ ├── ad_io.hpp │ │ │ ├── ad_to_string.hpp │ │ │ ├── ad_type.hpp │ │ │ ├── ad_valued.hpp │ │ │ ├── add.hpp │ │ │ ├── add_eq.hpp │ │ │ ├── arithmetic.hpp │ │ │ ├── atan2.hpp │ │ │ ├── atomic/ │ │ │ │ ├── four/ │ │ │ │ │ ├── atomic.hpp │ │ │ │ │ ├── call.hpp │ │ │ │ │ ├── ctor.hpp │ │ │ │ │ ├── devel/ │ │ │ │ │ │ ├── hes_sparsity.hpp │ │ │ │ │ │ └── jac_sparsity.hpp │ │ │ │ │ ├── for_type.hpp │ │ │ │ │ ├── forward.hpp │ │ │ │ │ ├── hes_sparsity.hpp │ │ │ │ │ ├── jac_sparsity.hpp │ │ │ │ │ ├── rev_depend.hpp │ │ │ │ │ └── reverse.hpp │ │ │ │ ├── one/ │ │ │ │ │ └── atomic.hpp │ │ │ │ ├── three/ │ │ │ │ │ ├── afun.hpp │ │ │ │ │ ├── atomic.hpp │ │ │ │ │ ├── ctor.hpp │ │ │ │ │ ├── for_type.hpp │ │ │ │ │ ├── forward.hpp │ │ │ │ │ ├── hes_sparsity.hpp │ │ │ │ │ ├── jac_sparsity.hpp │ │ │ │ │ ├── rev_depend.hpp │ │ │ │ │ └── reverse.hpp │ │ │ │ └── two/ │ │ │ │ ├── afun.hpp │ │ │ │ ├── atomic.hpp │ │ │ │ ├── clear.hpp │ │ │ │ ├── ctor.hpp │ │ │ │ ├── for_sparse_hes.hpp │ │ │ │ ├── for_sparse_jac.hpp │ │ │ │ ├── forward.hpp │ │ │ │ ├── option.hpp │ │ │ │ ├── rev_depend.hpp │ │ │ │ ├── rev_sparse_hes.hpp │ │ │ │ ├── rev_sparse_jac.hpp │ │ │ │ └── reverse.hpp │ │ │ ├── azmul.hpp │ │ │ ├── base2ad.hpp │ │ │ ├── base_complex.hpp │ │ │ ├── base_cond_exp.hpp │ │ │ ├── base_double.hpp │ │ │ ├── base_float.hpp │ │ │ ├── base_hash.hpp │ │ │ ├── base_limits.hpp │ │ │ ├── base_std_math.hpp │ │ │ ├── base_to_string.hpp │ │ │ ├── bender_quad.hpp │ │ │ ├── bool_fun.hpp │ │ │ ├── bool_valued.hpp │ │ │ ├── capacity_order.hpp │ │ │ ├── check_for_nan.hpp │ │ │ ├── chkpoint_one/ │ │ │ │ ├── chkpoint_one.hpp │ │ │ │ ├── ctor.hpp │ │ │ │ ├── for_sparse_jac.hpp │ │ │ │ ├── forward.hpp │ │ │ │ ├── rev_sparse_hes.hpp │ │ │ │ ├── rev_sparse_jac.hpp │ │ │ │ ├── reverse.hpp │ │ │ │ ├── set_hes_sparse_bool.hpp │ │ │ │ ├── set_hes_sparse_set.hpp │ │ │ │ ├── set_jac_sparse_bool.hpp │ │ │ │ └── set_jac_sparse_set.hpp │ │ │ ├── chkpoint_two/ │ │ │ │ ├── chkpoint_two.hpp │ │ │ │ ├── ctor.hpp │ │ │ │ ├── dynamic.hpp │ │ │ │ ├── for_type.hpp │ │ │ │ ├── forward.hpp │ │ │ │ ├── hes_sparsity.hpp │ │ │ │ ├── jac_sparsity.hpp │ │ │ │ ├── rev_depend.hpp │ │ │ │ └── reverse.hpp │ │ │ ├── compare.hpp │ │ │ ├── compound_assign.hpp │ │ │ ├── con_dyn_var.hpp │ │ │ ├── cond_exp.hpp │ │ │ ├── convert.hpp │ │ │ ├── cppad_assert.hpp │ │ │ ├── dependent.hpp │ │ │ ├── discrete/ │ │ │ │ └── discrete.hpp │ │ │ ├── div.hpp │ │ │ ├── div_eq.hpp │ │ │ ├── drivers.hpp │ │ │ ├── epsilon.hpp │ │ │ ├── equal_op_seq.hpp │ │ │ ├── for_hes_sparsity.hpp │ │ │ ├── for_jac_sparsity.hpp │ │ │ ├── for_one.hpp │ │ │ ├── for_sparse_hes.hpp │ │ │ ├── for_sparse_jac.hpp │ │ │ ├── for_two.hpp │ │ │ ├── forward/ │ │ │ │ └── forward.hpp │ │ │ ├── fun_check.hpp │ │ │ ├── fun_construct.hpp │ │ │ ├── fun_eval.hpp │ │ │ ├── graph/ │ │ │ │ ├── cpp_graph.hpp │ │ │ │ ├── from_graph.hpp │ │ │ │ ├── from_json.hpp │ │ │ │ ├── graph_op_enum.hpp │ │ │ │ ├── to_graph.hpp │ │ │ │ └── to_json.hpp │ │ │ ├── hash_code.hpp │ │ │ ├── hessian.hpp │ │ │ ├── identical.hpp │ │ │ ├── independent/ │ │ │ │ └── independent.hpp │ │ │ ├── integer.hpp │ │ │ ├── jacobian.hpp │ │ │ ├── lu_ratio.hpp │ │ │ ├── mul.hpp │ │ │ ├── mul_eq.hpp │ │ │ ├── near_equal_ext.hpp │ │ │ ├── new_dynamic.hpp │ │ │ ├── num_skip.hpp │ │ │ ├── numeric_limits.hpp │ │ │ ├── omp_max_thread.hpp │ │ │ ├── opt_val_hes.hpp │ │ │ ├── optimize.hpp │ │ │ ├── ordered.hpp │ │ │ ├── parallel_ad.hpp │ │ │ ├── pow.hpp │ │ │ ├── print_for.hpp │ │ │ ├── rev_hes_sparsity.hpp │ │ │ ├── rev_jac_sparsity.hpp │ │ │ ├── rev_one.hpp │ │ │ ├── rev_sparse_hes.hpp │ │ │ ├── rev_sparse_jac.hpp │ │ │ ├── rev_two.hpp │ │ │ ├── reverse.hpp │ │ │ ├── sign.hpp │ │ │ ├── sparse.hpp │ │ │ ├── sparse_hes.hpp │ │ │ ├── sparse_hessian.hpp │ │ │ ├── sparse_jac.hpp │ │ │ ├── sparse_jacobian.hpp │ │ │ ├── standard_math.hpp │ │ │ ├── std_math_11.hpp │ │ │ ├── sub.hpp │ │ │ ├── sub_eq.hpp │ │ │ ├── subgraph_jac_rev.hpp │ │ │ ├── subgraph_reverse.hpp │ │ │ ├── subgraph_sparsity.hpp │ │ │ ├── tape_link.hpp │ │ │ ├── testvector.hpp │ │ │ ├── to_csrc.hpp │ │ │ ├── unary_minus.hpp │ │ │ ├── unary_plus.hpp │ │ │ ├── undef.hpp │ │ │ ├── user_ad.hpp │ │ │ ├── value.hpp │ │ │ ├── var2par.hpp │ │ │ ├── vec_ad/ │ │ │ │ └── vec_ad.hpp │ │ │ └── zdouble.hpp │ │ ├── cppad.hpp │ │ ├── local/ │ │ │ ├── ad_tape.hpp │ │ │ ├── atom_state.hpp │ │ │ ├── atomic_index.hpp │ │ │ ├── color_general.hpp │ │ │ ├── color_symmetric.hpp │ │ │ ├── cppad_colpack.hpp │ │ │ ├── declare_ad.hpp │ │ │ ├── define.hpp │ │ │ ├── graph/ │ │ │ │ ├── cpp_graph_itr.hpp │ │ │ │ ├── cpp_graph_op.hpp │ │ │ │ ├── csrc_writer.hpp │ │ │ │ ├── json_lexer.hpp │ │ │ │ ├── json_parser.hpp │ │ │ │ └── json_writer.hpp │ │ │ ├── hash_code.hpp │ │ │ ├── independent.hpp │ │ │ ├── is_pod.hpp │ │ │ ├── is_pod.hpp.in │ │ │ ├── op_code_dyn.hpp │ │ │ ├── op_code_var.hpp │ │ │ ├── optimize/ │ │ │ │ ├── cexp_info.hpp │ │ │ │ ├── csum_op_info.hpp │ │ │ │ ├── csum_stacks.hpp │ │ │ │ ├── extract_option.hpp │ │ │ │ ├── get_cexp_info.hpp │ │ │ │ ├── get_dyn_previous.hpp │ │ │ │ ├── get_op_previous.hpp │ │ │ │ ├── get_op_usage.hpp │ │ │ │ ├── get_par_usage.hpp │ │ │ │ ├── hash_code.hpp │ │ │ │ ├── match_op.hpp │ │ │ │ ├── optimize_run.hpp │ │ │ │ ├── record_csum.hpp │ │ │ │ ├── record_pv.hpp │ │ │ │ ├── record_vp.hpp │ │ │ │ ├── record_vv.hpp │ │ │ │ ├── size_pair.hpp │ │ │ │ └── usage.hpp │ │ │ ├── play/ │ │ │ │ ├── addr_enum.hpp │ │ │ │ ├── atom_op_info.hpp │ │ │ │ ├── dyn_player.hpp │ │ │ │ ├── player.hpp │ │ │ │ ├── random_iterator.hpp │ │ │ │ ├── random_setup.hpp │ │ │ │ ├── sequential_iterator.hpp │ │ │ │ └── subgraph_iterator.hpp │ │ │ ├── pod_vector.hpp │ │ │ ├── record/ │ │ │ │ ├── comp_op.hpp │ │ │ │ ├── cond_exp.hpp │ │ │ │ ├── dyn_recorder.hpp │ │ │ │ ├── put_dyn_atomic.hpp │ │ │ │ ├── put_var_atomic.hpp │ │ │ │ ├── put_var_vecad.hpp │ │ │ │ └── recorder.hpp │ │ │ ├── set_get_in_parallel.hpp │ │ │ ├── sparse/ │ │ │ │ ├── binary_op.hpp │ │ │ │ ├── internal.hpp │ │ │ │ ├── list_setvec.hpp │ │ │ │ ├── pack_setvec.hpp │ │ │ │ ├── size_setvec.hpp │ │ │ │ ├── svec_setvec.hpp │ │ │ │ └── unary_op.hpp │ │ │ ├── std_set.hpp │ │ │ ├── subgraph/ │ │ │ │ ├── arg_variable.hpp │ │ │ │ ├── entire_call.hpp │ │ │ │ ├── get_rev.hpp │ │ │ │ ├── info.hpp │ │ │ │ ├── init_rev.hpp │ │ │ │ └── sparsity.hpp │ │ │ ├── sweep/ │ │ │ │ ├── call_atomic.hpp │ │ │ │ ├── dynamic.hpp │ │ │ │ ├── for_hes.hpp │ │ │ │ ├── for_jac.hpp │ │ │ │ ├── forward0.hpp │ │ │ │ ├── forward1.hpp │ │ │ │ ├── forward2.hpp │ │ │ │ ├── forward_0.hpp │ │ │ │ ├── forward_any.hpp │ │ │ │ ├── forward_dir.hpp │ │ │ │ ├── rev_hes.hpp │ │ │ │ ├── rev_jac.hpp │ │ │ │ └── reverse.hpp │ │ │ ├── temp_file.hpp │ │ │ ├── utility/ │ │ │ │ ├── cppad_vector_itr.hpp │ │ │ │ └── vector_bool.hpp │ │ │ ├── val_graph/ │ │ │ │ ├── base_op.hpp │ │ │ │ ├── binary_op.hpp │ │ │ │ ├── call_atomic.hpp │ │ │ │ ├── call_op.hpp │ │ │ │ ├── cexp_op.hpp │ │ │ │ ├── comp_op.hpp │ │ │ │ ├── compress.hpp │ │ │ │ ├── con_op.hpp │ │ │ │ ├── csum_op.hpp │ │ │ │ ├── cumulative.hpp │ │ │ │ ├── dead_code.hpp │ │ │ │ ├── dis_op.hpp │ │ │ │ ├── dyn_type.hpp │ │ │ │ ├── enable_parallel.hpp │ │ │ │ ├── fold_con.hpp │ │ │ │ ├── fun2val.hpp │ │ │ │ ├── op2arg_index.hpp │ │ │ │ ├── op_enum2class.hpp │ │ │ │ ├── op_hash_table.hpp │ │ │ │ ├── op_iterator.hpp │ │ │ │ ├── option.hpp │ │ │ │ ├── pri_op.hpp │ │ │ │ ├── print_op.hpp │ │ │ │ ├── record.hpp │ │ │ │ ├── record_new.hpp │ │ │ │ ├── renumber.hpp │ │ │ │ ├── rev_depend.hpp │ │ │ │ ├── summation.hpp │ │ │ │ ├── tape.hpp │ │ │ │ ├── unary_op.hpp │ │ │ │ ├── val2fun.hpp │ │ │ │ ├── val_optimize.hpp │ │ │ │ ├── val_type.hpp │ │ │ │ ├── var_type.hpp │ │ │ │ └── vector_op.hpp │ │ │ └── var_op/ │ │ │ ├── abs_op.hpp │ │ │ ├── acos_op.hpp │ │ │ ├── acosh_op.hpp │ │ │ ├── add_op.hpp │ │ │ ├── asin_op.hpp │ │ │ ├── asinh_op.hpp │ │ │ ├── atan_op.hpp │ │ │ ├── atanh_op.hpp │ │ │ ├── atomic_op.hpp │ │ │ ├── cexp_op.hpp │ │ │ ├── compare.hpp │ │ │ ├── compare_op.hpp │ │ │ ├── cond_op.hpp │ │ │ ├── cos_op.hpp │ │ │ ├── cosh_op.hpp │ │ │ ├── cskip_op.hpp │ │ │ ├── csum_op.hpp │ │ │ ├── dis_op.hpp │ │ │ ├── discrete_op.hpp │ │ │ ├── div_op.hpp │ │ │ ├── erf_op.hpp │ │ │ ├── exp_op.hpp │ │ │ ├── expm1_op.hpp │ │ │ ├── load_op.hpp │ │ │ ├── log1p_op.hpp │ │ │ ├── log_op.hpp │ │ │ ├── mul_op.hpp │ │ │ ├── neg_op.hpp │ │ │ ├── one_var.hpp │ │ │ ├── par_op.hpp │ │ │ ├── parameter_op.hpp │ │ │ ├── pow_op.hpp │ │ │ ├── pri_op.hpp │ │ │ ├── print_op.hpp │ │ │ ├── prototype_op.hpp │ │ │ ├── sign_op.hpp │ │ │ ├── sin_op.hpp │ │ │ ├── sinh_op.hpp │ │ │ ├── sqrt_op.hpp │ │ │ ├── store_op.hpp │ │ │ ├── sub_op.hpp │ │ │ ├── tan_op.hpp │ │ │ ├── tanh_op.hpp │ │ │ ├── two_var.hpp │ │ │ ├── var_op.hpp │ │ │ └── zmul_op.hpp │ │ ├── utility/ │ │ │ ├── check_numeric_type.hpp │ │ │ ├── check_simple_vector.hpp │ │ │ ├── create_dll_lib.hpp │ │ │ ├── elapsed_seconds.hpp │ │ │ ├── error_handler.hpp │ │ │ ├── index_sort.hpp │ │ │ ├── link_dll_lib.hpp │ │ │ ├── lu_factor.hpp │ │ │ ├── lu_invert.hpp │ │ │ ├── lu_solve.hpp │ │ │ ├── memory_leak.hpp │ │ │ ├── nan.hpp │ │ │ ├── near_equal.hpp │ │ │ ├── ode_err_control.hpp │ │ │ ├── ode_gear.hpp │ │ │ ├── ode_gear_control.hpp │ │ │ ├── omp_alloc.hpp │ │ │ ├── poly.hpp │ │ │ ├── pow_int.hpp │ │ │ ├── romberg_mul.hpp │ │ │ ├── romberg_one.hpp │ │ │ ├── rosen_34.hpp │ │ │ ├── runge_45.hpp │ │ │ ├── set_union.hpp │ │ │ ├── sparse2eigen.hpp │ │ │ ├── sparse_rc.hpp │ │ │ ├── sparse_rcv.hpp │ │ │ ├── speed_test.hpp │ │ │ ├── test_boolofvoid.hpp │ │ │ ├── thread_alloc.hpp │ │ │ ├── time_test.hpp │ │ │ ├── to_string.hpp │ │ │ ├── track_new_del.hpp │ │ │ ├── vector.hpp │ │ │ └── vector_bool.hpp │ │ ├── utility.hpp │ │ └── wno_conversion.hpp │ └── src/ │ ├── cpp_graph_op.cpp │ └── temp_file.cpp ├── fmt/ │ ├── CMakeLists.txt │ ├── include/ │ │ └── fmt/ │ │ ├── args.h │ │ ├── base.h │ │ ├── chrono.h │ │ ├── color.h │ │ ├── compile.h │ │ ├── core.h │ │ ├── format-inl.h │ │ ├── format.h │ │ ├── os.h │ │ ├── ostream.h │ │ ├── printf.h │ │ ├── ranges.h │ │ ├── std.h │ │ └── xchar.h │ └── src/ │ ├── fmt.cc │ ├── format.cc │ └── os.cc ├── notes.md ├── solvers/ │ ├── copt/ │ │ └── copt.h │ ├── gurobi/ │ │ └── gurobi_c.h │ ├── highs/ │ │ ├── HConfig.h │ │ ├── Highs.h │ │ ├── filereaderlp/ │ │ │ ├── builder.hpp │ │ │ ├── def.hpp │ │ │ ├── model.hpp │ │ │ └── reader.hpp │ │ ├── interfaces/ │ │ │ └── highs_c_api.h │ │ ├── io/ │ │ │ ├── Filereader.h │ │ │ ├── FilereaderEms.h │ │ │ ├── FilereaderLp.h │ │ │ ├── FilereaderMps.h │ │ │ ├── HMPSIO.h │ │ │ ├── HMpsFF.h │ │ │ ├── HighsIO.h │ │ │ └── LoadOptions.h │ │ ├── ipm/ │ │ │ ├── IpxSolution.h │ │ │ ├── IpxWrapper.h │ │ │ ├── basiclu/ │ │ │ │ ├── basiclu.h │ │ │ │ ├── basiclu_factorize.h │ │ │ │ ├── basiclu_get_factors.h │ │ │ │ ├── basiclu_initialize.h │ │ │ │ ├── basiclu_obj_factorize.h │ │ │ │ ├── basiclu_obj_free.h │ │ │ │ ├── basiclu_obj_get_factors.h │ │ │ │ ├── basiclu_obj_initialize.h │ │ │ │ ├── basiclu_obj_solve_dense.h │ │ │ │ ├── basiclu_obj_solve_for_update.h │ │ │ │ ├── basiclu_obj_solve_sparse.h │ │ │ │ ├── basiclu_obj_update.h │ │ │ │ ├── basiclu_object.h │ │ │ │ ├── basiclu_solve_dense.h │ │ │ │ ├── basiclu_solve_for_update.h │ │ │ │ ├── basiclu_solve_sparse.h │ │ │ │ ├── basiclu_update.h │ │ │ │ ├── lu_def.h │ │ │ │ ├── lu_file.h │ │ │ │ ├── lu_internal.h │ │ │ │ └── lu_list.h │ │ │ └── ipx/ │ │ │ ├── basiclu_kernel.h │ │ │ ├── basiclu_wrapper.h │ │ │ ├── basis.h │ │ │ ├── conjugate_residuals.h │ │ │ ├── control.h │ │ │ ├── crossover.h │ │ │ ├── diagonal_precond.h │ │ │ ├── forrest_tomlin.h │ │ │ ├── guess_basis.h │ │ │ ├── indexed_vector.h │ │ │ ├── info.h │ │ │ ├── ipm.h │ │ │ ├── ipx_c.h │ │ │ ├── ipx_config.h │ │ │ ├── ipx_info.h │ │ │ ├── ipx_internal.h │ │ │ ├── ipx_parameters.h │ │ │ ├── ipx_status.h │ │ │ ├── iterate.h │ │ │ ├── kkt_solver.h │ │ │ ├── kkt_solver_basis.h │ │ │ ├── kkt_solver_diag.h │ │ │ ├── linear_operator.h │ │ │ ├── lp_solver.h │ │ │ ├── lu_factorization.h │ │ │ ├── lu_update.h │ │ │ ├── maxvolume.h │ │ │ ├── model.h │ │ │ ├── multistream.h │ │ │ ├── normal_matrix.h │ │ │ ├── power_method.h │ │ │ ├── sparse_matrix.h │ │ │ ├── sparse_utils.h │ │ │ ├── splitted_normal_matrix.h │ │ │ ├── starting_basis.h │ │ │ ├── symbolic_invert.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ ├── lp_data/ │ │ │ ├── HConst.h │ │ │ ├── HStruct.h │ │ │ ├── HighsAnalysis.h │ │ │ ├── HighsCallback.h │ │ │ ├── HighsCallbackStruct.h │ │ │ ├── HighsDebug.h │ │ │ ├── HighsIis.h │ │ │ ├── HighsInfo.h │ │ │ ├── HighsInfoDebug.h │ │ │ ├── HighsLp.h │ │ │ ├── HighsLpSolverObject.h │ │ │ ├── HighsLpUtils.h │ │ │ ├── HighsModelUtils.h │ │ │ ├── HighsOptions.h │ │ │ ├── HighsRanging.h │ │ │ ├── HighsSolution.h │ │ │ ├── HighsSolutionDebug.h │ │ │ ├── HighsSolve.h │ │ │ └── HighsStatus.h │ │ ├── mip/ │ │ │ ├── HighsCliqueTable.h │ │ │ ├── HighsConflictPool.h │ │ │ ├── HighsCutGeneration.h │ │ │ ├── HighsCutPool.h │ │ │ ├── HighsDebugSol.h │ │ │ ├── HighsDomain.h │ │ │ ├── HighsDomainChange.h │ │ │ ├── HighsDynamicRowMatrix.h │ │ │ ├── HighsGFkSolve.h │ │ │ ├── HighsImplications.h │ │ │ ├── HighsLpAggregator.h │ │ │ ├── HighsLpRelaxation.h │ │ │ ├── HighsMipAnalysis.h │ │ │ ├── HighsMipSolver.h │ │ │ ├── HighsMipSolverData.h │ │ │ ├── HighsModkSeparator.h │ │ │ ├── HighsNodeQueue.h │ │ │ ├── HighsObjectiveFunction.h │ │ │ ├── HighsPathSeparator.h │ │ │ ├── HighsPrimalHeuristics.h │ │ │ ├── HighsPseudocost.h │ │ │ ├── HighsRedcostFixing.h │ │ │ ├── HighsSearch.h │ │ │ ├── HighsSeparation.h │ │ │ ├── HighsSeparator.h │ │ │ ├── HighsTableauSeparator.h │ │ │ ├── HighsTransformedLp.h │ │ │ ├── MipTimer.h │ │ │ └── feasibilityjump.hh │ │ ├── model/ │ │ │ ├── HighsHessian.h │ │ │ ├── HighsHessianUtils.h │ │ │ └── HighsModel.h │ │ ├── parallel/ │ │ │ ├── HighsBinarySemaphore.h │ │ │ ├── HighsCacheAlign.h │ │ │ ├── HighsCombinable.h │ │ │ ├── HighsMutex.h │ │ │ ├── HighsParallel.h │ │ │ ├── HighsRaceTimer.h │ │ │ ├── HighsSchedulerConstants.h │ │ │ ├── HighsSpinMutex.h │ │ │ ├── HighsSplitDeque.h │ │ │ ├── HighsTask.h │ │ │ └── HighsTaskExecutor.h │ │ ├── pdlp/ │ │ │ ├── CupdlpWrapper.h │ │ │ └── cupdlp/ │ │ │ ├── cupdlp_cs.h │ │ │ ├── cupdlp_defs.h │ │ │ ├── cupdlp_linalg.h │ │ │ ├── cupdlp_proj.h │ │ │ ├── cupdlp_restart.h │ │ │ ├── cupdlp_scaling.h │ │ │ ├── cupdlp_solver.h │ │ │ ├── cupdlp_step.h │ │ │ └── cupdlp_utils.c │ │ ├── pdqsort/ │ │ │ └── pdqsort.h │ │ ├── presolve/ │ │ │ ├── HPresolve.h │ │ │ ├── HPresolveAnalysis.h │ │ │ ├── HighsPostsolveStack.h │ │ │ ├── HighsSymmetry.h │ │ │ ├── ICrash.h │ │ │ ├── ICrashUtil.h │ │ │ ├── ICrashX.h │ │ │ └── PresolveComponent.h │ │ ├── qpsolver/ │ │ │ ├── a_asm.hpp │ │ │ ├── a_quass.hpp │ │ │ ├── basis.hpp │ │ │ ├── crashsolution.hpp │ │ │ ├── dantzigpricing.hpp │ │ │ ├── devexpricing.hpp │ │ │ ├── eventhandler.hpp │ │ │ ├── factor.hpp │ │ │ ├── feasibility_bounded.hpp │ │ │ ├── feasibility_highs.hpp │ │ │ ├── gradient.hpp │ │ │ ├── instance.hpp │ │ │ ├── matrix.hpp │ │ │ ├── perturbation.hpp │ │ │ ├── pricing.hpp │ │ │ ├── qpconst.hpp │ │ │ ├── qpvector.hpp │ │ │ ├── quass.hpp │ │ │ ├── ratiotest.hpp │ │ │ ├── runtime.hpp │ │ │ ├── scaling.hpp │ │ │ ├── settings.hpp │ │ │ ├── snippets.hpp │ │ │ ├── statistics.hpp │ │ │ └── steepestedgepricing.hpp │ │ ├── simplex/ │ │ │ ├── HApp.h │ │ │ ├── HEkk.h │ │ │ ├── HEkkDual.h │ │ │ ├── HEkkDualRHS.h │ │ │ ├── HEkkDualRow.h │ │ │ ├── HEkkPrimal.h │ │ │ ├── HSimplex.h │ │ │ ├── HSimplexDebug.h │ │ │ ├── HSimplexNla.h │ │ │ ├── HSimplexReport.h │ │ │ ├── HighsSimplexAnalysis.h │ │ │ ├── SimplexConst.h │ │ │ ├── SimplexStruct.h │ │ │ └── SimplexTimer.h │ │ ├── test_kkt/ │ │ │ ├── DevKkt.h │ │ │ └── KktCh2.h │ │ ├── util/ │ │ │ ├── FactorTimer.h │ │ │ ├── HFactor.h │ │ │ ├── HFactorConst.h │ │ │ ├── HFactorDebug.h │ │ │ ├── HSet.h │ │ │ ├── HVector.h │ │ │ ├── HVectorBase.h │ │ │ ├── HighsCDouble.h │ │ │ ├── HighsComponent.h │ │ │ ├── HighsDataStack.h │ │ │ ├── HighsDisjointSets.h │ │ │ ├── HighsHash.h │ │ │ ├── HighsHashTree.h │ │ │ ├── HighsInt.h │ │ │ ├── HighsIntegers.h │ │ │ ├── HighsLinearSumBounds.h │ │ │ ├── HighsMatrixPic.h │ │ │ ├── HighsMatrixSlice.h │ │ │ ├── HighsMatrixUtils.h │ │ │ ├── HighsMemoryAllocation.h │ │ │ ├── HighsRandom.h │ │ │ ├── HighsRbTree.h │ │ │ ├── HighsSort.h │ │ │ ├── HighsSparseMatrix.h │ │ │ ├── HighsSparseVectorSum.h │ │ │ ├── HighsSplay.h │ │ │ ├── HighsTimer.h │ │ │ ├── HighsUtils.h │ │ │ └── stringutil.h │ │ └── zstr/ │ │ ├── strict_fstream.hpp │ │ └── zstr.hpp │ ├── ipopt/ │ │ ├── IpReturnCodes.h │ │ ├── IpReturnCodes.inc │ │ ├── IpReturnCodes_inc.h │ │ ├── IpStdCInterface.h │ │ ├── IpTypes.h │ │ └── IpoptConfig.h │ ├── knitro/ │ │ └── knitro.h │ ├── mosek/ │ │ ├── mosek_linux.h │ │ └── mosek_win.h │ └── xpress/ │ ├── function_list.txt │ └── xpress_forward_decls.h └── tcc/ └── libtcc.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: Microsoft IndentWidth: 4 UseTab: ForIndentation SortIncludes: false ColumnLimit: 100 AlignEscapedNewlines: Left AlwaysBreakTemplateDeclarations : Yes ================================================ FILE: .github/actions/setup_optimizers_linux/action.yml ================================================ name: "Install optimizers on linux" description: "Install optimizers and Setup licenses on linux" inputs: GUROBI_WLS: description: "..." required: false default: '' COPT_CLIENT_INI: description: "..." required: false default: '' MOSEK_LICENSE: description: "..." required: false default: '' KNITRO_LICENSE: description: "..." required: false default: '' GITHUB_TOKEN: description: "..." required: true CHECK_LICENSE: description: "..." required: true ARCH: description: "..." required: true type: choice default: "X64" options: - "X64" - "ARM64" runs: using: "composite" steps: - name: Create directory to store installers shell: bash run: | mkdir -p ~/installers - name: Cache Installers id: cache-installers-linux uses: actions/cache@v4 env: cache-name: cache-installers-linux with: path: ~/installers key: ${{ runner.os }}-${{ runner.arch }}-build-${{ env.cache-name }}-${{ hashFiles('optimizer_version.toml') }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}-build-${{ env.cache-name }}- - if: ${{ (steps.cache-installers-linux.outputs.cache-hit != 'true') && (inputs.ARCH == 'X64') }} shell: bash name: Download X64 Installers run: | curl -L -o ~/installers/gurobi.tar.gz https://packages.gurobi.com/13.0/gurobi13.0.0_linux64.tar.gz curl -L -o ~/installers/copt.tar.gz https://pub.shanshu.ai/download/copt/8.0.2/linux64/CardinalOptimizer-8.0.2-lnx64.tar.gz curl -L -o ~/installers/mosek.tar.bz2 https://download.mosek.com/stable/10.2.0/mosektoolslinux64x86.tar.bz2 curl -L -o ~/installers/idaes-solvers.tar.gz https://github.com/IDAES/idaes-ext/releases/download/3.4.2/idaes-solvers-ubuntu2204-x86_64.tar.gz - if: ${{ (steps.cache-installers-linux.outputs.cache-hit != 'true') && (inputs.ARCH == 'ARM64') }} shell: bash name: Download ARM64 Installers run: | curl -L -o ~/installers/gurobi.tar.gz https://packages.gurobi.com/13.0/gurobi13.0.0_armlinux64.tar.gz curl -L -o ~/installers/copt.tar.gz https://pub.shanshu.ai/download/copt/8.0.2/aarch64/CardinalOptimizer-8.0.2-aarch64_lnx.tar.gz curl -L -o ~/installers/mosek.tar.bz2 https://download.mosek.com/stable/10.2.0/mosektoolslinuxaarch64.tar.bz2 curl -L -o ~/installers/idaes-solvers.tar.gz https://github.com/IDAES/idaes-ext/releases/download/3.4.2/idaes-solvers-ubuntu2204-aarch64.tar.gz - name: Setup Gurobi Installation Home if: ${{ (inputs.ARCH == 'X64') && (inputs.GUROBI_WLS != '') }} shell: bash run: | tar xfz ~/installers/gurobi.tar.gz -C ~/ ls ~/gurobi1300/linux64 # set environment variables export GUROBI_HOME="${HOME}/gurobi1300/linux64" echo "GUROBI_HOME=${GUROBI_HOME}" >> $GITHUB_ENV - name: Setup Gurobi Installation Home if: ${{ (inputs.ARCH == 'ARM64') && (inputs.GUROBI_WLS != '') }} shell: bash run: | tar xfz ~/installers/gurobi.tar.gz -C ~/ ls ~/gurobi1300/armlinux64 # set environment variables export GUROBI_HOME="${HOME}/gurobi1300/armlinux64" echo "GUROBI_HOME=${GUROBI_HOME}" >> $GITHUB_ENV - name: Setup Gurobi Installation if: ${{ inputs.GUROBI_WLS != '' }} shell: bash env: GUROBI_WLS: ${{ inputs.GUROBI_WLS }} run: | echo "PATH=${PATH}:${GUROBI_HOME}/bin" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${GUROBI_HOME}/lib" >> $GITHUB_ENV echo $GUROBI_HOME # setup license using secrets echo "$GUROBI_WLS" > ~/gurobi.lic echo "GRB_LICENSE_FILE=${HOME}/gurobi.lic" >> $GITHUB_ENV - name: Test Gurobi if: ${{ (inputs.CHECK_LICENSE == 'true') && (inputs.GUROBI_WLS != '') }} shell: bash run: | gurobi_cl - name: Setup COPT Installation shell: bash env: COPT_CLIENT_INI: ${{ inputs.COPT_CLIENT_INI }} run: | tar xfz ~/installers/copt.tar.gz -C ~/ ls ~/copt80 # set environment variables export COPT_HOME="${HOME}/copt80" echo "COPT_HOME=${COPT_HOME}" >> $GITHUB_ENV echo "PATH=${PATH}:${COPT_HOME}/bin" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${COPT_HOME}/lib" >> $GITHUB_ENV echo $COPT_HOME # Just use the size-limited license # echo "$COPT_CLIENT_INI" > ~/client.ini # echo "COPT_LICENSE_DIR=${HOME}" >> $GITHUB_ENV - name: Test COPT if: ${{ inputs.CHECK_LICENSE == 'true' }} shell: bash run: | copt_cmd -c "quit" - name: Setup MOSEK Installation Env if: ${{ (inputs.ARCH == 'X64') && (inputs.MOSEK_LICENSE != '') }} shell: bash run: | tar jxf ~/installers/mosek.tar.bz2 -C ~/ ls ~/mosek # set environment variables export MOSEK_10_2_BINDIR="${HOME}/mosek/10.2/tools/platform/linux64x86/bin" echo "MOSEK_10_2_BINDIR=${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV - name: Setup MOSEK Installation Env if: ${{ (inputs.ARCH == 'ARM64') && (inputs.MOSEK_LICENSE != '') }} shell: bash run: | tar jxf ~/installers/mosek.tar.bz2 -C ~/ ls ~/mosek # set environment variables export MOSEK_10_2_BINDIR="${HOME}/mosek/10.2/tools/platform/linuxaarch64/bin" echo "MOSEK_10_2_BINDIR=${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV - name: Setup MOSEK Installation if: ${{ inputs.MOSEK_LICENSE != '' }} shell: bash env: MOSEK_LICENSE: ${{ inputs.MOSEK_LICENSE }} run: | echo "PATH=${PATH}:${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo $MOSEK_10_2_BINDIR # setup license using secrets echo "$MOSEK_LICENSE" > ~/mosek.lic echo "MOSEKLM_LICENSE_FILE=${HOME}/mosek.lic" >> $GITHUB_ENV - name: Test MOSEK if: ${{ (inputs.CHECK_LICENSE == 'true') && (inputs.MOSEK_LICENSE != '') }} shell: bash run: | msktestlic - name: Setup IPOPT Installation shell: bash run: | sudo apt-get install -y libopenblas-dev liblapack3 libgfortran5 mkdir -p ~/ipopt tar xfz ~/installers/idaes-solvers.tar.gz -C ~/ipopt echo "PATH=${PATH}:${HOME}/ipopt" >> $GITHUB_ENV echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${HOME}/ipopt" >> $GITHUB_ENV ls ~/ipopt - name: Test IPOPT shell: bash run: | ipopt -v - name: Setup KNITRO license if: ${{ inputs.KNITRO_LICENSE != '' }} shell: bash env: KNITRO_LICENSE: ${{ inputs.KNITRO_LICENSE }} run: | echo "$KNITRO_LICENSE" > ${HOME}/artelys_lic.txt echo "ARTELYS_LICENSE=${HOME}/artelys_lic.txt" >> $GITHUB_ENV - name: Install KNITRO (using pip) if: ${{ inputs.KNITRO_LICENSE != '' }} shell: bash run: | python -m pip install knitro ================================================ FILE: .github/actions/setup_optimizers_macos/action.yml ================================================ name: "Install optimizers on macOS" description: "Install optimizers and Setup licenses on macOS" inputs: GUROBI_WLS: description: "..." required: false default: '' COPT_CLIENT_INI: description: "..." required: false default: '' MOSEK_LICENSE: description: "..." required: false default: '' KNITRO_LICENSE: description: "..." required: false default: '' GITHUB_TOKEN: description: "..." required: true CHECK_LICENSE: description: "..." required: true ARCH: description: "..." required: true type: choice default: "X64" options: - "X64" - "ARM64" runs: using: "composite" steps: - name: Create directory to store installers shell: bash run: | mkdir -p ~/installers - name: Cache Installers id: cache-installers-macos uses: actions/cache@v4 env: cache-name: cache-installers-macos with: path: ~/installers key: ${{ runner.os }}-${{ runner.arch }}-build-${{ env.cache-name }}-${{ hashFiles('optimizer_version.toml') }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}-build-${{ env.cache-name }}- - if: ${{ steps.cache-installers-macos.outputs.cache-hit != 'true' }} shell: bash name: Download Universal Installers run: | curl -L -o ~/installers/gurobi.pkg https://packages.gurobi.com/13.0/gurobi13.0.0_macos_universal2.pkg curl -L -o ~/installers/copt.tar.gz https://pub.shanshu.ai/download/copt/8.0.2/osx64/CardinalOptimizer-8.0.2-universal_mac.tar.gz - if: ${{ (steps.cache-installers-macos.outputs.cache-hit != 'true') && (inputs.ARCH == 'X64') }} shell: bash name: Download X64 Installers run: | curl -L -o ~/installers/mosek.tar.bz2 https://download.mosek.com/stable/10.2.0/mosektoolsosx64x86.tar.bz2 curl -L -o ~/installers/idaes-solvers.tar.gz https://github.com/IDAES/idaes-ext/releases/download/3.4.2/idaes-solvers-darwin-x86_64.tar.gz - if: ${{ (steps.cache-installers-macos.outputs.cache-hit != 'true') && (inputs.ARCH == 'ARM64') }} shell: bash name: Download ARM64 Installers run: | curl -L -o ~/installers/mosek.tar.bz2 https://download.mosek.com/stable/10.2.0/mosektoolsosxaarch64.tar.bz2 curl -L -o ~/installers/idaes-solvers.tar.gz https://github.com/IDAES/idaes-ext/releases/download/3.4.2/idaes-solvers-darwin-aarch64.tar.gz - name: Setup Gurobi Installation if: ${{ inputs.GUROBI_WLS != '' }} shell: bash env: GUROBI_WLS: ${{ inputs.GUROBI_WLS }} run: | pkgutil --expand-full ~/installers/gurobi.pkg ~/gurobi ls ~/gurobi # set environment variables export GUROBI_HOME="${HOME}/gurobi/gurobi13.0.0_macos_universal2.component.pkg/Payload/Library/gurobi1300/macos_universal2" echo "GUROBI_HOME=${GUROBI_HOME}" >> $GITHUB_ENV echo "PATH=${PATH}:${GUROBI_HOME}/bin" >> $GITHUB_ENV echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${GUROBI_HOME}/lib" >> $GITHUB_ENV echo $GUROBI_HOME ls $GUROBI_HOME # setup license using secrets echo "$GUROBI_WLS" > ~/gurobi.lic echo "GRB_LICENSE_FILE=${HOME}/gurobi.lic" >> $GITHUB_ENV - name: Test Gurobi if: ${{ (inputs.CHECK_LICENSE == 'true') && (inputs.GUROBI_WLS != '') }} shell: bash run: | gurobi_cl - name: Setup COPT Installation shell: bash env: COPT_CLIENT_INI: ${{ inputs.COPT_CLIENT_INI }} run: | tar xfz ~/installers/copt.tar.gz -C ~/ ls ~/copt80 # set environment variables export COPT_HOME="${HOME}/copt80" echo "COPT_HOME=${COPT_HOME}" >> $GITHUB_ENV echo "PATH=${PATH}:${COPT_HOME}/bin" >> $GITHUB_ENV echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${COPT_HOME}/lib" >> $GITHUB_ENV echo $COPT_HOME ls $COPT_HOME # Just use the size-limited license # echo "$COPT_CLIENT_INI" > ~/client.ini # echo "COPT_LICENSE_DIR=${HOME}" >> $GITHUB_ENV - name: Test COPT if: ${{ inputs.CHECK_LICENSE == 'true' }} shell: bash run: | copt_cmd -c "quit" - name: Setup MOSEK X64 Installation if: ${{ (inputs.ARCH == 'X64') && (inputs.MOSEK_LICENSE != '') }} shell: bash env: MOSEK_LICENSE: ${{ inputs.MOSEK_LICENSE }} run: | tar jxf ~/installers/mosek.tar.bz2 -C ~/ ls ~/mosek/10.2/tools/platform # set environment variables export MOSEK_10_2_BINDIR="${HOME}/mosek/10.2/tools/platform/osx64x86/bin" echo "MOSEK_10_2_BINDIR=${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo "PATH=${PATH}:${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo $MOSEK_10_2_BINDIR ls $MOSEK_10_2_BINDIR # setup license using secrets echo "$MOSEK_LICENSE" > ~/mosek.lic echo "MOSEKLM_LICENSE_FILE=${HOME}/mosek.lic" >> $GITHUB_ENV - name: Setup MOSEK ARM64 Installation if: ${{ (inputs.ARCH == 'ARM64') && (inputs.MOSEK_LICENSE != '') }} shell: bash env: MOSEK_LICENSE: ${{ inputs.MOSEK_LICENSE }} run: | tar jxf ~/installers/mosek.tar.bz2 -C ~/ ls ~/mosek/10.2/tools/platform # set environment variables export MOSEK_10_2_BINDIR="${HOME}/mosek/10.2/tools/platform/osxaarch64/bin" echo "MOSEK_10_2_BINDIR=${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo "PATH=${PATH}:${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${MOSEK_10_2_BINDIR}" >> $GITHUB_ENV echo $MOSEK_10_2_BINDIR ls $MOSEK_10_2_BINDIR # setup license using secrets echo "$MOSEK_LICENSE" > ~/mosek.lic echo "MOSEKLM_LICENSE_FILE=${HOME}/mosek.lic" >> $GITHUB_ENV - name: Test MOSEK if: ${{ (inputs.CHECK_LICENSE == 'true') && (inputs.MOSEK_LICENSE != '') }} shell: bash run: | msktestlic - name: Setup IPOPT Installation shell: bash run: | mkdir -p ~/ipopt tar xfz ~/installers/idaes-solvers.tar.gz -C ~/ipopt echo "PATH=${PATH}:${HOME}/ipopt" >> $GITHUB_ENV echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${HOME}/ipopt" >> $GITHUB_ENV ls ~/ipopt - name: Test IPOPT shell: bash run: | ipopt -v - name: Setup KNITRO License if: ${{ inputs.KNITRO_LICENSE != '' }} shell: bash env: KNITRO_LICENSE: ${{ inputs.KNITRO_LICENSE }} run: | echo "$KNITRO_LICENSE" > ~/artelys_lic.txt echo "ARTELYS_LICENSE=${HOME}/artelys_lic.txt" >> $GITHUB_ENV - name: Install KNITRO (using pip) if: ${{ inputs.KNITRO_LICENSE != '' }} shell: bash run: | python -m pip install knitro ================================================ FILE: .github/actions/setup_optimizers_windows/action.yml ================================================ name: "Install optimizers on windows" description: "Install optimizers and Setup licenses on windows" inputs: GUROBI_WLS: description: "..." required: false default: '' COPT_CLIENT_INI: description: "..." required: false default: '' MOSEK_LICENSE: description: "..." required: false default: '' KNITRO_LICENSE: description: "..." required: false default: '' GITHUB_TOKEN: description: "..." required: true CHECK_LICENSE: description: "..." required: true runs: using: "composite" steps: - name: Install lessmsi shell: pwsh run: | curl -L -o D:\lessmsi.zip https://github.com/activescott/lessmsi/releases/download/v1.10.0/lessmsi-v1.10.0.zip 7z x D:\lessmsi.zip -oD:\lessmsi echo "PATH=$env:PATH;D:\lessmsi" >> $env:GITHUB_ENV - name: Test lessmsi shell: pwsh run: | lessmsi h - name: Create directory to store installers shell: pwsh run: | New-Item -ItemType Directory -Force -Path "D:\installers" - name: Cache Installers id: cache-installers-windows uses: actions/cache@v4 env: cache-name: cache-installers-windows with: path: D:\installers key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('optimizer_version.toml') }} restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- - if: ${{ steps.cache-installers-windows.outputs.cache-hit != 'true' }} shell: pwsh name: Download Installers run: | curl -L -o D:\installers\gurobi.msi https://packages.gurobi.com/13.0/Gurobi-13.0.0-win64.msi curl -L -o D:\installers\copt.zip https://pub.shanshu.ai/download/copt/8.0.2/win64/CardinalOptimizer-8.0.2-win64.zip curl -L -o D:\installers\mosek.msi https://download.mosek.com/stable/10.2.0/moseksetupwin64x86.msi curl -L -o D:\installers\idaes-solvers.tar.gz https://github.com/IDAES/idaes-ext/releases/download/3.4.2/idaes-solvers-windows-x86_64.tar.gz - name: List Installers shell: pwsh run: | Get-ChildItem -Path D:\installers - name: Setup Gurobi Installation if: ${{ inputs.GUROBI_WLS != '' }} shell: pwsh env: GUROBI_WLS: ${{ inputs.GUROBI_WLS }} run: | lessmsi x D:\installers\gurobi.msi "D:\" gurobi_cl.exe lessmsi x D:\installers\gurobi.msi "D:\" gurobi130.dll gurobi130.lib lessmsi x D:\installers\gurobi.msi "D:\" gurobi_c.h ls D:\SourceDir\gurobi1300\win64 # set environment variables echo "GUROBI_HOME=D:\SourceDir\gurobi1300\win64" >> $env:GITHUB_ENV echo "PATH=$env:PATH;D:\SourceDir\gurobi1300\win64\bin" >> $env:GITHUB_ENV echo $env:GUROBI_HOME # setup license using secrets echo $env:GUROBI_WLS > D:\gurobi.lic echo "GRB_LICENSE_FILE=D:\gurobi.lic" >> $env:GITHUB_ENV - name: Test Gurobi if: ${{ (inputs.CHECK_LICENSE == 'true') && (inputs.GUROBI_WLS != '') }} shell: pwsh run: | gurobi_cl - name: Setup COPT Installation shell: pwsh env: COPT_CLIENT_INI: ${{ inputs.COPT_CLIENT_INI }} run: | # unzip with 7zip 7z x D:\installers\copt.zip -oD:\ ls D:\copt80 # set environment variables echo "COPT_HOME=D:\copt80" >> $env:GITHUB_ENV echo "PATH=$env:PATH;D:\copt80\bin" >> $env:GITHUB_ENV echo $env:COPT_HOME # Just use the size-limited license # echo $env:COPT_CLIENT_INI > D:\client.ini # echo "COPT_LICENSE_DIR=D:\" >> $env:GITHUB_ENV - name: Test COPT if: ${{ inputs.CHECK_LICENSE == 'true' }} shell: pwsh run: | copt_cmd -c "quit" - name: Setup MOSEK Installation if: ${{ inputs.MOSEK_LICENSE != '' }} shell: pwsh env: MOSEK_LICENSE: ${{ inputs.MOSEK_LICENSE }} run: | lessmsi x D:\installers\mosek.msi "D:\" msktestlic.exe lessmsi x D:\installers\mosek.msi "D:\" mosek64_10_2.dll mosek64_10_2.lib tbb12.dll svml_dispmd.dll lessmsi x D:\installers\mosek.msi "D:\" mosek.h ls D:\SourceDir\PFiles\Mosek\10.2\tools\platform\win64x86 # set environment variables echo "MOSEK_10_2_BINDIR=D:\SourceDir\PFiles\Mosek\10.2\tools\platform\win64x86\bin" >> $env:GITHUB_ENV echo "PATH=$env:PATH;D:\SourceDir\PFiles\Mosek\10.2\tools\platform\win64x86\bin" >> $env:GITHUB_ENV echo $env:MOSEK_10_2_BINDIR # setup license using secrets echo $env:MOSEK_LICENSE > D:\mosek.lic echo "MOSEKLM_LICENSE_FILE=D:\mosek.lic" >> $env:GITHUB_ENV - name: Test MOSEK if: ${{ (inputs.CHECK_LICENSE == 'true') && (inputs.MOSEK_LICENSE != '') }} shell: pwsh run: | msktestlic - name: Setup IPOPT solver shell: pwsh run: | 7z x -so D:\installers\idaes-solvers.tar.gz | 7z x -si -ttar -oD:\ipopt echo "PATH=D:\ipopt;$env:PATH" >> $env:GITHUB_ENV ls D:\ipopt - name: Test IPOPT shell: pwsh run: | ipopt -v - name: Setup KNITRO License if: ${{ inputs.KNITRO_LICENSE != '' }} shell: pwsh env: KNITRO_LICENSE: ${{ inputs.KNITRO_LICENSE }} run: | # setup license using secrets echo $env:KNITRO_LICENSE > D:\artelys_lic.txt echo "ARTELYS_LICENSE=D:\artelys_lic.txt" >> $env:GITHUB_ENV - name: Install KNITRO (using pip) if: ${{ inputs.KNITRO_LICENSE != '' }} shell: pwsh run: | python -m pip install knitro ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" ================================================ FILE: .github/workflows/doc-build.yml ================================================ name: gh-pages on: push: branches: - master permissions: contents: write jobs: doc_build: runs-on: ubuntu-latest strategy: fail-fast: true matrix: python-version: ["3.13"] env: PYTHON_VERSION: ${{ matrix.python-version }} steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' - uses: ./.github/actions/setup_optimizers_linux with: GUROBI_WLS: ${{ secrets.GUROBI_WLS }} COPT_CLIENT_INI: ${{ secrets.COPT_CLIENT_INI }} MOSEK_LICENSE: ${{ secrets.MOSEK_LICENSE }} KNITRO_LICENSE: ${{ secrets.KNITRO_LICENSE }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CHECK_LICENSE: false - name: Build run: | python -m pip list python -m pip install nanobind scikit-build-core[pyproject] typing_extensions python -m pip install --no-build-isolation -v . python -c "import pyoptinterface as poi; print(dir(poi))" - name: Build documentation run: | pip install -r docs/requirements.txt cd docs make html cd .. - name: Deploy uses: JamesIves/github-pages-deploy-action@v4 with: folder: docs/build/html ================================================ FILE: .github/workflows/linux-build.yml ================================================ name: linux-build on: pull_request: push: branches: - master jobs: linux_build: runs-on: ${{ matrix.os }} # Only allow one build at a time otherwise we can run out of licenses concurrency: group: linux_build strategy: fail-fast: false matrix: os: [ubuntu-latest, ubuntu-24.04-arm] # python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.13"] env: PYTHON_VERSION: ${{ matrix.python-version }} steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' - uses: ./.github/actions/setup_optimizers_linux with: GUROBI_WLS: ${{ secrets.GUROBI_WLS }} COPT_CLIENT_INI: ${{ secrets.COPT_CLIENT_INI }} MOSEK_LICENSE: ${{ secrets.MOSEK_LICENSE }} KNITRO_LICENSE: ${{ secrets.KNITRO_LICENSE }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CHECK_LICENSE: true ARCH: ${{ runner.arch }} - name: Build run: | python -m pip list python -m pip install nanobind scikit-build-core[pyproject] typing_extensions python -m pip install --no-build-isolation -v . python -c "import pyoptinterface as poi; print(dir(poi))" python -m pip wheel -w dist --no-build-isolation . - name: Test run: | python -m pip install pytest numpy scipy highsbox llvmlite tccbox python -m pytest tests -v - name: Upload artifact uses: actions/upload-artifact@v7 with: name: pyoptinterface-wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }} path: dist/ ================================================ FILE: .github/workflows/macos-build.yml ================================================ name: macos-build on: pull_request: push: branches: - master jobs: macos_build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-14] # python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.11", "3.13"] env: PYTHON_VERSION: ${{ matrix.python-version }} MACOSX_DEPLOYMENT_TARGET: 10.14 steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' - uses: ./.github/actions/setup_optimizers_macos with: GUROBI_WLS: ${{ secrets.GUROBI_WLS }} COPT_CLIENT_INI: ${{ secrets.COPT_CLIENT_INI }} MOSEK_LICENSE: ${{ secrets.MOSEK_LICENSE }} KNITRO_LICENSE: ${{ secrets.KNITRO_LICENSE }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CHECK_LICENSE: false ARCH: ${{ runner.arch }} - name: Build run: | python -m pip list python -m pip install nanobind scikit-build-core[pyproject] typing_extensions python -m pip install --no-build-isolation -v . python -c "import pyoptinterface as poi; print(dir(poi))" python -m pip wheel -w dist --no-build-isolation . - name: Test run: | python -m pip install pytest numpy scipy highsbox llvmlite tccbox python -m pytest tests -k "highs or ipopt" -v - name: Upload artifact uses: actions/upload-artifact@v7 with: name: pyoptinterface-wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }} path: dist/ ================================================ FILE: .github/workflows/wheel.yml ================================================ name: cibuildwheel on: workflow_dispatch: inputs: publish: description: 'Publish wheels to PyPI: (testpypi/pypi/none)' required: false type: choice options: - testpypi - pypi - none default: none jobs: build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest-large, macos-latest] steps: - uses: actions/checkout@v6 - name: Build wheels uses: pypa/cibuildwheel@v3.4.1 env: # Select wheels CIBW_BUILD: "*-manylinux_x86_64 *-manylinux_aarch64 *-win_amd64 *-macosx_x86_64 *-macosx_arm64" CIBW_SKIP: "cp38-* cp3??t-*" CIBW_ARCHS: "native" # use manylinux2014 CIBW_MANYLINUX_X86_64_IMAGE: "quay.io/pypa/manylinux2014_x86_64" CIBW_MANYLINUX_AARCH64_IMAGE: "quay.io/pypa/manylinux2014_aarch64" CIBW_ENVIRONMENT_MACOS: > MACOSX_DEPLOYMENT_TARGET=10.14 CIBW_TEST_COMMAND: "python -c \"import pyoptinterface as poi; print(dir(poi))\"" with: package-dir: . output-dir: wheelhouse config-file: "{package}/pyproject.toml" - uses: actions/upload-artifact@v7 with: name: cibw-wheels-${{ runner.os }}-${{ runner.arch }} path: ./wheelhouse/*.whl publish-to-testpypi: name: Publish Python wheels to TestPyPI needs: - build_wheels runs-on: ubuntu-latest if: github.event.inputs.publish == 'testpypi' environment: name: testpypi url: https://test.pypi.org/p/pyoptinterface permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists uses: actions/download-artifact@v8 with: pattern: cibw-wheels-* merge-multiple: true path: dist/ - name: List all the dists run: ls -l dist/ - name: Publish distribution 📦 to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ publish-to-pypi: name: Publish Python wheels to PyPI needs: - build_wheels runs-on: ubuntu-latest if: github.event.inputs.publish == 'pypi' environment: name: pypi url: https://pypi.org/project/pyoptinterface/ permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists uses: actions/download-artifact@v8 with: pattern: cibw-wheels-* merge-multiple: true path: dist/ - name: List all the dists run: ls -l dist/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://upload.pypi.org/legacy/ ================================================ FILE: .github/workflows/windows-build.yml ================================================ name: windows-build on: pull_request: push: branches: - master jobs: windows_build: runs-on: windows-latest # Only allow one build at a time otherwise we can run out of licenses concurrency: group: windows_build strategy: fail-fast: false matrix: python-version: ["3.9", "3.13"] env: PYTHON_VERSION: ${{ matrix.python-version }} steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' - uses: ./.github/actions/setup_optimizers_windows with: GUROBI_WLS: ${{ secrets.GUROBI_WLS }} COPT_CLIENT_INI: ${{ secrets.COPT_CLIENT_INI }} MOSEK_LICENSE: ${{ secrets.MOSEK_LICENSE }} KNITRO_LICENSE: ${{ secrets.KNITRO_LICENSE }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CHECK_LICENSE: true - name: Build run: | python -m pip list python -m pip install nanobind scikit-build-core[pyproject] typing_extensions python -m pip install --no-build-isolation -v . python -c "import pyoptinterface as poi; print(dir(poi))" python -m pip wheel -w dist --no-build-isolation . - name: Test run: | python -m pip install pytest numpy scipy highsbox llvmlite tccbox python -m pytest tests -v - name: Upload artifact uses: actions/upload-artifact@v7 with: name: pyoptinterface-wheel-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }} path: dist/ ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class build/ .pytest_cache/ .mypy_cache/ dist/ .idea/ .vscode/ .claude/ bench/local debug/ docs/jupyter_execute docs/build *.lp *.sol ================================================ FILE: .pre-commit-config.yaml ================================================ repos: # C++ 格式化 - clang-format - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.1 hooks: - id: clang-format types_or: [c++, c] files: \.(cpp|hpp|c|h|cc|cxx|hxx)$ exclude: ^thirdparty/ # Python 格式化 - black - repo: https://github.com/psf/black rev: 26.3.1 hooks: - id: black language_version: python3 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15...3.27) project(pyoptinterface) set(CMAKE_CXX_STANDARD 20) # Linux: -fPIC set(CMAKE_POSITION_INDEPENDENT_CODE ON) # we need to ensure all shared libraries can look up its own directory to load other shared libraries # this is very important for CppAD users because we extract its thread_alloc as a shared library function(set_rpath target) if(APPLE) set(CMAKE_MACOSX_RPATH 1) set_target_properties(${target} PROPERTIES INSTALL_RPATH "@loader_path") elseif(UNIX) set_target_properties(${target} PROPERTIES INSTALL_RPATH "$ORIGIN") elseif(WIN32) endif() endfunction() if(MSVC) # Add /MP flag for multi-processor compilation add_compile_options(/MP) add_compile_options(/utf-8) if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64") # use AVX2 add_compile_options(/arch:AVX2) endif() endif() if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") # add_compile_options(-march=haswell) endif() endif() add_subdirectory(thirdparty/fmt) add_subdirectory(thirdparty/cppad) set(POI_INSTALL_DIR ${SKBUILD_PLATLIB_DIR}/pyoptinterface/_src) add_library(core STATIC) target_sources(core PRIVATE include/pyoptinterface/cache_model.hpp include/pyoptinterface/core.hpp include/pyoptinterface/container.hpp include/pyoptinterface/dylib.hpp include/pyoptinterface/solver_common.hpp lib/cache_model.cpp lib/core.cpp ) target_include_directories(core PUBLIC include thirdparty) target_link_libraries(core PUBLIC fmt) add_library(nlexpr STATIC) target_sources(nlexpr PRIVATE include/pyoptinterface/nlexpr.hpp lib/nlexpr.cpp ) target_include_directories(nlexpr PUBLIC include thirdparty) target_link_libraries(nlexpr PUBLIC core) add_library(nleval STATIC) target_sources(nleval PRIVATE include/pyoptinterface/nleval.hpp lib/nleval.cpp ) target_link_libraries(nleval PUBLIC nlexpr core) add_library(cppad_interface STATIC) target_sources(cppad_interface PRIVATE include/pyoptinterface/cppad_interface.hpp lib/cppad_interface.cpp ) target_include_directories(cppad_interface PUBLIC include thirdparty) target_link_libraries(cppad_interface PUBLIC nlexpr cppad) add_library(tcc_interface STATIC) target_sources(tcc_interface PRIVATE include/pyoptinterface/tcc_interface.hpp lib/tcc_interface.cpp ) target_include_directories(tcc_interface PUBLIC include thirdparty) target_link_libraries(tcc_interface PUBLIC fmt) # Build Python extensions find_package(Python ${PYTHON_VERSION} REQUIRED COMPONENTS Interpreter Development.Module OPTIONAL_COMPONENTS Development.SABIModule) # Import nanobind through CMake's find_package mechanism find_package(nanobind CONFIG REQUIRED) nanobind_add_module( core_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/core_ext.cpp ) target_link_libraries(core_ext PUBLIC core) install(TARGETS core_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) nanobind_add_module( nlexpr_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/nlexpr_ext.cpp ) target_link_libraries(nlexpr_ext PUBLIC nlexpr) install(TARGETS nlexpr_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) nanobind_add_module( nleval_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/nleval_ext.cpp ) target_link_libraries(nleval_ext PUBLIC nleval) install(TARGETS nleval_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) nanobind_add_module( cppad_interface_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/cppad_interface_ext.cpp ) target_link_libraries(cppad_interface_ext PUBLIC cppad_interface) install(TARGETS cppad_interface_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) nanobind_add_module( tcc_interface_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/tcc_interface_ext.cpp ) target_link_libraries(tcc_interface_ext PUBLIC tcc_interface) install(TARGETS tcc_interface_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # Solvers # Gurobi add_library(gurobi_model STATIC) target_sources(gurobi_model PRIVATE include/pyoptinterface/gurobi_model.hpp lib/gurobi_model.cpp ) target_link_libraries(gurobi_model PUBLIC core nlexpr) nanobind_add_module( gurobi_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/gurobi_model_ext.cpp lib/gurobi_model_ext_constants.cpp ) target_link_libraries(gurobi_model_ext PUBLIC gurobi_model) install(TARGETS gurobi_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # COPT add_library(copt_model STATIC) target_sources(copt_model PRIVATE include/pyoptinterface/copt_model.hpp lib/copt_model.cpp ) target_link_libraries(copt_model PUBLIC core nlexpr) nanobind_add_module( copt_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/copt_model_ext.cpp lib/copt_model_ext_constants.cpp ) target_link_libraries(copt_model_ext PUBLIC copt_model) install(TARGETS copt_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # MOSEK add_library(mosek_model STATIC) target_sources(mosek_model PRIVATE include/pyoptinterface/mosek_model.hpp lib/mosek_model.cpp ) target_link_libraries(mosek_model PUBLIC core) nanobind_add_module( mosek_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/mosek_model_ext.cpp lib/mosek_model_ext_constants.cpp ) target_link_libraries(mosek_model_ext PUBLIC mosek_model) install(TARGETS mosek_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # HiGHS add_library(highs_model STATIC) target_sources(highs_model PRIVATE include/pyoptinterface/highs_model.hpp lib/highs_model.cpp ) target_include_directories(highs_model PUBLIC thirdparty/solvers/highs) target_link_libraries(highs_model PUBLIC core) # target_link_libraries(highs_model PUBLIC HiGHS::HiGHS) nanobind_add_module( highs_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/highs_model_ext.cpp lib/highs_model_ext_constants.cpp ) target_link_libraries(highs_model_ext PUBLIC highs_model) install(TARGETS highs_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # IPOPT add_library(ipopt_model STATIC) target_sources(ipopt_model PRIVATE include/pyoptinterface/ipopt_model.hpp lib/ipopt_model.cpp ) target_link_libraries(ipopt_model PUBLIC nlexpr nleval) nanobind_add_module( ipopt_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/ipopt_model_ext.cpp ) target_link_libraries(ipopt_model_ext PUBLIC ipopt_model) install(TARGETS ipopt_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # XPRESS add_library(xpress_model STATIC) target_sources(xpress_model PRIVATE include/pyoptinterface/xpress_model.hpp lib/xpress_model.cpp ) target_link_libraries(xpress_model PUBLIC core nlexpr) nanobind_add_module( xpress_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/xpress_model_ext.cpp lib/xpress_model_ext_constants.cpp ) target_link_libraries(xpress_model_ext PUBLIC xpress_model) install(TARGETS xpress_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) if(DEFINED ENV{XPRESSDIR}) message(STATUS "Detected Xpress header file: $ENV{XPRESSDIR}/include") target_include_directories(xpress_model PRIVATE $ENV{XPRESSDIR}/include) target_include_directories(xpress_model_ext PRIVATE $ENV{XPRESSDIR}/include) endif() # KNITRO add_library(knitro_model STATIC) target_sources(knitro_model PRIVATE include/pyoptinterface/knitro_model.hpp lib/knitro_model.cpp ) target_link_libraries(knitro_model PUBLIC core cppad_interface) nanobind_add_module( knitro_model_ext STABLE_ABI NB_STATIC NB_DOMAIN pyoptinterface lib/knitro_model_ext.cpp lib/knitro_model_ext_constants.cpp ) target_link_libraries(knitro_model_ext PUBLIC knitro_model) install(TARGETS knitro_model_ext LIBRARY DESTINATION ${POI_INSTALL_DIR}) # stub nanobind_add_stub( core_ext_stub INSTALL_TIME MODULE pyoptinterface._src.core_ext OUTPUT ${POI_INSTALL_DIR}/core_ext.pyi ) nanobind_add_stub( nlexpr_ext_stub INSTALL_TIME MODULE pyoptinterface._src.nlexpr_ext OUTPUT ${POI_INSTALL_DIR}/nlexpr_ext.pyi ) nanobind_add_stub( nleval_ext_stub INSTALL_TIME MODULE pyoptinterface._src.nleval_ext OUTPUT ${POI_INSTALL_DIR}/nleval_ext.pyi ) nanobind_add_stub( cppad_interface_ext_stub INSTALL_TIME MODULE pyoptinterface._src.cppad_interface_ext OUTPUT ${POI_INSTALL_DIR}/cppad_interface_ext.pyi ) nanobind_add_stub( tcc_interface_ext_stub INSTALL_TIME MODULE pyoptinterface._src.tcc_interface_ext OUTPUT ${POI_INSTALL_DIR}/tcc_interface_ext.pyi ) nanobind_add_stub( gurobi_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.gurobi_model_ext OUTPUT ${POI_INSTALL_DIR}/gurobi_model_ext.pyi ) nanobind_add_stub( copt_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.copt_model_ext OUTPUT ${POI_INSTALL_DIR}/copt_model_ext.pyi ) nanobind_add_stub( mosek_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.mosek_model_ext OUTPUT ${POI_INSTALL_DIR}/mosek_model_ext.pyi ) nanobind_add_stub( highs_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.highs_model_ext OUTPUT ${POI_INSTALL_DIR}/highs_model_ext.pyi ) nanobind_add_stub( ipopt_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.ipopt_model_ext OUTPUT ${POI_INSTALL_DIR}/ipopt_model_ext.pyi ) nanobind_add_stub( xpress_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.xpress_model_ext OUTPUT ${POI_INSTALL_DIR}/xpress_model_ext.pyi ) nanobind_add_stub( knitro_model_ext_stub INSTALL_TIME MODULE pyoptinterface._src.knitro_model_ext OUTPUT ${POI_INSTALL_DIR}/knitro_model_ext.pyi ) set(ENABLE_TEST_MAIN OFF BOOL "Enable test c++ function with a main.cpp") if(ENABLE_TEST_MAIN) add_executable(test_main lib/main.cpp) target_link_libraries(test_main PUBLIC core nleval cppad_interface) endif() ================================================ FILE: GEMINI.md ================================================ # PyOptInterface Modeling Guidelines When modeling with `pyoptinterface_native`, adhere to these patterns. ## Solver Initialization - Use `pyoptinterface..Model()`. Supported: `highs`, `gurobi`, `copt`, `ipopt`, `knitro`, `mosek`, `xpress`. - For Gurobi, you can manage environments with `poi.gurobi.Env()`. ## Variables - `model.add_variable(lb=None, ub=None, domain=poi.VariableDomain.Continuous, name="", start=None)` - `model.add_m_variables(shape, ...)` returns a NumPy array of variables. ## Constraints - Linear: `model.add_linear_constraint(expr, sense, rhs)` or `model.add_linear_constraint(lhs <= rhs)`. - Quadratic: `model.add_quadratic_constraint(expr, sense, rhs)`. - Matrix: `model.add_m_linear_constraints(A, x, sense, b)`. - Nonlinear: Wrap in `with nl.graph():` and use `model.add_nl_constraint(expr)`. ## Objective - `model.set_objective(expr, sense)`. - Nonlinear: `with nl.graph(): model.add_nl_objective(expr, sense)`. ## Attributes - Get: `model.get_model_attribute(poi.ModelAttribute.TerminationStatus)`. - Set: `model.set_variable_attribute(v, poi.VariableAttribute.LowerBound, 0.0)`. - Results: `model.get_variable_attribute(v, poi.VariableAttribute.Value)`. ## Common Utilities - `poi.quicksum(terms)`: Efficiently sum variables/expressions. - `poi.Eq`, `poi.Leq`, `poi.Geq`: Aliases for constraint senses. ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2023: Yue Yang The PyOptInterface is licensed under the **[MPL]** version 2.0: [MPL]: https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. The design and implementation of the PyOptInterface is inspired by the [JuMP.jl] project. [JuMP.jl]: https://jump.dev ================================================ FILE: README.md ================================================ PyOptInterface (Python Optimization Interface) ======= [![](https://img.shields.io/pypi/v/pyoptinterface.svg?color=brightgreen)](https://pypi.org/pypi/pyoptinterface/) **PyOptInterface** is an open-source Python library to provide a unified API to construct and solve optimization models with various optimizers. The detailed documentation can be found [here](https://metab0t.github.io/PyOptInterface/). ## Key features compared with other modeling interfaces It is designed as a very thin wrapper of native C API of optimizers and attempts to provide common abstractions of an algebraic modelling environment including model, variable, constraint and expression with the least overhead of performance. The key features of PyOptInterface include: - Very fast speed to construct optimization model (10x faster than Pyomo, comparable with JuMP.jl and some official Python bindings provided by vendors of optimizer) - Highly efficient structured automatic differentiation for nonlinear optimization with JIT compilation (faster than other NLP frameworks) - Low overhead to modify and re-solve the problem incrementally (including adding/removing variables/constraints, changing objective function, etc.) - Unified API to cover common usages, write once and the code works for all optimizers - You still have escape hatch to query or modify solver-specific parameter/attribute/information for different optimizers directly like the vendor-specific Python binding of optimizer ## Benchmark The benchmark comparing PyOptInterface with some other modeling interfaces can be found [here](https://metab0t.github.io/PyOptInterface/benchmark.html). PyOptInterface is among the fastest modeling interfaces in terms of model construction time and automatic differentiation of nonlinear optimization problems. ## Installation PyOptInterface is available on PyPI. You can install it via pip: ``` pip install pyoptinterface ``` After installation, you can import the package in Python console: ```python import pyoptinterface as poi ``` PyOptInterface has no dependencies other than Python itself. However, to use it with a specific optimizer, you need to install the corresponding optimizer manually. The details can be found on [the configurations of optimizers](https://metab0t.github.io/PyOptInterface/getting_started.html). In order to provide out-of-the-box support for open source optimizers (currently we support [HiGHS](https://github.com/ERGO-Code/HiGHS)), PyOptInterface can also be installed with pre-built optimizers. You can install them via pip: ``` pip install pyoptinterface[highs] ``` It will install a full-featured binary version of HiGHS optimizer via [highsbox](http://github.com/metab0t/highsbox), which can be used with PyOptInterface. ## What kind of problems can PyOptInterface solve? It currently supports the following problem types: - Linear Programming (LP) - Mixed-Integer Linear Programming (MILP) - Quadratic Programming (QP) - Mixed-Integer Quadratic Programming (MIQP) - Quadratically Constrained Quadratic Programming (QCQP) - Mixed-Integer Quadratically Constrained Quadratic Programming (MIQCQP) - Second-Order Cone Programming (SOCP) - Mixed-Integer Second-Order Cone Programming (MISOCP) - Exponential Cone Programming (ECP) - Mixed-Integer Exponential Cone Programming (MIECP) - Nonlinear Programming (NLP) ## What optimizers does PyOptInterface support? It currently supports the following optimizers: - [COPT](https://shanshu.ai/copt) ( Commercial ) - [Gurobi](https://www.gurobi.com/) ( Commercial ) - [Xpress](https://www.fico.com/en/products/fico-xpress-optimization) ( Commercial ) - [HiGHS](https://github.com/ERGO-Code/HiGHS) ( Open source ) - [Mosek](https://www.mosek.com/) ( Commercial ) - [Ipopt](https://github.com/coin-or/Ipopt) ( Open source ) - [KNITRO](https://www.artelys.com/solvers/knitro/) ( Commercial ) ## Short Example ```python import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() x = model.add_variable(lb=0, ub=1, domain=poi.VariableDomain.Continuous, name="x") y = model.add_variable(lb=0, ub=1, domain=poi.VariableDomain.Integer, name="y") con = model.add_linear_constraint(x + y >= 1.2, name="con") obj = 2*x model.set_objective(obj, poi.ObjectiveSense.Minimize) model.set_model_attribute(poi.ModelAttribute.Silent, False) model.optimize() print(model.get_model_attribute(poi.ModelAttribute.TerminationStatus)) # TerminationStatusCode.OPTIMAL x_val = model.get_value(x) # 0.2 y_val = model.get_value(y) # 1.0 ``` ## Citation If you use PyOptInterface in your research, please consider citing [the following paper](https://ieeexplore.ieee.org/document/10721402): ```bibtex @article{yang2024accelerating, title={Accelerating Optimal Power Flow with Structure-aware Automatic Differentiation and Code Generation}, author={Yang, Yue and Lin, Chenhui and Xu, Luo and Yang, Xiaodong and Wu, Wenchuan and Wang, Bin}, journal={IEEE Transactions on Power Systems}, year={2024}, publisher={IEEE} } ``` ## License PyOptInterface is licensed under MPL-2.0 License. It uses [nanobind](https://github.com/wjakob/nanobind), [fmtlib](https://github.com/fmtlib/fmt) and [martinus/unordered_dense](https://github.com/martinus/unordered_dense) as dependencies. The design of PyOptInterface is inspired by [JuMP.jl](https://jump.dev). Some solver-related code in `src` folder is adapted from the corresponding solver interface package in `JuMP.jl` ecosystem, which is licensed under MIT License. The header files in `thirdparty/solvers` directory are from the corresponding distribution of optimizers and are licensed under their own licenses. ================================================ FILE: bench/bench_linopy_cvxpy.py ================================================ import time from numpy import arange import numpy as np import pandas as pd import linopy import pyoptinterface as poi from pyoptinterface import copt, gurobi, highs import cvxpy def create_linopy_model(N): m = linopy.Model() x = m.add_variables(coords=[arange(N), arange(N)]) y = m.add_variables(coords=[arange(N), arange(N)]) m.add_constraints(x - y >= arange(N)) m.add_constraints(x + y >= 0) m.add_objective((2 * x).sum() + y.sum()) return m def create_cvxpy_model(N): x = cvxpy.Variable((N, N)) y = cvxpy.Variable((N, N)) constraints = [] for i in range(N): constraints.append(x[:, i] - y[:, i] >= np.arange(N)) constraints.append(x + y >= 0) objective = cvxpy.Minimize(2 * cvxpy.sum(x) + cvxpy.sum(y)) return cvxpy.Problem(objective, constraints) def create_poi_model(Model, N): m = Model() x = m.add_m_variables((N, N)) y = m.add_m_variables((N, N)) for i in range(N): for j in range(N): m.add_linear_constraint(x[i, j] - y[i, j], poi.Geq, i) m.add_linear_constraint(x[i, j] + y[i, j], poi.Geq, 0) expr = poi.ExprBuilder() poi.quicksum_(expr, x, lambda x: 2 * x) poi.quicksum_(expr, y) m.set_objective(expr) return m def bench(N, solver_name): results = {} t0 = time.time() Model = { "copt": copt.Model, "gurobi": gurobi.Model, "highs": highs.Model, }.get(solver_name, None) if Model: model = create_poi_model(Model, N) model.optimize() t1 = time.time() results["n_variables"] = 2 * N * N results["poi"] = t1 - t0 t0 = time.time() model = create_linopy_model(N) model.solve(solver_name=solver_name, io_api="direct") t1 = time.time() results["linopy"] = t1 - t0 t0 = time.time() solver = {"gurobi": cvxpy.GUROBI, "copt": cvxpy.COPT}.get(solver_name, None) if solver: model = create_cvxpy_model(N) model.solve(solver=cvxpy.GUROBI) t1 = time.time() results["cvxpy"] = t1 - t0 return results def main(solver_name="gurobi"): Ns = range(100, 501, 100) results = [] for N in Ns: results.append(bench(N, solver_name)) # create a DataFrame df = pd.DataFrame(results, index=Ns) # show result print(df) if __name__ == "__main__": # solver_name can be "copt", "gurobi", "highs" main("gurobi") ================================================ FILE: bench/bench_modify.jl ================================================ using JuMP import MathOptInterface as MOI using Gurobi using COPT function bench_jump(model, N::Int, M::Int) I = 1:N @variable(model, x[I]) @constraint(model, sum(x[i] for i = I) == N) @objective(model, Min, sum(i / N * x[i] for i = I)) # for j in 1:M:N for j in [1] last_variable = j + M - 1 if last_variable > N last_variable = N end deleted_variables = [x[i] for i in j:last_variable] delete(model, deleted_variables) optimize!(model) end end function gurobi_model() optimizer = optimizer_with_attributes( Gurobi.Optimizer, "Presolve" => 0, "TimeLimit" => 0.0, MOI.Silent() => true, ) model = direct_model(optimizer) return model end function copt_model() optimizer = optimizer_with_attributes( COPT.Optimizer, "Presolve" => 0, "TimeLimit" => 0.0, MOI.Silent() => true, ) model = direct_model(optimizer) return model end function bench_gurobi(N, M) model = gurobi_model() bench_jump(model, N, M) end function bench_copt(N, M) model = copt_model() bench_jump(model, N, M) end function main(N, M) time_dict = Dict{String,Float64}() println("N: $N, M: $M") println("Gurobi starts") t1 = time() bench_gurobi(N, M) t2 = time() println("Gurobi ends and takes ", t2 - t1, " seconds") time_dict["gurobi"] = t2 - t1 println("COPT starts") t1 = time() bench_copt(N, M) t2 = time() println("COPT ends and takes ", t2 - t1, " seconds") time_dict["copt"] = t2 - t1 return time_dict end main(5, 1) result_dict = Dict() for N in [1000000] for M in [10000] result_dict[(N, M)] = main(N, M) end end result_dict ================================================ FILE: bench/bench_modify.mod ================================================ param N; var x{1..N} >= 0; s.t. c1: sum {i in 1..N} x[i] = N; minimize obj: sum {i in 1..N div 2} i / N * x[i]; ================================================ FILE: bench/bench_modify.py ================================================ import pyoptinterface as poi from pyoptinterface import gurobi, copt import gurobipy as gp import coptpy as cp import time def bench_poi_base(model, N, M): I = range(N) x = poi.make_nd_variable(model, I) model.set_model_attribute(poi.ModelAttribute.Silent, True) model.set_model_attribute(poi.ModelAttribute.TimeLimitSec, 0.0) model.set_raw_parameter("Presolve", 0) expr = poi.ExprBuilder() for i in range(N): expr.add(i / N * x[i]) model.set_objective(expr, poi.ObjectiveSense.Minimize) expr = poi.quicksum(x) model.add_linear_constraint(expr, poi.ConstraintSense.GreaterEqual, N) # for i in range(0, N, M): for i in [0]: last_variable = min(i + M, N) deleted_variables = [x[j] for j in range(i, last_variable)] model.delete_variables(deleted_variables) model.optimize() def bench_poi_gurobi(N, M): model = gurobi.Model() bench_poi_base(model, N, M) def bench_poi_copt(N, M): model = copt.Model() bench_poi_base(model, N, M) def bench_gp(N, M): model = gp.Model() I = range(N) x = model.addVars(I) obj = gp.quicksum(i / N * x[i] for i in range(N)) model.setObjective(obj) model.addConstr(x.sum() == N) model.setParam("OutputFlag", 0) model.setParam("TimeLimit", 0.0) model.setParam("Presolve", 0) # for j in range(0, N, M): for j in [0]: last_variable = min(j + M, N) for k in range(j, last_variable): model.remove(x[k]) model.optimize() def bench_cp(N, M): env = cp.Envr() model = env.createModel("m") I = range(N) x = model.addVars(I) model.setParam("Logging", 0) model.setParam("TimeLimit", 0.0) model.setParam("Presolve", 0) obj = cp.quicksum(i / N * x[i] for i in range(N)) model.setObjective(obj) model.addConstr(x.sum() == N) # for j in range(0, N, M): for j in [0]: last_variable = min(j + M, N) for k in range(j, last_variable): x[k].remove() model.solve() def main(N, M): result = dict() t1 = time.perf_counter() bench_poi_gurobi(N, M) t2 = time.perf_counter() result["poi_gurobi"] = t2 - t1 t1 = time.perf_counter() bench_gp(N, M) t2 = time.perf_counter() result["gurobipy"] = t2 - t1 t1 = time.perf_counter() bench_poi_copt(N, M) t2 = time.perf_counter() result["poi_copt"] = t2 - t1 t1 = time.perf_counter() bench_cp(N, M) t2 = time.perf_counter() result["coptpy"] = t2 - t1 return result result_dict = dict() for N in [1000000]: for M in [100000]: print(f"N = {N}, M = {M}") result = main(N, M) result_dict[(N, M)] = result print(result_dict) ================================================ FILE: bench/bench_modify.run ================================================ param t0; param t1; let t0 := time(); model bench_modify.mod; let N := 10000; option solver_msg 0; option presolve 0; option solver 'gurobi'; option gurobi_options 'outlev=0 timelim=2 presolve=0'; solve >NUL; for {j in (N div 2 + 1)..N} { let x[j] := 0; solve >NUL; } let t1 := time(); printf "%d\n", t1-t0; ================================================ FILE: bench/bench_static.jl ================================================ using JuMP using Gurobi using COPT using MosekTools function bench_jump(model, M, N) @variable(model, x[1:M, 1:N] >= 0) @constraint(model, sum(x) == M * N / 2) @objective(model, Min, sum(x[i, j]^2 for i in 1:M, j in 1:N)) set_silent(model) set_time_limit_sec(model, 0.0) set_optimizer_attribute(model, "Presolve", 0) optimize!(model) end function bench_gurobi(M::Int, N::Int) model = direct_model(Gurobi.Optimizer()) bench_jump(model, M, N) end function bench_copt(M::Int, N::Int) model = direct_model(COPT.Optimizer()) bench_jump(model, M, N) end function bench_mosek(M::Int, N::Int) model = direct_model(Mosek.Optimizer()) bench_jump(model, M, N) end function main(M::Int, N::Int) println("Gurobi starts") t1 = time() bench_gurobi(M, N) t2 = time() println("Gurobi ends and takes ", t2 - t1, " seconds") println("COPT starts") t1 = time() bench_copt(M, N) t2 = time() println("COPT ends and takes ", t2 - t1, " seconds") println("Mosek starts") t1 = time() bench_mosek(M, N) t2 = time() println("Mosek ends and takes ", t2 - t1, " seconds") end ================================================ FILE: bench/bench_static.py ================================================ import pyoptinterface as poi from pyoptinterface import gurobi, copt, mosek import pyomo.environ as pyo from pyomo.common.timing import TicTocTimer import linopy import gurobipy as gp import coptpy as cp import mosek as msk def bench_poi_base(model, M, N): I = range(M) J = range(N) x = poi.make_nd_variable(model, I, J, lb=0.0) expr = poi.quicksum(x) con = model.add_linear_constraint(expr, poi.ConstraintSense.Equal, M * N / 2) obj = poi.quicksum(x, lambda v: v * v) model.set_objective(obj, poi.ObjectiveSense.Minimize) model.set_model_attribute(poi.ModelAttribute.Silent, True) model.set_model_attribute(poi.ModelAttribute.TimeLimitSec, 0.0) if isinstance(model, gurobi.Model) or isinstance(model, copt.Model): model.set_raw_parameter("Presolve", 0) elif isinstance(model, mosek.Model): model.set_raw_parameter("MSK_IPAR_PRESOLVE_USE", 0) model.optimize() def bench_poi_gurobi(M, N): model = gurobi.Model() bench_poi_base(model, M, N) def bench_poi_copt(M, N): model = copt.Model() bench_poi_base(model, M, N) def bench_poi_mosek(M, N): model = mosek.Model() bench_poi_base(model, M, N) def bench_pyomo_base(solver, M, N): model = pyo.ConcreteModel() I = range(M) J = range(N) model.x = pyo.Var(I, J) for var in model.x.values(): var.setlb(0.0) model.c = pyo.Constraint( expr=pyo.quicksum(var for var in model.x.values()) == M * N / 2 ) model.o = pyo.Objective(expr=sum(v * v for v in model.x.values())) solver.solve(model, load_solutions=False) def bench_pyomo_gurobi(M, N): solver = pyo.SolverFactory("gurobi_direct") solver.options["timelimit"] = 0.0 solver.options["presolve"] = False bench_pyomo_base(solver, M, N) def bench_pyomo_mosek(M, N): solver = pyo.SolverFactory("mosek_direct") solver.options["dparam.optimizer_max_time"] = 0.0 solver.options["iparam.presolve_use"] = 0 bench_pyomo_base(solver, M, N) def bench_linopy_gurobi(M, N): model = linopy.Model() I = range(M) J = range(N) x = model.add_variables(lower=0.0, coords=[I, J], name="x") model.add_constraints(x.sum() == M * N / 2) model.add_objective((x * x).sum()) model.solve("gurobi", io_api="direct", OutputFlag=0, TimeLimit=0.0, Presolve=0) def bench_gp(M, N): model = gp.Model() I = range(M) J = range(N) x = model.addVars(I, J, lb=0.0, name="x") expr = x.sum() con = model.addConstr(expr == M * N / 2) obj = gp.quicksum(v * v for v in x.values()) model.setObjective(obj) model.setParam("OutputFlag", 0) model.setParam("TimeLimit", 0.0) model.setParam("Presolve", 0) model.optimize() def bench_cp(M, N): env = cp.Envr() model = env.createModel("m") I = range(M) J = range(N) x = model.addVars(I, J, lb=0.0, nameprefix="x") expr = x.sum() con = model.addConstr(expr == M * N / 2) obj = cp.quicksum(v * v for v in x.values()) model.setObjective(obj) model.setParam("Logging", 0) model.setParam("TimeLimit", 0.0) model.setParam("Presolve", 0) model.solve() def streamprinter(text): pass def bench_msk(M, N): with msk.Env() as env: with env.Task(0, 0) as task: task.set_Stream(msk.streamtype.log, streamprinter) # Total number of variables (M*N for 'x') numvar = M * N # Add variables for j in range(numvar): task.appendvars(1) # Variable bounds - All variables are greater than 0.0 for j in range(numvar): task.putvarbound(j, msk.boundkey.ra, 0.0, 1e9) # Objective indx = list(range(M * N)) val = [2.0] * (M * N) task.putqobj(indx, indx, val) task.putobjsense(msk.objsense.minimize) # Constraint - Sum of elements in 'x' equals M * N / 2 task.appendcons(1) # One linear constraint indx = list(range(M * N)) # Indices of 'x' variables val = [1.0] * (M * N) # Coefficients are 1 task.putarow(0, indx, val) task.putconbound(0, msk.boundkey.fx, M * N / 2, M * N / 2) # Set solver parameters task.putdouparam(msk.dparam.optimizer_max_time, 0.0) task.putintparam(msk.iparam.presolve_use, msk.presolvemode.off) # Solve the problem task.optimize() M = 1000 N = 100 timer = TicTocTimer() tests = [ "gurobi", "copt", # "mosek", ] if "gurobi" in tests: timer.tic("poi_gurobi starts") bench_poi_gurobi(M, N) timer.toc("poi_gurobi ends") timer.tic("linopy_gurobi starts") bench_linopy_gurobi(M, N) timer.toc("linopy_gurobi ends") timer.tic("gurobi starts") bench_gp(M, N) timer.toc("gurobi ends") timer.tic("pyomo_gurobi starts") bench_pyomo_gurobi(M, N) timer.toc("pyomo_gurobi ends") if "copt" in tests: timer.tic("poi_copt starts") bench_poi_copt(M, N) timer.toc("poi_copt ends") timer.tic("cp starts") bench_cp(M, N) timer.toc("cp ends") if "mosek" in tests: timer.tic("poi_mosek starts") bench_poi_mosek(M, N) timer.toc("poi_mosek ends") timer.tic("mosek starts") bench_msk(M, N) timer.toc("mosek ends") timer.tic("pyomo_mosek starts") bench_pyomo_mosek(M, N) timer.toc("pyomo_mosek ends") ================================================ FILE: bench/nqueens/.gitignore ================================================ *.csv ================================================ FILE: bench/nqueens/nqueens.jl ================================================ using JuMP import Gurobi import LinearAlgebra function solve_nqueens(N) model = direct_model(Gurobi.Optimizer()) set_silent(model) set_time_limit_sec(model, 0.0) set_optimizer_attribute(model, "Presolve", 0) @variable(model, x[1:N, 1:N], Bin) for i in 1:N @constraint(model, sum(x[i, :]) == 1) @constraint(model, sum(x[:, i]) == 1) end for i in -(N - 1):(N-1) @constraint(model, sum(LinearAlgebra.diag(x, i)) <= 1) @constraint(model, sum(LinearAlgebra.diag(reverse(x; dims=1), i)) <= 1) end optimize!(model) end function main(io::IO, Ns = 800:400:2000) for n in Ns start = time() model = solve_nqueens(n) run_time = round(Int, time() - start) content = "jump nqueens-$n $run_time" println(stdout, content) println(io, content) end end main(stdout, [5]) open(joinpath(@__DIR__, "benchmarks.csv"), "a") do io main(io) end ================================================ FILE: bench/nqueens/nqueens_gurobipy.py ================================================ from gurobipy import * import numpy as np import os import time def solve_nqueens(N): model = Model("queens") model.setParam("OutputFlag", 0) model.setParam("TimeLimit", 0.0) model.setParam("Presolve", 0) x = np.empty((N, N), dtype=object) for i in range(N): for j in range(N): x[i, j] = model.addVar(vtype=GRB.BINARY) for i in range(N): # Row and column model.addConstr(quicksum(x[i, :]) == 1.0) model.addConstr(quicksum(x[:, i]) == 1.0) flipx = np.fliplr(x) for i in range(-N + 1, N): # Diagonal model.addConstr(quicksum(x.diagonal(i)) <= 1.0) # Anti-diagonal model.addConstr(quicksum(flipx.diagonal(i)) <= 1.0) model.optimize() def main(Ns=range(800, 2001, 400)): dir = os.path.realpath(os.path.dirname(__file__)) for n in Ns: start = time.time() solve_nqueens(n) run_time = round(time.time() - start) content = "gurobipy nqueens-%i %i" % (n, run_time) print(content) with open(dir + "/benchmarks.csv", "a") as io: io.write(f"{content}\n") return main() ================================================ FILE: bench/nqueens/nqueens_poi.py ================================================ import pyoptinterface as poi from pyoptinterface import gurobi import numpy as np import os import time def solve_nqueens(N): model = gurobi.Model() x = np.empty((N, N), dtype=object) for i in range(N): for j in range(N): x[i, j] = model.add_variable(domain=poi.VariableDomain.Binary) for i in range(N): # Row and column model.add_linear_constraint(poi.quicksum(x[i, :]), poi.Eq, 1.0) model.add_linear_constraint(poi.quicksum(x[:, i]), poi.Eq, 1.0) flipx = np.fliplr(x) for i in range(-N + 1, N): # Diagonal model.add_linear_constraint(poi.quicksum(x.diagonal(i)), poi.Leq, 1.0) # Anti-diagonal model.add_linear_constraint(poi.quicksum(flipx.diagonal(i)), poi.Leq, 1.0) model.set_model_attribute(poi.ModelAttribute.Silent, True) model.set_model_attribute(poi.ModelAttribute.TimeLimitSec, 0.0) model.set_raw_parameter("Presolve", 0) model.optimize() def main(Ns=range(800, 2001, 400)): dir = os.path.realpath(os.path.dirname(__file__)) for n in Ns: start = time.time() solve_nqueens(n) run_time = round(time.time() - start) content = "poi nqueens-%i %i" % (n, run_time) print(content) with open(dir + "/benchmarks.csv", "a") as io: io.write(f"{content}\n") return main() ================================================ FILE: bench/nqueens/nqueens_pythonmip.py ================================================ from mip.model import * from mip import GUROBI import numpy as np import os import time def solve_nqueens(N): model = Model("queens", solver_name=GUROBI) # set parameters model.solver.set_int_param("OutputFlag", 0) model.solver.set_dbl_param("TimeLimit", 0.0) model.solver.set_int_param("Presolve", 0) x = np.empty((N, N), dtype=object) for i in range(N): for j in range(N): x[i, j] = model.add_var(var_type="B") for i in range(N): # Row and column model.add_constr(xsum(x[i, :]) == 1.0) model.add_constr(xsum(x[:, i]) == 1.0) flipx = np.fliplr(x) for i in range(-N + 1, N): # Diagonal model.add_constr(xsum(x.diagonal(i)) <= 1.0) # Anti-diagonal model.add_constr(xsum(flipx.diagonal(i)) <= 1.0) model.optimize() def main(Ns=range(800, 2001, 400)): dir = os.path.realpath(os.path.dirname(__file__)) for n in Ns: start = time.time() solve_nqueens(n) run_time = round(time.time() - start) content = "pythonmip nqueens-%i %i" % (n, run_time) print(content) with open(dir + "/benchmarks.csv", "a") as io: io.write(f"{content}\n") return main() ================================================ FILE: bench/test_delete.py ================================================ import gurobipy as gp import coptpy as cp import pyoptinterface as poi from pyoptinterface import copt def bench_poi_base(N): model = copt.Model() I = range(N) x = poi.make_nd_variable(model, I, lb=0.0) model.add_linear_constraint(x[0] + x[N - 1], poi.ConstraintSense.GreaterEqual, 1.0) model.set_model_attribute(poi.ModelAttribute.Silent, False) model.set_model_attribute(poi.ModelAttribute.TimeLimitSec, 2.0) model.set_raw_parameter("Presolve", 0) obj = poi.ExprBuilder() for i in range(N): obj.add(x[i] * x[i]) model.set_objective(obj, poi.ObjectiveSense.Minimize) for i in range(N // 2): model.delete_variable(x[i]) model.optimize() def gp_delete(N): model = gp.Model() I = range(N) x = model.addVars(I, lb=0.0) model.addConstr(x[0] + x[N - 1] >= 1.0) obj = gp.quicksum(x[i] * x[i] for i in range(N)) model.setObjective(obj) model.setParam("OutputFlag", 1) model.setParam("TimeLimit", 2.0) model.setParam("Presolve", 0) for j in range(N // 2): model.remove(x[j]) model.optimize() def cp_delete(N): env = cp.Envr() model = env.createModel("m") I = range(N) x = model.addVars(I, lb=0.0) model.addConstr(x[0] + x[N - 1] >= 1.0) obj = cp.quicksum(x[i] * x[i] for i in range(N)) model.setObjective(obj) model.setParam("Logging", 1) model.setParam("TimeLimit", 2.0) model.setParam("Presolve", 0) for j in range(N // 2): x[j].remove() model.solve() if __name__ == "__main__": N = 20 cp_delete(N) ================================================ FILE: docs/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ================================================ FILE: docs/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) if "%1" == "" goto help %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd ================================================ FILE: docs/requirements.txt ================================================ sphinx myst-parser myst-nb sphinx-copybutton furo numpy scipy highsbox tccbox llvmlite matplotlib ================================================ FILE: docs/source/api/pyoptinterface.copt.rst ================================================ pyoptinterface.copt package ==================================== .. automodule:: pyoptinterface.copt :members: :inherited-members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/api/pyoptinterface.gurobi.rst ================================================ pyoptinterface.gurobi package ==================================== .. automodule:: pyoptinterface.gurobi :members: :inherited-members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/api/pyoptinterface.highs.rst ================================================ pyoptinterface.highs package ==================================== .. automodule:: pyoptinterface.highs :members: :inherited-members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/api/pyoptinterface.knitro.rst ================================================ pyoptinterface.knitro package ==================================== .. automodule:: pyoptinterface.knitro :members: :inherited-members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/api/pyoptinterface.mosek.rst ================================================ pyoptinterface.mosek package ==================================== .. automodule:: pyoptinterface.mosek :members: :inherited-members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/api/pyoptinterface.rst ================================================ pyoptinterface package =========================== .. automodule:: pyoptinterface :members: :undoc-members: :show-inheritance: Submodules ----------- .. toctree:: :maxdepth: 2 pyoptinterface.gurobi.rst pyoptinterface.copt.rst pyoptinterface.xpress.rst pyoptinterface.mosek.rst pyoptinterface.highs.rst ================================================ FILE: docs/source/api/pyoptinterface.xpress.rst ================================================ pyoptinterface.xpress package ==================================== .. automodule:: pyoptinterface.xpress :members: :inherited-members: :undoc-members: :show-inheritance: ================================================ FILE: docs/source/attribute/copt.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ❌ - ❌ * - ObjectiveSense - ✅ - ✅ * - DualStatus - ✅ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ✅ - ❌ * - DualObjectiveValue - ✅ - ❌ * - NodeCount - ✅ - ❌ * - NumberOfThreads - ✅ - ✅ * - ObjectiveBound - ✅ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ✅ - ✅ * - Silent - ✅ - ✅ * - SimplexIterations - ✅ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ✅ - ❌ * - SolveTimeSec - ✅ - ❌ * - TimeLimitSec - ✅ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ✅ - ✅ * - PrimalStart - ✅ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ✅ - ❌ * - IISUpperBound - ✅ - ❌ * - ReducedCost - ✅ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ * - IIS - ✅ - ❌ ::: ================================================ FILE: docs/source/attribute/gurobi.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - ObjectiveSense - ✅ - ✅ * - DualStatus - ✅ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ✅ - ❌ * - DualObjectiveValue - ✅ - ❌ * - NodeCount - ✅ - ❌ * - NumberOfThreads - ✅ - ✅ * - ObjectiveBound - ✅ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ✅ - ✅ * - Silent - ✅ - ✅ * - SimplexIterations - ✅ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ✅ - ❌ * - SolveTimeSec - ✅ - ❌ * - TimeLimitSec - ✅ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ✅ - ✅ * - PrimalStart - ✅ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ✅ - ❌ * - IISUpperBound - ✅ - ❌ * - ReducedCost - ✅ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ * - IIS - ✅ - ❌ ::: ================================================ FILE: docs/source/attribute/highs.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ❌ - ❌ * - ObjectiveSense - ✅ - ✅ * - DualStatus - ✅ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ❌ - ❌ * - DualObjectiveValue - ❌ - ❌ * - NodeCount - ❌ - ❌ * - NumberOfThreads - ✅ - ✅ * - ObjectiveBound - ❌ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ✅ - ✅ * - Silent - ✅ - ✅ * - SimplexIterations - ❌ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ✅ - ❌ * - SolveTimeSec - ✅ - ❌ * - TimeLimitSec - ✅ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ✅ - ✅ * - PrimalStart - ✅ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ❌ - ❌ * - IISUpperBound - ❌ - ❌ * - ReducedCost - ✅ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ * - IIS - ❌ - ❌ ::: ================================================ FILE: docs/source/attribute/ipopt.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ❌ - ❌ * - ObjectiveSense - ❌ - ❌ * - DualStatus - ✅ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ❌ - ❌ * - DualObjectiveValue - ❌ - ❌ * - NodeCount - ❌ - ❌ * - NumberOfThreads - ❌ - ❌ * - ObjectiveBound - ❌ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ❌ - ❌ * - Silent - ❌ - ✅ * - SimplexIterations - ❌ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ❌ - ❌ * - SolveTimeSec - ❌ - ❌ * - TimeLimitSec - ❌ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ❌ - ❌ * - PrimalStart - ✅ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ❌ - ❌ * - IISUpperBound - ❌ - ❌ * - ReducedCost - ❌ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ❌ - ❌ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ * - IIS - ❌ - ❌ ::: ================================================ FILE: docs/source/attribute/knitro.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ❌ - ❌ * - ObjectiveSense - ✅ - ✅ * - DualStatus - ❌ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ✅ - ❌ * - DualObjectiveValue - ❌ - ❌ * - NodeCount - ✅ - ❌ * - NumberOfThreads - ✅ - ✅ * - ObjectiveBound - ✅ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ✅ - ❌ * - Silent - ❌ - ✅ * - SimplexIterations - ❌ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ✅ - ❌ * - SolveTimeSec - ✅ - ❌ * - TimeLimitSec - ✅ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ✅ - ✅ * - PrimalStart - ❌ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ❌ - ❌ * - IISUpperBound - ❌ - ❌ * - ReducedCost - ✅ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ ::: ================================================ FILE: docs/source/attribute/mosek.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ❌ - ❌ * - ObjectiveSense - ✅ - ✅ * - DualStatus - ✅ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ❌ - ❌ * - DualObjectiveValue - ✅ - ❌ * - NodeCount - ❌ - ❌ * - NumberOfThreads - ✅ - ✅ * - ObjectiveBound - ❌ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ✅ - ✅ * - Silent - ✅ - ✅ * - SimplexIterations - ❌ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ✅ - ❌ * - SolveTimeSec - ✅ - ❌ * - TimeLimitSec - ✅ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ✅ - ✅ * - PrimalStart - ❌ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ❌ - ❌ * - IISUpperBound - ❌ - ❌ * - ReducedCost - ✅ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ * - IIS - ❌ - ❌ ::: ================================================ FILE: docs/source/attribute/xpress.md ================================================ ### Supported [model attribute](#pyoptinterface.ModelAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - ObjectiveSense - ✅ - ✅ * - DualStatus - ✅ - ❌ * - PrimalStatus - ✅ - ❌ * - RawStatusString - ✅ - ❌ * - TerminationStatus - ✅ - ❌ * - BarrierIterations - ✅ - ❌ * - DualObjectiveValue - ✅ - ❌ * - NodeCount - ✅ - ❌ * - NumberOfThreads - ✅ - ✅ * - ObjectiveBound - ✅ - ❌ * - ObjectiveValue - ✅ - ❌ * - RelativeGap - ✅ - ✅ * - Silent - ✅ - ✅ * - SimplexIterations - ✅ - ❌ * - SolverName - ✅ - ❌ * - SolverVersion - ✅ - ❌ * - SolveTimeSec - ✅ - ❌ * - TimeLimitSec - ✅ - ✅ ::: ### Supported [variable attribute](#pyoptinterface.VariableAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Value - ✅ - ❌ * - LowerBound - ✅ - ✅ * - UpperBound - ✅ - ✅ * - Domain - ✅ - ✅ * - PrimalStart - ✅ - ✅ * - Name - ✅ - ✅ * - IISLowerBound - ✅ - ❌ * - IISUpperBound - ✅ - ❌ * - ReducedCost - ✅ - ❌ ::: ### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute) :::{list-table} :header-rows: 1 * - Attribute - Get - Set * - Name - ✅ - ✅ * - Primal - ✅ - ❌ * - Dual - ✅ - ❌ * - IIS - ✅ - ❌ ::: ================================================ FILE: docs/source/benchmark.md ================================================ # Benchmark ## Model construction time The benchmark is adapted from [the JuMP paper](https://github.com/jump-dev/JuMPPaperBenchmarks). Eight optimization models with different sizes are selected as test cases, including four facility location models and four linear quadratic control problems. We conduct two rounds of benchmark using Gurobi and COPT as optimizer respectively. For each model, we measure the total time of modeling interface to generate model and pass it to the optimizer, and the time limit of optimizer is set to 0.0 seconds to avoid the influence of solution process. All code to run the benchmarks is available at [https://github.com/metab0t/PyOptInterface_benchmark](https://github.com/metab0t/PyOptInterface_benchmark). :::{table} Time (second) to generate model and pass it to Gurobi optimizer. :widths: auto :align: center | Model | Variables | C++ | PyOptInterface | JuMP | gurobipy | Pyomo | | --------- | --------- | ---- | -------------- | ---- | -------- | ----- | | fac-25 | 67651 | 0.2 | 0.2 | 0.2 | 1.2 | 4.1 | | fac-50 | 520301 | 0.8 | 1.2 | 1.8 | 9.7 | 32.7 | | fac-75 | 1732951 | 2.7 | 4.1 | 6.6 | 32.5 | 119.3 | | fac-100 | 4080601 | 6.3 | 10.0 | 17.8 | 79.1 | 286.3 | | lqcp-500 | 251501 | 0.9 | 1.5 | 1.3 | 6.3 | 23.8 | | lqcp-1000 | 1003001 | 3.7 | 6.0 | 6.1 | 26.7 | 106.6 | | lqcp-1500 | 2254501 | 8.3 | 14.0 | 17.7 | 61.8 | 234.0 | | lqcp-2000 | 4006001 | 14.5 | 24.9 | 38.3 | 106.9 | 444.1 | ::: :::{table} Time (second) to generate model and pass it to COPT optimizer. :widths: auto :align: center | Model | Variables | C++ | PyOptInterface | JuMP | coptpy | Pyomo | | --------- | --------- | ---- | -------------- | ---- | ------ | ----- | | fac-25 | 67651 | 0.3 | 0.2 | 0.3 | 0.6 | 4.1 | | fac-50 | 520301 | 2.2 | 1.5 | 2.7 | 5.4 | 32.8 | | fac-75 | 1732951 | 8.1 | 6.6 | 10.2 | 20.3 | 117.4 | | fac-100 | 4080601 | 22.4 | 23.4 | 30.3 | 58.0 | 284.0 | | lqcp-500 | 251501 | 3.8 | 3.1 | 3.0 | 6.6 | 26.4 | | lqcp-1000 | 1003001 | 16.0 | 15.5 | 13.9 | 28.1 | 112.1 | | lqcp-1500 | 2254501 | 37.6 | 32.4 | 33.7 | 64.6 | 249.3 | | lqcp-2000 | 4006001 | 68.2 | 60.3 | 66.2 | 118.4 | 502.4 | ::: Recently, there are a lot of requests to test the performance of PyOptInterface compared with [linopy](https://github.com/PyPSA/linopy) and [cvxpy](https://github.com/cvxpy/cvxpy), so we prepare a [benchmark](https://github.com/metab0t/PyOptInterface/blob/master/bench/bench_linopy_cvxpy.py). This is the result of benchmark, where the performance of PyOptInterface exceeds linopy and cvxpy significantly. :::{table} Time (second) to generate and solve a linear programming model with Gurobi optimizer. :widths: auto :align: center | N | Variables | PyOptInterface | linopy | cvxpy | | --- | --------- | -------------- | -------- | --------- | | 100 | 20000 | 0.076867 | 0.433379 | 0.224613 | | 200 | 80000 | 0.356767 | 0.959883 | 0.927248 | | 300 | 180000 | 0.796876 | 2.080950 | 2.681649 | | 400 | 320000 | 1.375459 | 3.715881 | 6.174171 | | 500 | 500000 | 2.222600 | 6.297467 | 12.153747 | ::: ## Nonlinear programming We use the AC Optimal Power Flow problem to benchmark performance of PyOptInterface against different modeling languages. The code is released at [GitHub](https://github.com/metab0t/opf_benchmark) and the result is published by our paper [Accelerating Optimal Power Flow with Structure-aware Automatic Differentiation and Code Generation](https://ieeexplore.ieee.org/document/10721402). ================================================ FILE: docs/source/callback.md ================================================ # Callback :::{attention} The behavior of callback function highly depends on the optimizer and the specific problem. Please refer to the official documentation of the optimizer for more details. ::: In most optimization problems, we build the model, set the parameters, and then call the optimizer to solve the problem. However, in some cases, we may want to monitor the optimization process and intervene in the optimization process. For example, we may want to stop the optimization process when a certain condition is met, or we may want to record the intermediate results of the optimization process. In these cases, we can use the callback function. The callback function is a user-defined function that is called by the optimizer at specific points during the optimization process. Callback is especially useful for mixed-integer programming problems, where we can control the branch and bound process in callback functions. Callback is not supported for all optimizers. Currently, we only support callback for Gurobi, COPT, and Xpress optimizer. Because callback is tightly coupled with the optimizer, we choose not to implement a strictly unified API for callback. Instead, we try to unify the common parts of the callback API and aim to provide all callback features included in the vendored Python bindings. In PyOptInterface, the callback function is simply a Python function that takes two arguments: - `model`: The instance of the [optimization model](model.md) - `where`: The flag indicates the stage of optimization process when our callback function is invoked. For Gurobi, the value of `where` is [CallbackCodes](https://www.gurobi.com/documentation/current/refman/cb_codes.html#sec:CallbackCodes). For COPT, the value of `where` is called as [callback contexts](https://guide.coap.online/copt/en-doc/callback.html) such as `COPT.CBCONTEXT_MIPNODE` and `COPT.CBCONTEXT_MIPRELAX`. For Xpress, the `where` value corresponds to specific callback points such as `XPRS.CB_CONTEXT.PREINTSOL` or `XPRS.CB_CONTEXT.OPTNODE`. A description of supported Xpress callbacks can be found [here](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/chapter5.html?scroll=section5002). In the function body of the callback function, we can do the following four kinds of things: - Query the current information of the optimization process. For scalar information, we can use `model.cb_get_info` function to get the information, and its argument is the value of [`what`](https://www.gurobi.com/documentation/current/refman/cb_codes.html) in Gurobi and the value of [callback information](https://guide.coap.online/copt/en-doc/information.html#chapinfo-cbc) in COPT. For Xpress, use regular attribute access methods such as `model.get_raw_attribute`. For array information such as the MIP solution or relaxation, PyOptInterface provides special functions such as `model.cb_get_solution` and `model.cb_get_relaxation`. - Add lazy constraint: Use `model.cb_add_lazy_constraint` just like `model.add_linear_constraint` except for the `name` argument. - Add user cut: Use `model.cb_add_user_cut` just like `model.add_linear_constraint` except for the `name` argument. - Set a heuristic solution: Use `model.cb_set_solution` to set individual values of variables and use `model.cb_submit_solution` to submit the solution to the optimizer immediately (`model.cb_submit_solution` will be called automatically in the end of callback if `model.cb_set_solution` is called). - Terminate the optimizer: Use `model.cb_exit`. Here is an example of a callback function that stops the optimization process when the objective value reaches a certain threshold: ```python import pyoptinterface as poi from pyoptinterface import gurobi, copt, xpress GRB = gurobi.GRB COPT = copt.COPT XPRS = xpress.XPRS def cb_gurobi(model, where): if where == GRB.Callback.MIPSOL: obj = model.cb_get_info(GRB.Callback.MIPSOL_OBJ) if obj < 10: model.cb_exit() def cb_copt(model, where): if where == COPT.CBCONTEXT_MIPSOL: obj = model.cb_get_info("MipCandObj") if obj < 10: model.cb_exit() def cb_xpress(model, where): if where == XPRS.CB_CONTEXT.PREINTSOL: obj = model.get_raw_attribute("LPOBJVAL") if obj < 10: model.cb_exit() ``` To use the callback function, we need to call `model.set_callback(cb)` to pass the callback function to the optimizer. For COPT and Xpress, `model.set_callback` needs an additional argument `where` to specify the context where the callback function is invoked. For Gurobi, the `where` argument is not needed. ```python model_gurobi = gurobi.Model() model_gurobi.set_callback(cb_gurobi) model_copt = copt.Model() model_copt.set_callback(cb_copt, COPT.CBCONTEXT_MIPSOL) # callback can also be registered for multiple contexts model_copt.set_callback(cb_copt, COPT.CBCONTEXT_MIPSOL + COPT.CBCONTEXT_MIPNODE) model_xpress = xpress.Model() model_xpress.set_callback(cb_xpress, XPRS.CB_CONTEXT.PREINTSOL) # callback can also be registered for multiple contexts model_xpress.set_callback(cb_xpress, XPRS.CB_CONTEXT.PREINTSOL + XPRS.CB_CONTEXT.CUTROUND) ``` In order to help users to migrate code using gurobipy, coptpy, and Xpress Python to PyOptInterface, we list a translation table as follows. :::{table} Callback in gurobipy and PyOptInterface :align: left | gurobipy | PyOptInterface | | -------------------------------------- | ------------------------------------------------------- | | `model.optimize(cb)` | `model.set_callback(cb) ` | | `model.cbGet(GRB.Callback.SPX_OBJVAL)` | `model.cb_get_info(GRB.Callback.SPX_OBJVAL)` | | `model.cbGetSolution(var)` | `model.cb_get_solution(var)` | | `model.cbGetNodelRel(var)` | `model.cb_get_relaxation(var)` | | `model.cbLazy(x[0] + x[1] <= 3)` | `model.cb_add_lazy_constraint(x[0] + x[1], poi.Leq, 3)` | | `model.cbCut(x[0] + x[1] <= 3)` | `model.cb_add_user_cut(x[0] + x[1], poi.Leq, 3)` | | `model.cbSetSolution(x, 1.0)` | `model.cb_set_solution(x, 1.0)` | | `objval = model.cbUseSolution()` | `objval = model.cb_submit_solution()` | | `model.termimate()` | `model.cb_exit()` | ::: :::{table} Callback in coptpy and PyOptInterface :align: left | coptpy | PyOptInterface | | ---------------------------------------------- | ------------------------------------------------------- | | `model.setCallback(cb, COPT.CBCONTEXT_MIPSOL)` | `model.set_callback(cb, COPT.CBCONTEXT_MIPSOL)` | | `CallbackBase.getInfo(COPT.CbInfo.BestBnd)` | `model.cb_get_info(COPT.CbInfo.BestBnd)` | | `CallbackBase.getSolution(var)` | `model.cb_get_solution(var)` | | `CallbackBase.getRelaxSol(var)` | `model.cb_get_relaxation(var)` | | `CallbackBase.getIncumbent(var)` | `model.cb_get_incumbent(var)` | | `CallbackBase.addLazyConstr(x[0] + x[1] <= 3)` | `model.cb_add_lazy_constraint(x[0] + x[1], poi.Leq, 3)` | | `CallbackBase.addUserCut(x[0] + x[1] <= 3)` | `model.cb_add_user_cut(x[0] + x[1], poi.Leq, 3)` | | `CallbackBase.setSolution(x, 1.0) ` | `model.cb_set_solution(x, 1.0)` | | `CallbackBase.loadSolution()` | `model.cb_submit_solution()` | | `CallbackBase.interrupt()` | `model.cb_exit()` | ::: :::{table} Callback in Xpress Python and PyOptInterface :align: left | Xpress Python | PyOptInterface | | ------------------------------------------------------ | ------------------------------------------------------------- | | `model.addPreIntsolCallback(cb)` | `model.set_callback(cb, XPRS.CB_CONTEXT.PREINTSOL)` | | `model.attributes.bestbound` | `model.get_raw_attribute("BESTBOUND")` | | `model.getCallbackSolution(var)` | `model.cb_get_solution(var)` | | `model.getCallbackSolution(var)` | `model.cb_get_relaxation(var)` | | `model.getSolution(var)` | `model.cb_get_incumbent(var)` | | `model.addCuts(0, 'L', 3, [0], [0, 1], [1, 1])` | `model.cb_add_lazy_constraint(x[0] + x[1], poi.Leq, 3)` | | `model.addManagedCuts(1, 'L', 3, [0], [0, 1], [1, 1])` | `model.cb_add_user_cut(x[0] + x[1], poi.Leq, 3)` | | `model.addMipSol([x], [1.0])` | `model.cb_set_solution(x, 1.0)` + `model.cb_submit_solution()` | | `model.interrupt()` | `model.cb_exit()` | ::: For a detailed example to use callbacks in PyOptInterface, we provide a [concrete callback example](https://github.com/metab0t/PyOptInterface/blob/master/tests/tsp_cb.py) to solve the Traveling Salesman Problem (TSP) with callbacks in PyOptInterface, gurobipy, coptpy, and Xpress Python. The example is adapted from the official Gurobi example [tsp.py](https://www.gurobi.com/documentation/current/examples/tsp_py.html). ================================================ FILE: docs/source/changelog.md ================================================ # Changelog ## 0.6.1 - Fix some bugs in Mosek interface - Update documentation of Knitro ## 0.6.0 - Add support for KNITRO solver, including nonlinear optimization, callbacks, license management, and documentation - Add support for Xpress solver, including linear, quadratic, NLP, callbacks, and reduced cost support - Add `ReducedCost` variable attribute - Add checks in IpoptModel to raise an error when accessing variable values, objective value, constraint primal or dual before calling `optimize()` or after modifying the model - Fix the sign of dual multiplier in IPOPT - Fix Hessian matrix ordering in HiGHS (sort elements in the same column by row number) - Fix initial value of nonlinear optimization in COPT - Fix `poi.ExprBuilder` operation with itself - Allow `poi.quicksum` for high dimensional numpy array directly without needing `.flat` - Support Gurobi 13, COPT 8, HiGHS 1.12 - Support finding Gurobi in pixi environment - Update CppAD to 20260000 and fmt to 12.1.0 ## 0.5.1 - Support llvmlite 0.45.0 ## 0.5.0 - Overhaul of the nonlinear programming interface and now PyOptInterface can solve nonlinear programming problems with COPT, Gurobi and IPOPT. - Use `model.add_linear_constraint(x+y, (1.0, 2.0))` to add two-sided linear constraints - Add `poi.ScalarAffineFunction.from_numpy` to create scalar affine functions from numpy arrays quickly ## 0.4.1 - Support writing solution files in HiGHS - Pass the names of variables and constraints to HiGHS - Add `model.close()` and `env.close()` methods to allow users release the license of commercial solvers manually ## 0.4.0 - Add `model.add_m_variables` and `model.add_m_linear_constraints` matrix modeling API - Add `model.computeIIS` and IIS related attributes for constraint and variable - Implement constraint based on compare operators, now you can use `model.add_linear_constraint(x + y <= 1.0)` directly - Drop support for Python 3.8 - Add wheels for Linux ARM64 - Supports HiGHS 1.9.0 and Mosek 11 ## 0.3.0 - Add `model.set_variable_bounds(variable, lb, ub)` to make it easier to change variable bounds - Introduce nonlinear programming support of Ipopt - Support new versions of optimizers - Various minor bug fixes ## 0.2.8 - Fix bugs in HiGHS and MOSEK when the quadratic objective function contains nondiagonal terms ## 0.2.7 - Fix bugs in HiGHS termination status ## 0.2.6 - Add rotated second-order cone support for COPT, Gurobi and Mosek - Add exponential cone support for COPT and Mosek - Requires COPT version >= 7.1.4 to support exponential cone ## 0.2.5 - Fix `add_linear_constraint` of HiGHS optimizer to consider the constant term in expression correctly - Make `make_tupledict` slightly faster ## 0.2.4 - Add `map` method for `tupledict` class - Add type stubs for C++ extension modules ## 0.2.3 - Fix a bug when deleting constraint in HiGHS ## 0.2.2 - Fix the performance issue with HiGHS optimizer ## 0.2.1 - Fix the DLL search paths on Windows ## 0.2.0 - Supports callback for Gurobi and COPT - Release GIL when calling `model.optimize()` ## 0.1.1 - Add `Model.write` method to write model to files ## 0.1.0 - First release on PyPI ================================================ FILE: docs/source/common_model_interface.md ================================================ # Common Model Interface Generally speaking, the following APIs are common to all optimizers except for adding constraint because different optimizers may support different types of constraints. ## Model ### Get/set model attributes ```{py:function} model.set_model_attribute(attr, value) set the value of a model attribute :param pyoptinterface.ModelAttribute attr: the attribute to set :param value: the value to set ``` ```{py:function} model.get_model_attribute(attr) get the value of a model attribute :param pyoptinterface.ModelAttribute attr: the attribute to get :return: the value of the attribute ``` ## Variable ### Add a variable to the model ```{py:function} model.add_variable([lb=-inf, ub=+inf, domain=pyoptinterface.VariableDomain.Continuous, name=""]) add a variable to the model :param float lb: the lower bound of the variable, optional, defaults to $-\infty$ :param float ub: the upper bound of the variable, optional, defaults to $+\infty$ :param pyoptinterface.VariableDomain domain: the domain of the variable, optional, defaults to continuous :param str name: the name of the variable, optional :return: the handle of the variable ``` ### Add multidimensional variables to the model as ```{py:function} model.add_variables(*coords, [lb=-inf, ub=+inf, domain=pyoptinterface.VariableDomain.Continuous, name=""]) add a multidimensional variable to the model :param coords: the coordinates of the variable, can be a list of Iterables :param float lb: the lower bound of the variable, optional, defaults to $-\infty$ :param float ub: the upper bound of the variable, optional, defaults to $+\infty$ :param pyoptinterface.VariableDomain domain: the domain of the variable, optional, defaults to continuous :param str name: the name of the variable, optional :return: the multi-dimensional variable :rtype: pyoptinterface.tupledict ``` ### Add multidimensional variables to the model as `numpy.ndarray` ```{py:function} model.add_m_variables(shape, [lb=-inf, ub=+inf, domain=pyoptinterface.VariableDomain.Continuous, name=""]) add a multidimensional variable to the model as `numpy.ndarray` :param shape: the shape of the variable, can be a tuple of integers or an integer :param float lb: the lower bound of the variable, optional, defaults to $-\infty$ :param float ub: the upper bound of the variable, optional, defaults to $+\infty$ :param pyoptinterface.VariableDomain domain: the domain of the variable, optional, defaults to continuous :param str name: the name of the variable, optional :return: the multidimensional variable :rtype: numpy.ndarray ``` ### Get/set variable attributes ```{py:function} model.set_variable_attribute(var, attr, value) set the value of a variable attribute :param var: the handle of the variable :param pyoptinterface.VariableAttribute attr: the attribute to set :param value: the value to set ``` ```{py:function} model.get_variable_attribute(var, attr) get the value of a variable attribute :param var: the handle of the variable :param pyoptinterface.VariableAttribute attr: the attribute to get :return: the value of the attribute ``` ### Delete variable ```{py:function} model.delete_variable(var) delete a variable from the model :param var: the handle of the variable ``` ```{py:function} model.is_variable_active(var) query whether a variable is active :param var: the handle of the variable :return: whether the variable is active :rtype: bool ``` ### Modify the bounds of variable ```{py:function} model.set_variable_bounds(var, lb, ub) set the lower and upper bounds of a variable :param var: the handle of the variable :param float lb: the new lower bound value :param float ub: the new upper bound value ``` ## Expression ### Get the value of an expression (including variable) ```{py:function} model.get_value(expr_or_var) get the value of an expression or a variable after optimization :param expr_or_var: the handle of the expression or the variable :return: the value of the expression or the variable :rtype: float ``` ### Pretty print expression (including variable) ```{py:function} model.pprint(expr_or_var) pretty print an expression in a human-readable format :param expr_or_var: the handle of the expression or the variable :return: the human-readable format of the expression :rtype: str ``` ## Constraint ### Add a constraint to the model - - - - ### Add linear constraints as matrix form to the model ```{py:function} model.add_m_linear_constraints(A, vars, sense, b, [name=""]) add linear constraints as matrix form to the model $Ax \le b$ or $Ax = b$ or $Ax \ge b$ :param A: the matrix of coefficients, can be a dense `numpy.ndarray` or a sparse matrix `scipy.sparse.sparray` :param vars: the variables in the constraints, can be a list or a 1-d `numpy.ndarray` returned by `add_m_variables` :param pyoptinterface.ConstraintSense sense: the sense of the constraints :param b: the right-hand side of the constraints, should be a 1-d `numpy.ndarray` :param str name: the name of the constraints, optional :return: the handles of linear constraints :rtype: numpy.ndarray ``` ### Get/set constraint attributes ```{py:function} model.set_constraint_attribute(con, attr, value) set the value of a constraint attribute :param con: the handle of the constraint :param pyoptinterface.ConstraintAttribute attr: the attribute to set :param value: the value to set ``` ```{py:function} model.get_constraint_attribute(con, attr) get the value of a constraint attribute :param con: the handle of the constraint :param pyoptinterface.ConstraintAttribute attr: the attribute to get :return: the value of the attribute ``` ### Delete constraint ```{py:function} model.delete_constraint(con) delete a constraint from the model :param con: the handle of the constraint ``` ```{py:function} model.is_constraint_active(con) query whether a constraint is active :param con: the handle of the constraint :return: whether the variable is active :rtype: bool ``` ### Modify constraint ```{py:function} model.set_normalized_rhs(con, value) set the right-hand side of a normalized constraint :param con: the handle of the constraint :param value: the new right-hand side value ``` ```{py:function} model.get_normalized_rhs(con) get the right-hand side of a normalized constraint :param con: the handle of the constraint :return: the right-hand side value ``` ```{py:function} model.set_normalized_coefficient(con, var, value) set the coefficient of a variable in a normalized constraint :param con: the handle of the constraint :param var: the handle of the variable :param value: the new coefficient value ``` ```{py:function} model.get_normalized_coefficient(con, var) get the coefficient of a variable in a normalized constraint :param con: the handle of the constraint :param var: the handle of the variable :return: the coefficient value ``` ## Objective ### Set the objective function ```{py:function} model.set_objective(expr, [sense=pyoptinterface.ObjectiveSense.Minimize]) set the objective function of the model :param expr: the handle of the expression :param pyoptinterface.ObjectiveSense sense: the sense of the objective function (Minimize/Maximize), defaults to Minimize ``` ### Modify the linear part of the objective function ```{py:function} model.set_objective_coefficient(var, value) modify the coefficient of a variable in the linear part of the objective function :param var: the handle of the variable :param float value: the new coefficient value ``` ```{py:function} model.get_objective_coefficient(var) get the coefficient of a variable in the linear part of the objective function :param var: the handle of the variable :return: the coefficient value ``` ================================================ FILE: docs/source/conf.py ================================================ # Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "PyOptInterface" copyright = "2024, Yue Yang" author = "Yue Yang" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ "sphinx.ext.autodoc", "sphinx.ext.githubpages", "sphinx_copybutton", "myst_nb", ] source_suffix = { ".rst": "restructuredtext", ".ipynb": "myst-nb", ".md": "myst-nb", } myst_enable_extensions = ["colon_fence", "amsmath", "dollarmath", "fieldlist"] templates_path = ["_templates"] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "furo" html_static_path = ["_static"] ================================================ FILE: docs/source/constraint.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Constraint PyOptInterface supports the following types of constraints: - Linear Constraint - Quadratic Constraint - Second-Order Cone Constraint - Special Ordered Set (SOS) Constraint :::{note} Not all optimizers support all types of constraints. Please refer to the documentation of the optimizer you are using to see which types of constraints are supported. ::: ```{code-cell} import pyoptinterface as poi from pyoptinterface import copt model = copt.Model() ``` ## Constraint Sense The sense of a constraint can be one of the following: - `poi.Eq`: equal - `poi.Leq`: less than or equal - `poi.Geq`: greater than or equal They are the abbreviations of `poi.ConstraintSense.Equal`, `poi.ConstraintSense.LessEqual` or `poi.ConstraintSense.GreaterEqual` and can be used in the `sense` argument of the constraint creation functions. ## Linear Constraint It is defined as: $$ \begin{align} \text{expr} &= a^T x + b &\leq \text{rhs} \\ \text{expr} &= a^T x + b &= \text{rhs} \\ \text{expr} &= a^T x + b &\geq \text{rhs} \end{align} $$ It can be added to the model using the `add_linear_constraint` method of the `Model` class. ```{code-cell} x = model.add_variable(name="x") y = model.add_variable(name="y") con = model.add_linear_constraint(2.0*x + 3.0*y, poi.Leq, 1.0) ``` ```{py:function} model.add_linear_constraint(expr, sense, rhs, [name=""]) add a linear constraint to the model :param expr: the expression of the constraint :param pyoptinterface.ConstraintSense sense: the sense of the constraint :param float rhs: the right-hand side of the constraint :param str name: the name of the constraint, optional :return: the handle of the constraint ``` :::{note} PyOptInterface provides , , and as alias of to represent the sense of the constraint with a shorter name. ::: The linear constraint can also be created with a comparison operator, like `<=`, `==`, or `>=`: ```{code-cell} model.add_linear_constraint(2.0*x + 3.0*y <= 1.0) model.add_linear_constraint(2.0*x + 3.0*y == 1.0) model.add_linear_constraint(2.0*x + 3.0*y >= 1.0) ``` If you want to express a two-sided linear constraint, you can use the `add_linear_constraint` method with a tuple to represent the left-hand side and right-hand side of the constraint like: ```{code-cell} model.add_linear_constraint(2.0*x + 3.0*y, (1.0, 2.0)) ``` which is equivalent to: ```{code-cell} model.add_linear_constraint(2.0*x + 3.0*y, poi.Leq, 2.0) model.add_linear_constraint(2.0*x + 3.0*y, poi.Geq, 1.0) ``` :::{note} The two-sided linear constraint is not implemented for Gurobi because of its [special handling of range constraints](https://docs.gurobi.com/projects/optimizer/en/12.0/reference/c/model.html#c.GRBaddrangeconstr). ::: ## Quadratic Constraint Like the linear constraint, it is defined as: $$ \begin{align} \text{expr} &= x^TQx + a^Tx + b &\leq \text{rhs} \\ \text{expr} &= x^TQx + a^Tx + b &= \text{rhs} \\ \text{expr} &= x^TQx + a^Tx + b &\geq \text{rhs} \end{align} $$ It can be added to the model using the `add_quadratic_constraint` method of the `Model` class. ```{code-cell} x = model.add_variable(name="x") y = model.add_variable(name="y") expr = x*x + 2.0*x*y + 4.0*y*y con = model.add_quadratic_constraint(expr, poi.ConstraintSense.LessEqual, 1.0) ``` ```{py:function} model.add_quadratic_constraint(expr, sense, rhs, [name=""]) add a quadratic constraint to the model :param expr: the expression of the constraint :param pyoptinterface.ConstraintSense sense: the sense of the constraint, which can be `GreaterEqual`, `Equal`, or `LessEqual` :param float rhs: the right-hand side of the constraint :param str name: the name of the constraint, optional :return: the handle of the constraint ``` Similarly, the quadratic constraint can also be created with a comparison operator, like `<=`, `==`, or `>=`: ```python model.add_quadratic_constraint(x*x + 2.0*x*y + 4.0*y*y <= 1.0) model.add_quadratic_constraint(x*x + 2.0*x*y + 4.0*y*y == 1.0) model.add_quadratic_constraint(x*x + 2.0*x*y + 4.0*y*y >= 1.0) ``` :::{note} Some solvers like COPT (as of 7.2.9) only supports convex quadratic constraints, which means the quadratic term must be positive semidefinite. If you try to add a non-convex quadratic constraint, an exception will be raised. ::: The two-sided quadratic constraint can also be created with a tuple to represent the left-hand side and right-hand side of the constraint like: ```python model.add_quadratic_constraint(x*x + 2.0*x*y + 4.0*y*y, (1.0, 2.0)) ``` :::{note} Currently, two-sided quadratic constraint is only implemented for IPOPT. ::: ## Second-Order Cone Constraint It is defined as: $$ variables=(t,x) \in \mathbb{R}^{N} : t \ge \lVert x \rVert_2 $$ It can be added to the model using the `add_second_order_cone_constraint` method of the `Model` class. ```{code-cell} N = 6 vars = [model.add_variable() for i in range(N)] con = model.add_second_order_cone_constraint(vars) ``` There is another form of second-order cone constraint called as rotated second-order cone constraint, which is defined as: $$ variables=(t_{1},t_{2},x) \in \mathbb{R}^{N} : 2 t_1 t_2 \ge \lVert x \rVert_2^2 $$ ```{py:function} model.add_second_order_cone_constraint(variables, [name="", rotated=False]) add a second order cone constraint to the model :param variables: the variables of the constraint, can be a list of variables :param str name: the name of the constraint, optional :param bool rotated: whether the constraint is a rotated second-order cone constraint, optional :return: the handle of the constraint ``` ## Exponential Cone Constraint It is defined as: $$ variables=(t,s,r) \in \mathbb{R}^{3} : t \ge s \exp(\frac{r}{s}), s \ge 0 $$ The dual form is: $$ variables=(t,s,r) \in \mathbb{R}^{3} : t \ge -r \exp(\frac{s}{r} - 1), r \le 0 $$ Currently, only COPT(after 7.1.4), Mosek support exponential cone constraint natively. Xpress supports exponential cones by mapping them into generic NLP formulas at the API level. It can be added to the model using the `add_exp_cone_constraint` method of the `Model` class. ```{py:function} model.add_exp_cone_constraint(variables, [name="", dual=False]) add a second order cone constraint to the model :param variables: the variables of the constraint, can be a list of variables :param str name: the name of the constraint, optional :param bool dual: whether the constraint is dual form of exponential cone, optional :return: the handle of the constraint ``` ## Special Ordered Set (SOS) Constraint SOS constraints are used to model special structures in the optimization problem. It contains two types: `SOS1` and `SOS2`, the details can be found in [Wikipedia](https://en.wikipedia.org/wiki/Special_ordered_set). It can be added to the model using the `add_sos_constraint` method of the `Model` class. ```{code-cell} N = 6 vars = [model.add_variable(domain=poi.VariableDomain.Binary) for i in range(N)] con = model.add_sos_constraint(vars, poi.SOSType.SOS1) ``` ```{py:function} model.add_sos_constraint(variables, sos_type, [weights]) add a special ordered set constraint to the model :param variables: the variables of the constraint, can be a list of variables :param pyoptinterface.SOSType sos_type: the type of the SOS constraint, which can be `SOS1` or `SOS2` :param weights: the weights of the variables, optional, will be set to 1 if not provided :type weights: list[float] :return: the handle of the constraint ``` ## Constraint Attributes After a constraint is created, we can query or modify its attributes. The following table lists the standard [constraint attributes](#pyoptinterface.ConstraintAttribute): :::{list-table} **Standard [constraint attributes](#pyoptinterface.ConstraintAttribute)** :header-rows: 1 :widths: 20 20 * - Attribute name - Type * - Name - str * - Primal - float * - Dual - float * - IIS - bool ::: The most common attribute we will use is the `Dual` attribute, which represents the dual multiplier of the constraint after optimization. ```python # get the dual multiplier of the constraint after optimization dual = model.get_constraint_attribute(con, poi.ConstraintAttribute.Dual) ``` ## Delete constraint We can delete a constraint by calling the `delete_constraint` method of the model: ```python model.delete_constraint(con) ``` After a constraint is deleted, it cannot be used in the model anymore, otherwise an exception will be raised. We can query whether a constraint is active by calling the `is_constraint_active` method of the model: ```python is_active = model.is_constraint_active(con) ``` ## Modify constraint For linear and quadratic constraints, we can modify the right-hand side of a constraint by calling the `set_normalized_rhs` method of the model. For linear constraints, we can modify the coefficients of the linear part of the constraint by calling the `set_normalized_coefficient` method of the model. ```python con = model.add_linear_constraint(x + y, poi.Leq, 1.0) # modify the right-hand side of the constraint model.set_normalized_rhs(con, 2.0) # modify the coefficient of the linear part of the constraint model.set_normalized_coefficient(con, x, 2.0) ``` ================================================ FILE: docs/source/container.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Container In our previous examples, we only use scalar variable, constraint and expression. However, in many cases, we need to handle a large number of variables, constraints and expressions. In general, PyOptInterface does not restrict the ways to store these objects. You can use a list, a dictionary, or any other data structure to store them, because PyOptInterface only requires the handle of the object to manipulate it and each handle is a compact C++ object that can be easily stored and passed around. In other words, we follow the Bring Your Own Container (BYOC) principle. However, in the context of optimization, we often needs to represent the model in a more structured way. The most classic example is [`Set`](https://ampl.com/wp-content/uploads/Chapter-5-Simple-Sets-and-Indexing-AMPL-Book.pdf) in AMPL, which provides a flexible way to represent multi-dimensional data with custom indexing. The concept is also widely used in other optimization modeling languages, such as [JuMP](https://jump.dev/JuMP.jl/stable/manual/containers/) in Julia and [Pyomo](https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Sets.html) in Python. In PyOptInterface, we provide a simple container named `tupledict` to represent multidimensional data. It is a dictionary-like object that can be indexed by multiple keys. It is a convenient way to store and manipulate multi-dimensional data in PyOptInterface. The design and usage of `tupledict` is inspired by the `tupledict` in [gurobipy](https://www.gurobi.com/documentation/current/refman/py_tupledict.html). We will use `tupledict` to demonstrate how to use a container to store and manipulate multi-dimensional data in PyOptInterface. Simply speaking, `tupledict` is a derived class of Python dict where the keys represent multidimensional indices as n-tuple, and the values are typically variables, constraints or expressions. ## Create a `tupledict` `tuplelist` can be created by calling the `tupledict` constructor. The following example creates a `tupledict` with two keys: ```{code-cell} import pyoptinterface as poi td = poi.tupledict() td[1, 2] = 3 td[4, 5] = 6 print(td) ``` It can also be created by passing a dictionary to the constructor: ```{code-cell} td = poi.tupledict({(1, 2): 3, (4, 5): 6}) ``` In most cases, we have multiple indices as the axis and define a rule to construct the `tupledict`. `make_tupledict` provide a convenient way to create a `tupledict` from a list of indices and a function that maps the indices to the values. The following example creates a `tupledict` with two keys: ```{code-cell} I = range(3) J = [6, 7] K = ("Asia", "Europe") def f(i, j, k): return f"value_{i}_{j}_{k}" td = poi.make_tupledict(I, J, K, rule=f) print(td) ``` Sometimes we need to create a `tupledict` with a sparse pattern where some combinations of indices are missing. `make_tupledict` also provides a convenient way to create a `tupledict` with a sparse pattern, you only need to return `None` when the corresponding value is unwanted. The following example creates a `tupledict` with a sparse pattern: ```{code-cell} I = range(3) J = [6, 7] K = ("Asia", "Europe") def f(i, j, k): if i == 0 and j == 6 and k == "Asia": return "value_0_6_Asia" else: return None td = poi.make_tupledict(I, J, K, rule=f) print(td) ``` For highly sparse patterns, you can provide the sparse indices directly to make it more efficient. ``` I = range(2) J = range(8) K = range(8) # We only want to construct (i, j, k) where j=k JK = [(j, j) for j in J] def f(i, j, k): return f"value_{i}_{j}_{k}" # JK will be flattened as j and k during construction td = poi.make_tupledict(I, JK, rule=f) print(td) ``` ## Set/get values Like normal dictionary in Python, the values of a `tupledict` can be set or get by using the `[]` operator. The following example sets and gets the values of a `tupledict`: ```{code-cell} td = poi.tupledict({(1, 2): 3, (4, 5): 6}) td[1, 2] = 4 print(f"{td[1, 2]=}") td[4, 8] = 7 print(f"{td[4, 8]=}") ``` As a representation of multidimensional data, `tupledict` also provides a way to iterate some axis efficiently. `tupledict.select` can be used to select a subset of the `tupledict` by fixing some indices. The following example selects a subset of the `tupledict`: ```{code-cell} td = poi.make_tupledict(range(3), range(3), range(3), rule=lambda i, j, k: f"value_{i}_{j}_{k}") # Select the subset where i=1 # "*" means wildcard that matches any value for j and k # select returns a generator and can be converted to a list subset_values_iterator = td.select(1, "*", "*") subset_values = list(subset_values_iterator) # Select the subset where j=2 and k=1 subset_values = list(td.select("*", 2, 1)) # the iterator can retuen the (key, value) pair if we pass with_key=True to select subset_kv_iterator = td.select(1, "*", "*", with_key=True) ``` ## Apply a function to values with `map` method `tupledict` provides a `map` method to apply a function to each value in the `tupledict`. The following example applies a function to each value in the `tupledict`: ```{code-cell} td = poi.make_tupledict(range(3), range(2), rule=lambda i, j: i+j) td_new = td.map(lambda x: x*x) td, td_new ``` ## Building a model with `tupledict` `tupledict` can be used to store and manipulate multi-dimensional variables, constraints and expressions. Using it correctly will make building model more easily. The following example demonstrates how to use `tupledict` to build a model: $$ \min \quad & \sum_{i=0}^{I} \sum_{j=0}^{J} x_{ij}^2 \\ \textrm{s.t.} \quad & \sum_{i=0}^{I} x_{ij} = 1, \quad \forall j \\ \quad & x_{ij} \geq 0, \quad \forall i, j $$ ```{code-cell} import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() I = range(10) J = range(20) x = poi.make_tupledict(I, J, rule=lambda i, j: model.add_variable(lb=0, name=f"x({i},{j})")) def constraint_rule(j): expr = poi.quicksum(x.select("*", j)) con = model.add_linear_constraint(expr, poi.ConstraintSense.Equal, 1, name=f"con_{j}") return con constraint = poi.make_tupledict(J, rule=constraint_rule) obj = poi.quicksum(x.values(), lambda x: x*x) model.set_objective(obj, poi.ObjectiveSense.Minimize) model.optimize() x_value = x.map(model.get_value) ``` Here we use two utility functions to simplify how we express the sum notation ```{py:function} quicksum(values, [f=None]) Create a new expression by summing up a list of values (optionally, you can apply a function to each value in advance) :param values: iterator of values :param f: a function that takes a value and returns a new value :return: the handle of the new expression :rtype: pyoptinterface.ExprBuilder ``` There is also an in-place version: ```{py:function} quicksum_(expr, values, [f=None]) Add a list of values to an existing expression (optionally, you can apply a function to each value in advance) :param pyoptinterface.ExprBuilder expr: the handle of the existing expression :param values: iterator of values :param f: a function that takes a value and returns a new value :return: None ``` We notice that `poi.make_tupledict(I, J, rule=lambda i, j: model.add_variable(lb=0, name=f"x({i},{j})"))` is a frequently used pattern to create a `tupledict` of variables, so we provide a convenient way to create a `tupledict` of variables by calling [`model.add_variables`](#model.add_variables): ```python x = model.add_variables(I, J, lb=0, name="x") ``` ================================================ FILE: docs/source/copt.md ================================================ # COPT ## Initial setup ```python from pyoptinterface import copt model = copt.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#copt) to set up the optimizer correctly. If you want to manage the license of COPT manually, you can create a `copt.Env` object and pass it to the constructor of the `copt.Model` object, otherwise we will initialize an implicit global `copt.Env` object automatically and use it. ```python env = copt.Env() model = copt.Model(env) ``` ## The capability of `copt.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ✅ * - - ✅ * - - ✅ * - - ✅ ::: ```{include} attribute/copt.md ``` ## Solver-specific operations ### Parameter For [solver-specific parameters](https://guide.coap.online/copt/en-doc/parameter.html), we provide `get_raw_parameter` and `set_raw_parameter` methods to get and set the parameters. ```python model = copt.Model() # get the value of the parameter value = model.get_raw_parameter("TimeLimit") # set the value of the parameter model.set_raw_parameter("TimeLimit", 10.0) ``` ### Attribute COPT supports [attribute](https://guide.coap.online/copt/en-doc/attribute.html) for the model. We provide `model.get_raw_attribute(name:str)` to get the value of attribute. ### Information COPT provides [information](https://guide.coap.online/copt/en-doc/information.html) for the model components. We provide methods to access the value of information. - Information of variable: `model.get_variable_info(variable, name: str)` - Information of constraint: `model.get_constraint_info(constraint, name: str)` We also provide `copt.COPT` to contain all the constants in `coptpy.COPT`. For number of variables (columns) in the problem: ```python cols = model.get_raw_attribute(copt.COPT.Attr.Cols) ``` For reduced cost of a variable: ```python rc = model.get_variable_info(variable, copt.COPT.Info.RedCost) ``` For upper bound of a constraint: ```python ub = model.get_constraint_info(constraint, copt.COPT.Info.UB) ``` ================================================ FILE: docs/source/develop.md ================================================ # Developer Guide This section is intended for developers who want to contribute to the PyOptInterface library. It provides an overview of the codebase, the development process, and the guidelines for contributing to the project. ## Codebase Overview The PyOptInterface library is a C++/Python mixed library. The core parts are implemented in C++ and exposed to Python via [nanobind](https://github.com/wjakob/nanobind). The build system is based on [scikit-build-core](https://github.com/scikit-build/scikit-build-core). The codebase is organized as follows: - `include/pyoptinterface`: The header files of the C++ library. - `lib`: The source files of the C++ library. - `src`: The Python interface. - `tests`: The test cases. - `thirdparty`: The third-party dependencies. ## Development Process Supposing you want to contribute to PyOptInterface, you can follow these steps: Firstly, fork the [PyOptInterface repository](https://github.com/metab0t/PyOptInterface) on GitHub and clone your forked repository to your local machine: `git clone https://github.com//PyOptInterface.git`. Next, you should set up the development environment. The third-party optimizers must be configured following the instructions in [getting started](getting_started.md). In order to build PyOptInterface from source, you need the following dependencies: - CMake - A recent C++ compiler (We routinely test with GCC 10, latest MSVC and Apple Clang on the CI) - Python 3.8 or later - Python packages: `scikit-build-core`, can be installed by running `pip install scikit-build-core[pyproject]` Then, you can build the project by running the following commands: ```bash pip install --no-build-isolation -ve . ``` You will see a new `build` directory created in the project root. The Python package is installed in editable mode, so you can modify the source code and test the changes without reinstalling the package. After making changes to the code, you should run the test cases to ensure that everything is working as expected. You can run the test cases by executing the following command (installing `pytest` is required): ```bash pytest tests ``` The tests of PyOptInterface are still scarce, so you are encouraged to write new test cases for the new features you add. Finally, you can submit a pull request to the [PyOptInterface repository](https://github.com/metab0t/PyOptInterface) ## Building Documentation The documentation of PyOptInterface is built using [Sphinx](https://www.sphinx-doc.org/). Firstly, you should install the dependencies for building the documentation: ```bash pip install -r docs/requirements.txt ``` You can build the documentation by running the following commands: ```bash cd docs make html ``` The docs are built in the `docs/build/html` directory. You can open the `index.html` file in a web browser to view the documentation. ## Contributing Guidelines When contributing to PyOptInterface, please follow these guidelines: - Make sure that the code is well formatted and documented. The C++ code is formatted using [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and the Python code is formatted using [black](https://black.readthedocs.io/en/stable/). - For big changes like adding interface for a new optimizer, please open a thread in [**Discussion**](https://github.com/metab0t/PyOptInterface/discussions) to discuss the design before starting the implementation. ================================================ FILE: docs/source/examples/economic_dispatch.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Economic Dispatch Economic dispatch is a classic optimization problem in power systems. It is used to determine the optimal power output of a set of generators to meet the demand at the lowest cost. The problem can be formulated as a linear programming problem or a quadratic programming problem depending on the formulation used to model the economic cost. In this example, we will show how to use PyOptInterface to solve an economic dispatch problem using the quadratic programming formulation. ## Problem Formulation $$ \min \quad & \sum_{t=1}^T \sum_{i=1}^{N_G} a_i P_{i,t}^2 + b_i P_{i,t} + c_i \\ \textrm{s.t.} \quad & \sum_{i=1}^{N_G} P_{i,t} = \sum_{i=1}^{N_D} D_{i,t}\\ \quad & P_{min,i} \leq P_{i,t} \leq P_{max,i} \\ \quad & -Rdn_{i} \leq P_{i,t} - P_{i,t-1} \leq Rup_{i} $$ We consider a system with $N_G$ generators and $N_D$ demands. The decision variables are the power output of the generators $P_{i,t}$ for $i=1,\ldots,N_G$ and $t=1,\ldots,T$. The objective function is the total cost of the generators, which is the sum of the quadratic cost, the linear cost, and the constant cost. The first constraint ensures that the total power output of the generators meets the demand. The second and third constraints are the power output limits of the generators. The last constraint is the ramping limits of the generators. ## Implementation Firstly, we need to create a model object. We will use the HiGHS solver in this example because it is an excellent open-source solver and supports quadratic programming problems. You need to read the [installation guide](../highs.md#initial-setup) to install the HiGHS solver manually and set the path to shared library of HiGHS as described in the guide. ```{code-cell} import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() ``` Then, we need to create new variables in the model to represent the power output of the generators. We will use the [`add_variables`](#model.add_variables) method to add multidimensional variables to the model. ```{code-cell} T = 24 N_G = 3 P_min = [50, 50, 50] P_max = [100, 100, 100] a_cost = [0.01, 0.015, 0.02] b_cost = [1, 1.5, 2] c_cost = [0, 0, 0] Rup = [20, 20, 20] Rdn = [20, 20, 20] P = model.add_variables(range(N_G), range(T), name="P") ``` For the lower and upper bound of variables, we can set the corresponding [variable attribute](#pyoptinterface.VariableAttribute) to set them. ```{code-cell} for i in range(N_G): for t in range(T): model.set_variable_attribute(P[i, t], poi.VariableAttribute.LowerBound, P_min[i]) model.set_variable_attribute(P[i, t], poi.VariableAttribute.UpperBound, P_max[i]) ``` Next, we need to add the power balance constraint to the model. We will use the [`add_linear_constraint`](#model.add_linear_constraint) method to add a linear constraint to the model. The multidimensional constraint is managed by `tupledict` and we use the `make_tupledict` method to create a multidimensional constraint. The total demand is assumed to be a constant in this example. ```{code-cell} total_demand = [220 for _ in range(T)] def con(t): lhs = poi.quicksum(P[i, t] for i in range(N_G)) rhs = total_demand[t] return model.add_linear_constraint(lhs, poi.Eq, rhs, name=f"powerbalance_{t}") powebalance_constraints = poi.make_tupledict(range(T), rule=con) ``` ```{code-cell} def rampup_con(i, t): if t == 0: return None lhs = P[i, t] - P[i, t-1] rhs = Rup[i] return model.add_linear_constraint(lhs, poi.Leq, rhs, name=f"rampup_{i}_{t}") rampup_constraints = poi.make_tupledict(range(N_G), range(T), rule=rampup_con) def rampdown_con(i, t): if t == 0: return None lhs = P[i, t] - P[i, t-1] rhs = -Rdn[i] return model.add_linear_constraint(lhs, poi.Geq, rhs, name=f"rampdown_{i}_{t}") rampdown_constraints = poi.make_tupledict(range(N_G), range(T), rule=rampdown_con) ``` Then, we need to add the quadratic objective function to the model. We will use the [`set_objective`](#model.set_objective) method to set the objective function of the model. ```{code-cell} obj = poi.ExprBuilder() for t in range(T): for i in range(N_G): obj += a_cost[i] * P[i, t] * P[i, t] + b_cost[i] * P[i, t] + c_cost[i] model.set_objective(obj) ``` Finally, we can solve the model and query the solution. ```{code-cell} model.optimize() print(model.get_model_attribute(poi.ModelAttribute.TerminationStatus)) print("Objective value: ", model.get_value(obj)) ``` The optimal value of decision variables can be queried via `get_value` function. ```{code-cell} import numpy as np P_value = np.fromiter( (model.get_value(P[i, t]) for i in range(N_G) for t in range(T)), float ).reshape(N_G, T) P_value ``` ## Change the load and solve the model again We can change the load and solve the model again without creating a new model from scratch by modifying the right-hand side of the power balance constraint. For example, we increase the load and solve the model again. ```{code-cell} total_demand = [220 + 1.0 * t for t in range(T)] for t in range(T): model.set_normalized_rhs(powebalance_constraints[t], total_demand[t]) model.optimize() print(model.get_model_attribute(poi.ModelAttribute.TerminationStatus)) print("Objective value: ", model.get_value(obj)) P_value = np.fromiter( (model.get_value(P[i, t]) for i in range(N_G) for t in range(T)), float ).reshape(N_G, T) print(P_value) ``` ================================================ FILE: docs/source/examples/optimal_control_rocket.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Optimal Control of a Rocket This example is adapted from [the JuMP tutorial](https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/rocket_control/). The goal is to show that there are explicit repeated structures in discretized optimal control problem regarded as a nonlinear program (NLP). We will use the optimal control of a rocket as an example to demonstrate how to exploit these structures to solve the problem more efficiently via PyOptInterface. ## Problem Formulation The problem is to find the optimal control of a rocket to maximize the altitude at the final time while satisfying the dynamics of the rocket. The dynamics of the rocket are described by the following ordinary differential equations (ODEs): $$ \begin{align*} \frac{dh}{dt} &= v \\ \frac{dv}{dt} &= -g(h) + \frac{u-D(h,v)}{m} \\ \frac{dm}{dt} &= -\frac{u}{c} \end{align*} $$ where $h$ is the altitude, $v$ is the velocity, $m$ is the mass, $u$ is the thrust, $g(h)$ is the gravitational acceleration, and $D(h,v)$ is the drag force. The thrust $u$ is the control variable. The drag force is given by $D(h,v) = D_c v^2 \exp(-h_c \frac{h-h_0}{h_0})$, and the gravitational acceleration is given by $g(h) = g_0 (\frac{h_0}{h})^2$. By discretizing the ODEs, we obtain the following nonlinear program: $$ \begin{align*} \frac{h_{t+1}-h_t}{\Delta t} &= v_t \\ \frac{v_{t+1}-v_t}{\Delta t} &= -g(h_t) + \frac{u_t-D(h_t,v_t)}{m_t} \\ \frac{m_{t+1}-m_t}{\Delta t} &= -\frac{u_t}{c} \end{align*} $$ where $h_t$, $v_t$, and $m_t$ are the altitude, velocity, and mass at time $t$, respectively, and $\Delta t$ is the time step. ## Implementation In the discretized optimal control problem, the variables at two adjacent time points share the same algebraic relationship. ```{code-cell} import math import pyoptinterface as poi from pyoptinterface import nl, ipopt model = ipopt.Model() h_0 = 1.0 v_0 = 0.0 m_0 = 1.0 g_0 = 1.0 T_c = 3.5 h_c = 500.0 v_c = 620.0 m_c = 0.6 c = 0.5 * math.sqrt(g_0 * h_0) m_f = m_c * m_0 D_c = 0.5 * v_c * (m_0 / g_0) T_max = T_c * m_0 * g_0 nh = 1000 ``` Then, we declare variables and set boundary conditions. ```{code-cell} h = model.add_m_variables(nh, lb=1.0) v = model.add_m_variables(nh, lb=0.0) m = model.add_m_variables(nh, lb=m_f, ub=m_0) T = model.add_m_variables(nh, lb=0.0, ub=T_max) step = model.add_variable(lb=0.0) # Boundary conditions model.set_variable_bounds(h[0], h_0, h_0) model.set_variable_bounds(v[0], v_0, v_0) model.set_variable_bounds(m[0], m_0, m_0) model.set_variable_bounds(m[-1], m_f, m_f) ``` Next, we add the dynamics constraints. ```{code-cell} for i in range(nh - 1): with nl.graph(): h1 = h[i] h2 = h[i + 1] v1 = v[i] v2 = v[i + 1] m1 = m[i] m2 = m[i + 1] T1 = T[i] T2 = T[i + 1] model.add_nl_constraint(h2 - h1 - 0.5 * step * (v1 + v2) == 0) D1 = D_c * v1 * v1 * nl.exp(-h_c * (h1 - h_0)) / h_0 D2 = D_c * v2 * v2 * nl.exp(-h_c * (h2 - h_0)) / h_0 g1 = g_0 * h_0 * h_0 / (h1 * h1) g2 = g_0 * h_0 * h_0 / (h2 * h2) dv1 = (T1 - D1) / m1 - g1 dv2 = (T2 - D2) / m2 - g2 model.add_nl_constraint(v2 - v1 - 0.5 * step * (dv1 + dv2) == 0) model.add_nl_constraint(m2 - m1 + 0.5 * step * (T1 + T2) / c == 0) ``` Finally, we add the objective function. We want to maximize the altitude at the final time, so we set the objective function to be the negative of the altitude at the final time. ```{code-cell} model.set_objective(-h[-1]) ``` After solving the problem, we can plot the results. ```{code-cell} model.optimize() ``` ```{code-cell} h_value = [] for i in range(nh): h_value.append(model.get_value(h[i])) print("Optimal altitude: ", h_value[-1]) ``` The plot of the altitude of the rocket is shown below. ```{code-cell} import matplotlib.pyplot as plt plt.plot(h_value) plt.xlabel("Time") plt.ylabel("Altitude") plt.show() ``` ================================================ FILE: docs/source/examples/optimal_power_flow.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Optimal Power Flow Alternating current optimal power flow (ACOPF) is a fundamental nonlinear optimization problem in power systems. It is used to determine the optimal power flow of a power system to minimize the generation cost while satisfying the power flow equations and system constraints. In this example, we will show how to use PyOptInterface to solve an single-period optimal power flow problem using the structured nonlinear programming formulation. ## Problem Formulation $$ \min \quad & \sum_{i \in G} a_i P_{i}^2 + b_i P_{i} + c_i \\ \textrm{s.t.} \quad & \theta_r = 0 \quad & \forall r \in R \\ \quad & P_{min,i} \leq P_{i} \leq P_{max,i} \quad & \forall i \in G \\ \quad & Q_{min,i} \leq Q_{i} \leq Q_{max,i} \quad & \forall i \in G \\ \quad & V_{min,b} \leq V_{b} \leq V_{max,b} \quad & \forall b \in BUS \\ \quad & \sum_{i \in G_b} P_{i} - \sum_{i \in D_b} L^P_{i} - G_{sh,b} V_b^2 = \sum_{(i, j) \in BRANCH} P_{ij} \quad & \forall b \in BUS \\ \quad & \sum_{i \in G_b} Q_{i} - \sum_{i \in D_b} L^Q_{i} + B_{sh,b} V_b^2 = \sum_{(i, j) \in BRANCH} Q_{ij} \quad & \forall b \in BUS \\ \quad & P_{ij} = G_{ij} V_i^2 - V_i V_j (G_{ij}\cos(\theta_i-\theta_j)+B_{ij}\sin(\theta_i-\theta_j)) \quad & \forall (i, j) \in BRANCH \\ \quad & Q_{ij} = -B^C_{ij} V_i^2 - B_{ij} V_i^2 - V_i V_j (G_{ij}\sin(\theta_i-\theta_j)-B_{ij}\cos(\theta_i-\theta_j)) \quad & \forall (i, j) \in BRANCH \\ \quad & P_{ji} = G_{ij} V_j^2 - V_i V_j (G_{ij}\cos(\theta_j-\theta_i)+B_{ij}\sin(\theta_j-\theta_i)) \quad & \forall (i, j) \in BRANCH \\ \quad & Q_{ji} = -B^C_{ij} V_j^2 - B_{ij} V_j^2 - V_i V_j (G_{ij}\sin(\theta_j-\theta_i)-B_{ij}\cos(\theta_j-\theta_i)) \quad & \forall (i, j) \in BRANCH \\ \quad & -S_{max,ij} \leq P_{ij}^2 + Q_{ij}^2 \leq S_{max,ij} \quad & \forall (i, j) \in BRANCH \\ \quad & -\Delta\theta_{min,ij} \leq \theta_i - \theta_j \leq -\Delta\theta_{max,ij} \quad & \forall (i, j) \in BRANCH $$ The decision variables are the active power output of the generators $P_{i}$, reactive power output of the generators $Q_{i}$, voltage magnitude of the buses $V_{b}$, phase angle of the buses $\theta_{b}$ for $b \in BUS$ and the branch power flows $P_{ij}$ and $Q_{ij}$ for $(i, j) \in BRANCH$. The objective function is the total cost of the generators, which is the sum of the quadratic cost, the linear cost, and the constant cost. The first constraint ensures that the phase angle of the reference bus is zero. The second and third constraints are the active and reactive power output limits of the generators. The fourth constraint is the voltage magnitude limits of the buses. The fifth and sixth constraints are the power balance equations of the buses. The seventh and eighth constraints are the power flow equations of the branches. The ninth and tenth constraints are the power flow equations of the branches in the opposite direction. The eleventh and twelfth constraints are the apparent power limits of the branches. The last constraint is the phase angle difference limits of the branches. ## Implementation We will use PJM 5-bus system as an example to demonstrate the implementation of the optimal power flow problem. The PJM 5-bus system is a small power system with 5 buses and 6 branches. The system data is shown below. ```{code-cell} branches = [ # (from, to, R, X, B, angmin, angmax, Smax) (0, 1, 0.00281, 0.0281, 0.00712, -30.0, 30.0, 4.00), (0, 3, 0.00304, 0.0304, 0.00658, -30.0, 30.0, 4.26), (0, 4, 0.00064, 0.0064, 0.03126, -30.0, 30.0, 4.26), (1, 2, 0.00108, 0.0108, 0.01852, -30.0, 30.0, 4.26), (2, 3, 0.00297, 0.0297, 0.00674, -30.0, 30.0, 4.26), (3, 4, 0.00297, 0.0297, 0.00674, -30.0, 30.0, 2.40), ] buses = [ # (Pd, Qd, Gs, Bs, Vmin, Vmax) (0.0, 0.0000, 0.0, 0.0, 0.9, 1.1), (3.0, 0.9861, 0.0, 0.0, 0.9, 1.1), (3.0, 0.9861, 0.0, 0.0, 0.9, 1.1), (4.0, 1.3147, 0.0, 0.0, 0.9, 1.1), (0.0, 0.0000, 0.0, 0.0, 0.9, 1.1), ] generators = [ # (bus, Pmin, Pmax, Qmin, Qmax, a, b, c) (0, 0.0, 0.4, -0.300, 0.300, 0.0, 1400, 0.0), (0, 0.0, 1.7, -1.275, 1.275, 0.0, 1500, 0.0), (2, 0.0, 5.2, -3.900, 3.900, 0.0, 3000, 0.0), (3, 0.0, 2.0, -1.500, 1.500, 0.0, 4000, 0.0), (4, 0.0, 6.0, -4.500, 4.500, 0.0, 1000, 0.0), ] slack_bus = 3 ``` Then we declare the variables: ```{code-cell} import math import pyoptinterface as poi from pyoptinterface import nl, ipopt model = ipopt.Model() N_branch = len(branches) N_bus = len(buses) N_gen = len(generators) Pbr_from = model.add_m_variables(N_branch) Qbr_from = model.add_m_variables(N_branch) Pbr_to = model.add_m_variables(N_branch) Qbr_to = model.add_m_variables(N_branch) V = model.add_m_variables(N_bus, name="V") theta = model.add_m_variables(N_bus, name="theta") for i in range(N_bus): Vmin, Vmax = buses[i][4], buses[i][5] model.set_variable_bounds(V[i], Vmin, Vmax) model.set_variable_bounds(theta[slack_bus], 0.0, 0.0) P = model.add_variables(range(N_gen), name="P") Q = model.add_variables(range(N_gen), name="Q") for i in range(N_gen): model.set_variable_bounds(P[i], generators[i][1], generators[i][2]) model.set_variable_bounds(Q[i], generators[i][3], generators[i][4]) ``` Next, we add the constraints: ```{code-cell} # nonlinear constraints for k in range(N_branch): with nl.graph(): branch = branches[k] R, X, Bc2 = branch[2], branch[3], branch[4] G = R / (R**2 + X**2) B = -X / (R**2 + X**2) Bc = Bc2 / 2 i = branch[0] j = branch[1] Vi = V[i] Vj = V[j] theta_i = theta[i] theta_j = theta[j] Pij = Pbr_from[k] Qij = Qbr_from[k] Pji = Pbr_to[k] Qji = Qbr_to[k] sin_ij = nl.sin(theta_i - theta_j) cos_ij = nl.cos(theta_i - theta_j) Pij_eq = G * Vi**2 - Vi * Vj * (G * cos_ij + B * sin_ij) - Pij Qij_eq = -(B + Bc) * Vi**2 - Vi * Vj * (G * sin_ij - B * cos_ij) - Qij Pji_eq = G * Vj**2 - Vi * Vj * (G * cos_ij - B * sin_ij) - Pji Qji_eq = -(B + Bc) * Vj**2 - Vi * Vj * (-G * sin_ij - B * cos_ij) - Qji model.add_nl_constraint( Pij_eq == 0.0, ) model.add_nl_constraint( Qij_eq == 0.0, ) model.add_nl_constraint( Pji_eq == 0.0, ) model.add_nl_constraint( Qji_eq == 0.0, ) # power balance constraints P_balance_eq = [poi.ExprBuilder() for i in range(N_bus)] Q_balance_eq = [poi.ExprBuilder() for i in range(N_bus)] for b in range(N_bus): Pd, Qd = buses[b][0], buses[b][1] Gs, Bs = buses[b][2], buses[b][3] Vb = V[b] P_balance_eq[b] -= poi.quicksum( Pbr_from[k] for k in range(N_branch) if branches[k][0] == b ) P_balance_eq[b] -= poi.quicksum( Pbr_to[k] for k in range(N_branch) if branches[k][1] == b ) P_balance_eq[b] += poi.quicksum( P[i] for i in range(N_gen) if generators[i][0] == b ) P_balance_eq[b] -= Pd P_balance_eq[b] -= Gs * Vb * Vb Q_balance_eq[b] -= poi.quicksum( Qbr_from[k] for k in range(N_branch) if branches[k][0] == b ) Q_balance_eq[b] -= poi.quicksum( Qbr_to[k] for k in range(N_branch) if branches[k][1] == b ) Q_balance_eq[b] += poi.quicksum( Q[i] for i in range(N_gen) if generators[i][0] == b ) Q_balance_eq[b] -= Qd Q_balance_eq[b] += Bs * Vb * Vb model.add_quadratic_constraint(P_balance_eq[b], poi.Eq, 0.0) model.add_quadratic_constraint(Q_balance_eq[b], poi.Eq, 0.0) for k in range(N_branch): branch = branches[k] i = branch[0] j = branch[1] theta_i = theta[i] theta_j = theta[j] angmin = branch[5] / 180 * math.pi angmax = branch[6] / 180 * math.pi model.add_linear_constraint(theta_i - theta_j, (angmin, angmax)) Smax = branch[7] Pij = Pbr_from[k] Qij = Qbr_from[k] Pji = Pbr_to[k] Qji = Qbr_to[k] model.add_quadratic_constraint(Pij * Pij + Qij * Qij, poi.Leq, Smax * Smax) model.add_quadratic_constraint(Pji * Pji + Qji * Qji, poi.Leq, Smax * Smax) ``` Finally, we set the objective function: ```{code-cell} cost = poi.ExprBuilder() for i in range(N_gen): a, b, c = generators[i][5], generators[i][6], generators[i][7] cost += a * P[i] * P[i] + b * P[i] + c model.set_objective(cost) ``` After optimization, we can retrieve the optimal solution: ```{code-cell} model.optimize() ``` ```{code-cell} print(model.get_model_attribute(poi.ModelAttribute.TerminationStatus)) P_value = P.map(model.get_value) print("Optimal active power output of the generators:") for i in range(N_gen): print(f"Generator {i}: {P_value[i]}") ``` ================================================ FILE: docs/source/expression.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Expression ## Basic expression PyOptInterface currently supports polynomial expressions with degree up to 2, including - quadratic expression - linear expression - constant expression The expression can be expressed by arithmetic operations of variables and constants. For example, we can create a quadratic expression by adding two quadratic expressions, multiplying a linear expression with a constant expression, etc. ```{code-cell} import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() x = model.add_variable() # create a quadratic expression expr1 = x * x + 2 * x + 1 # create a linear expression expr2 = 2 * x + 1 # create a quadratic expression by adding two quadratic expressions expr3 = x * expr2 + expr1 ``` ## Efficient expression construction PyOptInterface provides a special class `ExprBuilder` to construct expressions efficiently. It is especially useful when we need to construct a large expression with many terms. It supports the following in-place assignment operations: - `+=`: add a term to the expression - `-=`: subtract a term from the expression - `*=`: multiply the expression with a constant or another expression - `/=`: divide the expression with a constant For example, we can use `ExprBuilder` to construct the following expression efficiently: $$ \frac{1}{2} \sum_{i=1}^N x_i^2 - \sum_{i=1}^N x_i $$ ```{code-cell} N = 1000 x = [model.add_variable() for _ in range(N)] def fast_expr(): expr = poi.ExprBuilder() for i in range(N): expr += 0.5 * x[i] * x[i] expr -= x[i] def slow_expr(): expr = 0 for i in range(N): expr += 0.5 * x[i] * x[i] expr -= x[i] ``` ```{code-cell} %time fast_expr() ``` ```{code-cell} %time slow_expr() ``` ## Pretty print expression If the names of variables are specified, We can use the `pprint` method to print the expression in a human-readable format: ```{code-cell} x = model.add_variable(name="x") y = model.add_variable(name="y") expr = x * x + 2 * x * y + y * y model.pprint(expr) ``` ## Value of expression We can use the `get_value` method to get the value of an expression after optimization: ```python expr = x*y + x*x expr_value = model.get_value(expr) ``` ================================================ FILE: docs/source/faq.md ================================================ # Frequently Asked Questions ## How to suppress the output of the optimizer? There are two kinds of output that you may want to suppress: 1. The log of optimization process. 2. The default license message printed when initializing the optimizer. For example, when using Gurobi, the message is `Academic license - for non-commercial use only - expires yyyy-mm-dd`. Normally we only want to suppress the log of optimization process, you can use `model.set_model_attribute(poi.ModelAttribute.Silent, True)` to disable the output. For example: ```python import pyoptinterface as poi from pyoptinterface import gurobi model = gurobi.Model() model.set_model_attribute(poi.ModelAttribute.Silent, True) ``` Suppressing the default license message is a bit tricky and solver-specific. For Gurobi, you can use the following code: ```python import pyoptinterface as poi from pyoptinterface import gurobi env = gurobi.Env(empty=True) env.set_raw_parameter("OutputFlag", 0) env.start() model = gurobi.Model(env) ``` ## How to add linear constraints in matrix form like $Ax \leq b$? In YALMIP, you can use the matrix form $Ax \leq b$ to add linear constraints, which is quite convenient. In PyOptInterface, you can use [`model.add_m_linear_constraints`]() to add linear constraints in matrix form. ## Will PyOptInterface support new optimizers in the future? In short, no, there are no plans to support new optimizers. Supporting a new optimizer is not a trivial task, as it requires a lot of work to implement, test and maintain the interface. Basically, a new optimizer should satisfy the following criteria to be considered for support: - Actively maintained - Good performance (open source or commercial) - Not difficult to acquire an academic license - Have well-defined C/C++ API Support for a new optimizer will only happen if one or more of the following conditions are met: - I am personally interested in the optimizer and plan to use it in my research, so I am willing to invest time in implementing it. - Someone steps up to implement and maintain the interface for the optimizer in PyOptInterface. - External funding or sponsorship become available to support the development and maintenance of the optimizer interface. Finally, we are always open to external contributions. If you have a specific optimizer in mind and plan to implement it, feel free to open an issue on our GitHub repository to discuss it. ================================================ FILE: docs/source/getting_started.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Getting Started ## Installation PyOptInterface is available on PyPI. You can install it via pip: ``` pip install pyoptinterface ``` After installation, you can import the package in Python console: ```python import pyoptinterface as poi ``` PyOptInterface has no dependencies other than Python itself. However, to use it with a specific optimizer, you need to install the corresponding optimizer manually. The details can be found on [the configurations of optimizers](https://metab0t.github.io/PyOptInterface/getting-started.html). In order to provide out-of-the-box support for open source optimizers (currently we support [HiGHS](https://github.com/ERGO-Code/HiGHS)), PyOptInterface can also be installed with pre-built optimizers. You can install them via pip: ``` pip install pyoptinterface[highs] ``` It will install a full-featured binary version of HiGHS optimizer via [highsbox](http://github.com/metab0t/highsbox), which can be used with PyOptInterface. In order to use nonlinear programming solvers (currently we only support IPOPT), you should install extra dependencies like: ``` pip install pyoptinterface[nlp] ``` It will install the [`llvmlite`](https://github.com/numba/llvmlite) and [`tccbox`](https://github.com/metab0t/tccbox) package as the JIT compilers required by nonlinear programming. We will introduce how to set up the optimizers to use with PyOptInterface in this page. ## Setup of optimizers PyOptInterface uses the `Dynamic Loading` technique to load the dynamic library of optimizers at runtime, so the optimizer it uses can be changed manually without recompiling the code. The set up of optimizers includes two approaches: 1. Automatic detection of the installation directory of the optimizers. 2. Manually specifying the path of the dynamic library of optimizer. We will introduce the automatic detection and manual configuration in details ## Automatic detection of the installation directory of the optimizers The automatic detection includes three steps: 1. Environment variable set by the installer of optimizer 2. The official Python binding of the optimizer (if available) 3. Search directly in the system loadable path (e.g. `/usr/lib`, `/usr/local/lib` on Linux or PATH on Windows) For the 3rd step, we want to explain more for novices. For example, in order to make the dynamic library of HiGHS `highs.dll`/`libhighs.so`/`libhighs.dylib` loadable, we need to put it in a loadable path recognized by the operating system. The typical loadable path on Linux is `/usr/lib`, `/usr/local/lib`, and the typical loadable path on Windows is `PATH`. On Linux, we use the `LD_LIBRARY_PATH` environment variable to specify the dynamic library path. On macOS, we use the `DYLD_LIBRARY_PATH` environment variable to specify the dynamic library path. If the dynamic library is in path `C:\highs\lib`/`/opt/highs/lib`: - Windows: `set PATH=%PATH%;C:\highs\lib` - Linux: `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/highs/lib` - macOS: `export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/opt/highs/lib` The typical paths where the dynamic library of optimizers are located are as follows: :::{list-table} :header-rows: 1 * - Optimizer - Windows - Linux - macOS(ARM) - macOS(Intel) * - Gurobi - `C:\gurobi1101\win64\bin` - `/opt/gurobi1100/linux64/lib` - `/opt/gurobi1100/macos_universal2/lib` - `/opt/gurobi1100/macos_universal2/lib` * - Xpress - `C:\xpressmp\bin` - `/opt/xpressmp/lib` - `/Applications/FICO Xpress/xpressmp/lib` - `/Applications/FICO Xpress/xpressmp/lib` * - COPT - `C:\Program Files\copt80\bin` - `/opt/copt80/lib` - `/opt/copt80/lib` - `/opt/copt80/lib` * - Mosek - `C:\Program Files\Mosek\10.2\tools\platform\win64x86\bin` - `/opt/mosek/10.2/tools/platform/linux64x86/bin` - `/opt/mosek/10.2/tools/platform/osxaarch64/bin` - `/opt/mosek/10.2/tools/platform/osx64x86/bin` * - HiGHS - `D:\highs\bin` - `/opt/highs/lib` - `/opt/highs/lib` - `/opt/highs/lib` * - KNITRO - `C:\Program Files\Artelys\KNITRO 15.1\lib` - `/opt/knitro/15.1/lib` - `/opt/knitro/15.1/lib` - `` ::: ### Gurobi The currently supported version is **13.0.0**. Other versions may work but are not tested. For Gurobi, the automatic detection looks for the following things in order: 1. The environment variable `GUROBI_HOME` set by the installer of Gurobi 2. The installation of `gurobipy` 3. `gurobi130.dll`/`libgurobi130.so`/`libgurobi130.dylib` in the system loadable path ### Xpress The currently supported version is **9.8**. Other versions may work but are not tested. For Xpress, the automatic detection looks for the following things in order: 1. The environment variable `XPRESSDIR` set by the installer of Xpress 2. `xprs.dll`/`libxprs.so`/`libxprs.dylib` int the system loadable path ### COPT The currently supported version is **8.0.x**. Other versions may work but are not tested. For COPT, the automatic detection looks for the following things in order: 1. The environment variable `COPT_HOME` set by the installer of COPT 2. `copt.dll`/`libcopt.so`/`libcopt.dylib` in the system loadable path ### Mosek The currently supported version is **10.2.x**. Other versions may work but are not tested. For Mosek, the automatic detection looks for the following things in order: 1. The environment variable `MOSEK_10_2_BINDIR` set by the installer of Mosek 2. The installation of `mosek` PyPI package 3. `mosek64_10_2.dll`/`libmosek64.so`/`libmosek64.dylib` in the system loadable path ### HiGHS The currently supported version is **1.12.x**. Other versions may work but are not tested. For HiGHS, the automatic detection looks for the following things in order: 1. The environment variable `HiGHS_HOME` set by the user 2. The installation of `highsbox` PyPI package (recommended) 3. `highs.dll`/`libhighs.so`/`libhighs.dylib` in the system For HiGHS, we recommend installing the `highsbox` PyPI package, which provides a full-featured binary version of HiGHS optimizer for you. ### Ipopt The currently supported version is **3.14.x**. Other versions may work but are not tested. For Ipopt, the automatic detection looks for the following things in order: 1. `ipopt.dll`/`libipopt.so`/`libipopt.dylib` in the system, we also look for `ipopt-3.dll`/`libipopt.dll`/`libipopt-3.dll` on Windows. We recommend using the official binary from [GitHub](https://github.com/coin-or/Ipopt/releases) if you work on Windows, since compiling Ipopt on Windows from source is not an easy task. ### KNITRO The currently supported version is **15.1.x**. Other versions may work but are not tested. For KNITRO, the automatic detection looks for the following things in order: 1. The environment variable `KNITRODIR` set by the installer of KNITRO 2. `knitro.dll`/`libknitro.so`/`libknitro.dylib` in the system loadable path 3. The installation of `knitro` PyPI package. KNITRO dropped support for MacOS intel since version 15.0, so using KNITRO on MacOS intel is not supported. ## Manually specifying the path of the dynamic library of optimizer If the automatic detection fails or you want to use the optimizer in a customized location, you can manually specify the path of the dynamic library of the optimizer. We take HiGHS as an example. Whether the optimizer has been successfully loaded can be checked via the following code: ```python from pyoptinterface import highs print(highs.is_library_loaded()) ``` If the optimizer has not been successfully loaded, you can manually specify the path of the dynamic library of the optimizer via the following code: ```python ret = highs.load_library("path/to/libhighs.so") print(f"Loading from custom path manually: {ret}") ``` The `load_library` function returns `True` if the library is successfully loaded, otherwise it returns `False`. If you want to revert to use the automatic detection, you can call the `autoload_library` function: ```python ret = highs.autoload_library() print(f"Loading from automatically detected location: {ret}") ``` For other optimizers, just replace `highs` with the corresponding optimizer name like `gurobi`, `xpress`, `copt`, `mosek`. The typical paths where the dynamic library of optimizers are located are as follows: :::{list-table} :header-rows: 1 * - Optimizer - Windows - Linux - macOS(ARM) - macOS(Intel) * - Gurobi - `C:\gurobi1101\win64\bin\gurobi110.dll` - `/opt/gurobi1100/linux64/lib/libgurobi110.so` - `/opt/gurobi1100/macos_universal2/lib/libgurobi110.dylib` - `/opt/gurobi1100/macos_universal2/lib/libgurobi110.dylib` * - COPT - `C:\Program Files\copt71\bin\copt.dll` - `/opt/copt72/lib/libcopt.so` - `/opt/copt72/lib/libcopt.dylib` - `/opt/copt72/lib/libcopt.dylib` * - Xpress - `C:\xpressmp\bin\xprs.dll` - `/opt/xpressmp/lib/libxprs.so` - `/Applications/FICO Xpress/xpressmp/lib/libxprs.dylib` - `/Applications/FICO Xpress/xpressmp/lib/libxprs.dylib` * - Mosek - `C:\Program Files\Mosek\10.2\tools\platform\win64x86\bin\mosek64_10_1.dll` - `/opt/mosek/10.2/tools/platform/linux64x86/bin/libmosek64.so` - `/opt/mosek/10.2/tools/platform/osxaarch64/bin/libmosek64.dylib` - `/opt/mosek/10.2/tools/platform/osx64x86/bin/libmosek64.dylib` * - HiGHS - `D:\highs\bin\highs.dll` - `/opt/highs/lib/libhighs.so` - `/opt/highs/lib/libhighs.dylib` - `/opt/highs/lib/libhighs.dylib` * - KNITRO - `C:\Program Files\Artelys\KNITRO 15.1\lib\knitro.dll` - `/opt/knitro/15.1/lib/libknitro.so` - `/opt/knitro/15.1/lib/libknitro.dylib` - `` ::: ## Let's build a simple model and solve it After setting up the optimizers, we can build a simple model and solve it. As the first step, we will solve the following simple Quadratic Programming (QP) problem: ```{math} \min \quad & x^{2}_{1} + 2x^{2}_{2} \\ \textrm{s.t.} \quad & x_{1} + x_{2} = 1 \\ \quad & x_{1}, x_{2} \geq 0 ``` First, we need to create a model object: ```{code-cell} import pyoptinterface as poi from pyoptinterface import highs # from pyoptinterface import copt, gurobi, xpress, mosek (if you want to use other optimizers) model = highs.Model() ``` Then, we need to add variables to the model: ```{code-cell} x1 = model.add_variable(lb=0, name="x1") x2 = model.add_variable(lb=0, name="x2") ``` The `lb` argument specifies the lower bound of the variable. It is optional and defaults to $-\infty$. The `name` argument is optional and can be used to specify the name of the variable. Then, we need to add constraints to the model: ```{code-cell} con = model.add_linear_constraint(x1+x2, poi.ConstraintSense.Equal, 1, name="con") ``` `model.add_linear_constraint` adds a linear constraint to the model. - The first argument `x1+x2` is the left-hand side of the constraint. - The second argument is the sense of the constraint. It can be `poi.ConstraintSense.Equal`, `poi.ConstraintSense.LessEqual` or `poi.ConstraintSense.GreaterEqual` which can also be written as `poi.Eq`, `poi.Leq`, and `poi.Geq`. - The third argument is the right-hand side of the constraint. It must be a constant. - The fourth argument is optional and can be used to specify the name of the constraint. Finally, we need to set the objective function and solve the model: ```{code-cell} obj = x1*x1 + 2*x2*x2 model.set_objective(obj, poi.ObjectiveSense.Minimize) ``` The model can be solved via: ```{code-cell} model.optimize() ``` The HiGHS optimizer will be invoked to solve the model and writes the log to the console. We can query the status of the model via: ```{code-cell} model.get_model_attribute(poi.ModelAttribute.TerminationStatus) ``` The solution of the model can be queried via: ```{code-cell} print("x1 = ", model.get_value(x1)) print("x2 = ", model.get_value(x2)) print("obj = ", model.get_value(obj)) ``` ================================================ FILE: docs/source/gurobi.md ================================================ # Gurobi ## Initial setup ```python from pyoptinterface import gurobi model = gurobi.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#gurobi) to set up the optimizer correctly. If you want to manage the license of Gurobi manually, you can create a `gurobi.Env` object and pass it to the constructor of the `gurobi.Model` object, otherwise we will initialize an implicit global `gurobi.Env` object automatically and use it. ```python env = gurobi.Env() model = gurobi.Model(env) ``` For example, you can set the parameter of the `gurobi.Env` object to choose the licensing behavior. ```python env = gurobi.Env(empty=True) env.set_raw_parameter("ComputeServer", "myserver1:32123") env.set_raw_parameter("ServerPassword", "pass") env.start() model = gurobi.Model(env) ``` For users who want to release the license immediately after the optimization, you can call the `close` method of all models created and the `gurobi.Env` object. It applies to other commercial solvers supported as well. ```python env = gurobi.Env() model = gurobi.Model(env) # do something with the model model.close() env.close() ``` ## The capability of `gurobi.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ✅ * - - ✅ * - - ✅ ::: ```{include} attribute/gurobi.md ``` ## Solver-specific operations ### Parameter For [solver-specific parameters](https://docs.gurobi.com/projects/optimizer/en/current/reference/parameters.html), we provide `get_raw_parameter` and `set_raw_parameter` methods to get and set the parameters. ```python model = gurobi.Model() # get the value of the parameter value = model.get_raw_parameter("TimeLimit") # set the value of the parameter model.set_raw_parameter("TimeLimit", 10.0) ``` ### Attribute Gurobi supports a lot of [attributes](https://docs.gurobi.com/projects/optimizer/en/current/reference/attributes.html) for the model, variable, and constraint. We provide methods to get or set the value of the attribute. - Model attribute: `model.get_model_raw_attribute(name: str)` and `model.set_model_raw_attribute(name: str, value: Any)` - Variable attribute: `model.get_variable_raw_attribute(variable, name: str)` and `model.set_variable_raw_attribute(variable, name: str, value: Any)` - Constraint attribute: `model.get_constraint_raw_attribute(constraint, name: str)` and `model.set_constraint_raw_attribute(constraint, name: str, value: Any)` We also provide `gurobi.GRB` to contain all the constants in `gurobipy.GRB`. For model status: ```python status = model.get_model_raw_attribute(gurobi.GRB.Attr.Status) if status == gurobi.GRB.OPTIMAL: ... elif status == gurobi.GRB.INFEASIBLE: ... ``` For reduced cost of a variable: ```python rc = model.get_variable_raw_attribute(variable, gurobi.GRB.Attr.RC) ``` For right-hand side value of a constraint: ```python rhs = model.get_constraint_raw_attribute(constraint, gurobi.GRB.Attr.RHS) ``` ================================================ FILE: docs/source/highs.md ================================================ # HiGHS ## Initial setup ```python from pyoptinterface import highs model = highs.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#highs) to set up the optimizer correctly. ## The capability of `highs.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ❌ * - - ❌ * - - ❌ ::: ```{include} attribute/highs.md ``` ## Solver-specific operations ### Parameter For [solver-specific parameters](https://ergo-code.github.io/HiGHS/stable/options/definitions/), we provide `get_raw_parameter` and `set_raw_parameter` methods to get and set the parameters. ```python model = highs.Model() # get the value of the parameter value = model.get_raw_parameter("time_limit") # set the value of the parameter model.set_raw_parameter("time_limit", 10.0) ``` ### Information HiGHS provides [information](https://ergo-code.github.io/HiGHS/stable/structures/classes/HighsInfo/) for the model. We provide `model.get_raw_information(name: str)` method to access the value of information. ================================================ FILE: docs/source/index.md ================================================ ```{include} ../../README.md ``` ## Contents ```{toctree} :maxdepth: 2 :titlesonly: :caption: User Guide getting_started.md benchmark.md faq.md model.md variable.md expression.md constraint.md objective.md nonlinear.md container.md numpy.md structure.md common_model_interface.md infeasibility.md callback.md gurobi.md xpress.md copt.md mosek.md highs.md ipopt.md knitro.md changelog.md ``` ```{toctree} :maxdepth: 2 :titlesonly: :caption: Examples examples/economic_dispatch.md examples/optimal_power_flow.md examples/optimal_control_rocket.md ``` ```{toctree} :maxdepth: 2 :titlesonly: :caption: Advanced develop.md ``` ```{toctree} :maxdepth: 1 :titlesonly: :caption: API docs api/pyoptinterface.rst ``` ## Indices and tables - {ref}`genindex` - {ref}`modindex` - {ref}`search` ================================================ FILE: docs/source/infeasibility.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Infeasibility Analysis The optimization model is not ways feasible, and the optimizer may tell us some information about the infeasibility to diagnose the problem. There are two ways to handle the infeasibilities: - Find the IIS (Irreducible Infeasible Set) to identify the minimal set of constraints that cause the infeasibility. - Relax the constraints and solve a weaker problem to find out which constraints are violated and how much. PyOptInterface currently supports the first method to find the IIS (only with Gurobi, Xpress, and COPT). The following code snippet shows how to find the IIS of an infeasible model: ```{code-cell} import pyoptinterface as poi from pyoptinterface import copt model = copt.Model() x = model.add_variable(lb=0.0, name="x") y = model.add_variable(lb=0.0, name="y") con1 = model.add_linear_constraint(x + y, poi.Geq, 5.0) con2 = model.add_linear_constraint(x + 2 * y, poi.Leq, 1.0) model.set_objective(x) model.computeIIS() con1_iis = model.get_constraint_attribute(con1, poi.ConstraintAttribute.IIS) con2_iis = model.get_constraint_attribute(con2, poi.ConstraintAttribute.IIS) print(f"Constraint 1 IIS: {con1_iis}") print(f"Constraint 2 IIS: {con2_iis}") ``` This code snippet creates an infeasible model with two constraints and finds the IIS of the model. Obviously, the constraints are contradictory because `x + 2 * y <= 1` and `x + y >= 5` cannot be satisfied at the same time when `x` and `y` are non-negative. The optimizer will detect that the model is infeasible and return the IIS, which is the set of constraints that cause the infeasibility. We can query whether a constraint is in the IIS by calling `get_constraint_attribute` with the `ConstraintAttribute.IIS` attribute. Sometimes, the bounds of the variables are not consistent with the constraints, and we need to query the IIS of the bounds of variables by calling `get_variable_attribute` with the `VariableAttribute.IISLowerBound` and `VariableAttribute.IISUpperBound` attributes. The following code snippet shows how to tell if the bounds of a variable are in the IIS: ```{code-cell} model = copt.Model() x = model.add_variable(lb=0.0, ub=2.0, name="x") y = model.add_variable(lb=0.0, ub=3.0, name="y") con1 = model.add_linear_constraint(x + y, poi.Geq, 6.0) model.set_objective(x) model.computeIIS() con1_iis = model.get_constraint_attribute(con1, poi.ConstraintAttribute.IIS) x_lb_iis = model.get_variable_attribute(x, poi.VariableAttribute.IISLowerBound) x_ub_iis = model.get_variable_attribute(x, poi.VariableAttribute.IISUpperBound) y_lb_iis = model.get_variable_attribute(y, poi.VariableAttribute.IISLowerBound) y_ub_iis = model.get_variable_attribute(y, poi.VariableAttribute.IISUpperBound) print(f"Constraint 1 IIS: {con1_iis}") print(f"Variable x lower bound IIS: {x_lb_iis}") print(f"Variable x upper bound IIS: {x_ub_iis}") print(f"Variable y lower bound IIS: {y_lb_iis}") print(f"Variable y upper bound IIS: {y_ub_iis}") ``` ================================================ FILE: docs/source/ipopt.md ================================================ # Ipopt ## Initial setup ```python from pyoptinterface import ipopt model = ipopt.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#ipopt) to set up the optimizer correctly. :::{attention} Due to the internal API design of Ipopt, the `ipopt.Model` lacks some features compared to other solvers. Some of these restrictions may be lifted in the future. - It does not support `delete_variable` and `delete_constraint` methods. - It does not support incremental modification of linear constraint like `set_normalized_rhs` and `set_normalized_coefficient`. - It only supports to minimize the objective function. If you want to maximize the objective function, you need to multiply the objective function by -1 manually. ::: ## The capability of `ipopt.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ✅ * - - ❌ * - - ❌ * - - ✅ ::: ```{include} attribute/ipopt.md ``` ## Solver-specific operations ### Parameter For [solver-specific parameters](https://coin-or.github.io/Ipopt/OPTIONS.html), we provide `set_raw_parameter` methods to get and set the parameters. ```python model = ipopt.Model() # set the value of the parameter model.set_raw_parameter("tol", 1e-5) model.set_raw_parameter("max_iter", 200) # For HSL library model.set_raw_parameter("hsllib", "/path/to/libhsl.so") model.set_raw_parameter("linear_solver", "ma27") ``` ## JIT compiler used by Ipopt interface The interface of Ipopt uses the JIT compiler to compile the nonlinear objective function, constraints and their derivatives. We have two implementations of JIT based on `llvmlite` and `tccbox`(Tiny C Compiler). The default JIT compiler is `llvmlite` and we advise you to use it for better performance brought by optimization capability of LLVM. If you want to use `tccbox`, you can specify `jit="C"` when creating the `ipopt.Model` object. ```python model = ipopt.Model() # equivalent to model = ipopt.Model(jit="LLVM") # If you want to use tccbox model = ipopt.Model(jit="C") ``` ================================================ FILE: docs/source/knitro.md ================================================ # KNITRO ## Initial setup ```python from pyoptinterface import knitro model = knitro.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#knitro) to set up the optimizer correctly. If you want to manage the license of KNITRO manually, you can create a `knitro.Env` object and pass it to the constructor of the `knitro.Model` object, otherwise a check of the license will be performed when initializing the `knitro.Model` object. ```python env = knitro.Env() model = knitro.Model(env) ``` For users who want to release the license immediately after the optimization, you can call the `close` method of all models created and the `knitro.Env` object. ```python env = knitro.Env() model = knitro.Model(env) # do something with the model model.close() env.close() ``` ## The capability of `knitro.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ✅ * - - ✅ * - - ❌ * - - ❌ * - - ✅ ::: ```{include} attribute/knitro.md ``` ## Solver-specific operations ### Parameters For [solver-specific parameters](https://www.artelys.com/app/docs/knitro/2_userGuide/knitroOptions.html), we provide `get_raw_parameter` and `set_raw_parameter` methods to get and set the parameters. ```python model = knitro.Model() # Set the value of a parameter by name model.set_raw_parameter("nlp_algorithm", 1) model.set_raw_parameter("feastol", 1e-8) model.set_raw_parameter("opttol", 1e-8) # Set the value of a parameter by ID (using knitro.KN constants) model.set_raw_parameter(knitro.KN.PARAM_MAXIT, 1000) ``` We also provide `knitro.KN` to contain common constants from the KNITRO C API. ```python # Using constants for parameter IDs model.set_raw_parameter(knitro.KN.PARAM_FEASTOL, 1e-6) # Algorithm selection model.set_raw_parameter(knitro.KN.PARAM_NLP_ALGORITHM, knitro.KN.NLP_ALG_BAR_DIRECT) # Output level model.set_raw_parameter(knitro.KN.PARAM_OUTLEV, knitro.KN.OUTLEV_ITER) # MIP parameters model.set_raw_parameter(knitro.KN.PARAM_MIP_METHOD, knitro.KN.MIP_METHOD_BB) model.set_raw_parameter(knitro.KN.PARAM_MIP_OPTGAPREL, 1e-4) ``` ### Variable and Constraint Properties Common variable and constraint properties are provided through PyOptInterface dedicated methods: **Variable methods:** - **Bounds**: `set_variable_lb`, `get_variable_lb`, `set_variable_ub`, `get_variable_ub` - **Type and name**: `set_variable_name`, `get_variable_name`, `set_variable_domain` - **Starting point**: `set_variable_start` - **Solution values**: `get_value`, `get_variable_rc` **Constraint methods:** - **Name**: `set_constraint_name`, `get_constraint_name` - **Solution values**: `get_constraint_primal`, `get_constraint_dual` **Usage examples:** Variable properties: ```python # Bounds model.set_variable_lb(variable, 0.0) lb = model.get_variable_lb(variable) model.set_variable_ub(variable, 10.0) ub = model.get_variable_ub(variable) # Type and name model.set_variable_name(variable, "x") name = model.get_variable_name(variable) # Starting point model.set_variable_start(variable, 1.0) # Solution values value = model.get_value(variable) rc = model.get_variable_rc(variable) ``` Constraint properties: ```python # Name model.set_constraint_name(constraint, "c1") name = model.get_constraint_name(constraint) # Solution values primal = model.get_constraint_primal(constraint) dual = model.get_constraint_dual(constraint) ``` ## Support for KNITRO callbacks Unfortunately, KNITRO's callback interface is not supported in PyOptInterface at the moment. ================================================ FILE: docs/source/model.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Model Model is the central class of the PyOptInterface package. It provides the interface to the solver and the user. The user can add variables, constraints and the objective function to the model. The model can be solved and the solution can be queried. In this document we will only discuss the common interface of the model. For solver-specific interface, please refer to the documentation of the corresponding solver. ## Create a model A model is a concrete instance tied to a specific solver. To create a model, we need to import the corresponding module and call the constructor of the model class. For example, to create a HiGHS model, we can do: ```{code-cell} import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() ``` You can replace `highs` with the name of the solver you want to use. The available solvers includes `copt`, `gurobi`, `highs` and `mosek`. Most commercial solvers require a `Environment`-like object to be initialized before creating a model in order to manage the license. By default, PyOptInterface creates the global environment for each solver. If you want to create a model in a specific environment, you can pass the environment object to the constructor of the model class. The details can be found on the documentation of the corresponding optimizer: [Gurobi](gurobi.md), [HiGHS](highs.md), [COPT](copt.md), [Xpress](xpress.md), [MOSEK](mosek.md). ```python env = gurobi.Env() model = gurobi.Model(env) ``` ## Inspect and customize the model We can query and modify the parameters of the model to manipulate the behavior of the underlying solver. Like the design in `JuMP.jl`, we define a small subset of parameters that are common to all solvers. They are defined as [pyoptinterface.ModelAttribute](#pyoptinterface.ModelAttribute) enum class. The meanings of these standard attributes are the same as [Model attributes](https://jump.dev/JuMP.jl/stable/moi/reference/models/#Model-attributes) and [Optimizer attributes](https://jump.dev/JuMP.jl/stable/moi/reference/models/#Optimizer-attributes) in MathOptInterface.jl. :::{list-table} **Standard [model attributes](#pyoptinterface.ModelAttribute)** :header-rows: 1 :widths: 20 20 * - Attribute name - Type * - Name - str * - ObjectiveSense - [ObjectiveSense](project:#pyoptinterface.ObjectiveSense) * - DualStatus - [ResultStatusCode](project:#pyoptinterface.ResultStatusCode) * - PrimalStatus - [ResultStatusCode](project:#pyoptinterface.ResultStatusCode) * - RawStatusString - str * - TerminationStatus - [TerminationStatusCode](project:#pyoptinterface.TerminationStatusCode) * - BarrierIterations - int * - DualObjectiveValue - float * - NodeCount - int * - NumberOfThreads - int * - ObjectiveBound - float * - ObjectiveValue - float * - RelativeGap - float * - Silent - float * - SimplexIterations - int * - SolverName - str * - SolverVersion - str * - SolveTimeSec - float * - TimeLimitSec - float ::: We can set the value of a parameter by calling the `set_model_attribute` method of the model: ```{code-cell} # suppress the output of the solver model.set_model_attribute(poi.ModelAttribute.Silent, False) # set the time limit to 10 seconds model.set_model_attribute(poi.ModelAttribute.TimeLimitSec, 10.0) ``` The value of parameter can be queried by calling the `get_model_attribute` method of the model. For example, we build and solve a simple quadratic programming model, then query the objective value of the model: ```{code-cell} x = model.add_variables(range(2), lb=0.0, ub=1.0) model.add_linear_constraint(x[0] + x[1], poi.Eq, 1.0) model.set_objective(x[0]*x[0] + x[1]*x[1], sense=poi.ObjectiveSense.Minimize) model.optimize() objval = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) print(f"Objective value: {objval}") ``` Besides the standard attributes, we can also set/get the solver-specific attributes by calling the `set_raw_parameter`/`get_raw_parameter` method of the model: ```python # Gurobi model.set_raw_parameter("OutputFlag", 0) # COPT model.set_raw_parameter("Presolve", 0) # Xpress model.set_raw_control("XPRS_OUTPUTLOG", 0) # MOSEK model.set_raw_parameter("MSK_IPAR_INTPNT_BASIS", 0) ``` ## Solve the model We can solve the model by calling the `optimize` method of the model: ```python model.optimize() ``` ## Query the solution We can query the termination status of the model after optimization by query the `TerminationStatus` attribute of the model: ```{code-cell} # tell if the optimizer obtains the optimal solution termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMAL ``` For value of variables and expressions, we can use `get_value` method of the model: ```{code-cell} x0_value = model.get_value(x[0]) expr_value = model.get_value(x[0]*x[0]) print(f"x[0] = {x0_value}") print(f"x[0]^2 = {expr_value}") ``` ## Write the model to file The optimization model can be written to file in LP, MPS or other formats. The `write` method of the model can be used to write the model to file: ```{code-cell} model.write("model.lp") ``` The file format is determined by the file extension. Because we use the native IO procedure of the optimizer, their supported file formats and the content of output files may vary. Please refer to the documentation of the corresponding optimizer for more details. - COPT: [Doc](https://guide.coap.online/copt/en-doc/fileformats.html) - Gurobi: [Doc](https://www.gurobi.com/documentation/current/refman/c_write.html) - HiGHS: [Doc](https://ergo-code.github.io/HiGHS/stable/interfaces/c/#Highs_writeModel-Tuple{Any,%20Any}) - Mosek: [Doc](https://docs.mosek.com/latest/capi/supported-file-formats.html) - Xpress: [`.lp`, `.mps`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.writeProb.html), [`.svf`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.saveAs.html), [`.bss`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.writeBasis.html), [`.asc`, `.hdr`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.writeSol.html), [`.sol`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.writeBinSol.html), [`.prt`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.writePrtSol.html), [`.slx`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.writeSlxSol.html), ================================================ FILE: docs/source/mosek.md ================================================ # Mosek ## Initial setup ```python from pyoptinterface import mosek model = mosek.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#mosek) to set up the optimizer correctly. If you want to manage the license of Mosek manually, you can create a `mosek.Env` object and pass it to the constructor of the `mosek.Model` object, otherwise we will initialize an implicit global `mosek.Env` object automatically and use it. ```python env = mosek.Env() model = mosek.Model(env) ``` ## The capability of `mosek.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ✅ * - - ✅ * - - ✅ * - - ❌ ::: ```{include} attribute/mosek.md ``` ## Solver-specific operations ### Parameter For [solver-specific parameters](https://docs.mosek.com/latest/capi/param-groups.html), we provide `get_raw_parameter` and `set_raw_parameter` methods to get and set the parameters. ```python model = mosek.Model() # get the value of the parameter value = model.get_raw_parameter("MSK_DPAR_OPTIMIZER_MAX_TIME") # set the value of the parameter model.set_raw_parameter("MSK_DPAR_OPTIMIZER_MAX_TIME", 10.0) ``` ### Information Mosek provides [information](https://docs.mosek.com/latest/capi/solver-infitems.html) for the model. We provide `model.get_raw_information(name: str)` method to access the value of information. ================================================ FILE: docs/source/nonlinear.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Nonlinear Programming ## Introduction Compared with the linear and quadratic expressions and objectives we have discussed in the previous sections, nonlinear programming is more general and can handle a wider range of problems. In this section, we will introduce how to use PyOptInterface to formulate and solve general nonlinear programming problems. :::{note} Before trying out the code snippets, please ensure that you have completed the installation of PyOptInterface with correct dependencies via `pip install pyoptinterface[nlp]` and solvers that support nonlinear programming (IPOPT, COPT, Xpress, Gurobi, KNITRO) as described in the [Getting Started](getting_started.md) section. ::: ## Construct nonlinear expressions Nonlinear expressions are more complex than linear and quadratic expressions, and they can include various nonlinear functions such as trigonometric functions, exponential functions, logarithmic functions, etc. In PyOptInterface, we must declare a `nl.graph()` context to construct nonlinear expressions. The `nl.graph()` context is used to trace the computational graph of the nonlinear expression, which allows PyOptInterface to automatically differentiate the expression and calculate the gradients and Hessians. ```{code-cell} import pyoptinterface as poi from pyoptinterface import nl, ipopt model = ipopt.Model() x = model.add_variable(name="x") y = model.add_variable(name="y") with nl.graph(): z = nl.exp(x) * nl.pow(y, 2) model.add_nl_constraint(z <= 10.0) ``` In the code snippet above, we first import the `nl` module, which contains the nonlinear programming utilities including a set of nonlinear functions that can be used in PyOptInterface. Then, we create an `ipopt.Model` object to represent the optimization problem. We then add two variables `x` and `y` to the model. After that, we enter the `nl.graph()` context, where we can construct nonlinear expressions using the functions provided by the `nl` module. In this case, we define a nonlinear expression `z = exp(x) * pow(y, 2)` and add a nonlinear constraint `z <= 10.0` to the model. In the `nl.graph()` context, we can use various nonlinear functions provided by the `nl` module to construct nonlinear expressions. The functions are designed to be similar to the mathematical notation, making it easy to read and write nonlinear expressions. PyOptInterface currently supports the following nonlinear operators: :::{list-table} **Unary functions** :header-rows: 1 * - Operator - Example * - `-` (negation) - `y = -x` * - sin - `y = nl.sin(x)` * - cos - `y = nl.cos(x)` * - tan - `y = nl.tan(x)` * - asin - `y = nl.asin(x)` * - acos - `y = nl.acos(x)` * - atan - `y = nl.atan(x)` * - abs - `y = nl.abs(x)` * - sqrt - `y = nl.sqrt(x)` * - exp - `y = nl.exp(x)` * - log - `y = nl.log(x)` * - log10 - `y = nl.log10(x)` ::: :::{list-table} **Binary functions** :header-rows: 1 * - Operator - Example * - `+` - `z = x + y` * - `-` - `z = x - y` * - `*` - `z = x * y` * - `/` - `z = x / y` * - `**` - `z = x ** y` * - pow - `z = nl.pow(x, y)` ::: We can also use `nl.ifelse` to implement the conditional operator. For example, the following code snippet defines a absolute value function $f(x) = |x|$: ```{code-cell} def f(x): return nl.ifelse(x >= 0, x, -x) with nl.graph(): y = f(x) model.add_nl_constraint(y <= 2) ``` `nl.ifelse` accepts three arguments: a condition, a value when the condition is true, and a value when the condition is false. The function returns the value of the second argument if the condition is true; otherwise, it returns the value of the third argument. The condition variable must be a boolean variable, which can be obtained by comparing two variables using the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=`. Another interesting fact is that you can construct nonlinear expressions in the context of `nl.graph` that are prohibited outside the context. For example, you can construct $(x+2)^3$ in the following way: ```{code-cell} with nl.graph(): z = (x + 2) ** 3 # Illegal outside nl.graph context because it is a nonlinear expression # z = (x + 2) ** 3 # This will raise an error ``` ## Nonlinear constraints and objectives After constructing nonlinear expressions, we can use them in constraints and objectives. For example, the following code snippet defines a nonlinear programming problem. ```{code-cell} model = ipopt.Model() x = model.add_variable(lb = 0.0) y = model.add_variable(lb = 0.0) with nl.graph(): model.add_nl_constraint(x ** 4 + y ** 4 <= 4.0) model.add_nl_constraint(x * y >= 1.0) model.add_nl_objective(nl.exp(x) + nl.exp(y)) model.optimize() ``` ```{code-cell} x_value = model.get_value(x) y_value = model.get_value(y) print(f"x = {x_value}, y = {y_value}") ``` Nonlinear constraint can be declared by calling `add_nl_constraint` method. Like the linear and quadratic constraints, you can specify the sense/right-hand-side of constraint, use an interval of values to represent a two-sided constraint, or use a comparison operator like `<=`, `==`, or `>=` to create the constraint. ```{code-cell} # One-sided nonlinear constraint with nl.graph(): model.add_nl_constraint(x ** 2 + y ** 2 <= 1.0) # equivalent to model.add_nl_constraint(x ** 2 + y ** 2, poi.Leq, 1.0) # Two-sided nonlinear constraint with nl.graph(): model.add_nl_constraint(x ** 2 + y ** 2, (1.0, 2.0)) # equivalent to model.add_nl_constraint(x ** 2 + y ** 2, poi.Leq, 2.0) model.add_nl_constraint(x ** 2 + y ** 2, poi.Geq, 1.0) ``` Similarly, the nonlinear objective can be declared by calling `add_nl_objective` method. It is noteworthy that `add_nl_objective` only adds a nonlinear term to the objective and can be called multiple times to construct a sum of nonlinear terms as objective. ```{code-cell} # Nonlinear objective with nl.graph(): model.add_nl_objective(nl.sin(x) + nl.cos(y)) ``` Because the nonlinear expressions are captured by the current `nl.graph()` context, both `add_nl_constraint` and `add_nl_objective` methods must be called within the same `nl.graph()` context. If you try to call them outside the context, it will raise an error. Finally, we will use the well-known [Rosenbrock function](https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/simple_examples/#The-Rosenbrock-function) as another example: ```{code-cell} from pyoptinterface import nl, ipopt model = ipopt.Model() x = model.add_variable() y = model.add_variable() with nl.graph(): model.add_nl_objective((1 - x) ** 2 + 100 * (y - x ** 2) ** 2) model.optimize() ``` ```{code-cell} x_value = model.get_value(x) y_value = model.get_value(y) print(f"x = {x_value}, y = {y_value}") ``` ## Mixing nonlinear and linear/quadratic constraints together Introducing nonlinear functions does not prevent you from using the `add_linear_constraint`, `add_quadratic_constraint` methods. You can add both nonlinear constraints and linear/quadratic constraints in the same optimization problem. We will use the [clnlbeam problem](https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/simple_examples/#The-clnlbeam-problem) as example: ```{code-cell} from pyoptinterface import nl, ipopt import pyoptinterface as poi model = ipopt.Model() N = 1000 h = 1 / N alpha = 350 t = model.add_m_variables(N + 1, lb=-1.0, ub=1.0) x = model.add_m_variables(N + 1, lb=-0.05, ub=0.05) u = model.add_m_variables(N + 1) for i in range(N): with nl.graph(): model.add_nl_objective( 0.5 * h * (u[i] * u[i] + u[i + 1] * u[i + 1]) + 0.5 * alpha * h * (nl.cos(t[i]) + nl.cos(t[i + 1])) ) model.add_nl_constraint( x[i + 1] - x[i] - 0.5 * h * (nl.sin(t[i]) + nl.sin(t[i + 1])) == 0.0 ) model.add_linear_constraint( t[i + 1] - t[i] - 0.5 * h * u[i + 1] - 0.5 * h * u[i] == 0.0 ) model.optimize() ``` ```{code-cell} objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) print(f"Objective value: {objective_value}") ``` ## How to use `nl.graph` to capture the similar structure You might be wondering how to place the `nl.graph()` context manager correctly in your code. The key is to ensure that all nonlinear constraints and objectives that share the same structure are enclosed within the same `nl.graph()` context. As a rule of thumb, `nl.graph` should always be used inside the `for` loop instead of outside, so that each graph will have the same pattern and PyOptInterface can recognize the similar structures to accelerate the automatic differentiation process. This is particularly important when you have a large number of nonlinear constraints or objectives that share the same structure, as it can significantly improve the performance of the optimization process as discussed in our research paper [Accelerating Optimal Power Flow with Structure-aware Automatic Differentiation and Code Generation](https://ieeexplore.ieee.org/document/10721402). In the clnlbeam example above, we have placed the `nl.graph()` context inside the `for` loop, which allows us to capture the structure of the nonlinear constraints and objectives for each iteration. ## More complex examples In practice, the nonlinear constraints may have the same structure but with different parameters, we encourage you to read our [optimal power flow](examples/optimal_power_flow.md) and [optimal control](examples/optimal_control_rocket.md) examples to learn how to construct more complex nonlinear programming problems. ================================================ FILE: docs/source/numpy.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Matrix Modeling In the previous [container](container.md) section, we have introduced the `tupledict` container to store and manipulate multidimensional data. However, due to the Bring Your Own Container (BYOC) principle, variables and constraints in PyOptInterface can just simple Python objects that can be stored in Numpy `ndarray` directly as a multidimensional array, and you can enjoy the features of Numpy such like [fancy-indexing](https://numpy.org/doc/stable/user/basics.indexing.html) automatically. ## N-queen problem We will use N-queens problem as example to show how to use Numpy `ndarray` as container to store 2-dimensional variables and construct optimization model. Firstly, we import the necessary modules: ```{code-cell} import numpy as np import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() ``` Then we create a 2-dimensional variable `x` with shape $N \times N$ to represent the placement of queens. Each element of `x` is a binary variable that indicates whether a queen is placed at the corresponding position. We use `object` as the data type of `x` to store the binary variables. The following code snippet creates the variables: ```{code-cell} N = 8 x = np.empty((N, N), dtype=object) for i in range(N): for j in range(N): x[i, j] = model.add_variable(domain=poi.VariableDomain.Binary) ``` Next, we add the constraints to ensure that each row, each column has exact one queen, and each diagonal has at most one queen. ```{code-cell} for i in range(N): # Row and column model.add_linear_constraint(poi.quicksum(x[i, :]), poi.Eq, 1.0) model.add_linear_constraint(poi.quicksum(x[:, i]), poi.Eq, 1.0) for i in range(-N+1, N): # Diagonal model.add_linear_constraint(poi.quicksum(x.diagonal(i)), poi.Leq, 1.0) # Anti-diagonal model.add_linear_constraint(poi.quicksum(np.fliplr(x).diagonal(i)), poi.Leq, 1.0) ``` Finally, we solve the model. ```{code-cell} model.optimize() print("Termination status:", model.get_model_attribute(poi.ModelAttribute.TerminationStatus)) ``` The solution can be obtained and visualized by the following code: ```{code-cell} get_v = np.vectorize(lambda x: model.get_value(x)) x_value = get_v(x) print(x_value.astype(int)) ``` ## Built-in functions to add variables and constraints as Numpy `ndarray` Although you can construct the `ndarray` of variables and constraints manually, PyOptInterface provides built-in functions to simplify the process. The following code snippet shows how to use the built-in functions to add variables and constraints as Numpy `ndarray`: ```{code-cell} model = highs.Model() x = model.add_m_variables(N) A = np.eye(N) b_ub = np.ones(N) b_lb = np.ones(N) model.add_m_linear_constraints(A, x, poi.Leq, b_ub) model.add_m_linear_constraints(A, x, poi.Geq, b_lb) model.set_objective(poi.quicksum(x)) model.optimize() ``` Here we use two built-in functions `add_m_variables` and `add_m_linear_constraints` to add variables and constraints as Numpy `ndarray` respectively. The reference of these functions are listed in and . `add_m_variables` returns a `ndarray` of variables with the specified shape. `add_m_linear_constraints` adds multiple linear constraints to the model at once formulated as $Ax \le b$ or $Ax = b$ or $Ax \ge b$ where the matrix $A$ can be a dense `numpy.ndarray` or a sparse matrix `scipy.sparse.sparray`. ================================================ FILE: docs/source/objective.md ================================================ # Objective The objective is a function of the variables that the optimization algorithm seeks to minimize or maximize. Currently PyOptInterface supports the following types of objective functions: - Linear objective function - Quadratic objective function The objective function is typically minimized, but it can also be maximized. The objective function is defined as: ```python objective = x*x model.set_objective(objective, poi.ObjectiveSense.Minimize) ``` where `objective` is the handle of the objective function and `sense` is the optimization sense, which can be either `pyoptinterface.ObjectiveSense.Minimize` or `pyoptinterface.ObjectiveSense.Maximize`. The `set_objective` function can be called multiple times to change the objective function of the model. ## Modify objective function The linear part of the objective function can be modified by calling the `set_objective_coefficient` method of the model: ```python obj = 2*x + 3*y model.set_objective(obj, poi.ObjectiveSense.Minimize) # modify the coefficient of the variable x model.set_objective_coefficient(x, 5) ``` ================================================ FILE: docs/source/roadmap.md ================================================ ## Roadmap This is the roadmap for the project. It is a living document and will be updated as the project progresses. - Write model to lp, mps files - User-defined callbacks in optimization (like JuMP.jl, we can support lazy constraints and user-cuts to selected solvers) - Compute conflict constraints of infeasible model ================================================ FILE: docs/source/structure.md ================================================ --- file_format: mystnb kernelspec: name: python3 --- # Building Bigger Optimization Model In this document, we will introduce how to decompose the process of building a large optimization model into smaller parts and how to compose them together. This is a common practice in optimization modeling to make the code more readable and maintainable. Generally speaking, we need two important parts to build an optimization model: 1. A [model](model.md) object that represents the optimization problem and communicates with the underlying optimizer. Due to the lightweight design philosophy of PyOptInterface, the model object is just a thin wrapper around the optimizer API, and it does not store the optimization problem itself. The model object is responsible for creating and managing variables, constraints, and the objective function, as well as solving the optimization problem. 2. A dict-like container to store the variables and constraints. We recommend the [`types.SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) object as the container to store the variables and constraints. It is a simple way to store and manipulate the optimization problem in Python. You can use attribute access to get and set the variables and constraints. ```{code-cell} import types container = types.SimpleNamespace() container.x = 1 container.y = 2 print(container.x, container.y) ``` Thus, we can define a class to represent the optimization model and use the container to store the variables and constraints. ```{code-cell} import numpy as np import pyoptinterface as poi from pyoptinterface import highs class OptModel: def __init__(self): self.container = types.SimpleNamespace() self.model = highs.Model() ``` Then we can define small functions to declare the variables and constraints and add them to the model. We take the N-queens problem as an example. ```{code-cell} def declare_queen_variables(m:OptModel, N): model = m.model x = np.empty((N, N), dtype=object) for i in range(N): for j in range(N): x[i, j] = model.add_variable(domain=poi.VariableDomain.Binary) container = m.container container.x = x container.N = N def add_row_column_constraints(m:OptModel): container = m.container N = container.N x = container.x model = m.model for i in range(N): # Row and column model.add_linear_constraint(poi.quicksum(x[i, :]), poi.Eq, 1.0) model.add_linear_constraint(poi.quicksum(x[:, i]), poi.Eq, 1.0) def add_diagonal_constraints(m:OptModel): container = m.container N = container.N x = container.x model = m.model for i in range(-N+1, N): # Diagonal model.add_linear_constraint(poi.quicksum(x.diagonal(i)), poi.Leq, 1.0) # Anti-diagonal model.add_linear_constraint(poi.quicksum(np.fliplr(x).diagonal(i)), poi.Leq, 1.0) ``` Finally, we can compose these functions to build the optimization model and solve it. ```{code-cell} m = OptModel() N = 8 declare_queen_variables(m, N) add_row_column_constraints(m) add_diagonal_constraints(m) m.model.optimize() print("Termination status:", m.model.get_model_attribute(poi.ModelAttribute.TerminationStatus)) ``` ================================================ FILE: docs/source/variable.md ================================================ # Variable Variable represents a decision variable in the optimization problem. It can be created by calling the [`add_variable`](#model.add_variable) method of the model: ```python import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() x = model.add_variable(lb=0, ub=1.0, domain=poi.VariableDomain.Continuous, name="x") ``` ## Variable Attributes After a variable is created, we can query or modify its attributes. The following table lists the standard [variable attributes](#pyoptinterface.VariableAttribute): :::{list-table} **Standard [variable attributes](#pyoptinterface.VariableAttribute)** :header-rows: 1 :widths: 20 20 * - Attribute name - Type * - Name - str * - LowerBound - float * - UpperBound - float * - Domain - [VariableDomain](project:#pyoptinterface.VariableDomain) * - PrimalStart - float * - Value - float * - IISLowerBound - bool * - IISUpperBound - bool * - ReducedCost - float ::: ```python # set the lower bound of the variable model.set_variable_attribute(x, poi.VariableAttribute.LowerBound, 0.0) # set the upper bound of the variable model.set_variable_attribute(x, poi.VariableAttribute.UpperBound, 1.0) # For mixed-integer programming, we can set the initial value of the variable model.set_variable_attribute(x, poi.VariableAttribute.PrimalStart, 0.5) # get the value of the variable after optimization x_value = model.get_variable_attribute(x, poi.VariableAttribute.Value) ``` The most common operation is to set the bounds of the variable. We can use the [`set_variable_bounds`](#model.set_variable_bounds) method of the model to set the lower and upper bounds of the variable at the same time: ```python x = model.add_variable(name="x") model.set_variable_bounds(x, 0.0, 1.0) ``` ## Delete variable We can delete a variable by calling the [`delete_variable`](#model.delete_variable) method of the model: ```python model.delete_variable(x) ``` After a variable is deleted, it cannot be used in the model anymore, otherwise an exception will be raised. We can query whether a variable is active by calling the [`is_variable_active`](#model.is_variable_active) method of the model: ```python is_active = model.is_variable_active(x) ``` ================================================ FILE: docs/source/xpress.md ================================================ # Xpress ## Initial setup ```python from pyoptinterface import xpress model = xpress.Model() ``` You need to follow the instructions in [Getting Started](getting_started.md#xpress) to set up the optimizer correctly. If you want to manage the license of Xpress manually, you can create a `xpress.Env` object and pass it to the constructor of the `xpress.Model` object, otherwise we will initialize an implicit global `xpress.Env` object automatically and use it. ```python env = xpress.Env() model = xpress.Model(env) ``` For users who want to release the license immediately after the optimization, you can call the `close` method of all models created and the `xpress.Env` object. ```python env = xpress.Env() model = xpress.Model(env) # do something with the model model.close() env.close() ``` ## The capability of `xpress.Model` ### Supported constraints :::{list-table} :header-rows: 1 * - Constraint - Supported * - - ✅ * - - ✅ * - - ✅ * - - ✅ * - - ✅ * - - ✅ ::: ```{include} attribute/xpress.md ``` ## Solver-specific operations ### Controls and Attributes Xpress uses different terminology than PyOptInterface: - **Controls** govern the solution procedure and output format (similar to PyOptInterface parameters) - **Attributes** are read-only properties of the problem and solution PyOptInterface maps these as follows: - PyOptInterface **parameters** correspond to Xpress controls - PyOptInterface **attributes** may access Xpress controls, attributes, or variable/constraint properties through dedicated methods ### Controls (Parameters) For [solver-specific controls](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/chapter7.html), we provide `get_raw_control` and `set_raw_control` methods. ```python model = xpress.Model() # Get the value of a control value = model.get_raw_control("XPRS_TIMELIMIT") # Set the value of a control model.set_raw_control("XPRS_TIMELIMIT", 10.0) ``` ### Attributes For [problem attributes](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/chapter8.html), we provide `get_raw_attribute` method. ```python # Get number of columns in the problem cols = model.get_raw_attribute("XPRS_COLS") ``` We also provide `xpress.XPRS` to contain common constants from the Xpress C API. ```python # Using constants value = model.get_raw_control_dbl_by_id(xpress.XPRS.TIMELIMIT) ``` ### Variable and Constraint Properties Common variable and constraint properties are provided through PyOptInterface dedicated methods: **Variable methods:** - **Bounds**: `set_variable_lowerbound`, `get_variable_lowerbound`, `set_variable_upperbound`, `get_variable_upperbound`, `set_variable_bounds` - **Objective**: `set_objective_coefficient`, `get_objective_coefficient` - **Type and name**: `set_variable_type`, `get_variable_type`, `set_variable_name`, `get_variable_name` - **Solution values**: `get_variable_value`, `get_variable_rc`, `get_variable_primal_ray` - **Basis status**: `is_variable_basic`, `is_variable_nonbasic_lb`, `is_variable_nonbasic_ub`, `is_variable_superbasic` - **IIS information**: `is_variable_lowerbound_IIS`, `is_variable_upperbound_IIS` **Constraint methods:** - **Definition**: `set_constraint_sense`, `get_constraint_sense`, `set_constraint_rhs`, `get_constraint_rhs`, `set_constraint_name`, `get_constraint_name` - **Coefficients**: `set_normalized_coefficient`, `get_normalized_coefficient`, `set_normalized_rhs`, `get_normalized_rhs` - **Solution values**: `get_constraint_dual`, `get_constraint_slack`, `get_constraint_dual_ray` - **Basis status**: `is_constraint_basic`, `is_constraint_nonbasic_lb`, `is_constraint_nonbasic_ub`, `is_constraint_superbasic` - **IIS information**: `is_constraint_in_IIS` **Usage examples:** Variable properties: ```python # Bounds model.set_variable_lowerbound(variable, 0.0) lb = model.get_variable_lowerbound(variable) model.set_variable_upperbound(variable, 10.0) ub = model.get_variable_upperbound(variable) # Objective coefficient model.set_objective_coefficient(variable, 2.0) coef = model.get_objective_coefficient(variable) # Type and name model.set_variable_type(variable, VariableDomain.Integer) vtype = model.get_variable_type(variable) model.set_variable_name(variable, "x") name = model.get_variable_name(variable) # Solution values value = model.get_variable_value(variable) rc = model.get_variable_rc(variable) ray = model.get_variable_primal_ray(variable) # Basis status if model.is_variable_basic(variable): ... ``` Constraint properties: ```python # Sense and RHS model.set_constraint_sense(constraint, ConstraintSense.LessEqual) sense = model.get_constraint_sense(constraint) model.set_constraint_rhs(constraint, 5.0) rhs = model.get_constraint_rhs(constraint) # Name model.set_constraint_name(constraint, "c1") name = model.get_constraint_name(constraint) # Solution values dual = model.get_constraint_dual(constraint) slack = model.get_constraint_slack(constraint) ray = model.get_constraint_dual_ray(constraint) # Basis status if model.is_constraint_basic(constraint): ... ``` ================================================ FILE: include/pyoptinterface/cache_model.hpp ================================================ #pragma once #include #include #include // This file defines some common utilities to store the optimization model in a compact way template struct LinearExpressionCache { std::vector column_ptr = {0}; std::vector variables; std::vector coefficients; template void add_row(std::span row_variables, std::span row_coefficients) { variables.insert(variables.end(), row_variables.begin(), row_variables.end()); coefficients.insert(coefficients.end(), row_coefficients.begin(), row_coefficients.end()); column_ptr.push_back(variables.size()); } }; template struct QuadraticExpressionCache { std::vector column_ptr = {0}; std::vector variable_1s; std::vector variable_2s; std::vector coefficients; std::vector lin_column_ptr = {0}; std::vector lin_variables; std::vector lin_coefficients; template void add_row(std::span row_variable_1s, std::span row_variable_2s, std::span row_quadratic_coefficients, std::span row_lin_variables, std::span row_lin_coefficients) { variable_1s.insert(variable_1s.end(), row_variable_1s.begin(), row_variable_1s.end()); variable_2s.insert(variable_2s.end(), row_variable_2s.begin(), row_variable_2s.end()); coefficients.insert(coefficients.end(), row_quadratic_coefficients.begin(), row_quadratic_coefficients.end()); column_ptr.push_back(variable_1s.size()); lin_variables.insert(lin_variables.end(), row_lin_variables.begin(), row_lin_variables.end()); lin_coefficients.insert(lin_coefficients.end(), row_lin_coefficients.begin(), row_lin_coefficients.end()); lin_column_ptr.push_back(lin_variables.size()); } }; ================================================ FILE: include/pyoptinterface/container.hpp ================================================ #pragma once #include #include #include #include #include "pyoptinterface/core.hpp" // index as the key, variable as the value // index is monotone increasing // if we want to delete a index, we can set the value to -1 template class MonotoneVector { private: std::vector m_data; T m_start = 0; T m_update_start = 0; std::size_t m_last_correct_index = 0; std::size_t m_cardinality = 0; public: MonotoneVector() = default; MonotoneVector(T start) : m_start(start), m_update_start(start) { } IndexT add_index() { IndexT index = m_data.size(); m_data.push_back(m_start); m_cardinality += 1; return index; } void delete_index(const IndexT &index) { if (m_data[index] < 0) { return; } if (index < m_last_correct_index) { m_update_start = m_data[index]; m_last_correct_index = index; } m_data[index] = -1; m_cardinality -= 1; } bool has_index(const IndexT &index) { return get_index(index) >= 0; } T get_index(const IndexT &index) { if (index >= m_data.size()) { throw std::runtime_error("Index out of range"); } if (m_data[index] >= 0 && index > m_last_correct_index) { update_to(index); } return m_data[index]; } std::size_t num_active_indices() const { return m_cardinality; } std::vector get_active_indices() const { std::vector indices; indices.reserve(m_cardinality); for (IndexT i = 0; i < m_data.size(); i++) { if (m_data[i] >= 0) { indices.push_back(i); } } return indices; } void update_to(IndexT index) { // we ensure that m_data[index] >= 0 T counter = m_update_start; constexpr int STEP_STATE = 1; constexpr int JUMP_STATE = 2; int state; std::size_t jump_start_index; if (m_data[m_last_correct_index] < 0) { state = JUMP_STATE; jump_start_index = m_last_correct_index; } else { state = STEP_STATE; counter++; } for (IndexT i = m_last_correct_index + 1; i <= index;) { switch (state) { case JUMP_STATE: if (m_data[i] >= 0) { m_data[jump_start_index] = -i; state = STEP_STATE; goto CONTINUE_STEP; } else { CONTINUE_JUMP: T new_i = -m_data[i]; if (new_i > index) { i = new_i; } else { i++; } } break; case STEP_STATE: if (m_data[i] < 0) { state = JUMP_STATE; jump_start_index = i; goto CONTINUE_JUMP; } else { CONTINUE_STEP: m_data[i] = counter; counter++; i++; } break; default: throw std::runtime_error("Unknown state"); } } m_last_correct_index = index; m_update_start = m_data[index]; } void clear() { m_data.clear(); m_update_start = m_start; m_last_correct_index = 0; m_cardinality = 0; } }; template class ChunkedBitVector { public: ChunkedBitVector(ResultT start = 0) : m_start(start) { clear(); } IndexT add_index() { IndexT result; if (m_next_bit == CHUNK_WIDTH) { result = m_data.size() << LOG2_CHUNK_WIDTH; m_data.push_back(1); m_cumulated_ranks.push_back(m_cumulated_ranks.back()); m_chunk_ranks.push_back(-1); m_next_bit = 1; } else { auto last_chunk_end = (m_data.size() - 1) << LOG2_CHUNK_WIDTH; result = last_chunk_end + m_next_bit; ChunkT &last_chunk = m_data.back(); // set m_next_bit to 1 last_chunk |= (ChunkT{1} << m_next_bit); m_next_bit++; } return result; } // Add N new indices, return the start of index IndexT add_indices(int N) { assert(N >= 0); if (N == 1) { return add_index(); } auto current_size = m_data.size(); auto last_chunk_end = (current_size - 1) << LOG2_CHUNK_WIDTH; IndexT result = last_chunk_end + m_next_bit; // all bits set to 1 ChunkT newelement = ~0; // The current chunk needs to be filled as 1 int extra_bits_in_current_chunk = CHUNK_WIDTH - m_next_bit; extra_bits_in_current_chunk = std::min(extra_bits_in_current_chunk, N); if (extra_bits_in_current_chunk > 0) { ChunkT &last_chunk = m_data.back(); // set the bits in [m_next_bit, m_next_bit + extra_bits_in_current_chunk) to 1 ChunkT mask = (newelement << (m_next_bit)) & (newelement >> (CHUNK_WIDTH - m_next_bit - extra_bits_in_current_chunk)); last_chunk |= mask; } N -= extra_bits_in_current_chunk; if (N <= 0) { m_next_bit += extra_bits_in_current_chunk; return result; } auto N_full_elements = N >> LOG2_CHUNK_WIDTH; auto N_remaining_bits = N & (CHUNK_WIDTH - 1); if (N_full_elements > 0) { auto new_size = current_size + N_full_elements; m_data.resize(new_size, newelement); m_cumulated_ranks.resize(new_size, m_cumulated_ranks.back()); m_chunk_ranks.resize(new_size, CHUNK_WIDTH); } if (N_remaining_bits > 0) { // set the bits in [0, N_remaining_bits) to 1 ChunkT remaining_chunk = (ChunkT{1} << N_remaining_bits) - 1; m_data.push_back(remaining_chunk); m_cumulated_ranks.push_back(m_cumulated_ranks.back()); m_chunk_ranks.push_back(N_remaining_bits); m_next_bit = N_remaining_bits; } else { m_next_bit = CHUNK_WIDTH; } return result; } void delete_index(const IndexT &index) { std::size_t chunk_index; std::uint8_t bit_index; locate_index(index, chunk_index, bit_index); if (chunk_index >= m_data.size()) { return; } ChunkT &chunk = m_data[chunk_index]; ChunkT mask = ChunkT{1} << bit_index; if (chunk & mask) { // set bit_index to 0 chunk &= ~(mask); // m_last_correct_chunk ensures m_cumulated_ranks[0, m_last_correct_chunk] // and m_chunk_ranks[0, m_last_correct_chunk) are all correct // m_last_correct_chunk > chunk_index means that m_cumulated_ranks(chunk_index, +inf) // should be recalculated if (m_last_correct_chunk > chunk_index) { m_last_correct_chunk = chunk_index; } // rank of this chunk should also be recalculated m_chunk_ranks[chunk_index] = -1; } } bool has_index(const IndexT &index) const { std::size_t chunk_index; std::uint8_t bit_index; locate_index(index, chunk_index, bit_index); const ChunkT &chunk = m_data[chunk_index]; return chunk & (ChunkT{1} << bit_index); } ResultT get_index(const IndexT &index) { if (index >= m_data.size() * CHUNK_WIDTH) { return -1; } std::size_t chunk_index; std::uint8_t bit_index; locate_index(index, chunk_index, bit_index); ChunkT &chunk = m_data[chunk_index]; bool bit = chunk & (ChunkT{1} << bit_index); if (!bit) { return -1; } if (chunk_index > m_last_correct_chunk) { update_to(chunk_index); } // count the 1 on the right of bit_index ChunkT mask = (ChunkT{1} << bit_index) - 1; std::uint8_t current_chunk_index = std::popcount(chunk & mask); return m_cumulated_ranks[chunk_index] + current_chunk_index; } void update_to(std::size_t chunk_index) { // m_cumulated_ranks[0, m_last_correct_chunk] and m_chunk_ranks[0, m_last_correct_chunk) are // all correct // we need to update m_cumulated_ranks[m_last_correct_chunk + 1, chunk_index] // and m_chunk_ranks[m_last_correct_chunk, chunk_index) for (int ichunk = m_last_correct_chunk; ichunk < chunk_index; ichunk++) { if (m_chunk_ranks[ichunk] < 0) { m_chunk_ranks[ichunk] = std::popcount(m_data[ichunk]); } m_cumulated_ranks[ichunk + 1] = m_cumulated_ranks[ichunk] + m_chunk_ranks[ichunk]; } m_last_correct_chunk = chunk_index; } void locate_index(IndexT index, std::size_t &chunk_index, std::uint8_t &bit_index) const { chunk_index = index >> LOG2_CHUNK_WIDTH; bit_index = index & (CHUNK_WIDTH - 1); } void clear() { m_data.resize(1, 0); m_cumulated_ranks.resize(1, m_start); m_chunk_ranks.resize(1, -1); m_last_correct_chunk = 0; m_next_bit = 0; } private: enum : std::uint8_t { CHUNK_WIDTH = sizeof(ChunkT) * 8, LOG2_CHUNK_WIDTH = std::countr_zero(CHUNK_WIDTH) }; ResultT m_start; std::vector m_data; std::vector m_cumulated_ranks; std::vector m_chunk_ranks; // -1 represents tainted std::size_t m_last_correct_chunk; std::uint8_t m_next_bit; }; template class SimpleMonotoneVector { private: std::vector m_data; T m_start = 0; public: SimpleMonotoneVector() = default; SimpleMonotoneVector(T start) : m_start(start) { } IndexT add_index() { IndexT index = m_data.size(); m_data.push_back(index); return index; } void delete_index(const IndexT &index) { if (m_data[index] < 0) { return; } m_data[index] = -1; for (IndexT i = index + 1; i < m_data.size(); i++) { m_data[i] -= 1; } } bool has_index(const IndexT &index) { return get_index(index) >= 0; } T get_index(const IndexT &index) { if (index >= m_data.size()) { throw std::runtime_error("Index out of range"); } return m_data[index]; } void clear() { m_data.clear(); } }; template using MonotoneIndexer = ChunkedBitVector; // using MonotoneIndexer = SimpleMonotoneVector; ================================================ FILE: include/pyoptinterface/copt_model.hpp ================================================ #pragma once #include #include "solvers/copt/copt.h" #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #include "pyoptinterface/nlexpr.hpp" #define USE_NLMIXIN #include "pyoptinterface/solver_common.hpp" #include "pyoptinterface/dylib.hpp" extern "C" { int COPT_SearchParamAttr(copt_prob *prob, const char *name, int *p_type); } #define APILIST \ B(COPT_GetRetcodeMsg); \ B(COPT_CreateProb); \ B(COPT_DeleteProb); \ B(COPT_WriteMps); \ B(COPT_WriteLp); \ B(COPT_WriteCbf); \ B(COPT_WriteBin); \ B(COPT_WriteBasis); \ B(COPT_WriteSol); \ B(COPT_WriteMst); \ B(COPT_WriteParam); \ B(COPT_AddCol); \ B(COPT_DelCols); \ B(COPT_AddRow); \ B(COPT_AddQConstr); \ B(COPT_AddSOSs); \ B(COPT_AddCones); \ B(COPT_AddExpCones); \ B(COPT_AddNLConstr); \ B(COPT_DelRows); \ B(COPT_DelQConstrs); \ B(COPT_DelSOSs); \ B(COPT_DelCones); \ B(COPT_DelExpCones); \ B(COPT_DelQuadObj); \ B(COPT_ReplaceColObj); \ B(COPT_SetObjConst); \ B(COPT_SetObjSense); \ B(COPT_SetQuadObj); \ B(COPT_SetNLObj); \ B(COPT_Solve); \ B(COPT_SearchParamAttr); \ B(COPT_SetIntParam); \ B(COPT_SetDblParam); \ B(COPT_GetIntParam); \ B(COPT_GetDblParam); \ B(COPT_GetIntAttr); \ B(COPT_GetDblAttr); \ B(COPT_GetColInfo); \ B(COPT_GetColName); \ B(COPT_SetColNames); \ B(COPT_GetColType); \ B(COPT_SetColType); \ B(COPT_SetColLower); \ B(COPT_SetColUpper); \ B(COPT_GetRowInfo); \ B(COPT_GetQConstrInfo); \ B(COPT_GetNLConstrInfo); \ B(COPT_GetRowName); \ B(COPT_GetQConstrName); \ B(COPT_GetNLConstrName); \ B(COPT_SetRowNames); \ B(COPT_SetQConstrNames); \ B(COPT_SetNLConstrNames); \ B(COPT_AddMipStart); \ B(COPT_SetNLPrimalStart); \ B(COPT_GetQConstrRhs); \ B(COPT_SetRowLower); \ B(COPT_SetRowUpper); \ B(COPT_SetQConstrRhs); \ B(COPT_GetElem); \ B(COPT_SetElem); \ B(COPT_SetColObj); \ B(COPT_GetBanner); \ B(COPT_SetCallback); \ B(COPT_GetCallbackInfo); \ B(COPT_AddCallbackSolution); \ B(COPT_AddCallbackLazyConstr); \ B(COPT_AddCallbackUserCut); \ B(COPT_Interrupt); \ B(COPT_CreateEnv); \ B(COPT_CreateEnvWithConfig); \ B(COPT_DeleteEnv); \ B(COPT_CreateEnvConfig); \ B(COPT_DeleteEnvConfig); \ B(COPT_SetEnvConfig); \ B(COPT_ComputeIIS); \ B(COPT_GetColLowerIIS); \ B(COPT_GetColUpperIIS); \ B(COPT_GetRowLowerIIS); \ B(COPT_GetRowUpperIIS); \ B(COPT_GetSOSIIS); \ B(COPT_SetLogCallback); namespace copt { #define B DYLIB_EXTERN_DECLARE APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); } // namespace copt class COPTEnvConfig { public: COPTEnvConfig(); ~COPTEnvConfig(); void set(const char *param_name, const char *value); private: copt_env_config *m_config; friend class COPTEnv; }; class COPTEnv { public: COPTEnv(); COPTEnv(COPTEnvConfig &config); ~COPTEnv(); void close(); private: copt_env *m_env; friend class COPTModel; }; struct COPTfreemodelT { void operator()(copt_prob *model) const { copt::COPT_DeleteProb(&model); }; }; class COPTModel; using COPTCallback = std::function; struct COPTCallbackUserdata { void *model = nullptr; COPTCallback callback; int n_variables = 0; int where = 0; // store result of cbget bool cb_get_mipsol_called = false; std::vector mipsol; bool cb_get_mipnoderel_called = false; std::vector mipnoderel; bool cb_get_mipincumbent_called = false; std::vector mipincumbent; // Cache for cbsolution bool cb_set_solution_called = false; std::vector heuristic_solution; bool cb_requires_submit_solution = false; }; using COPTLoggingCallback = std::function; struct COPTLoggingCallbackUserdata { COPTLoggingCallback callback; }; class COPTModel : public OnesideLinearConstraintMixin, public TwosideLinearConstraintMixin, public OnesideQuadraticConstraintMixin, public TwosideNLConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { public: COPTModel() = default; COPTModel(const COPTEnv &env); void init(const COPTEnv &env); void close(); double get_infinity() const; void write(const std::string &filename); VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous, double lb = -COPT_INFINITY, double ub = COPT_INFINITY, const char *name = nullptr); void delete_variable(const VariableIndex &variable); void delete_variables(const Vector &variables); bool is_variable_active(const VariableIndex &variable); double get_variable_value(const VariableIndex &variable); std::string pprint_variable(const VariableIndex &variable); void set_variable_bounds(const VariableIndex &variable, double lb, double ub); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_sos_constraint(const Vector &variables, SOSType sos_type); ConstraintIndex add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights); // x[0]^2 >= x[1]^2 + x[2]^2 + ... + x[n-1]^2 ConstraintIndex add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated = false); ConstraintIndex add_exp_cone_constraint(const Vector &variables, const char *name, bool dual = false); // Nonlinear constraint void decode_expr(const ExpressionGraph &graph, const ExpressionHandle &expr, std::vector &opcodes, std::vector &constants); void decode_graph_prefix_order(ExpressionGraph &graph, const ExpressionHandle &result, std::vector &opcodes, std::vector &constants); ConstraintIndex add_single_nl_constraint(ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name = nullptr); void delete_constraint(const ConstraintIndex &constraint); bool is_constraint_active(const ConstraintIndex &constraint); void _set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic); void set_objective(const ScalarAffineFunction &function, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense); void set_objective(const ExprBuilder &function, ObjectiveSense sense); void add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result); void set_nl_objective(); void optimize(); void *get_raw_model(); std::string version_string(); /* * Returns the type of a COPT parameter or attribute, given its name. * -1: unknown * 0: double parameter * 1: int parameter * 2: double attribute * 3: int attribute * * Use undocumented COPT function * int COPT_SearchParamAttr(copt_prob* prob, const char* name, int* p_type) */ int raw_parameter_attribute_type(const char *name); // parameter void set_raw_parameter_int(const char *param_name, int value); void set_raw_parameter_double(const char *param_name, double value); int get_raw_parameter_int(const char *param_name); double get_raw_parameter_double(const char *param_name); // attribute int get_raw_attribute_int(const char *attr_name); double get_raw_attribute_double(const char *attr_name); // Accessing information of problem double get_variable_info(const VariableIndex &variable, const char *info_name); std::string get_variable_name(const VariableIndex &variable); void set_variable_name(const VariableIndex &variable, const char *name); VariableDomain get_variable_type(const VariableIndex &variable); void set_variable_type(const VariableIndex &variable, VariableDomain domain); void set_variable_lower_bound(const VariableIndex &variable, double lb); void set_variable_upper_bound(const VariableIndex &variable, double ub); double get_constraint_info(const ConstraintIndex &constraint, const char *info_name); std::string get_constraint_name(const ConstraintIndex &constraint); void set_constraint_name(const ConstraintIndex &constraint, const char *name); void set_obj_sense(ObjectiveSense sense); // MIPStart void add_mip_start(const Vector &variables, const Vector &values); // NLP start void add_nl_start(const Vector &variables, const Vector &values); // Modifications of model // 1. set/get RHS of a constraint double get_normalized_rhs(const ConstraintIndex &constraint); void set_normalized_rhs(const ConstraintIndex &constraint, double value); // 2. set/get coefficient of variable in constraint double get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable); void set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value); // 3. set/get linear coefficient of variable in objective double get_objective_coefficient(const VariableIndex &variable); void set_objective_coefficient(const VariableIndex &variable, double value); int _variable_index(const VariableIndex &variable); int _checked_variable_index(const VariableIndex &variable); int _constraint_index(const ConstraintIndex &constraint); int _checked_constraint_index(const ConstraintIndex &constraint); // Control logging void set_logging(const COPTLoggingCallback &callback); COPTLoggingCallbackUserdata m_logging_callback_userdata; // Callback void set_callback(const COPTCallback &callback, int cbctx); // For callback bool has_callback = false; void *m_cbdata = nullptr; COPTCallbackUserdata m_callback_userdata; int cb_get_info_int(const std::string &what); double cb_get_info_double(const std::string &what); void cb_get_info_doublearray(const std::string &what); double cb_get_solution(const VariableIndex &variable); double cb_get_relaxation(const VariableIndex &variable); double cb_get_incumbent(const VariableIndex &variable); void cb_set_solution(const VariableIndex &variable, double value); double cb_submit_solution(); void cb_exit(); void cb_add_lazy_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); void cb_add_lazy_constraint(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs); void cb_add_user_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); void cb_add_user_cut(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs); // IIS related void computeIIS(); int _get_variable_upperbound_IIS(const VariableIndex &variable); int _get_variable_lowerbound_IIS(const VariableIndex &variable); int _get_constraint_IIS(const ConstraintIndex &constraint); private: MonotoneIndexer m_variable_index; MonotoneIndexer m_linear_constraint_index; MonotoneIndexer m_quadratic_constraint_index; MonotoneIndexer m_sos_constraint_index; MonotoneIndexer m_cone_constraint_index; MonotoneIndexer m_exp_cone_constraint_index; MonotoneIndexer m_nl_constraint_index; // Store the nonlinear objectives int m_nl_objective_num = 0; std::vector m_nl_objective_opcodes = {COPT_NL_SUM, 0}; std::vector m_nl_objective_constants; /* COPT part */ std::unique_ptr m_model; }; ================================================ FILE: include/pyoptinterface/core.hpp ================================================ #pragma once #include #include #include #include "ankerl/unordered_dense.h" using IndexT = std::int32_t; using CoeffT = double; constexpr CoeffT COEFTHRESHOLD = 1e-12; template using Hashmap = ankerl::unordered_dense::map; template using Hashset = ankerl::unordered_dense::set; template using Vector = std::vector; struct VariableIndex; struct ScalarAffineFunction; struct ScalarQuadraticFunction; struct ExprBuilder; enum class VariableDomain { Continuous, Integer, Binary, SemiContinuous, }; struct VariableIndex { IndexT index; VariableIndex() = default; VariableIndex(IndexT v); }; struct ScalarAffineFunction { Vector coefficients; Vector variables; std::optional constant; ScalarAffineFunction() = default; ScalarAffineFunction(CoeffT v); ScalarAffineFunction(const VariableIndex &v); // v * c ScalarAffineFunction(const VariableIndex &v, CoeffT c); // v * c1 + c2 ScalarAffineFunction(const VariableIndex &v, CoeffT c1, CoeffT c2); ScalarAffineFunction(const Vector &, const Vector &); ScalarAffineFunction(const Vector &, const Vector &, const std::optional &); ScalarAffineFunction(const ExprBuilder &t); size_t size() const; void canonicalize(CoeffT threshold = COEFTHRESHOLD); void reserve(size_t n); void add_term(const VariableIndex &v, CoeffT c); void add_constant(CoeffT c); }; struct ScalarQuadraticFunction { Vector coefficients; Vector variable_1s; Vector variable_2s; std::optional affine_part; ScalarQuadraticFunction() = default; ScalarQuadraticFunction(const Vector &, const Vector &, const Vector &); ScalarQuadraticFunction(const Vector &, const Vector &, const Vector &, const std::optional &); ScalarQuadraticFunction(const ExprBuilder &t); size_t size() const; void canonicalize(CoeffT threshold = COEFTHRESHOLD); void reserve_quadratic(size_t n); void reserve_affine(size_t n); void add_quadratic_term(const VariableIndex &v1, const VariableIndex &v2, CoeffT c); void add_affine_term(const VariableIndex &v, CoeffT c); void add_constant(CoeffT c); }; struct VariablePair { IndexT var_1; IndexT var_2; bool operator==(const VariablePair &x) const; bool operator<(const VariablePair &x) const; VariablePair() = default; VariablePair(IndexT v1, IndexT v2) : var_1(v1), var_2(v2) { } }; template <> struct ankerl::unordered_dense::hash { using is_avalanching = void; [[nodiscard]] auto operator()(VariablePair const &x) const noexcept -> uint64_t { static_assert(std::has_unique_object_representations_v); return detail::wyhash::hash(&x, sizeof(x)); } }; struct ExprBuilder { Hashmap quadratic_terms; Hashmap affine_terms; std::optional constant_term; ExprBuilder() = default; ExprBuilder(CoeffT c); ExprBuilder(const VariableIndex &v); ExprBuilder(const ScalarAffineFunction &a); ExprBuilder(const ScalarQuadraticFunction &q); ExprBuilder &operator+=(CoeffT c); ExprBuilder &operator+=(const VariableIndex &v); ExprBuilder &operator+=(const ScalarAffineFunction &a); ExprBuilder &operator+=(const ScalarQuadraticFunction &q); ExprBuilder &operator+=(const ExprBuilder &t); ExprBuilder &operator-=(CoeffT c); ExprBuilder &operator-=(const VariableIndex &v); ExprBuilder &operator-=(const ScalarAffineFunction &a); ExprBuilder &operator-=(const ScalarQuadraticFunction &q); ExprBuilder &operator-=(const ExprBuilder &t); ExprBuilder &operator*=(CoeffT c); ExprBuilder &operator*=(const VariableIndex &v); ExprBuilder &operator*=(const ScalarAffineFunction &a); ExprBuilder &operator*=(const ScalarQuadraticFunction &q); ExprBuilder &operator*=(const ExprBuilder &t); ExprBuilder &operator/=(CoeffT c); bool empty() const; int degree() const; void reserve_quadratic(size_t n); void reserve_affine(size_t n); void clear(); void clean_nearzero_terms(CoeffT threshold = COEFTHRESHOLD); void _add_quadratic_term(IndexT i, IndexT j, CoeffT coeff); void _set_quadratic_coef(IndexT i, IndexT j, CoeffT coeff); void add_quadratic_term(const VariableIndex &i, const VariableIndex &j, CoeffT coeff); void set_quadratic_coef(const VariableIndex &i, const VariableIndex &j, CoeffT coeff); void _add_affine_term(IndexT i, CoeffT coeff); void _set_affine_coef(IndexT i, CoeffT coeff); void add_affine_term(const VariableIndex &i, CoeffT coeff); void set_affine_coef(const VariableIndex &i, CoeffT coeff); }; auto operator+(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction; auto operator+(CoeffT a, const VariableIndex &b) -> ScalarAffineFunction; auto operator+(const VariableIndex &a, const VariableIndex &b) -> ScalarAffineFunction; auto operator+(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction; auto operator+(CoeffT a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator+(const ScalarAffineFunction &a, const VariableIndex &b) -> ScalarAffineFunction; auto operator+(const VariableIndex &a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator+(const ScalarAffineFunction &a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator+(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction; auto operator+(CoeffT a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator+(const ScalarQuadraticFunction &a, const VariableIndex &b) -> ScalarQuadraticFunction; auto operator+(const VariableIndex &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator+(const ScalarQuadraticFunction &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction; auto operator+(const ScalarAffineFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator+(const ScalarQuadraticFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator-(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction; auto operator-(CoeffT a, const VariableIndex &b) -> ScalarAffineFunction; auto operator-(const VariableIndex &a, const VariableIndex &b) -> ScalarAffineFunction; auto operator-(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction; auto operator-(CoeffT a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator-(const ScalarAffineFunction &a, const VariableIndex &b) -> ScalarAffineFunction; auto operator-(const VariableIndex &a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator-(const ScalarAffineFunction &a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator-(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction; auto operator-(CoeffT a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator-(const ScalarQuadraticFunction &a, const VariableIndex &b) -> ScalarQuadraticFunction; auto operator-(const VariableIndex &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator-(const ScalarQuadraticFunction &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction; auto operator-(const ScalarAffineFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator-(const ScalarQuadraticFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator*(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction; auto operator*(CoeffT a, const VariableIndex &b) -> ScalarAffineFunction; auto operator*(const VariableIndex &a, const VariableIndex &b) -> ScalarQuadraticFunction; auto operator*(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction; auto operator*(CoeffT a, const ScalarAffineFunction &b) -> ScalarAffineFunction; auto operator*(const ScalarAffineFunction &a, const VariableIndex &b) -> ScalarQuadraticFunction; auto operator*(const VariableIndex &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction; auto operator*(const ScalarAffineFunction &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction; auto operator*(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction; auto operator*(CoeffT a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction; auto operator/(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction; auto operator/(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction; auto operator/(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction; // unary minus operator auto operator-(const VariableIndex &a) -> ScalarAffineFunction; auto operator-(const ScalarAffineFunction &a) -> ScalarAffineFunction; auto operator-(const ScalarQuadraticFunction &a) -> ScalarQuadraticFunction; auto operator-(const ExprBuilder &a) -> ExprBuilder; // Operator overloading for ExprBuilder // Sadly, they are inefficient than the +=,-=,*=,/= functions but they are important for a // user-friendly interface // The functions are like ScalarQuadraticFunction but returns a ExprBuilder auto operator+(const ExprBuilder &a, CoeffT b) -> ExprBuilder; auto operator+(CoeffT b, const ExprBuilder &a) -> ExprBuilder; auto operator+(const ExprBuilder &a, const VariableIndex &b) -> ExprBuilder; auto operator+(const VariableIndex &b, const ExprBuilder &a) -> ExprBuilder; auto operator+(const ExprBuilder &a, const ScalarAffineFunction &b) -> ExprBuilder; auto operator+(const ScalarAffineFunction &b, const ExprBuilder &a) -> ExprBuilder; auto operator+(const ExprBuilder &a, const ScalarQuadraticFunction &b) -> ExprBuilder; auto operator+(const ScalarQuadraticFunction &b, const ExprBuilder &a) -> ExprBuilder; auto operator+(const ExprBuilder &a, const ExprBuilder &b) -> ExprBuilder; auto operator-(const ExprBuilder &a, CoeffT b) -> ExprBuilder; auto operator-(CoeffT b, const ExprBuilder &a) -> ExprBuilder; auto operator-(const ExprBuilder &a, const VariableIndex &b) -> ExprBuilder; auto operator-(const VariableIndex &b, const ExprBuilder &a) -> ExprBuilder; auto operator-(const ExprBuilder &a, const ScalarAffineFunction &b) -> ExprBuilder; auto operator-(const ScalarAffineFunction &b, const ExprBuilder &a) -> ExprBuilder; auto operator-(const ExprBuilder &a, const ScalarQuadraticFunction &b) -> ExprBuilder; auto operator-(const ScalarQuadraticFunction &b, const ExprBuilder &a) -> ExprBuilder; auto operator-(const ExprBuilder &a, const ExprBuilder &b) -> ExprBuilder; auto operator*(const ExprBuilder &a, CoeffT b) -> ExprBuilder; auto operator*(CoeffT b, const ExprBuilder &a) -> ExprBuilder; auto operator*(const ExprBuilder &a, const VariableIndex &b) -> ExprBuilder; auto operator*(const VariableIndex &b, const ExprBuilder &a) -> ExprBuilder; auto operator*(const ExprBuilder &a, const ScalarAffineFunction &b) -> ExprBuilder; auto operator*(const ScalarAffineFunction &b, const ExprBuilder &a) -> ExprBuilder; auto operator*(const ExprBuilder &a, const ScalarQuadraticFunction &b) -> ExprBuilder; auto operator*(const ScalarQuadraticFunction &b, const ExprBuilder &a) -> ExprBuilder; auto operator*(const ExprBuilder &a, const ExprBuilder &b) -> ExprBuilder; auto operator/(const ExprBuilder &a, CoeffT b) -> ExprBuilder; enum class ConstraintType { Linear, Quadratic, SOS, SecondOrderCone, ExponentialCone, NL, SolverDefined, }; enum class SOSType { SOS1, SOS2, }; enum class ConstraintSense { LessEqual, GreaterEqual, Equal }; struct ConstraintIndex { ConstraintType type; IndexT index; ConstraintIndex() = default; ConstraintIndex(ConstraintType t, IndexT i) : type(t), index(i) { } }; // struct LinearConstraint //{ // ScalarAffineFunction function; // ConstraintSense sense; // CoeffT rhs; // }; // // struct QuadraticConstraint //{ // ScalarQuadraticFunction function; // ConstraintSense sense; // CoeffT rhs; // }; // // struct SOSConstraint //{ // Vector variables; // Vector weights; // }; enum class ObjectiveSense { Minimize, Maximize }; ================================================ FILE: include/pyoptinterface/cppad_interface.hpp ================================================ #pragma once #include "cppad/cppad.hpp" #include "pyoptinterface/nlexpr.hpp" #include "pyoptinterface/nleval.hpp" using ADFunDouble = CppAD::ADFun; ADFunDouble dense_jacobian(const ADFunDouble &f); using sparsity_pattern_t = CppAD::sparse_rc>; struct JacobianHessianSparsityPattern { sparsity_pattern_t jacobian; sparsity_pattern_t hessian; sparsity_pattern_t reduced_hessian; }; JacobianHessianSparsityPattern jacobian_hessian_sparsity(ADFunDouble &f, HessianSparsityType hessian_sparsity); // [p, x] -> Jacobian ADFunDouble sparse_jacobian(const ADFunDouble &f, const sparsity_pattern_t &pattern_jac, const std::vector &x_values, const std::vector &p_values); // [p, w, x] -> \Sigma w_i * Hessian_i ADFunDouble sparse_hessian(const ADFunDouble &f, const sparsity_pattern_t &pattern_hes, const sparsity_pattern_t &pattern_subset, const std::vector &x_values, const std::vector &p_values); // Transform ExpressionGraph to CppAD function // selected: indices of outputs to trace, empty means all outputs ADFunDouble cppad_trace_graph_constraints(const ExpressionGraph &graph, const std::vector &selected = {}); ADFunDouble cppad_trace_graph_objective(const ExpressionGraph &graph, const std::vector &selected = {}, bool aggregate = true); struct CppADAutodiffGraph { CppAD::cpp_graph f_graph, jacobian_graph, hessian_graph; }; // Generate computational graph for the CppAD function (itself, Jacobian and Hessian) // Analyze its sparsity as well void cppad_autodiff(ADFunDouble &f, AutodiffSymbolicStructure &structure, CppADAutodiffGraph &graph, const std::vector &x_values, const std::vector &p_values); ================================================ FILE: include/pyoptinterface/dylib.hpp ================================================ #pragma once #if defined(_MSC_VER) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #else #include #endif class DynamicLibrary { public: DynamicLibrary() : handle(nullptr) { } ~DynamicLibrary() { if (handle != nullptr) { #if defined(_MSC_VER) FreeLibrary(static_cast(handle)); #else dlclose(handle); #endif } } bool try_load(const char *library) { #if defined(_MSC_VER) handle = static_cast(LoadLibraryA(library)); if (handle == nullptr) { handle = static_cast(LoadLibraryExA(library, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)); } #else handle = dlopen(library, RTLD_NOW); #endif return handle != nullptr; } bool LibraryIsLoaded() const { return handle != nullptr; } void *get_symbol(const char *name) { #if defined(_MSC_VER) FARPROC function_address = GetProcAddress(static_cast(handle), name); #else void *function_address = dlsym(handle, name); #endif return reinterpret_cast(function_address); } private: void *handle = nullptr; }; // Next we will introduce some magic macros to declare function pointers and load them from dynamic // library robustly #define DYLIB_EXTERN_DECLARE(f) extern decltype(&::f) f #define DYLIB_DECLARE(f) decltype(&::f) f = nullptr #define DYLIB_LOAD_INIT \ ankerl::unordered_dense::map _function_pointers; \ bool _load_success = true #define DYLIB_LOAD_FUNCTION(f) \ { \ auto ptr = reinterpret_cast(lib.get_symbol(#f)); \ if (ptr == nullptr) \ { \ fmt::print("function {} is not loaded correctly\n", #f); \ _load_success = false; \ } \ _function_pointers[#f] = reinterpret_cast(ptr); \ } #define IS_DYLIB_LOAD_SUCCESS _load_success #define DYLIB_SAVE_FUNCTION(f) f = reinterpret_cast(_function_pointers[#f]) ================================================ FILE: include/pyoptinterface/gurobi_model.hpp ================================================ #pragma once #include #include "solvers/gurobi/gurobi_c.h" #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #include "pyoptinterface/nlexpr.hpp" #define USE_NLMIXIN #include "pyoptinterface/solver_common.hpp" #include "pyoptinterface/dylib.hpp" // define Gurobi C APIs #define APILIST \ B(GRBnewmodel); \ B(GRBfreemodel); \ B(GRBreset); \ B(GRBgetenv); \ B(GRBwrite); \ B(GRBaddvar); \ B(GRBdelvars); \ B(GRBaddconstr); \ B(GRBaddqconstr); \ B(GRBaddsos); \ B(GRBaddgenconstrNL); \ B(GRBdelconstrs); \ B(GRBdelqconstrs); \ B(GRBdelsos); \ B(GRBdelgenconstrs); \ B(GRBdelq); \ B(GRBsetdblattrarray); \ B(GRBaddqpterms); \ B(GRBoptimize); \ B(GRBupdatemodel); \ B(GRBgetparamtype); \ B(GRBsetintparam); \ B(GRBsetdblparam); \ B(GRBsetstrparam); \ B(GRBgetintparam); \ B(GRBgetdblparam); \ B(GRBgetstrparam); \ B(GRBgetattrinfo); \ B(GRBsetintattr); \ B(GRBsetdblattr); \ B(GRBsetstrattr); \ B(GRBgetintattr); \ B(GRBgetdblattr); \ B(GRBgetstrattr); \ B(GRBgetdblattrarray); \ B(GRBgetdblattrlist); \ B(GRBsetdblattrlist); \ B(GRBsetintattrelement); \ B(GRBsetcharattrelement); \ B(GRBsetdblattrelement); \ B(GRBsetstrattrelement); \ B(GRBgetintattrelement); \ B(GRBgetcharattrelement); \ B(GRBgetdblattrelement); \ B(GRBgetstrattrelement); \ B(GRBgetcoeff); \ B(GRBchgcoeffs); \ B(GRBgeterrormsg); \ B(GRBversion); \ B(GRBsetcallbackfunc); \ B(GRBcbget); \ B(GRBcbproceed); \ B(GRBterminate); \ B(GRBcbsolution); \ B(GRBcblazy); \ B(GRBcbcut); \ B(GRBemptyenv); \ B(GRBloadenv); \ B(GRBfreeenv); \ B(GRBstartenv); \ B(GRBsetlogcallbackfunc); \ B(GRBconverttofixed); \ B(GRBcomputeIIS); namespace gurobi { #define B DYLIB_EXTERN_DECLARE APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); } // namespace gurobi class GurobiEnv { public: GurobiEnv(bool empty = false); ~GurobiEnv(); // parameter int raw_parameter_type(const char *param_name); void set_raw_parameter_int(const char *param_name, int value); void set_raw_parameter_double(const char *param_name, double value); void set_raw_parameter_string(const char *param_name, const char *value); void start(); void close(); void check_error(int error); GRBenv *m_env = nullptr; }; struct GRBfreemodelT { void operator()(GRBmodel *model) const { gurobi::GRBfreemodel(model); }; }; class GurobiModel; using GurobiCallback = std::function; struct GurobiCallbackUserdata { void *model = nullptr; GurobiCallback callback; int n_variables = 0; int where = 0; // store result of cbget bool cb_get_mipsol_called = false; std::vector mipsol; bool cb_get_mipnoderel_called = false; std::vector mipnoderel; // Cache for cbsolution bool cb_set_solution_called = false; std::vector heuristic_solution; bool cb_requires_submit_solution = false; }; using GurobiLoggingCallback = std::function; struct GurobiLoggingCallbackUserdata { GurobiLoggingCallback callback; }; class GurobiModel : public OnesideLinearConstraintMixin, public OnesideQuadraticConstraintMixin, public TwosideNLConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { public: GurobiModel() = default; GurobiModel(const GurobiEnv &env); void init(const GurobiEnv &env); void close(); void _reset(int clearall); double get_infinity() const; void write(const std::string &filename); VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous, double lb = -GRB_INFINITY, double ub = GRB_INFINITY, const char *name = nullptr); void delete_variable(const VariableIndex &variable); void delete_variables(const Vector &variables); bool is_variable_active(const VariableIndex &variable); double get_variable_value(const VariableIndex &variable); std::string pprint_variable(const VariableIndex &variable); void set_variable_bounds(const VariableIndex &variable, double lb, double ub); void set_variable_name(const VariableIndex &variable, const char *name); void set_constraint_name(const ConstraintIndex &constraint, const char *name); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_sos_constraint(const Vector &variables, SOSType sos_type); ConstraintIndex add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights); // Nonlinear constraint void information_of_expr(const ExpressionGraph &graph, const ExpressionHandle &expr, int &opcode, double &data); void decode_graph(const ExpressionGraph &graph, const ExpressionHandle &result, std::vector &opcodes, std::vector &parents, std::vector &datas); ConstraintIndex add_single_nl_constraint(const ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name = nullptr); void delete_constraint(const ConstraintIndex &constraint); bool is_constraint_active(const ConstraintIndex &constraint); void _set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic); void set_objective(const ScalarAffineFunction &function, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense); void set_objective(const ExprBuilder &function, ObjectiveSense sense); void add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result); void set_nl_objective(); void optimize(); void update(); void *get_raw_model(); std::string version_string(); // parameter int raw_parameter_type(const char *param_name); void set_raw_parameter_int(const char *param_name, int value); void set_raw_parameter_double(const char *param_name, double value); void set_raw_parameter_string(const char *param_name, const char *value); int get_raw_parameter_int(const char *param_name); double get_raw_parameter_double(const char *param_name); std::string get_raw_parameter_string(const char *param_name); // attribute int raw_attribute_type(const char *attr_name); // model attribute void set_model_raw_attribute_int(const char *attr_name, int value); void set_model_raw_attribute_double(const char *attr_name, double value); void set_model_raw_attribute_string(const char *attr_name, const char *value); int get_model_raw_attribute_int(const char *attr_name); double get_model_raw_attribute_double(const char *attr_name); std::string get_model_raw_attribute_string(const char *attr_name); std::vector get_model_raw_attribute_vector_double(const char *attr_name, int start, int len); std::vector get_model_raw_attribute_list_double(const char *attr_name, const std::vector &ind); // variable attribute void set_variable_raw_attribute_int(const VariableIndex &variable, const char *attr_name, int value); void set_variable_raw_attribute_char(const VariableIndex &variable, const char *attr_name, char value); void set_variable_raw_attribute_double(const VariableIndex &variable, const char *attr_name, double value); void set_variable_raw_attribute_string(const VariableIndex &variable, const char *attr_name, const char *value); int get_variable_raw_attribute_int(const VariableIndex &variable, const char *attr_name); char get_variable_raw_attribute_char(const VariableIndex &variable, const char *attr_name); double get_variable_raw_attribute_double(const VariableIndex &variable, const char *attr_name); std::string get_variable_raw_attribute_string(const VariableIndex &variable, const char *attr_name); int _variable_index(const VariableIndex &variable); int _checked_variable_index(const VariableIndex &variable); // constraint attribute void set_constraint_raw_attribute_int(const ConstraintIndex &constraint, const char *attr_name, int value); void set_constraint_raw_attribute_char(const ConstraintIndex &constraint, const char *attr_name, char value); void set_constraint_raw_attribute_double(const ConstraintIndex &constraint, const char *attr_name, double value); void set_constraint_raw_attribute_string(const ConstraintIndex &constraint, const char *attr_name, const char *value); int get_constraint_raw_attribute_int(const ConstraintIndex &constraint, const char *attr_name); char get_constraint_raw_attribute_char(const ConstraintIndex &constraint, const char *attr_name); double get_constraint_raw_attribute_double(const ConstraintIndex &constraint, const char *attr_name); std::string get_constraint_raw_attribute_string(const ConstraintIndex &constraint, const char *attr_name); int _constraint_index(const ConstraintIndex &constraint); int _checked_constraint_index(const ConstraintIndex &constraint); // Modifications of model // 1. set/get RHS of a constraint double get_normalized_rhs(const ConstraintIndex &constraint); void set_normalized_rhs(const ConstraintIndex &constraint, double value); // 2. set/get coefficient of variable in constraint double get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable); void set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value); // 3. set/get linear coefficient of variable in objective double get_objective_coefficient(const VariableIndex &variable); void set_objective_coefficient(const VariableIndex &variable, double value); // Gurobi-specific convertofixed void _converttofixed(); // IIS related void computeIIS(); // Non-exported functions void check_error(int error); // Control logging void set_logging(const GurobiLoggingCallback &callback); GurobiLoggingCallbackUserdata m_logging_callback_userdata; // Callback void set_callback(const GurobiCallback &callback); // For callback bool has_callback = false; void *m_cbdata = nullptr; GurobiCallbackUserdata m_callback_userdata; int cb_get_info_int(int what); double cb_get_info_double(int what); void cb_get_info_doublearray(int what); double cb_get_solution(const VariableIndex &variable); double cb_get_relaxation(const VariableIndex &variable); void cb_set_solution(const VariableIndex &variable, double value); double cb_submit_solution(); void cb_exit(); void cb_add_lazy_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); void cb_add_lazy_constraint(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs); void cb_add_user_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); void cb_add_user_cut(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs); private: MonotoneIndexer m_variable_index; MonotoneIndexer m_linear_constraint_index; MonotoneIndexer m_quadratic_constraint_index; MonotoneIndexer m_sos_constraint_index; MonotoneIndexer m_general_constraint_index; // Gurobi only accepts y = f(x) style nonlinear constraint // so for each lb <= f(x) <= ub, we need to convert it to // lb <= y <= ub, and add y = f(x) as a nonlinear constraint // y is called the result variable (resvar) Hashmap m_nlcon_resvar_map; // for each nonlinear term in objective function, we need to record the resvar to set their // coefficient in objective as 1.0 // add f(x) to the objective is divided into two steps: // 1. add a new nonlinear constraint y = f(x) // 2. set the coefficient of y in objective to 1.0 int m_nlobj_num = 0; std::vector m_nlobj_con_indices; std::vector m_nlobj_resvar_indices; /* flag to indicate whether the model needs update */ enum : std::uint64_t { m_variable_creation = 1, m_variable_deletion = 1 << 1, m_linear_constraint_creation = 1 << 2, m_linear_constraint_deletion = 1 << 3, m_quadratic_constraint_creation = 1 << 4, m_quadratic_constraint_deletion = 1 << 5, m_sos_constraint_creation = 1 << 6, m_sos_constraint_deletion = 1 << 7, m_general_constraint_creation = 1 << 8, m_general_constraint_deletion = 1 << 9, m_objective_update = 1 << 10, m_attribute_update = 1 << 11, m_constraint_coefficient_update = 1 << 12, }; std::uint64_t m_update_flag = 0; void _update_for_information(); void _update_for_variable_index(); void _update_for_constraint_index(ConstraintType type); /* Gurobi part */ GRBenv *m_env = nullptr; std::unique_ptr m_model; }; ================================================ FILE: include/pyoptinterface/highs_model.hpp ================================================ #pragma once #include #include "interfaces/highs_c_api.h" #include "lp_data/HConst.h" #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #include "pyoptinterface/solver_common.hpp" #include "pyoptinterface/dylib.hpp" #define APILIST \ B(Highs_create); \ B(Highs_destroy); \ B(Highs_writeModel); \ B(Highs_writeSolution); \ B(Highs_writeSolutionPretty); \ B(Highs_addCol); \ B(Highs_passColName); \ B(Highs_getColName); \ B(Highs_getNumCol); \ B(Highs_changeColIntegrality); \ B(Highs_deleteColsBySet); \ B(Highs_addRow); \ B(Highs_passRowName); \ B(Highs_getRowName); \ B(Highs_getNumRow); \ B(Highs_deleteRowsBySet); \ B(Highs_passHessian); \ B(Highs_changeColsCostByRange); \ B(Highs_changeObjectiveOffset); \ B(Highs_changeObjectiveSense); \ B(Highs_run); \ B(Highs_getNumCols); \ B(Highs_getNumRows); \ B(Highs_getModelStatus); \ B(Highs_getDualRay); \ B(Highs_getPrimalRay); \ B(Highs_getIntInfoValue); \ B(Highs_getSolution); \ B(Highs_getHessianNumNz); \ B(Highs_getBasis); \ B(Highs_version); \ B(Highs_getRunTime); \ B(Highs_getOptionType); \ B(Highs_setBoolOptionValue); \ B(Highs_setIntOptionValue); \ B(Highs_setDoubleOptionValue); \ B(Highs_setStringOptionValue); \ B(Highs_getBoolOptionValue); \ B(Highs_getIntOptionValue); \ B(Highs_getDoubleOptionValue); \ B(Highs_getStringOptionValue); \ B(Highs_getInfoType); \ B(Highs_getInt64InfoValue); \ B(Highs_getDoubleInfoValue); \ B(Highs_getColIntegrality); \ B(Highs_changeColsBoundsBySet); \ B(Highs_getColsBySet); \ B(Highs_getObjectiveSense); \ B(Highs_getObjectiveValue); \ B(Highs_getColsByRange); \ B(Highs_setSolution); \ B(Highs_getRowsBySet); \ B(Highs_changeRowsBoundsBySet); \ B(Highs_changeCoeff); \ B(Highs_changeColCost); namespace highs { #define B DYLIB_EXTERN_DECLARE APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); } // namespace highs struct HighsfreemodelT { void operator()(void *model) const { highs::Highs_destroy(model); }; }; enum class HighsSolutionStatus { OPTIMIZE_NOT_CALLED, OPTIMIZE_OK, OPTIMIZE_ERROR, }; struct POIHighsSolution { HighsSolutionStatus status = HighsSolutionStatus::OPTIMIZE_NOT_CALLED; HighsInt model_status; std::vector colvalue; std::vector coldual; std::vector colstatus; std::vector rowvalue; std::vector rowdual; std::vector rowstatus; HighsInt primal_solution_status; HighsInt dual_solution_status; bool has_primal_ray; bool has_dual_ray; std::vector primal_ray; std::vector dual_ray; }; class POIHighsModel : public OnesideLinearConstraintMixin, public TwosideLinearConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { public: POIHighsModel(); void init(); void close(); void write(const std::string &filename, bool pretty); VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous, double lb = -kHighsInf, double ub = kHighsInf, const char *name = nullptr); void delete_variable(const VariableIndex &variable); void delete_variables(const Vector &variables); bool is_variable_active(const VariableIndex &variable); double get_variable_value(const VariableIndex &variable); std::string pprint_variable(const VariableIndex &variable); void set_variable_bounds(const VariableIndex &variable, double lb, double ub); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); void delete_constraint(const ConstraintIndex &constraint); bool is_constraint_active(const ConstraintIndex &constraint); void _set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic); void set_objective(const ScalarAffineFunction &function, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense); void set_objective(const ExprBuilder &function, ObjectiveSense sense); void optimize(); void *get_raw_model(); std::string version_string(); double getruntime(); int getnumrow(); int getnumcol(); // option int raw_option_type(const char *param_name); void set_raw_option_bool(const char *param_name, bool value); void set_raw_option_int(const char *param_name, int value); void set_raw_option_double(const char *param_name, double value); void set_raw_option_string(const char *param_name, const char *value); bool get_raw_option_bool(const char *param_name); int get_raw_option_int(const char *param_name); double get_raw_option_double(const char *param_name); std::string get_raw_option_string(const char *param_name); // information int raw_info_type(const char *info_name); int get_raw_info_int(const char *info_name); std::int64_t get_raw_info_int64(const char *info_name); double get_raw_info_double(const char *info_name); // Accessing information of m_problem std::string get_variable_name(const VariableIndex &variable); void set_variable_name(const VariableIndex &variable, const char *name); VariableDomain get_variable_type(const VariableIndex &variable); void set_variable_type(const VariableIndex &variable, VariableDomain domain); double get_variable_lower_bound(const VariableIndex &variable); double get_variable_upper_bound(const VariableIndex &variable); void set_variable_lower_bound(const VariableIndex &variable, double lb); void set_variable_upper_bound(const VariableIndex &variable, double ub); std::string get_constraint_name(const ConstraintIndex &constraint); void set_constraint_name(const ConstraintIndex &constraint, const char *name); double get_constraint_primal(const ConstraintIndex &constraint); double get_constraint_dual(const ConstraintIndex &constraint); // dual of variable as reduced cost double get_variable_dual(const VariableIndex &variable); ObjectiveSense get_obj_sense(); void set_obj_sense(ObjectiveSense sense); double get_obj_value(); HighsInt _variable_index(const VariableIndex &variable); HighsInt _checked_variable_index(const VariableIndex &variable); HighsInt _constraint_index(const ConstraintIndex &constraint); HighsInt _checked_constraint_index(const ConstraintIndex &constraint); // Primal start void set_primal_start(const Vector &variables, const Vector &values); // Modifications of model // 1. set/get RHS of a constraint double get_normalized_rhs(const ConstraintIndex &constraint); void set_normalized_rhs(const ConstraintIndex &constraint, double value); // 2. set/get coefficient of variable in constraint double get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable); void set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value); // 3. set/get linear coefficient of variable in objective double get_objective_coefficient(const VariableIndex &variable); void set_objective_coefficient(const VariableIndex &variable, double value); private: MonotoneIndexer m_variable_index; MonotoneIndexer m_linear_constraint_index; // Highs does not discriminate between integer variable and binary variable // So we need to keep track of binary variables Hashset binary_variables; /* Highs part */ std::unique_ptr m_model; public: // cache the solution POIHighsSolution m_solution; // cache the number of variable and constraints because querying them via HiGHS API is very // expensive HighsInt m_n_variables = 0; HighsInt m_n_constraints = 0; }; ================================================ FILE: include/pyoptinterface/ipopt_model.hpp ================================================ #pragma once #include "solvers/ipopt/IpStdCInterface.h" #include "pyoptinterface/nlexpr.hpp" #include "pyoptinterface/nleval.hpp" #include "pyoptinterface/dylib.hpp" #include "pyoptinterface/solver_common.hpp" #include #include #define APILIST \ B(CreateIpoptProblem); \ B(FreeIpoptProblem); \ B(AddIpoptStrOption); \ B(AddIpoptNumOption); \ B(AddIpoptIntOption); \ B(IpoptSolve); namespace ipopt { #define B DYLIB_EXTERN_DECLARE APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); } // namespace ipopt struct IpoptfreeproblemT { void operator()(IpoptProblemInfo *model) const { ipopt::FreeIpoptProblem(model); }; }; struct IpoptResult { bool is_valid = false; // store results std::vector x, g, mult_g, mult_x_L, mult_x_U; double obj_val; }; struct IpoptModel : public OnesideLinearConstraintMixin, public TwosideLinearConstraintMixin, public OnesideQuadraticConstraintMixin, public TwosideQuadraticConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { /* Methods */ IpoptModel(); void close(); VariableIndex add_variable(double lb = -INFINITY, double ub = INFINITY, double start = 0.0, const char *name = nullptr); double get_variable_lb(const VariableIndex &variable); double get_variable_ub(const VariableIndex &variable); void set_variable_lb(const VariableIndex &variable, double lb); void set_variable_ub(const VariableIndex &variable, double ub); void set_variable_bounds(const VariableIndex &variable, double lb, double ub); double get_variable_start(const VariableIndex &variable); void set_variable_start(const VariableIndex &variable, double start); std::string get_variable_name(const VariableIndex &variable); void set_variable_name(const VariableIndex &variable, const std::string &name); double get_variable_value(const VariableIndex &variable); std::string pprint_variable(const VariableIndex &variable); double get_obj_value(); int _constraint_internal_index(const ConstraintIndex &constraint); double get_constraint_primal(const ConstraintIndex &constraint); double get_constraint_dual(const ConstraintIndex &constraint); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &f, ConstraintSense sense, double rhs, const char *name = nullptr); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &f, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &f, ConstraintSense sense, double rhs, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &f, const std::tuple &interval, const char *name = nullptr); void set_objective(const ScalarAffineFunction &expr, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &expr, ObjectiveSense sense); void set_objective(const ExprBuilder &expr, ObjectiveSense sense); void _set_linear_objective(const ScalarAffineFunction &expr); void _set_quadratic_objective(const ScalarQuadraticFunction &expr); // new implementation size_t n_graph_instances = 0; std::vector> m_graph_instance_variables; std::vector> m_graph_instance_constants; // record graph instances with constraint output and objective output struct GraphInstancesInfo { // hash of this graph instance std::vector hashes; // index of this graph instance std::vector instance_indices; size_t n_instances_since_last_aggregation; } nl_constraint_info, nl_objective_info; // length = n_graph_instances struct GraphInstancesGroupInfo { // which group it belongs to std::vector group_indices; // the number in that group std::vector group_orders; } nl_constraint_group_info, nl_objective_group_info; // graph groups struct { Hashmap hash_to_group; size_t n_group = 0; std::vector representative_graph_indices; std::vector> instance_indices; std::vector autodiff_structures; std::vector autodiff_evaluators; // where to store the hessian matrix, each group length = n_instance * local_hessian_nnz std::vector> hessian_indices; } nl_constraint_groups; struct { Hashmap hash_to_group; size_t n_group = 0; std::vector representative_graph_indices; std::vector> instance_indices; std::vector autodiff_structures; std::vector autodiff_evaluators; // where to store the gradient vector, each group length = n_instance * local_jacobian_nnz std::vector> gradient_indices; // where to store the hessian matrix, each group length = n_instance * local_hessian_nnz std::vector> hessian_indices; } nl_objective_groups; int add_graph_index(); void finalize_graph_instance(size_t graph_index, const ExpressionGraph &graph); int aggregate_nl_constraint_groups(); int get_nl_constraint_group_representative(int group_index) const; int aggregate_nl_objective_groups(); int get_nl_objective_group_representative(int group_index) const; void assign_nl_constraint_group_autodiff_structure(int group_index, const AutodiffSymbolicStructure &structure); void assign_nl_constraint_group_autodiff_evaluator( int group_index, const ConstraintAutodiffEvaluator &evaluator); void assign_nl_objective_group_autodiff_structure(int group_index, const AutodiffSymbolicStructure &structure); void assign_nl_objective_group_autodiff_evaluator(int group_index, const ObjectiveAutodiffEvaluator &evaluator); ConstraintIndex add_single_nl_constraint(size_t graph_index, const ExpressionGraph &graph, double lb, double ub); // void clear_nl_objective(); void analyze_structure(); void optimize(); // load current solution as initial guess void load_current_solution(); // set options void set_raw_option_int(const std::string &name, int value); void set_raw_option_double(const std::string &name, double value); void set_raw_option_string(const std::string &name, const std::string &value); /* Members */ size_t n_variables = 0; size_t n_nl_constraints = 0; /* * record the constraint indices mapping from the monotonic one (the order of adding * constraint) to the reordered one (linear, quadratic, NL group 0 -> con0, con1 ,..., conN0, NL * group1 -> con0, con1,..., conN1) */ // this is maintained when adding NL constraint struct ConstraintGraphMembership { // which graph it belongs to int graph; // the rank in that graph int rank; }; // record the graph a nonlinear constraint belongs to std::vector nl_constraint_graph_memberships; // this vector is constructed before optimization // ext means the external monotonic order // int means the internal order that passes to Ipopt std::vector nl_constraint_map_ext2int; // we need a sparse vector to store the gradient std::vector sparse_gradient_values; std::vector sparse_gradient_indices; std::vector m_var_lb, m_var_ub, m_var_init; std::vector m_linear_con_lb, m_linear_con_ub, m_quadratic_con_lb, m_quadratic_con_ub, m_nl_con_lb, m_nl_con_ub, m_con_lb, m_con_ub; Hashmap m_var_names; size_t m_jacobian_nnz = 0; std::vector m_jacobian_rows, m_jacobian_cols; size_t m_hessian_nnz = 0; std::vector m_hessian_rows, m_hessian_cols; Hashmap, int> m_hessian_index_map; LinearEvaluator m_linear_con_evaluator; QuadraticEvaluator m_quadratic_con_evaluator; std::optional m_linear_obj_evaluator; std::optional m_quadratic_obj_evaluator; NonlinearEvaluator m_nl_evaluator; // The options of the Ipopt solver, we cache them before constructing the m_problem Hashmap m_options_int; Hashmap m_options_num; Hashmap m_options_str; IpoptResult m_result; bool m_is_dirty = true; enum ApplicationReturnStatus m_status; std::unique_ptr m_problem = nullptr; }; ================================================ FILE: include/pyoptinterface/knitro_model.hpp ================================================ #pragma once #include #include #include #include #include "solvers/knitro/knitro.h" #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #include "pyoptinterface/cppad_interface.hpp" #define USE_NLMIXIN #include "pyoptinterface/solver_common.hpp" #include "pyoptinterface/dylib.hpp" // Define Knitro C APIs to be dynamically loaded #define APILIST \ B(KN_checkout_license); \ B(KN_release_license); \ B(KN_new_lm); \ B(KN_new); \ B(KN_free); \ B(KN_update); \ B(KN_solve); \ B(KN_get_param_id); \ B(KN_get_param_type); \ B(KN_set_int_param); \ B(KN_set_char_param); \ B(KN_set_double_param); \ B(KN_get_int_param); \ B(KN_get_double_param); \ B(KN_add_var); \ B(KN_add_con); \ B(KN_set_var_lobnd); \ B(KN_set_var_upbnd); \ B(KN_get_var_lobnd); \ B(KN_get_var_upbnd); \ B(KN_set_var_type); \ B(KN_get_var_type); \ B(KN_set_var_name); \ B(KN_get_var_name); \ B(KN_set_con_lobnd); \ B(KN_set_con_upbnd); \ B(KN_get_con_lobnd); \ B(KN_get_con_upbnd); \ B(KN_set_con_name); \ B(KN_get_con_name); \ B(KN_set_obj_goal); \ B(KN_get_obj_goal); \ B(KN_set_var_primal_init_value); \ B(KN_add_obj_constant); \ B(KN_del_obj_constant); \ B(KN_add_obj_linear_struct); \ B(KN_del_obj_linear_struct_all); \ B(KN_add_obj_quadratic_struct); \ B(KN_del_obj_quadratic_struct_all); \ B(KN_chg_obj_linear_term); \ B(KN_add_con_constant); \ B(KN_add_con_linear_struct); \ B(KN_add_con_linear_term); \ B(KN_add_con_quadratic_struct); \ B(KN_add_con_quadratic_term); \ B(KN_chg_con_linear_term); \ B(KN_add_eval_callback); \ B(KN_set_cb_user_params); \ B(KN_set_cb_grad); \ B(KN_set_cb_hess); \ B(KN_del_obj_eval_callback_all); \ B(KN_get_var_primal_value); \ B(KN_get_var_dual_value); \ B(KN_get_con_value); \ B(KN_get_con_dual_value); \ B(KN_get_obj_value); \ B(KN_get_number_iters); \ B(KN_get_mip_number_nodes); \ B(KN_get_mip_relaxation_bnd); \ B(KN_get_mip_rel_gap); \ B(KN_get_solve_time_real); \ B(KN_get_release); namespace knitro { #define B DYLIB_EXTERN_DECLARE APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); bool has_valid_license(); } // namespace knitro struct KNITROFreeProblemT { void operator()(KN_context *kc) const { if (kc != nullptr) { knitro::KN_free(&kc); } } }; struct KNITROFreeLicenseT { void operator()(LM_context *lmc) const { if (lmc != nullptr) { knitro::KN_release_license(&lmc); } } }; enum ObjectiveFlags { OBJ_CONSTANT = 1 << 0, // 0x01 OBJ_LINEAR = 1 << 1, // 0x02 OBJ_QUADRATIC = 1 << 2, // 0x04 OBJ_NONLINEAR = 1 << 3 // 0x08 }; enum ConstraintSenseFlags { CON_LOBND = 1 << 0, // 0x01 CON_UPBND = 1 << 1, // 0x02 }; template struct CallbackPattern { std::vector indexCons; std::vector objGradIndexVars; std::vector jacIndexCons; std::vector jacIndexVars; std::vector hessIndexVars1; std::vector hessIndexVars2; }; using namespace CppAD; template struct CallbackEvaluator { static inline constexpr const char *CLRNG = "cppad"; std::vector indexVars; std::vector indexCons; ADFun fun; /// < CppAD tape. ADFun jfun; /// < CppAD tape for Aggregated Jacobian /// Sparsity patterns sparse_rc> jp; sparse_rc> hp; /// Workspaces for Jacobian and Hessian calculations sparse_jac_work jw; sparse_jac_work hw; /// Temporary vectors for evaluations vector x; vector xw; sparse_rcv, vector> jac; sparse_rcv, vector> hes; void setup() { fun.optimize(); size_t nx = fun.Domain(); size_t ny = fun.Range(); vector dom(nx, true); vector rng(ny, true); fun.subgraph_sparsity(dom, rng, false, jp); ADFun, V> af = fun.base2ad(); vector> jaxw(nx + ny); Independent(jaxw); vector> jax(nx); vector> jaw(ny); vector> jaz(nx); for (size_t i = 0; i < nx; i++) { jax[i] = jaxw[i]; } for (size_t i = 0; i < ny; i++) { jaw[i] = jaxw[nx + i]; } af.Forward(0, jax); jaz = af.Reverse(1, jaw); jfun.Dependent(jaxw, jaz); jfun.optimize(); vector jdom(nx + ny, false); for (size_t i = 0; i < nx; i++) { jdom[i] = true; } vector jrng(nx, true); sparse_rc> hsp; jfun.subgraph_sparsity(jdom, jrng, false, hsp); auto &hrow = hsp.row(); auto &hcol = hsp.col(); for (size_t k = 0; k < hsp.nnz(); k++) { if (hrow[k] <= hcol[k]) { hp.push_back(hrow[k], hcol[k]); } } x.resize(nx); xw.resize(nx + ny); jac = sparse_rcv, vector>(jp); hes = sparse_rcv, vector>(hp); } bool is_objective() const { return indexCons.empty(); } void eval_fun(const V *req_x, V *res_y) { copy(fun.Domain(), req_x, indexVars.data(), x.data()); auto y = fun.Forward(0, x); int mode = is_objective() ? 2 : 0; copy(fun.Range(), y.data(), (const I *)nullptr, res_y, mode); } void eval_jac(const V *req_x, V *res_jac) { copy(fun.Domain(), req_x, indexVars.data(), x.data()); fun.sparse_jac_rev(x, jac, jp, CLRNG, jw); copy(jac.nnz(), jac.val().data(), (const I *)nullptr, res_jac); } void eval_hess(const V *req_x, const V *req_w, V *res_hess) { copy(fun.Domain(), req_x, indexVars.data(), xw.data()); int mode = is_objective() ? 1 : 0; copy(fun.Range(), req_w, indexCons.data(), xw.data() + fun.Domain(), mode); jfun.sparse_jac_rev(xw, hes, hp, CLRNG, hw); copy(hes.nnz(), hes.val().data(), (const I *)nullptr, res_hess); } CallbackPattern get_callback_pattern() const { CallbackPattern p; p.indexCons = indexCons; auto &jrow = jp.row(); auto &jcol = jp.col(); if (indexCons.empty()) { for (size_t k = 0; k < jp.nnz(); k++) { p.objGradIndexVars.push_back(indexVars[jcol[k]]); } } else { for (size_t k = 0; k < jp.nnz(); k++) { p.jacIndexCons.push_back(indexCons[jrow[k]]); p.jacIndexVars.push_back(indexVars[jcol[k]]); } } auto &hrow = hp.row(); auto &hcol = hp.col(); for (size_t k = 0; k < hp.nnz(); k++) { p.hessIndexVars1.push_back(indexVars[hrow[k]]); p.hessIndexVars2.push_back(indexVars[hcol[k]]); } return p; } private: // Copy mode: // - 0: normal copy // - 1: duplicate (copy first element of src to all elements of dst) // - 2: aggregate (sum all elements of src and copy to all elements of dst) static void copy(const size_t n, const V *src, const I *idx, V *dst, int mode = 0) { if (mode == 1) { for (size_t i = 0; i < n; i++) { dst[i] = src[0]; } } else if (mode == 2) { if (n == 0) { return; } dst[0] = src[0]; for (size_t i = 1; i < n; i++) { dst[0] += src[i]; } } else { if (idx == nullptr) { for (size_t i = 0; i < n; i++) { dst[i] = src[i]; } } else { for (size_t i = 0; i < n; i++) { dst[i] = src[idx[i]]; } } } } }; inline bool is_name_empty(const char *name) { return name == nullptr || name[0] == '\0'; } inline int knitro_var_type(VariableDomain domain) { switch (domain) { case VariableDomain::Continuous: return KN_VARTYPE_CONTINUOUS; case VariableDomain::Integer: return KN_VARTYPE_INTEGER; case VariableDomain::Binary: return KN_VARTYPE_BINARY; default: throw std::runtime_error("Unknown variable domain"); } } inline VariableDomain knitro_variable_domain(int var_type) { switch (var_type) { case KN_VARTYPE_CONTINUOUS: return VariableDomain::Continuous; case KN_VARTYPE_INTEGER: return VariableDomain::Integer; case KN_VARTYPE_BINARY: return VariableDomain::Binary; default: throw std::runtime_error("Unknown variable type"); } } inline int knitro_obj_goal(ObjectiveSense sense) { switch (sense) { case ObjectiveSense::Minimize: return KN_OBJGOAL_MINIMIZE; case ObjectiveSense::Maximize: return KN_OBJGOAL_MAXIMIZE; default: throw std::runtime_error("Unknown objective sense"); } } inline ObjectiveSense knitro_obj_sense(int goal) { switch (goal) { case KN_OBJGOAL_MINIMIZE: return ObjectiveSense::Minimize; case KN_OBJGOAL_MAXIMIZE: return ObjectiveSense::Maximize; default: throw std::runtime_error("Unknown objective goal"); } } inline void knitro_throw(int error) { if (error != 0) { throw std::runtime_error(fmt::format("KNITRO error code: {}", error)); } } class KNITROEnv { public: KNITROEnv(bool empty = false); KNITROEnv(const KNITROEnv &) = delete; KNITROEnv &operator=(const KNITROEnv &) = delete; KNITROEnv(KNITROEnv &&) = default; KNITROEnv &operator=(KNITROEnv &&) = default; void start(); bool empty() const; std::shared_ptr get_lm() const; void close(); private: void _check_error(int code) const; std::shared_ptr m_lm = nullptr; }; class KNITROModel : public OnesideLinearConstraintMixin, public TwosideLinearConstraintMixin, public OnesideQuadraticConstraintMixin, public TwosideQuadraticConstraintMixin, public TwosideNLConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { public: // Constructor/Init/Close KNITROModel(); KNITROModel(const KNITROEnv &env); KNITROModel(const KNITROModel &) = delete; KNITROModel &operator=(const KNITROModel &) = delete; KNITROModel(KNITROModel &&) = default; KNITROModel &operator=(KNITROModel &&) = default; void init(); void init(const KNITROEnv &env); void close(); // Model information double get_infinity() const; std::string get_solver_name() const; std::string get_release() const; // Variable functions VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous, double lb = -KN_INFINITY, double ub = KN_INFINITY, const char *name = nullptr); void delete_variable(const VariableIndex &variable); double get_variable_lb(const VariableIndex &variable) const; double get_variable_ub(const VariableIndex &variable) const; void set_variable_lb(const VariableIndex &variable, double lb); void set_variable_ub(const VariableIndex &variable, double ub); void set_variable_bounds(const VariableIndex &variable, double lb, double ub); double get_variable_value(const VariableIndex &variable) const; void set_variable_start(const VariableIndex &variable, double start); std::string get_variable_name(const VariableIndex &variable) const; void set_variable_name(const VariableIndex &variable, const std::string &name); void set_variable_domain(const VariableIndex &variable, VariableDomain domain); VariableDomain get_variable_domain(const VariableIndex &variable) const; double get_variable_rc(const VariableIndex &variable) const; std::string pprint_variable(const VariableIndex &variable) const; // Constraint functions ConstraintIndex add_linear_constraint(const ScalarAffineFunction &f, ConstraintSense sense, double rhs, const char *name = nullptr); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &f, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &f, ConstraintSense sense, double rhs, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &f, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated); ConstraintIndex add_single_nl_constraint(ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_single_nl_constraint_sense_rhs(ExpressionGraph &graph, const ExpressionHandle &result, ConstraintSense sense, double rhs, const char *name = nullptr); void delete_constraint(const ConstraintIndex &constraint); void set_constraint_name(const ConstraintIndex &constraint, const std::string &name); std::string get_constraint_name(const ConstraintIndex &constraint) const; double get_constraint_primal(const ConstraintIndex &constraint) const; double get_constraint_dual(const ConstraintIndex &constraint) const; void set_normalized_rhs(const ConstraintIndex &constraint, double rhs); double get_normalized_rhs(const ConstraintIndex &constraint) const; void set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double coefficient); // Objective functions void set_objective(const ScalarAffineFunction &f, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &f, ObjectiveSense sense); void set_objective(const ExprBuilder &expr, ObjectiveSense sense); void add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result); double get_obj_value() const; void set_obj_sense(ObjectiveSense sense); ObjectiveSense get_obj_sense() const; void set_objective_coefficient(const VariableIndex &variable, double coefficient); // Solve functions void optimize(); // Solve information size_t get_number_iterations() const; size_t get_mip_node_count() const; double get_obj_bound() const; double get_mip_relative_gap() const; double get_solve_time() const; // Model state bool dirty() const; bool empty() const; // Solve status int get_solve_status() const; // Parameter management template void set_raw_parameter(const std::string &name, T value) { int param_id = _get_value(knitro::KN_get_param_id, name.c_str()); set_raw_parameter(param_id, value); } template void set_raw_parameter(int param_id, T value) { if constexpr (std::is_same_v) { _set_value(knitro::KN_set_int_param, param_id, value); } else if constexpr (std::is_same_v) { _set_value(knitro::KN_set_double_param, param_id, value); } else if constexpr (std::is_same_v || std::is_same_v) { if constexpr (std::is_same_v) { _set_value(knitro::KN_set_char_param, param_id, value.c_str()); } else { _set_value(knitro::KN_set_char_param, param_id, value); } } else { static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be int, double, std::string, or const char*"); } } template T get_raw_parameter(const std::string &name) { int param_id = _get_value(knitro::KN_get_param_id, name.c_str()); return get_raw_parameter(param_id); } template T get_raw_parameter(int param_id) { if constexpr (std::is_same_v) { return _get_value(knitro::KN_get_int_param, param_id); } else if constexpr (std::is_same_v) { return _get_value(knitro::KN_get_double_param, param_id); } else { static_assert(std::is_same_v || std::is_same_v, "T must be int or double for get_raw_parameter"); } } // Internal helpers void _check_error(int error) const; void _mark_dirty(); void _unmark_dirty(); void _check_dirty() const; KNINT _variable_index(const VariableIndex &variable) const; KNINT _constraint_index(const ConstraintIndex &constraint) const; size_t get_num_vars() const; size_t get_num_cons(std::optional type = std::nullopt) const; private: // Member variables size_t m_n_vars = 0; std::unordered_map m_n_cons_map; std::shared_ptr m_lm = nullptr; std::unique_ptr m_kc = nullptr; std::unordered_map>> m_soc_aux_cons; std::unordered_map m_con_sense_flags; uint8_t m_obj_flag = 0; struct Outputs { std::vector objective_outputs; std::vector constraint_outputs; std::vector constraints; }; using Evaluator = CallbackEvaluator; std::unordered_map m_pending_outputs; std::vector> m_evaluators; bool m_has_pending_callbacks = false; int m_solve_status = 0; bool m_is_dirty = true; private: void _init(); void _reset_state(); std::tuple _sense_to_interval(ConstraintSense sense, double rhs); void _update_con_sense_flags(const ConstraintIndex &constraint, ConstraintSense sense); void _set_linear_constraint(const ConstraintIndex &constraint, const ScalarAffineFunction &f); void _set_quadratic_constraint(const ConstraintIndex &constraint, const ScalarQuadraticFunction &f); void _set_second_order_cone_constraint(const ConstraintIndex &constraint, const Vector &variables); void _set_second_order_cone_constraint_rotated(const ConstraintIndex &constraint, const Vector &variables); void _set_linear_objective(const ScalarAffineFunction &f); void _set_quadratic_objective(const ScalarQuadraticFunction &f); void _reset_objective(); void _add_graph(ExpressionGraph &graph); void _add_pending_callbacks(); void _add_callbacks(const ExpressionGraph &graph, const Outputs &outputs); void _add_callback(const ExpressionGraph &graph, const std::vector &outputs, const std::vector &constraints); void _register_callback(Evaluator *evaluator); void _update(); void _pre_solve(); void _solve(); void _post_solve(); template ConstraintIndex _add_constraint_impl(ConstraintType type, const std::tuple &interval, const char *name, const F &setter) { KNINT indexCon; int error = knitro::KN_add_con(m_kc.get(), &indexCon); _check_error(error); IndexT index = indexCon; ConstraintIndex constraint(type, index); double lb = std::get<0>(interval); double ub = std::get<1>(interval); error = knitro::KN_set_con_lobnd(m_kc.get(), indexCon, lb); _check_error(error); error = knitro::KN_set_con_upbnd(m_kc.get(), indexCon, ub); _check_error(error); setter(constraint); if (!is_name_empty(name)) { error = knitro::KN_set_con_name(m_kc.get(), indexCon, name); _check_error(error); } m_con_sense_flags[indexCon] = CON_UPBND; auto it = m_n_cons_map.find(type); if (it != m_n_cons_map.end()) { it->second++; } else { m_n_cons_map[type] = 1; } m_is_dirty = true; return constraint; } template ConstraintIndex _add_constraint_with_sense(const S &function, ConstraintSense sense, double rhs, const char *name, const F &add) { auto interval = _sense_to_interval(sense, rhs); auto constraint = add(function, interval, name); _update_con_sense_flags(constraint, sense); return constraint; } template void _set_objective_impl(ObjectiveSense sense, const F &setter) { _reset_objective(); setter(); set_obj_sense(sense); m_is_dirty = true; } template using Getter = std::function; template using Setter = std::function; template V _get_value(Getter get) const { V value; int error = get(m_kc.get(), &value); _check_error(error); return value; } template void _set_value(Setter set, V value) { int error = set(m_kc.get(), value); _check_error(error); } template using GetterMap = std::function; template using SetterMap = std::function; template V _get_value(GetterMap get, K key) const { V value; int error = get(m_kc.get(), key, &value); _check_error(error); return value; } template void _set_value(SetterMap set, K key, V value) { int error = set(m_kc.get(), key, value); _check_error(error); } template std::string _get_name(F get, K key, const char *prefix) const { char name[1024]; name[0] = '\0'; int error = get(m_kc.get(), key, 1024, name); _check_error(error); if (name[0] != '\0') { return std::string(name); } else { return fmt::format("{}{}", prefix, key); } } template KNINT _get_index(const I &index) const { return index.index; } }; ================================================ FILE: include/pyoptinterface/mosek_model.hpp ================================================ #pragma once #include #ifdef _MSC_VER #include "solvers/mosek/mosek_win.h" #else #include "solvers/mosek/mosek_linux.h" #endif #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #include "pyoptinterface/solver_common.hpp" #include "pyoptinterface/dylib.hpp" #define APILIST \ B(MSK_getcodedesc); \ B(MSK_makeemptytask); \ B(MSK_deletetask); \ B(MSK_writedata); \ B(MSK_writesolution); \ B(MSK_appendvars); \ B(MSK_getnumvar); \ B(MSK_putvartype); \ B(MSK_putvarbound); \ B(MSK_putvarname); \ B(MSK_removevars); \ B(MSK_getxxslice); \ B(MSK_appendcons); \ B(MSK_getnumcon); \ B(MSK_putarow); \ B(MSK_putconbound); \ B(MSK_putconname); \ B(MSK_putqconk); \ B(MSK_getnumafe); \ B(MSK_appendafes); \ B(MSK_putafefentrylist); \ B(MSK_appendquadraticconedomain); \ B(MSK_appendrquadraticconedomain); \ B(MSK_appendprimalexpconedomain); \ B(MSK_appenddualexpconedomain); \ B(MSK_appendacc); \ B(MSK_putaccname); \ B(MSK_getaccn); \ B(MSK_getaccafeidxlist); \ B(MSK_appendrdomain); \ B(MSK_putacc); \ B(MSK_removecons); \ B(MSK_putqobj); \ B(MSK_putcslice); \ B(MSK_putcfix); \ B(MSK_optimize); \ B(MSK_whichparam); \ B(MSK_putnaintparam); \ B(MSK_putnadouparam); \ B(MSK_putnastrparam); \ B(MSK_getnaintparam); \ B(MSK_getnadouparam); \ B(MSK_getnastrparam); \ B(MSK_getnaintinf); \ B(MSK_getnadouinf); \ B(MSK_getprosta); \ B(MSK_getsolsta); \ B(MSK_getprimalobj); \ B(MSK_getdualobj); \ B(MSK_linkfunctotaskstream); \ B(MSK_getvarname); \ B(MSK_getvartype); \ B(MSK_getvarbound); \ B(MSK_putxxslice); \ B(MSK_getxcslice); \ B(MSK_getyslice); \ B(MSK_getslxslice); \ B(MSK_getsuxslice); \ B(MSK_getconnamelen); \ B(MSK_getconname); \ B(MSK_getobjsense); \ B(MSK_putobjsense); \ B(MSK_getconbound); \ B(MSK_getaij); \ B(MSK_putaij); \ B(MSK_getcj); \ B(MSK_putcj); \ B(MSK_getversion); \ B(MSK_solutiondef); \ B(MSK_makeenv); \ B(MSK_deleteenv); \ B(MSK_putlicensecode); namespace mosek { #define B(f) extern decltype(&::f) f APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); } // namespace mosek class MOSEKEnv { public: MOSEKEnv(); ~MOSEKEnv(); void close(); void putlicensecode(const std::vector &code); private: MSKenv_t m_env; friend class MOSEKModel; }; struct MOSEKfreemodelT { void operator()(MSKtask *model) const { mosek::MSK_deletetask(&model); }; }; using MOSEKLoggingCallback = std::function; struct MOSEKLoggingCallbackUserdata { MOSEKLoggingCallback callback; }; class MOSEKModel : public OnesideLinearConstraintMixin, public TwosideLinearConstraintMixin, public OnesideQuadraticConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { public: bool m_is_dirty = true; MOSEKModel() = default; MOSEKModel(const MOSEKEnv &env); void init(const MOSEKEnv &env); void close(); void write(const std::string &filename); VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous, double lb = -MSK_INFINITY, double ub = MSK_INFINITY, const char *name = nullptr); /*VariableIndex add_variables(int N, VariableDomain domain = VariableDomain::Continuous, double lb = -MSK_INFINITY, double ub = MSK_INFINITY);*/ void delete_variable(const VariableIndex &variable); void delete_variables(const Vector &variables); bool is_variable_active(const VariableIndex &variable); double get_variable_value(const VariableIndex &variable); std::string pprint_variable(const VariableIndex &variable); void set_variable_bounds(const VariableIndex &variable, double lb, double ub); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); std::vector add_variables_as_afe(const Vector &variables); ConstraintIndex add_variables_in_cone_constraint(const Vector &variables, MSKint64t domain_index, ConstraintType type, const char *name); ConstraintIndex add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated = false); ConstraintIndex add_exp_cone_constraint(const Vector &variables, const char *name, bool dual = false); void delete_constraint(const ConstraintIndex &constraint); bool is_constraint_active(const ConstraintIndex &constraint); void _set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic); void set_objective(const ScalarAffineFunction &function, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense); void set_objective(const ExprBuilder &function, ObjectiveSense sense); int optimize(); void *get_raw_model(); std::string version_string(); // solution MSKsoltypee get_current_solution(); std::optional select_available_solution_after_optimization(); // parameter int raw_parameter_type(const char *name); void set_raw_parameter_int(const char *param_name, int value); void set_raw_parameter_double(const char *param_name, double value); void set_raw_parameter_string(const char *param_name, const char *value); int get_raw_parameter_int(const char *param_name); double get_raw_parameter_double(const char *param_name); std::string get_raw_parameter_string(const char *param_name); // information int get_raw_information_int(const char *attr_name); double get_raw_information_double(const char *attr_name); MSKint32t getnumvar(); MSKint32t getnumcon(); int getprosta(); int getsolsta(); double getprimalobj(); double getdualobj(); void disable_log(); // Accessing information of problem std::string get_variable_name(const VariableIndex &variable); void set_variable_name(const VariableIndex &variable, const char *name); VariableDomain get_variable_type(const VariableIndex &variable); void set_variable_type(const VariableIndex &variable, VariableDomain domain); double get_variable_lower_bound(const VariableIndex &variable); double get_variable_upper_bound(const VariableIndex &variable); void set_variable_lower_bound(const VariableIndex &variable, double lb); void set_variable_upper_bound(const VariableIndex &variable, double ub); void set_variable_primal(const VariableIndex &variable, double primal); // reduced cost of variable double get_variable_dual(const VariableIndex &variable); double get_constraint_primal(const ConstraintIndex &constraint); double get_constraint_dual(const ConstraintIndex &constraint); std::string get_constraint_name(const ConstraintIndex &constraint); void set_constraint_name(const ConstraintIndex &constraint, const char *name); ObjectiveSense get_obj_sense(); void set_obj_sense(ObjectiveSense sense); // Modifications of model // 1. set/get RHS of a constraint double get_normalized_rhs(const ConstraintIndex &constraint); void set_normalized_rhs(const ConstraintIndex &constraint, double value); // 2. set/get coefficient of variable in constraint double get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable); void set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value); // 3. set/get linear coefficient of variable in objective double get_objective_coefficient(const VariableIndex &variable); void set_objective_coefficient(const VariableIndex &variable, double value); MSKint32t _variable_index(const VariableIndex &variable); MSKint32t _checked_variable_index(const VariableIndex &variable); MSKint32t _constraint_index(const ConstraintIndex &constraint); MSKint32t _checked_constraint_index(const ConstraintIndex &constraint); // Control logging void set_logging(const MOSEKLoggingCallback &callback); MOSEKLoggingCallbackUserdata m_logging_callback_userdata; private: MonotoneIndexer m_variable_index; MonotoneIndexer m_linear_quadratic_constraint_index; // ACC cannot be removed from the model, so we just keeps track of the state whether it is // deleted If a constraint is deleted, we will not remove it from the model, but just mark it as // deleted and set the domain to R^n (i.e. no constraint) std::vector m_acc_index; // Mosek does not discriminate between integer variable and binary variable // So we need to keep track of binary variables Hashset binary_variables; // Cache current available solution after optimization std::optional m_soltype; /* MOSEK part */ std::unique_ptr m_model; }; ================================================ FILE: include/pyoptinterface/nleval.hpp ================================================ #pragma once #include #include #include "pyoptinterface/core.hpp" #include "pyoptinterface/nlexpr.hpp" enum class HessianSparsityType { Upper, Lower }; struct AutodiffSymbolicStructure { size_t nx = 0, np = 0, ny = 0; std::vector m_jacobian_rows, m_jacobian_cols; size_t m_jacobian_nnz = 0; std::vector m_hessian_rows, m_hessian_cols; size_t m_hessian_nnz = 0; bool has_parameter = false; bool has_jacobian = false; bool has_hessian = false; }; // define the jit-compiled function pointer using f_funcptr = void (*)(const double *x, const double *p, double *y, const int *xi); using jacobian_funcptr = void (*)(const double *x, const double *p, double *jacobian, const int *xi); using additive_grad_funcptr = void (*)(const double *x, const double *p, double *grad, const int *xi, const int *gradi); using hessian_funcptr = void (*)(const double *x, const double *p, const double *w, double *hessian, const int *xi, const int *hessiani); // no parameter version using f_funcptr_noparam = void (*)(const double *x, double *y, const int *xi); using jacobian_funcptr_noparam = void (*)(const double *x, double *jacobian, const int *xi); using additive_grad_funcptr_noparam = void (*)(const double *x, double *grad, const int *xi, const int *gradi); using hessian_funcptr_noparam = void (*)(const double *x, const double *w, double *hessian, const int *xi, const int *hessiani); struct ConstraintAutodiffEvaluator { union { f_funcptr p = nullptr; f_funcptr_noparam nop; } f_eval; union { jacobian_funcptr p = nullptr; jacobian_funcptr_noparam nop; } jacobian_eval; union { hessian_funcptr p = nullptr; hessian_funcptr_noparam nop; } hessian_eval; ConstraintAutodiffEvaluator() = default; ConstraintAutodiffEvaluator(bool has_parameter, uintptr_t fp, uintptr_t jp, uintptr_t hp); }; struct ObjectiveAutodiffEvaluator { union { f_funcptr p = nullptr; f_funcptr_noparam nop; } f_eval; union { additive_grad_funcptr p = nullptr; additive_grad_funcptr_noparam nop; } grad_eval; union { hessian_funcptr p = nullptr; hessian_funcptr_noparam nop; } hessian_eval; ObjectiveAutodiffEvaluator() = default; ObjectiveAutodiffEvaluator(bool has_parameter, uintptr_t fp, uintptr_t ajp, uintptr_t hp); }; #define restrict __restrict struct LinearEvaluator { int n_constraints = 0; std::vector coefs; std::vector indices; std::vector constant_values; std::vector constant_indices; std::vector constraint_intervals = {0}; void add_row(const ScalarAffineFunction &f); void eval_function(const double *restrict x, double *restrict f); void analyze_jacobian_structure(size_t &global_jacobian_nnz, std::vector &global_jacobian_rows, std::vector &global_jacobian_cols) const; void eval_jacobian(const double *restrict x, double *restrict jacobian) const; }; struct QuadraticEvaluator { int n_constraints = 0; std::vector diag_coefs; std::vector diag_indices; std::vector diag_intervals = {0}; std::vector offdiag_coefs; std::vector offdiag_rows; std::vector offdiag_cols; std::vector offdiag_intervals = {0}; std::vector linear_coefs; std::vector linear_indices; std::vector linear_intervals = {0}; std::vector linear_constant_values; std::vector linear_constant_indices; int jacobian_nnz = 0; // This is the constant part // jacobian_constant.size() = jacobian_nnz std::vector jacobian_constant; std::vector jacobian_variable_indices; std::vector jacobian_constraint_intervals = {0}; // jacobian_diag_indices.size() = diag_indices.size() std::vector jacobian_diag_indices; // jacobian_offdiag_row_indices.size() = offdiag_rows.size() std::vector jacobian_offdiag_row_indices; std::vector jacobian_offdiag_col_indices; // Hessian // = diag_coefs.size() std::vector hessian_diag_indices; // = offdiag_coefs.size() std::vector hessian_offdiag_indices; void add_row(const ScalarQuadraticFunction &f); void eval_function(const double *restrict x, double *restrict f) const; void analyze_jacobian_structure(size_t row_base, size_t &global_jacobian_nnz, std::vector &global_jacobian_rows, std::vector &global_jacobian_cols) const; void eval_jacobian(const double *restrict x, double *restrict jacobian) const; void analyze_hessian_structure(size_t &global_hessian_nnz, std::vector &global_hessian_rows, std::vector &global_hessian_cols, Hashmap, int> &hessian_index_map, HessianSparsityType hessian_type); void eval_lagrangian_hessian(const double *restrict lambda, double *restrict hessian) const; }; struct NonlinearEvaluator { // How many graph instances are there size_t n_graph_instances = 0; // record the inputs of graph instances struct GraphInput { std::vector variables; std::vector constants; }; std::vector graph_inputs; // record graph instances with constraint output and objective output struct GraphHash { // hash of this graph instance uint64_t hash; // index of this graph instance int index; }; struct GraphHashes { std::vector hashes; size_t n_hashes_since_last_aggregation = 0; } constraint_graph_hashes, objective_graph_hashes; // length = n_graph_instances // record which group this graph instance belongs to struct GraphGroupMembership { // which group it belongs to int group; // the rank in that group int rank; }; std::vector constraint_group_memberships, objective_group_memberships; // record which index of constraint this graph starts std::vector constraint_indices_offsets; // graph groups struct ConstraintGraphGroup { std::vector instance_indices; AutodiffSymbolicStructure autodiff_structure; ConstraintAutodiffEvaluator autodiff_evaluator; // where to store the hessian matrix // length = instance_indices.size() * hessian_nnz std::vector hessian_indices; }; std::vector constraint_groups; Hashmap hash_to_constraint_group; struct ObjectiveGraphGroup { std::vector instance_indices; AutodiffSymbolicStructure autodiff_structure; ObjectiveAutodiffEvaluator autodiff_evaluator; // where to store the gradient vector // length = instance_indices.size() * jacobian_nnz std::vector gradient_indices; // where to store the hessian matrix // length = instance_indices.size() * hessian_nnz std::vector hessian_indices; }; std::vector objective_groups; Hashmap hash_to_objective_group; int add_graph_instance(); void finalize_graph_instance(size_t graph_index, const ExpressionGraph &graph); int aggregate_constraint_groups(); int get_constraint_group_representative(int group_index) const; int aggregate_objective_groups(); int get_objective_group_representative(int group_index) const; void assign_constraint_group_autodiff_structure(int group_index, const AutodiffSymbolicStructure &structure); void assign_constraint_group_autodiff_evaluator(int group_index, const ConstraintAutodiffEvaluator &evaluator); void assign_objective_group_autodiff_structure(int group_index, const AutodiffSymbolicStructure &structure); void assign_objective_group_autodiff_evaluator(int group_index, const ObjectiveAutodiffEvaluator &evaluator); void calculate_constraint_graph_instances_offset(); // functions to evaluate the nonlinear constraints and objectives // f void eval_constraints(const double *restrict x, double *restrict f) const; double eval_objective(const double *restrict x) const; // first order derivative void analyze_constraints_jacobian_structure(size_t row_base, size_t &global_jacobian_nnz, std::vector &global_jacobian_rows, std::vector &global_jacobian_cols); void analyze_objective_gradient_structure(std::vector &global_gradient_cols, Hashmap &sparse_gradient_map); void eval_constraints_jacobian(const double *restrict x, double *restrict jacobian) const; void eval_objective_gradient(const double *restrict x, double *restrict grad_f) const; // second order derivative void analyze_constraints_hessian_structure( size_t &global_hessian_nnz, std::vector &global_hessian_rows, std::vector &global_hessian_cols, Hashmap, int> &hessian_index_map, HessianSparsityType hessian_type); void analyze_objective_hessian_structure(size_t &global_hessian_nnz, std::vector &global_hessian_rows, std::vector &global_hessian_cols, Hashmap, int> &hessian_index_map, HessianSparsityType hessian_type); void eval_lagrangian_hessian(const double *restrict x, const double *restrict lambda, const double sigma, double *restrict hessian) const; }; ================================================ FILE: include/pyoptinterface/nlexpr.hpp ================================================ #pragma once #include #include #include "ankerl/unordered_dense.h" #include "core.hpp" using NodeId = uint32_t; using EntityId = int; using VariableNode = EntityId; using ConstantNode = double; using ParameterNode = EntityId; enum class ArrayType { Constant, Variable, Parameter, Unary, Binary, Ternary, Nary }; enum class UnaryOperator { Neg, Sin, Cos, Tan, Asin, Acos, Atan, Abs, Sqrt, Exp, Log, Log10 }; enum class BinaryOperator { Sub, Div, Pow, // compare LessThan, LessEqual, Equal, NotEqual, GreaterEqual, GreaterThan, // Compatibility issue where some solvers only accepts two-arg multiplication Add2, Mul2 }; bool is_binary_compare_op(BinaryOperator op); enum class TernaryOperator { IfThenElse, }; enum class NaryOperator { Add, Mul, }; std::string unary_operator_to_string(UnaryOperator op); std::string binary_operator_to_string(BinaryOperator op); std::string ternary_operator_to_string(TernaryOperator op); std::string nary_operator_to_string(NaryOperator op); struct ExpressionHandle { ArrayType array; NodeId id; bool operator==(const ExpressionHandle &x) const; ExpressionHandle() = default; ExpressionHandle(ArrayType array, NodeId id) : array(array), id(id) { } std::string to_string() const; }; template <> struct ankerl::unordered_dense::hash { using is_avalanching = void; [[nodiscard]] auto operator()(ExpressionHandle const &x) const noexcept -> uint64_t { static_assert(std::has_unique_object_representations_v); return detail::wyhash::hash(&x, sizeof(x)); } }; struct UnaryNode { UnaryOperator op; ExpressionHandle operand; UnaryNode(UnaryOperator op, ExpressionHandle operand) : op(op), operand(operand) { } }; struct BinaryNode { BinaryOperator op; ExpressionHandle left; ExpressionHandle right; BinaryNode(BinaryOperator op, ExpressionHandle left, ExpressionHandle right) : op(op), left(left), right(right) { } }; struct TernaryNode { TernaryOperator op; ExpressionHandle left; ExpressionHandle middle; ExpressionHandle right; TernaryNode(TernaryOperator op, ExpressionHandle left, ExpressionHandle middle, ExpressionHandle right) : op(op), left(left), middle(middle), right(right) { } }; struct NaryNode { NaryOperator op; std::vector operands; NaryNode(NaryOperator op, const std::vector &operands) : op(op), operands(operands) { } }; struct ExpressionGraph { Hashmap m_variable_index_map; std::vector m_variables; std::vector m_constants; std::vector m_parameters; std::vector m_unaries; std::vector m_binaries; std::vector m_ternaries; std::vector m_naries; std::vector m_constraint_outputs; std::vector m_objective_outputs; ExpressionGraph() = default; std::string to_string() const; size_t n_variables() const; size_t n_constants() const; size_t n_parameters() const; ExpressionHandle add_variable(EntityId id); ExpressionHandle add_constant(double value); ExpressionHandle add_parameter(EntityId id); ExpressionHandle add_unary(UnaryOperator op, ExpressionHandle operand); ExpressionHandle add_binary(BinaryOperator op, ExpressionHandle left, ExpressionHandle right); ExpressionHandle add_ternary(TernaryOperator op, ExpressionHandle left, ExpressionHandle middle, ExpressionHandle right); ExpressionHandle add_nary(NaryOperator op, const std::vector &operands); ExpressionHandle add_repeat_nary(NaryOperator op, ExpressionHandle operand, int N); void append_nary(const ExpressionHandle &expression, const ExpressionHandle &operand); NaryOperator get_nary_operator(const ExpressionHandle &expression) const; void add_constraint_output(const ExpressionHandle &expression); void add_objective_output(const ExpressionHandle &expression); bool has_constraint_output() const; bool has_objective_output() const; // Merge VariableIndex/ScalarAffineFunction/ScalarQuadraticFunction/ExprBuilder into // ExpressionGraph ExpressionHandle merge_variableindex(const VariableIndex &v); ExpressionHandle merge_scalaraffinefunction(const ScalarAffineFunction &f); ExpressionHandle merge_scalarquadraticfunction(const ScalarQuadraticFunction &f); ExpressionHandle merge_exprbuilder(const ExprBuilder &expr); // recognize compare expression bool is_compare_expression(const ExpressionHandle &expr) const; // tag the structure uint64_t main_structure_hash() const; uint64_t constraint_structure_hash(uint64_t hash) const; uint64_t objective_structure_hash(uint64_t hash) const; }; void unpack_comparison_expression(ExpressionGraph &graph, const ExpressionHandle &expr, ExpressionHandle &real_expr, double &lb, double &ub); ================================================ FILE: include/pyoptinterface/solver_common.hpp ================================================ #pragma once #include #include #include #include #include #include "fmt/format.h" #include "fmt/ranges.h" #include "pyoptinterface/core.hpp" template concept OnesideLinearConstraintMixinConcept = requires(T &model) { { model.add_linear_constraint(ScalarAffineFunction(), ConstraintSense::Equal, 0.0, "") } -> std::same_as; }; template class OnesideLinearConstraintMixin { private: T *get_base() { static_assert(OnesideLinearConstraintMixinConcept); return static_cast(this); } public: ConstraintIndex add_linear_constraint_from_var(const VariableIndex &variable, ConstraintSense sense, CoeffT rhs, const char *name = nullptr) { ScalarAffineFunction f(variable); return get_base()->add_linear_constraint(f, sense, rhs, name); } ConstraintIndex add_linear_constraint_from_expr(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr) { if (function.degree() >= 2) { throw std::runtime_error("add_linear_constraint expects linear expression but receives " "a quadratic expression."); } ScalarAffineFunction f(function); return get_base()->add_linear_constraint(f, sense, rhs, name); } }; template concept TwosideLinearConstraintMixinConcept = requires(T &model) { { model.add_linear_constraint(ScalarAffineFunction(), std::make_tuple(0.0, 1.0), "") } -> std::same_as; }; template class TwosideLinearConstraintMixin { private: T *get_base() { static_assert(TwosideLinearConstraintMixinConcept); return static_cast(this); } public: ConstraintIndex add_linear_interval_constraint_from_var( const VariableIndex &variable, const std::tuple &interval, const char *name = nullptr) { ScalarAffineFunction f(variable); return get_base()->add_linear_constraint(f, interval, name); } ConstraintIndex add_linear_interval_constraint_from_expr( const ExprBuilder &function, const std::tuple &interval, const char *name = nullptr) { ScalarAffineFunction f(function); return get_base()->add_linear_constraint(f, interval, name); } }; template concept OnesideQuadraticConstraintMixinConcept = requires(T &model) { { model.add_quadratic_constraint(ScalarQuadraticFunction(), ConstraintSense::Equal, 0.0, "") } -> std::same_as; }; template class OnesideQuadraticConstraintMixin { private: T *get_base() { static_assert(OnesideQuadraticConstraintMixinConcept); return static_cast(this); } public: ConstraintIndex add_quadratic_constraint_from_var(const VariableIndex &variable, ConstraintSense sense, CoeffT rhs, const char *name = nullptr) { ScalarQuadraticFunction f(variable); return get_base()->add_quadratic_constraint(f, sense, rhs, name); } ConstraintIndex add_quadratic_constraint_from_saf(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr) { ScalarQuadraticFunction f(function); return get_base()->add_quadratic_constraint(f, sense, rhs, name); } ConstraintIndex add_quadratic_constraint_from_expr(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr) { ScalarQuadraticFunction f(function); return get_base()->add_quadratic_constraint(f, sense, rhs, name); } }; template concept TwosideQuadraticConstraintMixinConcept = requires(T &model) { { model.add_quadratic_constraint(ScalarQuadraticFunction(), std::make_tuple(0.0, 1.0), "") } -> std::same_as; }; template class TwosideQuadraticConstraintMixin { private: T *get_base() { static_assert(TwosideQuadraticConstraintMixinConcept); return static_cast(this); } public: ConstraintIndex add_quadratic_interval_constraint_from_var( const VariableIndex &variable, const std::tuple &interval, const char *name = nullptr) { ScalarQuadraticFunction f(variable); return get_base()->add_quadratic_constraint(f, interval, name); } ConstraintIndex add_quadratic_interval_constraint_from_expr( const ExprBuilder &function, const std::tuple &interval, const char *name = nullptr) { ScalarQuadraticFunction f(function); return get_base()->add_quadratic_constraint(f, interval, name); } }; #ifdef USE_NLMIXIN #include "pyoptinterface/nlexpr.hpp" template concept TwosideNLConstraintMixinConcept = requires(T &model) { { model.add_single_nl_constraint(std::declval(), ExpressionHandle(), std::make_tuple(0.0, 1.0), "") } -> std::same_as; { model.get_infinity() } -> std::convertible_to; }; template class TwosideNLConstraintMixin { private: T *get_base() { static_assert(TwosideNLConstraintMixinConcept); return static_cast(this); } public: ConstraintIndex add_single_nl_constraint_sense_rhs(ExpressionGraph &graph, const ExpressionHandle &result, ConstraintSense sense, CoeffT rhs, const char *name = nullptr) { double infinity = get_base()->get_infinity(); double lb, ub; if (sense == ConstraintSense::Equal) { lb = rhs; ub = rhs; } else if (sense == ConstraintSense::LessEqual) { lb = -infinity; ub = rhs; } else if (sense == ConstraintSense::GreaterEqual) { lb = rhs; ub = infinity; } return get_base()->add_single_nl_constraint(graph, result, {lb, ub}, name); } ConstraintIndex add_single_nl_constraint_from_comparison(ExpressionGraph &graph, const ExpressionHandle &expr, const char *name) { ExpressionHandle real_expr; double lb = -get_base()->get_infinity(), ub = get_base()->get_infinity(); unpack_comparison_expression(graph, expr, real_expr, lb, ub); auto constraint = get_base()->add_single_nl_constraint(graph, real_expr, {lb, ub}, name); return constraint; } }; #endif template concept GetValueMixinConcept = requires(T &model) { { model.get_variable_value(VariableIndex()) } -> std::convertible_to; }; template class GetValueMixin { private: T *get_base() { static_assert(GetValueMixinConcept); return static_cast(this); } public: double get_expression_value(const ScalarAffineFunction &function) { T *model = get_base(); auto N = function.size(); double value = 0.0; for (auto i = 0; i < N; ++i) { value += function.coefficients[i] * model->get_variable_value(function.variables[i]); } value += function.constant.value_or(0.0); return value; } double get_expression_value(const ScalarQuadraticFunction &function) { T *model = get_base(); auto N = function.size(); double value = 0.0; for (auto i = 0; i < N; ++i) { auto var1 = function.variable_1s[i]; auto var2 = function.variable_2s[i]; auto coef = function.coefficients[i]; auto v1 = model->get_variable_value(var1); if (var1 == var2) { value += coef * v1 * v1; } else { auto v2 = model->get_variable_value(var2); value += coef * v1 * v2; } } if (function.affine_part) { auto affine_value = model->get_expression_value(function.affine_part.value()); value += affine_value; } return value; } double get_expression_value(const ExprBuilder &function) { T *model = get_base(); double value = 0.0; for (const auto &[varpair, coef] : function.quadratic_terms) { auto var1 = varpair.var_1; auto var2 = varpair.var_2; auto v1 = model->get_variable_value(var1); if (var1 == var2) { value += coef * v1 * v1; } else { auto v2 = model->get_variable_value(var2); value += coef * v1 * v2; } } for (const auto &[var, coef] : function.affine_terms) { value += coef * model->get_variable_value(var); } value += function.constant_term.value_or(0.0); return value; } }; template concept PPrintMixinConcept = requires(T &model) { { model.pprint_variable(VariableIndex()) } -> std::convertible_to; }; template class PPrintMixin { private: T *get_base() { static_assert(PPrintMixinConcept); return static_cast(this); } public: std::string pprint_expression(const ScalarAffineFunction &function, int precision = 4) { T *model = get_base(); auto N = function.size(); std::vector terms; terms.reserve(N + 1); for (auto i = 0; i < N; ++i) { auto &coef = function.coefficients[i]; std::string var_str = model->pprint_variable(function.variables[i]); std::string term; if (coef > 0) { term = fmt::format("{:.{}g}*{}", coef, precision, var_str); } else if (coef < 0) { term = fmt::format("({:.{}g})*{}", coef, precision, var_str); } terms.push_back(term); } if (function.constant) { terms.push_back(fmt::format("{:.{}g}", function.constant.value(), precision)); } return fmt::format("{}", fmt::join(terms, "+")); } std::string pprint_expression(const ScalarQuadraticFunction &function, int precision = 4) { T *model = get_base(); auto N = function.size(); std::vector terms; terms.reserve(N + 1); for (auto i = 0; i < N; ++i) { auto &coef = function.coefficients[i]; std::string var1_str = model->pprint_variable(function.variable_1s[i]); std::string var2_str; if (function.variable_1s[i] == function.variable_2s[i]) { var2_str = var1_str; } else { var2_str = model->pprint_variable(function.variable_2s[i]); } std::string term; if (coef > 0) { term = fmt::format("{:.{}g}*{}*{}", coef, precision, var1_str, var2_str); } else if (coef < 0) { term = fmt::format("({:.{}g})*{}*{}", coef, precision, var1_str, var2_str); } terms.push_back(term); } if (function.affine_part) { auto affine_value = model->pprint_expression(function.affine_part.value(), precision); terms.push_back(affine_value); } return fmt::format("{}", fmt::join(terms, "+")); } std::string pprint_expression(const ExprBuilder &function, int precision = 4) { T *model = get_base(); std::vector terms; terms.reserve(function.quadratic_terms.size() + function.affine_terms.size() + 1); for (const auto &[varpair, coef] : function.quadratic_terms) { std::string var1_str = model->pprint_variable(varpair.var_1); std::string var2_str; if (varpair.var_1 == varpair.var_2) { var2_str = var1_str; } else { var2_str = model->pprint_variable(varpair.var_2); } std::string term; if (coef > 0) { term = fmt::format("{:.{}g}*{}*{}", coef, precision, var1_str, var2_str); } else if (coef < 0) { term = fmt::format("({:.{}g})*{}*{}", coef, precision, var1_str, var2_str); } terms.push_back(term); } for (const auto &[var, coef] : function.affine_terms) { auto term = fmt::format("{:.{}g}*{}", coef, precision, model->pprint_variable(var)); terms.push_back(term); } if (function.constant_term) { auto term = fmt::format("{:.{}g}", function.constant_term.value(), precision); terms.push_back(term); } return fmt::format("{}", fmt::join(terms, "+")); } }; template concept LinearObjectiveMixinConcept = requires(T &model) { { model.set_objective(ScalarAffineFunction(), ObjectiveSense()) } -> std::same_as; }; template class LinearObjectiveMixin { private: T *get_base() { static_assert(LinearObjectiveMixinConcept); return static_cast(this); } public: void set_objective_as_constant(CoeffT c, ObjectiveSense sense) { T *model = get_base(); model->set_objective(ScalarAffineFunction(c), sense); } void set_objective_as_variable(const VariableIndex &variable, ObjectiveSense sense) { T *model = get_base(); model->set_objective(ScalarAffineFunction(variable), sense); } }; /* This concept combined with partial specialization causes ICE on gcc 10 */ // template // concept VarIndexModel = requires(T *model, const VariableIndex &v) { // { // model->_variable_index(v) // } -> std::convertible_to; // }; #define VarIndexModel typename template struct AffineFunctionPtrForm { NZT numnz; IDXT *index; VALT *value; std::vector index_storage; std::vector value_storage; template void make(T *model, const ScalarAffineFunction &function) { auto f_numnz = function.size(); numnz = f_numnz; index_storage.resize(numnz); for (int i = 0; i < numnz; ++i) { index_storage[i] = model->_variable_index(function.variables[i]); } index = index_storage.data(); if constexpr (std::is_same_v) { value = (VALT *)function.coefficients.data(); } else { value_storage.resize(numnz); for (int i = 0; i < numnz; ++i) { value_storage[i] = function.coefficients[i]; } } } }; template struct QuadraticFunctionPtrForm { NZT numnz; IDXT *row; IDXT *col; VALT *value; std::vector row_storage; std::vector col_storage; std::vector value_storage; template void make(T *model, const ScalarQuadraticFunction &function) { auto f_numnz = function.size(); numnz = f_numnz; row_storage.resize(numnz); col_storage.resize(numnz); for (int i = 0; i < numnz; ++i) { row_storage[i] = model->_variable_index(function.variable_1s[i]); if (function.variable_1s[i] == function.variable_2s[i]) { col_storage[i] = row_storage[i]; } else { col_storage[i] = model->_variable_index(function.variable_2s[i]); } } row = row_storage.data(); col = col_storage.data(); if constexpr (std::is_same_v) { value = (VALT *)function.coefficients.data(); } else { value_storage.resize(numnz); for (int i = 0; i < numnz; ++i) { value_storage[i] = function.coefficients[i]; } } } }; enum class HessianTriangular { Upper, Lower, }; template struct CSCMatrix { NZT numnz; NZT numcol; std::vector values_CSC; std::vector rows_CSC; std::vector colStarts_CSC; template void make(T *model, const ScalarQuadraticFunction &function, int i_numcol, HessianTriangular triangular_format) { auto f_numnz = function.size(); numnz = f_numnz; numcol = i_numcol; std::vector rows(numnz); // Row indices std::vector cols(numnz); // Column indices std::vector values(numnz); // Values for (int i = 0; i < numnz; ++i) { auto v1 = model->_variable_index(function.variable_1s[i]); auto v2 = v1; if (function.variable_1s[i] != function.variable_2s[i]) { v2 = model->_variable_index(function.variable_2s[i]); } if (triangular_format == HessianTriangular::Upper) { if (v1 > v2) { std::swap(v1, v2); } } else { if (v1 < v2) { std::swap(v1, v2); } } if (v1 < 0 || v2 < 0) { throw std::runtime_error( "Variable index in quadratic function cannot be negative!"); } rows[i] = v1; cols[i] = v2; auto coef = function.coefficients[i]; if (v1 != v2) { // Non-diagonal element, should multiply by 0.5 coef *= 0.5; } values[i] = coef; } // Sorting based on column indices std::vector idx(numnz); std::iota(idx.begin(), idx.end(), 0); std::sort(idx.begin(), idx.end(), [&](int i, int j) { if (cols[i] == cols[j]) { return rows[i] < rows[j]; } else { return cols[i] < cols[j]; } }); // Creating CSC arrays values_CSC.reserve(numnz); rows_CSC.reserve(numnz); colStarts_CSC.resize(numcol + 1, 0); int currentCol = 0; for (auto i : idx) { while (currentCol < cols[i]) { colStarts_CSC[currentCol + 1] = values_CSC.size(); currentCol++; } values_CSC.push_back(values[i]); rows_CSC.push_back(rows[i]); } // Filling up remaining columns in colStarts_CSC std::fill(colStarts_CSC.begin() + currentCol + 1, colStarts_CSC.end(), numnz); } }; ================================================ FILE: include/pyoptinterface/tcc_interface.hpp ================================================ #pragma once #include #include #include "tcc/libtcc.h" #include "ankerl/unordered_dense.h" #include "pyoptinterface/dylib.hpp" #define APILIST \ B(tcc_new); \ B(tcc_delete); \ B(tcc_add_include_path); \ B(tcc_add_sysinclude_path); \ B(tcc_define_symbol); \ B(tcc_undefine_symbol); \ B(tcc_compile_string); \ B(tcc_set_output_type); \ B(tcc_add_library_path); \ B(tcc_add_library); \ B(tcc_add_symbol); \ B(tcc_relocate); \ B(tcc_get_symbol); namespace tcc { #define B DYLIB_EXTERN_DECLARE APILIST #undef B bool is_library_loaded(); bool load_library(const std::string &path); } // namespace tcc struct TCCFreeT { void operator()(TCCState *state) const { tcc::tcc_delete(state); }; }; struct TCCInstance { TCCInstance() = default; void init(); void add_include_path(const std::string &path); void add_sysinclude_path(const std::string &path); void add_library_path(const std::string &path); void add_library(const std::string &name); void import_math_symbols(); void compile_string(const std::string &code); uintptr_t get_symbol(const std::string &name); std::unique_ptr m_state; }; ================================================ FILE: include/pyoptinterface/xpress_model.hpp ================================================ #pragma #include #include #include // #include #include "../thirdparty/solvers/xpress/xpress_forward_decls.h" #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #define USE_NLMIXIN #include "pyoptinterface/solver_common.hpp" #include "pyoptinterface/dylib.hpp" // PyOptInterface has been compiled and tested with Xpress version 46.1.1 #define XPRS_VER_MAJOR 46 #define XPRS_VER_MINOR 1 #define XPRS_VER_BUILD 1 #if POI_XPVERSION_MAJOR < XPRS_VER_MAJOR #warning "System Xpress library major version is older than the officially supported version. " \ "Some features may not work correctly." #endif // Xpress Callback Abstraction Layer // // Xpress supports multiple callbacks that we want to expose to PyOptInterface as "contexts" to // match the design of other solver interfaces. To bridge Xpress's multiple-callback system to // PyOptInterface's single-callback-with-context model, we need to repeat the same setup for each // callback: define the low-level C function, register it with Xpress, handle its arguments, and // bind everything through Nanobind. // // Rather than copy-paste this boilerplate for each callback (error-prone and hard to maintain), we // use macros to generate everything from a single list. // // How does it work: // We define XPRSCB_LIST below with all supported callbacks and their signatures. This list is // parameterized by two macros that let us generate different code from the same data: // // MACRO - Receives callback metadata (ID, name, return type, arguments) and expands to whatever // we're generating (function declarations, enum values, registration code, etc.) // // ARG - Formats each callback *optional* argument. MACRO receives arguments wrapped by ARG, so it // can extract type/name information as needed. // // When we need to do something with all callbacks (e.g., declare wrapper functions), we pass // specific MACRO and ARG definitions to XPRSCB_LIST, and it generates the code. // // MACRO is invoked with: // 1. ID - Unique number for enum bit-flags (0-63, used for callback identification) // 2. NAME - Xpress callback name (e.g., "optnode" for XPRSaddcboptnode) // 3. RET - Return type of the Xpress callback function // 4. ...ARGS - Callback arguments (excluding XPRSprob and void* which are always first), // each wrapped in ARG(TYPE, NAME) // // ARG is invoked with: // 1. TYPE - Argument type from Xpress documentation // 2. NAME - Argument name from Xpress documentation // // Example: To declare all callback wrappers, define MACRO to generate a function declaration and // ARG to format parameters, then invoke XPRSCB_LIST(MACRO, ARG). // clang-format off #define XPRSCB_LIST(MACRO, ARG) \ MACRO(0, bariteration, void, \ ARG(int *, p_action)) \ MACRO(1, barlog, int) \ MACRO(2, afterobjective, void) \ MACRO(3, beforeobjective, void) \ MACRO(5, presolve, void) \ MACRO(6, checktime,int) \ MACRO(7, chgbranchobject, void, \ ARG(XPRSbranchobject, obranch) \ ARG(XPRSbranchobject *, p_newobject)) \ MACRO(8, cutlog, int) \ MACRO(9, cutround, void, \ ARG(int, ifxpresscuts) \ ARG(int*, p_action)) \ MACRO(10, destroymt, void) \ MACRO(11, gapnotify, void, \ ARG(double*, p_relgapnotifytarget) \ ARG(double*, p_absgapnotifytarget) \ ARG(double*, p_absgapnotifyobjtarget) \ ARG(double*, p_absgapnotifyboundtarget)) \ MACRO(12, miplog, int) \ MACRO(13, infnode, void) \ MACRO(14, intsol, void) \ MACRO(15, lplog, int) \ MACRO(16, message, void, \ ARG(const char*, msg) \ ARG(int, msglen) \ ARG(int, msgtype)) \ /* This CB is currently incompatible with this wapper design MACRO(17, mipthread, void, \ ARG(XPRSprob, threadprob)) */ \ MACRO(18, newnode, void, \ ARG(int, parentnode) \ ARG(int, node) \ ARG(int, branch)) \ MACRO(19, nodecutoff, void, \ ARG(int, node)) \ MACRO(20, nodelpsolved, void) \ MACRO(21, optnode, void, \ ARG(int*, p_infeasible)) \ MACRO(22, preintsol, void, \ ARG(int, soltype) \ ARG(int*, p_reject) \ ARG(double*, p_cutoff)) \ MACRO(23, prenode, void, \ ARG(int*, p_infeasible)) \ MACRO(24, usersolnotify, void, \ ARG(const char*, solname) \ ARG(int, status)) // clang-format on // Common callbacks optional arguments formatting macros: #define XPRSCB_ARG_IGNORE(TYPE, NAME) // Ingore arguments #define XPRSCB_ARG_FN(TYPE, NAME) , TYPE NAME // As args of a function #define XPRSCB_ARG_TYPE(TYPE, NAME) , TYPE // As args of a template // Expand to 2 elements of APILIST #define XPRSCB_ADDREMOVE_FN(ID, NAME, ...) \ B(XPRSaddcb##NAME); \ B(XPRSremovecb##NAME); // Define Xpress C APIs list. // Similar idea to the CBs list, here the B macro is externally provided as in the other solvers // interface implementation. #define APILIST \ B(XPRSaddcols64); \ B(XPRSaddcuts64); \ B(XPRSaddmanagedcuts64); \ B(XPRSaddmipsol); \ B(XPRSaddnames); \ B(XPRSaddqmatrix64); \ B(XPRSaddrows64); \ B(XPRSaddsets64); \ B(XPRSbeginlicensing); \ B(XPRSchgbounds); \ B(XPRSchgcoef); \ B(XPRSchgcoltype); \ B(XPRSchgmqobj64); \ B(XPRSchgobj); \ B(XPRSchgobjsense); \ B(XPRSchgrhs); \ B(XPRSchgrowtype); \ B(XPRScreateprob); \ B(XPRSdelcols); \ B(XPRSdelobj); \ B(XPRSdelqmatrix); \ B(XPRSdelrows); \ B(XPRSdelsets); \ B(XPRSdestroyprob); \ B(XPRSendlicensing); \ B(XPRSfree); \ B(XPRSgetattribinfo); \ B(XPRSgetbasisval); \ B(XPRSgetcallbacksolution); \ B(XPRSgetcoef); \ B(XPRSgetcoltype); \ B(XPRSgetcontrolinfo); \ B(XPRSgetdblattrib); \ B(XPRSgetdblcontrol); \ B(XPRSgetdualray); \ B(XPRSgetduals); \ B(XPRSgetiisdata); \ B(XPRSgetintattrib64); \ B(XPRSgetintcontrol64); \ B(XPRSgetlasterror); \ B(XPRSgetlb); \ B(XPRSgetlicerrmsg); \ B(XPRSgetlpsol); \ B(XPRSgetnamelist); \ B(XPRSgetobj); \ B(XPRSgetprimalray); \ B(XPRSgetprobname); \ B(XPRSgetredcosts); \ B(XPRSgetrhs); \ B(XPRSgetrowtype); \ B(XPRSgetslacks); \ B(XPRSgetsolution); \ B(XPRSgetstrattrib); \ B(XPRSgetstrcontrol); \ B(XPRSgetstringattrib); \ B(XPRSgetstringcontrol); \ B(XPRSgetub); \ B(XPRSgetversion); \ B(XPRSgetversionnumbers); \ B(XPRSiisall); \ B(XPRSiisfirst); \ B(XPRSinit); \ B(XPRSinterrupt); \ B(XPRSlicense); \ B(XPRSnlpaddformulas); \ B(XPRSnlploadformulas); \ B(XPRSnlppostsolve); \ B(XPRSoptimize); \ B(XPRSpostsolve); \ B(XPRSpresolverow); \ B(XPRSsaveas); \ B(XPRSsetdblcontrol); \ B(XPRSsetintcontrol); \ B(XPRSsetintcontrol64); \ B(XPRSsetlogfile); \ B(XPRSsetprobname); \ B(XPRSsetstrcontrol); \ B(XPRSwritebasis); \ B(XPRSwritebinsol); \ B(XPRSwriteprob); \ B(XPRSwriteprtsol); \ B(XPRSwriteslxsol); \ B(XPRSwritesol); \ XPRSCB_LIST(XPRSCB_ADDREMOVE_FN, XPRSCB_ARG_IGNORE); namespace xpress { // Define xpress function inside the xpress namespace #define B DYLIB_EXTERN_DECLARE APILIST #undef B // Libray loading functions bool is_library_loaded(); bool load_library(const std::string &path); std::pair license(int p_i, const char *p_c); bool beginlicensing(); void endlicensing(); // Xpress doesn't have an Env pointer, however, POI manages environment // initialization with an OOP interface. struct Env { Env(const char *path = nullptr); ~Env(); void close(); static inline std::mutex mtx; static inline int init_count = 0; bool initialized = false; }; // Some of the Xpress enums are here re-defined to enforce type safety // Types associated with Xpress attribute and controls enum class CATypes : int { NOTDEFINED = POI_XPRS_TYPE_NOTDEFINED, INT = POI_XPRS_TYPE_INT, INT64 = POI_XPRS_TYPE_INT64, DOUBLE = POI_XPRS_TYPE_DOUBLE, STRING = POI_XPRS_TYPE_STRING, }; enum class SOLSTATUS : int { NOTFOUND = POI_XPRS_SOLSTATUS_NOTFOUND, OPTIMAL = POI_XPRS_SOLSTATUS_OPTIMAL, FEASIBLE = POI_XPRS_SOLSTATUS_FEASIBLE, INFEASIBLE = POI_XPRS_SOLSTATUS_INFEASIBLE, UNBOUNDED = POI_XPRS_SOLSTATUS_UNBOUNDED }; enum class SOLVESTATUS : int { UNSTARTED = POI_XPRS_SOLVESTATUS_UNSTARTED, STOPPED = POI_XPRS_SOLVESTATUS_STOPPED, FAILED = POI_XPRS_SOLVESTATUS_FAILED, COMPLETED = POI_XPRS_SOLVESTATUS_COMPLETED }; enum class LPSTATUS : int { UNSTARTED = POI_XPRS_LP_UNSTARTED, OPTIMAL = POI_XPRS_LP_OPTIMAL, INFEAS = POI_XPRS_LP_INFEAS, CUTOFF = POI_XPRS_LP_CUTOFF, UNFINISHED = POI_XPRS_LP_UNFINISHED, UNBOUNDED = POI_XPRS_LP_UNBOUNDED, CUTOFF_IN_DUAL = POI_XPRS_LP_CUTOFF_IN_DUAL, UNSOLVED = POI_XPRS_LP_UNSOLVED, NONCONVEX = POI_XPRS_LP_NONCONVEX }; enum class MIPSTATUS : int { NOT_LOADED = POI_XPRS_MIP_NOT_LOADED, LP_NOT_OPTIMAL = POI_XPRS_MIP_LP_NOT_OPTIMAL, LP_OPTIMAL = POI_XPRS_MIP_LP_OPTIMAL, NO_SOL_FOUND = POI_XPRS_MIP_NO_SOL_FOUND, SOLUTION = POI_XPRS_MIP_SOLUTION, INFEAS = POI_XPRS_MIP_INFEAS, OPTIMAL = POI_XPRS_MIP_OPTIMAL, UNBOUNDED = POI_XPRS_MIP_UNBOUNDED }; enum class NLPSTATUS : int { UNSTARTED = POI_XPRS_NLPSTATUS_UNSTARTED, SOLUTION = POI_XPRS_NLPSTATUS_SOLUTION, LOCALLY_OPTIMAL = POI_XPRS_NLPSTATUS_LOCALLY_OPTIMAL, OPTIMAL = POI_XPRS_NLPSTATUS_OPTIMAL, NOSOLUTION = POI_XPRS_NLPSTATUS_NOSOLUTION, LOCALLY_INFEASIBLE = POI_XPRS_NLPSTATUS_LOCALLY_INFEASIBLE, INFEASIBLE = POI_XPRS_NLPSTATUS_INFEASIBLE, UNBOUNDED = POI_XPRS_NLPSTATUS_UNBOUNDED, UNFINISHED = POI_XPRS_NLPSTATUS_UNFINISHED, UNSOLVED = POI_XPRS_NLPSTATUS_UNSOLVED, }; enum class IISSOLSTATUS : int { UNSTARTED = POI_XPRS_IIS_UNSTARTED, FEASIBLE = POI_XPRS_IIS_FEASIBLE, COMPLETED = POI_XPRS_IIS_COMPLETED, UNFINISHED = POI_XPRS_IIS_UNFINISHED }; enum class SOLAVAILABLE : int { NOTFOUND = POI_XPRS_SOLAVAILABLE_NOTFOUND, OPTIMAL = POI_XPRS_SOLAVAILABLE_OPTIMAL, FEASIBLE = POI_XPRS_SOLAVAILABLE_FEASIBLE }; enum class OPTIMIZETYPE : int { NONE = POI_XPRS_OPTIMIZETYPE_NONE, LP = POI_XPRS_OPTIMIZETYPE_LP, MIP = POI_XPRS_OPTIMIZETYPE_MIP, LOCAL = POI_XPRS_OPTIMIZETYPE_LOCAL, GLOBAL = POI_XPRS_OPTIMIZETYPE_GLOBAL }; //////////////////////////////////////////////////////////////////////////////// // CALLBACKS TYPES AND DEFINITIONS // //////////////////////////////////////////////////////////////////////////////// // Callback contexts enum. Defines an unique id for each callback/context. // The values of this enum class are defined as bitflags, to allow for multiple context manipulation // using a single uint64 value. enum class CB_CONTEXT : unsigned long long { // Define a enum element #define XPRSCB_ENUM(ID, NAME, ...) NAME = (1ULL << ID), XPRSCB_LIST(XPRSCB_ENUM, XPRSCB_ARG_IGNORE) #undef XPRSCB_ENUM }; class Model; // Define later in this file using Callback = std::function; // Callback opaque container // xpress::Model operates in two modes to handle callback local XPRSprob objects: // // MAIN mode - The model owns its XPRSprob and manages all optimization state normally. // CALLBACK mode - A temporary, non-owning wrapper around Xpress's thread-local problem clone that // gets passed to callbacks. Since Xpress creates separate problem clones for callbacks, we need to // temporarily "borrow" this clone while preserving xpress::Model's internal state // (variable/constraint indexers, etc.) without copies. // // During a callback, we swap the XPRSprob pointer from the main model into the callback pointer, // execute the user's callback with a full xpress::Model object, then swap it back. // // Thread safety: This swap-based design assumes callbacks execute sequentially. Python's GIL // enforces this anyway (only one callback can execute Python code at a time), and we configure // Xpress to mutex callbacks invocations, so concurrent callback execution isn't an issue. enum class XPRESS_MODEL_MODE { MAIN, // Owns XPRSprob; holds global callback state CALLBACK_, // Non-owning wrapper around Xpress's callback problem clone }; // Simple conditional struct that define the ret_code field only if it is not void template struct ReturnValue { T ret_code; T get_return_value() const { return ret_code; } }; template <> struct ReturnValue { void get_return_value() const {}; }; // Since we use the CB macro list, we get the original types passed at the low-level CBs // This helper struct is used to inject type-exceptions on the callback struct field types. In most // cases this is not required, except when dealing with pointers to opaque objects, which Nanobind // doesn't handle automatically. template struct StructFieldType { using type = T; // Default behavior: just use the original type }; // (At the best of my knowledge) Nanobind does not support binding for opaque types (pointers to // declared structs but whose definition is not available in that point). Some callbacks works with // opaque objects that are passed around (e.g., branch object), we don't need to access them, // just to pass them back to other Xpress APIs. // XPRSbranchobject is a pointer to an opaque type, which nanobind struggle with. So we pass it // as an opaque void*, which nanobind knows how to handle. template <> struct StructFieldType { using type = void *; }; template <> struct StructFieldType { using type = void *; }; // Callback Argument Structs // // PyOptInterface callbacks have a uniform signature: callback(model, context). On the other hand, // Xpress callbacks have varying signatures with callback-specific arguments (e.g., node IDs, // branching info, solution status). To bridge this gap, we generate a struct for each callback type // that holds its specific arguments. // // During a callback, we populate the appropriate struct with the current arguments, and we provide // it to the CB through the "Model.cb_get_arguments" function. The Python callback can then access // these arguments as named fields. // // For example, the 'optnode' callback receives an int* p_infeasible parameter. We generate: // struct optnode_struct { int* p_infeasible; }; // The Python callback accesses this as: model.cb_get_arguments().p_infeasible #define XPRSCB_ARG_STRUCT(TYPE, NAME) StructFieldType::type NAME; #define XPRSCB_STRUCT(ID, NAME, RET, ...) \ struct NAME##_struct : ReturnValue \ { \ __VA_ARGS__ \ }; XPRSCB_LIST(XPRSCB_STRUCT, XPRSCB_ARG_STRUCT) #undef XPRSCB_STRUCT // Callback Data Variant // // We use std::variant to store pointers to callback-specific argument structs, rather than // a generic void*. This provides two benefits: // // 1. Type safety - The variant ensures we can only store pointers to known callback structs, // catching type errors at compile time. // // 2. Automatic Python binding - Nanobind automatically converts std::variant to Python union // types, giving Python callbacks properly-typed access to callback arguments without manual // type casting or wrapper code. // // The variant contains nullptr_t plus a pointer type for each callback struct (e.g., // optnode_struct*, intsol_struct*, etc.) #define XPRSCB_STRUCT_NAME(ID, NAME, RET, ...) , NAME##_struct * using xpress_cbs_data = std::variant; #undef XPRSCB_STRUCT_NAME // Type-value pair mainly used for NLP formulas struct Tvp { int type; double value; }; // xpress::Model - Main solver interface for building and solving Xpress optimization models. // Inherits standard PyOptInterface modeling API through CRTP mixins for constraints, objectives, // and solution queries. class Model : public OnesideLinearConstraintMixin, public TwosideLinearConstraintMixin, public OnesideQuadraticConstraintMixin, public TwosideNLConstraintMixin, public LinearObjectiveMixin, public PPrintMixin, public GetValueMixin { public: Model() = default; ~Model(); // Avoid involuntary copies Model(const Model &) = delete; Model &operator=(const Model &) = delete; // Move is fine, and we need it to wrap callback XPRSprob Model(Model &&) noexcept = default; Model &operator=(Model &&) noexcept = default; Model(const Env &env); void init(const Env &env); void close(); void optimize(); bool _is_mip(); static double get_infinity(); void write(const std::string &filename); std::string get_problem_name(); void set_problem_name(const std::string &probname); void add_mip_start(const std::vector &variables, const std::vector &values); void *get_raw_model(); void computeIIS(); std::string version_string(); // Index mappings int _constraint_index(ConstraintIndex constraint); int _variable_index(VariableIndex variable); int _checked_constraint_index(ConstraintIndex constraint); int _checked_variable_index(VariableIndex variable); // Variables VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous, double lb = POI_XPRS_MINUSINFINITY, double ub = POI_XPRS_PLUSINFINITY, const char *name = nullptr); void delete_variable(VariableIndex variable); void delete_variables(const Vector &variables); void set_objective_coefficient(VariableIndex variable, double value); void set_variable_bounds(VariableIndex variable, double lb, double ub); void set_variable_lowerbound(VariableIndex variable, double lb); void set_variable_name(VariableIndex variable, const char *name); void set_variable_type(VariableIndex variable, VariableDomain vtype); void set_variable_upperbound(VariableIndex variable, double ub); bool is_variable_active(VariableIndex variable); bool is_variable_basic(VariableIndex variable); bool is_variable_lowerbound_IIS(VariableIndex variable); bool is_variable_nonbasic_lb(VariableIndex variable); bool is_variable_nonbasic_ub(VariableIndex variable); bool is_variable_superbasic(VariableIndex variable); bool is_variable_upperbound_IIS(VariableIndex variable); double get_objective_coefficient(VariableIndex variable); double get_variable_lowerbound(VariableIndex variable); double get_variable_primal_ray(VariableIndex variable); double get_variable_rc(VariableIndex variable); double get_variable_upperbound(VariableIndex variable); double get_variable_value(VariableIndex variable); std::string get_variable_name(VariableIndex variable); std::string pprint_variable(VariableIndex variable); VariableDomain get_variable_type(VariableIndex variable); // Constraints ConstraintIndex add_exp_cone_constraint(const Vector &variables, const char *name, bool dual); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name); ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name = nullptr); ConstraintIndex add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated); ConstraintIndex add_single_nl_constraint(ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name = nullptr); ConstraintIndex add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights); ConstraintIndex add_sos_constraint(const Vector &variables, SOSType sos_type); void delete_constraint(ConstraintIndex constraint); void set_constraint_name(ConstraintIndex constraint, const char *name); void set_constraint_rhs(ConstraintIndex constraint, CoeffT rhs); void set_constraint_sense(ConstraintIndex constraint, ConstraintSense sense); void set_normalized_coefficient(ConstraintIndex constraint, VariableIndex variable, double value); void set_normalized_rhs(ConstraintIndex constraint, double value); bool is_constraint_active(ConstraintIndex constraint); bool is_constraint_basic(ConstraintIndex constraint); bool is_constraint_in_IIS(ConstraintIndex constraint); bool is_constraint_nonbasic_lb(ConstraintIndex constraint); bool is_constraint_nonbasic_ub(ConstraintIndex constraint); bool is_constraint_superbasic(ConstraintIndex constraint); double get_constraint_dual_ray(ConstraintIndex constraint); double get_constraint_dual(ConstraintIndex constraint); double get_constraint_slack(ConstraintIndex constraint); double get_normalized_coefficient(ConstraintIndex constraint, VariableIndex variable); double get_normalized_rhs(ConstraintIndex constraint); CoeffT get_constraint_rhs(ConstraintIndex constraint); std::string get_constraint_name(ConstraintIndex constraint); ConstraintSense get_constraint_sense(ConstraintIndex constraint); // Objective function void set_objective(const ScalarAffineFunction &function, ObjectiveSense sense); void set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense); void set_objective(const ExprBuilder &function, ObjectiveSense sense); void add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result); // Xpress->POI exit status mappings LPSTATUS get_lp_status(); MIPSTATUS get_mip_status(); NLPSTATUS get_nlp_status(); SOLSTATUS get_sol_status(); SOLVESTATUS get_solve_status(); OPTIMIZETYPE get_optimize_type(); IISSOLSTATUS get_iis_sol_status(); // Native Attribute/Control access using integer IDs void set_raw_control_dbl_by_id(int control, double value); void set_raw_control_int_by_id(int control, XPRSint64 value); void set_raw_control_str_by_id(int control, const char *value); XPRSint64 get_raw_control_int_by_id(int control); double get_raw_control_dbl_by_id(int control); std::string get_raw_control_str_by_id(int control); XPRSint64 get_raw_attribute_int_by_id(int attrib); double get_raw_attribute_dbl_by_id(int attrib); std::string get_raw_attribute_str_by_id(int attrib); // Attribute/Control access through string IDs (converted to integer IDs) void set_raw_control_int(const char *control, XPRSint64 value); void set_raw_control_dbl(const char *control, double value); void set_raw_control_str(const char *control, const char *value); XPRSint64 get_raw_control_int(const char *control); double get_raw_control_dbl(const char *control); std::string get_raw_control_str(const char *control); XPRSint64 get_raw_attribute_int(const char *attrib); double get_raw_attribute_dbl(const char *attrib); std::string get_raw_attribute_str(const char *attrib); // Type generic Attribute/Control access through string IDs using xprs_type_variant_t = std::variant; void set_raw_control(const char *control, xprs_type_variant_t &value); xprs_type_variant_t get_raw_attribute(const char *attrib); xprs_type_variant_t get_raw_control(const char *control); // Callback void set_callback(const Callback &callback, unsigned long long cbctx); xpress_cbs_data cb_get_arguments(); double cb_get_solution(VariableIndex variable); double cb_get_relaxation(VariableIndex variable); double cb_get_incumbent(VariableIndex variable); void cb_set_solution(VariableIndex variable, double value); void cb_submit_solution(); void cb_exit(); // NOTE: Xpress only provide ways to add local cuts, so, all these functions map to the same // XPRSaddcuts operation void cb_add_lazy_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); void cb_add_lazy_constraint(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs); void cb_add_user_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); void cb_add_user_cut(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs); private: // HELPER FUNCTIONS void _check(int error); void _clear_caches(); // Modeling helpers void _set_entity_name(int etype, int index, const char *name); ConstraintIndex _add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights); std::string _get_entity_name(int etype, int eidx); char _get_variable_bound_IIS(VariableIndex variable); int _get_basis_stat(int entity_idx, bool is_row = false); // NLP helpers Tvp _decode_expr(const ExpressionGraph &graph, const ExpressionHandle &expr); std::pair, std::vector> _decode_graph_postfix_order( ExpressionGraph &graph, const ExpressionHandle &result); // Attributes/Controls helpers int _get_checked_attribute_id(const char *attrib, CATypes expected, CATypes backup = CATypes::NOTDEFINED); int _get_checked_control_id(const char *control, CATypes expected, CATypes backup = CATypes::NOTDEFINED); std::pair _get_attribute_info(const char *attrib); std::pair _get_control_info(const char *control); // Callback/Mode helpers void _check_expected_mode(XPRESS_MODEL_MODE mode); void _ensure_postsolved(); double _cb_get_context_solution(VariableIndex variable); void _cb_add_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs); XPRSprob _toggle_model_mode(XPRSprob model); private: // TYPES // Helper struct used to spawn lower level C callbacks with matching signature template struct CbWrap; // XPRSprob deleter to automatically RAII using unique_ptr struct ProbDeleter { void operator()(XPRSprob prob) { if (xpress::XPRSdestroyprob(prob) != 0) { throw std::runtime_error("Error while destroying Xpress problem object"); } } }; private: // MEMBER VARIABLES // Xpress problem pointer std::unique_ptr m_model; // Current operating mode (MAIN or CALLBACK) - enables runtime validation that // callback-only methods aren't called outside callbacks and vice versa XPRESS_MODEL_MODE m_mode; // Index management for variables and constraints // Note that Xpress uses the same index set to refer to almost all its constraint types. The // only exception are SOS which are handled in a separate pool. MonotoneIndexer m_variable_index; MonotoneIndexer m_constraint_index; MonotoneIndexer m_sos_constraint_index; // Tracks whether the model requires postsolving before queries can be made. // When optimization is interrupted, the model remains in a presolved state where variable // and constraint indices don't match the original problem structure. Querying model data // in this state would return incorrect results. Postsolving restores the original index // mappings but discards all optimization progress, forcing any subsequent optimization // to restart from scratch. bool m_need_postsolve = false; // Any non-linear or non-convex quadratic constraint is handled with Xpress non linear solver. // We keep count of all the non-linear (strictly speaking) constraints. int m_quad_nl_constr_num = 0; bool has_quad_objective = false; bool has_nlp_objective = false; VariableIndex m_nlp_obj_variable; ConstraintIndex m_nlp_obj_constraint; // Cached vectors std::vector m_primal_ray; std::vector m_dual_ray; std::vector m_iis_cols; std::vector m_iss_rows; std::vector m_iis_bound_types; // Message callback state - we register a default handler but allow user override bool is_default_message_cb_set; // User callback and active contexts Callback m_callback = nullptr; unsigned long long m_curr_contexts = 0; // Bitwise OR of enabled callback contexts // Exception propagation - if a callback throws, we capture it here to let Xpress // complete cleanup gracefully before rethrowing to Python std::vector m_captured_exceptions; // Current callback context being executed CB_CONTEXT cb_where; // Heuristic solution builder - accumulates (variable, value) pairs during callback; // submitted to Xpress when callback completes std::vector> cb_sol_cache; // Callback-specific arguments - variant holding pointers to context-specific structs // (e.g., optnode_struct*, intsol_struct*) based on current callback type xpress_cbs_data cb_args = nullptr; }; } // namespace xpress ================================================ FILE: lib/cache_model.cpp ================================================ // #include // // #include "pyoptinterface/core.hpp" // #include "pyoptinterface/container.hpp" // // struct VariableInfo //{ // VariableDomain domain; // }; // // class CacheModel //{ // public: // VariableIndex add_variable(VariableDomain domain = VariableDomain::Continuous); // void delete_variable(const VariableIndex &variable); // bool is_variable_active(const VariableIndex &variable) const; // // ConstraintIndex add_linear_constraint(const LinearConstraint &constraint); // ConstraintIndex add_quadratic_constraint(const QuadraticConstraint &constraint); // ConstraintIndex add_sos1_constraint(const SOSConstraint &constraint); // ConstraintIndex add_sos2_constraint(const SOSConstraint &constraint); // // void delete_constraint(const ConstraintIndex &constraint); // // bool is_constraint_active(const ConstraintIndex &constraint) const; // // private: // int num_variable = 0; // MonotoneVector m_variable_index; // Vector> m_variables; // // int num_linear_constraint = 0; // MonotoneVector m_linear_constraint_index; // Vector> m_linear_constraints; // // int num_quadratic_constraint = 0; // MonotoneVector m_quadratic_constraint_index; // Vector> m_quadratic_constraints; // // int num_sos1_constraint = 0; // MonotoneVector m_sos1_constraint_index; // Vector> m_sos1_constraints; // // int num_sos2_constraint = 0; // MonotoneVector m_sos2_constraint_index; // Vector> m_sos2_constraints; // }; // // VariableIndex CacheModel::add_variable(VariableDomain domain) //{ // IndexT index = m_variable_index.add_index(); // m_variables.push_back(VariableInfo{domain}); // num_variable++; // return VariableIndex(index); // } // // void CacheModel::delete_variable(const VariableIndex &variable) //{ // m_variable_index.delete_index(variable.index); // m_variables[variable.index] = std::nullopt; // num_variable--; // } // // bool CacheModel::is_variable_active(const VariableIndex &variable) const //{ // return m_variables[variable.index].has_value(); // } // // ConstraintIndex CacheModel::add_linear_constraint(const LinearConstraint &constraint) //{ // IndexT index = m_linear_constraint_index.add_index(); // m_linear_constraints.push_back(constraint); // num_linear_constraint++; // return ConstraintIndex{ConstraintType::Linear, index}; // } // // ConstraintIndex CacheModel::add_quadratic_constraint(const QuadraticConstraint &constraint) //{ // IndexT index = m_quadratic_constraint_index.add_index(); // m_quadratic_constraints.push_back(constraint); // num_quadratic_constraint++; // return ConstraintIndex{ConstraintType::Quadratic, index}; // } // // ConstraintIndex CacheModel::add_sos1_constraint(const SOSConstraint &constraint) //{ // IndexT index = m_sos1_constraint_index.add_index(); // m_sos1_constraints.push_back(constraint); // num_sos1_constraint++; // return ConstraintIndex{ConstraintType::SOS1, index}; // } // // ConstraintIndex CacheModel::add_sos2_constraint(const SOSConstraint &constraint) //{ // IndexT index = m_sos2_constraint_index.add_index(); // m_sos2_constraints.push_back(constraint); // num_sos2_constraint++; // return ConstraintIndex{ConstraintType::SOS2, index}; // } // // void CacheModel::delete_constraint(const ConstraintIndex &constraint) //{ // auto index = constraint.index; // switch (constraint.type) // { // case ConstraintType::Linear: // m_linear_constraint_index.delete_index(index); // m_linear_constraints[index] = std::nullopt; // num_linear_constraint--; // break; // case ConstraintType::Quadratic: // m_quadratic_constraint_index.delete_index(index); // m_quadratic_constraints[index] = std::nullopt; // num_quadratic_constraint--; // break; // case ConstraintType::SOS1: // m_sos1_constraint_index.delete_index(index); // m_sos1_constraints[index] = std::nullopt; // num_sos1_constraint--; // break; // case ConstraintType::SOS2: // m_sos2_constraint_index.delete_index(index); // m_sos2_constraints[index] = std::nullopt; // num_sos2_constraint--; // break; // } // } // // bool CacheModel::is_constraint_active(const ConstraintIndex &constraint) const //{ // auto index = constraint.index; // switch (constraint.type) // { // case ConstraintType::Linear: // return m_linear_constraint_index.has_index(index); // case ConstraintType::Quadratic: // return m_quadratic_constraint_index.has_index(index); // case ConstraintType::SOS1: // return m_sos1_constraint_index.has_index(index); // case ConstraintType::SOS2: // return m_sos2_constraint_index.has_index(index); // } // } ================================================ FILE: lib/copt_model.cpp ================================================ #include "pyoptinterface/copt_model.hpp" #include "fmt/core.h" #include namespace copt { #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } } // namespace copt static void check_error(int error) { if (error) { char errmsg[COPT_BUFFSIZE]; copt::COPT_GetRetcodeMsg(error, errmsg, COPT_BUFFSIZE); throw std::runtime_error(errmsg); } } static char copt_con_sense(ConstraintSense sense) { switch (sense) { case ConstraintSense::LessEqual: return COPT_LESS_EQUAL; case ConstraintSense::Equal: return COPT_EQUAL; case ConstraintSense::GreaterEqual: return COPT_GREATER_EQUAL; default: throw std::runtime_error("Unknown constraint sense"); } } static int copt_obj_sense(ObjectiveSense sense) { switch (sense) { case ObjectiveSense::Minimize: return COPT_MINIMIZE; case ObjectiveSense::Maximize: return COPT_MAXIMIZE; default: throw std::runtime_error("Unknown objective sense"); } } static char copt_vtype(VariableDomain domain) { switch (domain) { case VariableDomain::Continuous: return COPT_CONTINUOUS; case VariableDomain::Integer: return COPT_INTEGER; case VariableDomain::Binary: return COPT_BINARY; default: throw std::runtime_error("Unknown variable domain"); } } static VariableDomain copt_vtype_to_domain(char vtype) { switch (vtype) { case COPT_CONTINUOUS: return VariableDomain::Continuous; case COPT_INTEGER: return VariableDomain::Integer; case COPT_BINARY: return VariableDomain::Binary; default: throw std::runtime_error("Unknown variable domain"); } } static int copt_sostype(SOSType type) { switch (type) { case SOSType::SOS1: return COPT_SOS_TYPE1; case SOSType::SOS2: return COPT_SOS_TYPE2; default: throw std::runtime_error("Unknown SOS type"); } } COPTModel::COPTModel(const COPTEnv &env) { init(env); } void COPTModel::init(const COPTEnv &env) { if (!copt::is_library_loaded()) { throw std::runtime_error("COPT library is not loaded"); } copt_prob *model; int error = copt::COPT_CreateProb(env.m_env, &model); check_error(error); m_model = std::unique_ptr(model); } void COPTModel::close() { m_model.reset(); } double COPTModel::get_infinity() const { return COPT_INFINITY; } void COPTModel::write(const std::string &filename) { int error; if (filename.ends_with(".mps")) { error = copt::COPT_WriteMps(m_model.get(), filename.c_str()); } else if (filename.ends_with(".lp")) { error = copt::COPT_WriteLp(m_model.get(), filename.c_str()); } else if (filename.ends_with(".cbf")) { error = copt::COPT_WriteCbf(m_model.get(), filename.c_str()); } else if (filename.ends_with(".bin")) { error = copt::COPT_WriteBin(m_model.get(), filename.c_str()); } else if (filename.ends_with(".bas")) { error = copt::COPT_WriteBasis(m_model.get(), filename.c_str()); } else if (filename.ends_with(".sol")) { error = copt::COPT_WriteSol(m_model.get(), filename.c_str()); } else if (filename.ends_with(".mst")) { error = copt::COPT_WriteMst(m_model.get(), filename.c_str()); } else if (filename.ends_with(".par")) { error = copt::COPT_WriteParam(m_model.get(), filename.c_str()); } else { throw std::runtime_error("Unknown file extension"); } check_error(error); } VariableIndex COPTModel::add_variable(VariableDomain domain, double lb, double ub, const char *name) { if (name != nullptr && name[0] == '\0') { name = nullptr; } IndexT index = m_variable_index.add_index(); VariableIndex variable(index); char vtype = copt_vtype(domain); int error = copt::COPT_AddCol(m_model.get(), 0.0, 0, NULL, NULL, vtype, lb, ub, name); check_error(error); return variable; } void COPTModel::delete_variable(const VariableIndex &variable) { if (!is_variable_active(variable)) { throw std::runtime_error("Variable does not exist"); } int variable_column = _variable_index(variable); int error = copt::COPT_DelCols(m_model.get(), 1, &variable_column); check_error(error); m_variable_index.delete_index(variable.index); } void COPTModel::delete_variables(const Vector &variables) { int n_variables = variables.size(); if (n_variables == 0) return; std::vector columns; columns.reserve(n_variables); for (int i = 0; i < n_variables; i++) { if (!is_variable_active(variables[i])) { continue; } auto column = _variable_index(variables[i]); columns.push_back(column); } int error = copt::COPT_DelCols(m_model.get(), columns.size(), columns.data()); check_error(error); for (int i = 0; i < n_variables; i++) { m_variable_index.delete_index(variables[i].index); } } bool COPTModel::is_variable_active(const VariableIndex &variable) { return m_variable_index.has_index(variable.index); } double COPTModel::get_variable_value(const VariableIndex &variable) { return get_variable_info(variable, COPT_DBLINFO_VALUE); } std::string COPTModel::pprint_variable(const VariableIndex &variable) { return get_variable_name(variable); } void COPTModel::set_variable_bounds(const VariableIndex &variable, double lb, double ub) { auto column = _checked_variable_index(variable); int error; error = copt::COPT_SetColLower(m_model.get(), 1, &column, &lb); check_error(error); error = copt::COPT_SetColUpper(m_model.get(), 1, &column, &ub); check_error(error); } ConstraintIndex COPTModel::add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { IndexT index = m_linear_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; char g_sense = copt_con_sense(sense); double g_rhs = rhs - function.constant.value_or(0.0); if (name != nullptr && name[0] == '\0') { name = nullptr; } int error = copt::COPT_AddRow(m_model.get(), numnz, cind, cval, g_sense, g_rhs, g_rhs, name); check_error(error); return constraint_index; } ConstraintIndex COPTModel::add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name) { auto lb = std::get<0>(interval); auto ub = std::get<1>(interval); if (function.constant.has_value()) { lb -= function.constant.value_or(0.0); ub -= function.constant.value_or(0.0); } IndexT index = m_linear_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; if (name != nullptr && name[0] == '\0') { name = nullptr; } int error = copt::COPT_AddRow(m_model.get(), numnz, cind, cval, 0, lb, ub, name); check_error(error); return constraint_index; return ConstraintIndex(); } ConstraintIndex COPTModel::add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { IndexT index = m_quadratic_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Quadratic, index); const auto &affine_part = function.affine_part; int numlnz = 0; int *lind = NULL; double *lval = NULL; AffineFunctionPtrForm affine_ptr_form; if (affine_part.has_value()) { const auto &affine_function = affine_part.value(); affine_ptr_form.make(this, affine_function); numlnz = affine_ptr_form.numnz; lind = affine_ptr_form.index; lval = affine_ptr_form.value; } QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); int numqnz = ptr_form.numnz; int *qrow = ptr_form.row; int *qcol = ptr_form.col; double *qval = ptr_form.value; char g_sense = copt_con_sense(sense); double g_rhs = rhs; if (affine_part) g_rhs -= affine_part->constant.value_or(0.0); if (name != nullptr && name[0] == '\0') { name = nullptr; } int error = copt::COPT_AddQConstr(m_model.get(), numlnz, lind, lval, numqnz, qrow, qcol, qval, g_sense, g_rhs, name); check_error(error); return constraint_index; } ConstraintIndex COPTModel::add_sos_constraint(const Vector &variables, SOSType sos_type) { Vector weights(variables.size(), 1.0); return add_sos_constraint(variables, sos_type, weights); } ConstraintIndex COPTModel::add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights) { IndexT index = m_sos_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::SOS, index); int numsos = 1; int nummembers = variables.size(); int types = copt_sostype(sos_type); int beg[] = {0}; int cnt[] = {nummembers}; std::vector ind_v(nummembers); for (int i = 0; i < nummembers; i++) { ind_v[i] = _variable_index(variables[i].index); } int *ind = ind_v.data(); double *weight = (double *)weights.data(); int error = copt::COPT_AddSOSs(m_model.get(), numsos, &types, beg, cnt, ind, weight); check_error(error); return constraint_index; } ConstraintIndex COPTModel::add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated) { IndexT index = m_cone_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::SecondOrderCone, index); int N = variables.size(); std::vector ind_v(N); for (int i = 0; i < N; i++) { ind_v[i] = _checked_variable_index(variables[i]); } int cone = COPT_CONE_QUAD; if (rotated) { cone = COPT_CONE_RQUAD; } int coneType[] = {cone}; int coneBeg[] = {0}; int coneCnt[] = {N}; int *coneIdx = ind_v.data(); int error = copt::COPT_AddCones(m_model.get(), 1, coneType, coneBeg, coneCnt, coneIdx); check_error(error); // COPT does not support name for cone constraints return constraint_index; } ConstraintIndex COPTModel::add_exp_cone_constraint(const Vector &variables, const char *name, bool dual) { IndexT index = m_exp_cone_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::ExponentialCone, index); int N = variables.size(); if (N != 3) { throw std::runtime_error("Exponential cone constraint must have 3 variables"); } std::vector ind_v(N); for (int i = 0; i < N; i++) { ind_v[i] = _checked_variable_index(variables[i]); } int cone = COPT_EXPCONE_PRIMAL; if (dual) { cone = COPT_EXPCONE_DUAL; } int coneType[] = {cone}; int coneBeg[] = {0}; int coneCnt[] = {N}; int *coneIdx = ind_v.data(); int error = copt::COPT_AddExpCones(m_model.get(), 1, coneType, coneIdx); check_error(error); // COPT does not support name for cone constraints return constraint_index; } int unary_opcode(const UnaryOperator &op) { switch (op) { case UnaryOperator::Neg: return COPT_NL_NEG; case UnaryOperator::Sin: return COPT_NL_SIN; case UnaryOperator::Cos: return COPT_NL_COS; case UnaryOperator::Tan: return COPT_NL_TAN; case UnaryOperator::Asin: return COPT_NL_ASIN; case UnaryOperator::Acos: return COPT_NL_ACOS; case UnaryOperator::Atan: return COPT_NL_ATAN; case UnaryOperator::Abs: return COPT_NL_ABS; case UnaryOperator::Sqrt: return COPT_NL_SQRT; case UnaryOperator::Exp: return COPT_NL_EXP; case UnaryOperator::Log: return COPT_NL_LOG; case UnaryOperator::Log10: return COPT_NL_LOG10; default: { auto opname = unary_operator_to_string(op); auto msg = fmt::format("Unknown unary operator for COPT: {}", opname); throw std::runtime_error(msg); } } } int binary_opcode(const BinaryOperator &op) { switch (op) { case BinaryOperator::Sub: return COPT_NL_MINUS; case BinaryOperator::Div: return COPT_NL_DIV; case BinaryOperator::Pow: return COPT_NL_POW; case BinaryOperator::Mul2: return COPT_NL_MULT; default: { auto opname = binary_operator_to_string(op); auto msg = fmt::format("Unknown binary operator for COPT: {}", opname); throw std::runtime_error(msg); } } } int nary_opcode(const NaryOperator &op) { switch (op) { case NaryOperator::Add: return COPT_NL_SUM; default: { auto opname = nary_operator_to_string(op); auto msg = fmt::format("Unknown n-ary operator for COPT: {}", opname); throw std::runtime_error(msg); } } } void COPTModel::decode_expr(const ExpressionGraph &graph, const ExpressionHandle &expr, std::vector &opcodes, std::vector &constants) { auto array_type = expr.array; auto index = expr.id; switch (array_type) { case ArrayType::Constant: { opcodes.push_back(COPT_NL_GET); constants.push_back(graph.m_constants[index]); break; } case ArrayType::Variable: { auto column = _checked_variable_index(graph.m_variables[index]); opcodes.push_back(column); break; } case ArrayType::Parameter: { throw std::runtime_error("Parameter is not supported in COPT"); break; } case ArrayType::Unary: { auto &unary = graph.m_unaries[index]; int opcode = unary_opcode(unary.op); opcodes.push_back(opcode); break; } case ArrayType::Binary: { auto &binary = graph.m_binaries[index]; int opcode = binary_opcode(binary.op); opcodes.push_back(opcode); break; } case ArrayType::Ternary: { throw std::runtime_error("Ternary operator is not supported in COPT"); break; } case ArrayType::Nary: { auto &nary = graph.m_naries[index]; int opcode = nary_opcode(nary.op); int n_operands = nary.operands.size(); if (opcode == COPT_NL_SUM && n_operands == 1) { // COPT errors when sum 1 operand } else { opcodes.push_back(opcode); opcodes.push_back(n_operands); } } break; } } void COPTModel::decode_graph_prefix_order(ExpressionGraph &graph, const ExpressionHandle &result, std::vector &opcodes, std::vector &constants) { std::stack expr_stack; // init stack expr_stack.push(result); while (!expr_stack.empty()) { auto expr = expr_stack.top(); expr_stack.pop(); // We need to convert the n-arg multiplication to 2-arg multiplication if (expr.array == ArrayType::Nary && graph.m_naries[expr.id].op == NaryOperator::Mul) { auto &nary = graph.m_naries[expr.id]; int n_operands = nary.operands.size(); if (n_operands == 1) { expr = nary.operands[0]; } else if (n_operands >= 2) { ExpressionHandle left = nary.operands[0]; ExpressionHandle right = nary.operands[1]; ExpressionHandle new_expr = graph.add_binary(BinaryOperator::Mul2, left, right); for (int i = 2; i < n_operands; i++) { new_expr = graph.add_binary(BinaryOperator::Mul2, new_expr, nary.operands[i]); } expr = new_expr; } } decode_expr(graph, expr, opcodes, constants); auto array_type = expr.array; auto index = expr.id; switch (array_type) { case ArrayType::Unary: { auto &unary = graph.m_unaries[index]; expr_stack.push(unary.operand); break; } case ArrayType::Binary: { auto &binary = graph.m_binaries[index]; expr_stack.push(binary.right); expr_stack.push(binary.left); break; } case ArrayType::Nary: { auto &nary = graph.m_naries[index]; for (int i = nary.operands.size() - 1; i >= 0; i--) { expr_stack.push(nary.operands[i]); } break; } default: break; } } } ConstraintIndex COPTModel::add_single_nl_constraint(ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name) { double lb = std::get<0>(interval); double ub = std::get<1>(interval); std::vector opcodes; std::vector constants; decode_graph_prefix_order(graph, result, opcodes, constants); if (name != nullptr && name[0] == '\0') { name = nullptr; } // add NL constraint int error = copt::COPT_AddNLConstr(m_model.get(), opcodes.size(), constants.size(), opcodes.data(), constants.data(), 0, nullptr, nullptr, 0, lb, ub, name); check_error(error); IndexT constraint_index = m_nl_constraint_index.add_index(); ConstraintIndex constraint(ConstraintType::NL, constraint_index); return constraint; } void COPTModel::delete_constraint(const ConstraintIndex &constraint) { int error = 0; int constraint_row = _constraint_index(constraint); if (constraint_row >= 0) { switch (constraint.type) { case ConstraintType::Linear: m_linear_constraint_index.delete_index(constraint.index); error = copt::COPT_DelRows(m_model.get(), 1, &constraint_row); break; case ConstraintType::Quadratic: m_quadratic_constraint_index.delete_index(constraint.index); error = copt::COPT_DelQConstrs(m_model.get(), 1, &constraint_row); break; case ConstraintType::SOS: m_sos_constraint_index.delete_index(constraint.index); error = copt::COPT_DelSOSs(m_model.get(), 1, &constraint_row); break; case ConstraintType::SecondOrderCone: m_cone_constraint_index.delete_index(constraint.index); error = copt::COPT_DelCones(m_model.get(), 1, &constraint_row); break; case ConstraintType::ExponentialCone: m_exp_cone_constraint_index.delete_index(constraint.index); error = copt::COPT_DelExpCones(m_model.get(), 1, &constraint_row); break; default: throw std::runtime_error("Unknown constraint type"); } } check_error(error); } bool COPTModel::is_constraint_active(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: return m_linear_constraint_index.has_index(constraint.index); case ConstraintType::Quadratic: return m_quadratic_constraint_index.has_index(constraint.index); case ConstraintType::SOS: return m_sos_constraint_index.has_index(constraint.index); case ConstraintType::SecondOrderCone: return m_cone_constraint_index.has_index(constraint.index); case ConstraintType::ExponentialCone: return m_exp_cone_constraint_index.has_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } void COPTModel::_set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic) { int error = 0; if (clear_quadratic) { // First delete all quadratic terms error = copt::COPT_DelQuadObj(m_model.get()); check_error(error); } // Set Obj attribute of each variable int n_variables = get_raw_attribute_int(COPT_INTATTR_COLS); std::vector ind_v(n_variables); for (int i = 0; i < n_variables; i++) { ind_v[i] = i; } std::vector obj_v(n_variables, 0.0); int numnz = function.size(); for (int i = 0; i < numnz; i++) { auto column = _variable_index(function.variables[i]); if (column < 0) { throw std::runtime_error("Variable does not exist"); } obj_v[column] = function.coefficients[i]; } error = copt::COPT_ReplaceColObj(m_model.get(), n_variables, ind_v.data(), obj_v.data()); check_error(error); error = copt::COPT_SetObjConst(m_model.get(), function.constant.value_or(0.0)); check_error(error); int obj_sense = copt_obj_sense(sense); error = copt::COPT_SetObjSense(m_model.get(), obj_sense); check_error(error); } void COPTModel::set_objective(const ScalarAffineFunction &function, ObjectiveSense sense) { _set_affine_objective(function, sense, true); } void COPTModel::set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense) { int error = 0; // First delete all quadratic terms error = copt::COPT_DelQuadObj(m_model.get()); check_error(error); // Add quadratic term int numqnz = function.size(); if (numqnz > 0) { QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); int numqnz = ptr_form.numnz; int *qrow = ptr_form.row; int *qcol = ptr_form.col; double *qval = ptr_form.value; error = copt::COPT_SetQuadObj(m_model.get(), numqnz, qrow, qcol, qval); check_error(error); } // Affine part const auto &affine_part = function.affine_part; if (affine_part) { const auto &affine_function = affine_part.value(); _set_affine_objective(affine_function, sense, false); } else { ScalarAffineFunction zero; _set_affine_objective(zero, sense, false); } } void COPTModel::set_objective(const ExprBuilder &function, ObjectiveSense sense) { auto deg = function.degree(); if (deg <= 1) { ScalarAffineFunction f(function); set_objective(f, sense); } else if (deg == 2) { ScalarQuadraticFunction f(function); set_objective(f, sense); } else { throw std::runtime_error("Objective must be linear or quadratic"); } } void COPTModel::add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result) { decode_graph_prefix_order(graph, result, m_nl_objective_opcodes, m_nl_objective_constants); m_nl_objective_num += 1; m_nl_objective_opcodes[1] = m_nl_objective_num; } void COPTModel::set_nl_objective() { int error = 0; if (m_nl_objective_num > 0) { int nToken = m_nl_objective_opcodes.size(); int nTokenElem = m_nl_objective_constants.size(); int *token = m_nl_objective_opcodes.data(); double *tokenElem = m_nl_objective_constants.data(); if (m_nl_objective_num == 1) { nToken -= 2; token += 2; } /*fmt::print("COPT_SetNLObj opcodes: {}\n", std::vector(token, token + nToken)); fmt::print("COPT_SetNLObj constants: {}\n", std::vector(tokenElem, tokenElem + nTokenElem));*/ error = copt::COPT_SetNLObj(m_model.get(), nToken, nTokenElem, token, tokenElem); check_error(error); } } void COPTModel::optimize() { if (has_callback) { // Store the number of variables for the callback m_callback_userdata.n_variables = get_raw_attribute_int("Cols"); } set_nl_objective(); int error = copt::COPT_Solve(m_model.get()); check_error(error); } int COPTModel::raw_parameter_attribute_type(const char *name) { int retval; int error = copt::COPT_SearchParamAttr(m_model.get(), name, &retval); check_error(error); return retval; } void COPTModel::set_raw_parameter_int(const char *param_name, int value) { int error = copt::COPT_SetIntParam(m_model.get(), param_name, value); check_error(error); } void COPTModel::set_raw_parameter_double(const char *param_name, double value) { int error = copt::COPT_SetDblParam(m_model.get(), param_name, value); check_error(error); } int COPTModel::get_raw_parameter_int(const char *param_name) { int retval; int error = copt::COPT_GetIntParam(m_model.get(), param_name, &retval); check_error(error); return retval; } double COPTModel::get_raw_parameter_double(const char *param_name) { double retval; int error = copt::COPT_GetDblParam(m_model.get(), param_name, &retval); check_error(error); return retval; } int COPTModel::get_raw_attribute_int(const char *attr_name) { int retval; int error = copt::COPT_GetIntAttr(m_model.get(), attr_name, &retval); check_error(error); return retval; } double COPTModel::get_raw_attribute_double(const char *attr_name) { double retval; int error = copt::COPT_GetDblAttr(m_model.get(), attr_name, &retval); check_error(error); return retval; } double COPTModel::get_variable_info(const VariableIndex &variable, const char *info_name) { auto column = _checked_variable_index(variable); double retval; int error = copt::COPT_GetColInfo(m_model.get(), info_name, 1, &column, &retval); check_error(error); return retval; } std::string COPTModel::get_variable_name(const VariableIndex &variable) { auto column = _checked_variable_index(variable); int error; int reqsize; error = copt::COPT_GetColName(m_model.get(), column, NULL, 0, &reqsize); check_error(error); std::string retval(reqsize - 1, '\0'); error = copt::COPT_GetColName(m_model.get(), column, retval.data(), reqsize, &reqsize); check_error(error); return retval; } void COPTModel::set_variable_name(const VariableIndex &variable, const char *name) { auto column = _checked_variable_index(variable); const char *names[] = {name}; int error = copt::COPT_SetColNames(m_model.get(), 1, &column, names); check_error(error); } VariableDomain COPTModel::get_variable_type(const VariableIndex &variable) { auto column = _checked_variable_index(variable); char vtype; int error = copt::COPT_GetColType(m_model.get(), 1, &column, &vtype); check_error(error); return copt_vtype_to_domain(vtype); } void COPTModel::set_variable_type(const VariableIndex &variable, VariableDomain domain) { char vtype = copt_vtype(domain); auto column = _checked_variable_index(variable); int error = copt::COPT_SetColType(m_model.get(), 1, &column, &vtype); check_error(error); } void COPTModel::set_variable_lower_bound(const VariableIndex &variable, double lb) { auto column = _checked_variable_index(variable); int error = copt::COPT_SetColLower(m_model.get(), 1, &column, &lb); check_error(error); } void COPTModel::set_variable_upper_bound(const VariableIndex &variable, double ub) { auto column = _checked_variable_index(variable); int error = copt::COPT_SetColUpper(m_model.get(), 1, &column, &ub); check_error(error); } double COPTModel::get_constraint_info(const ConstraintIndex &constraint, const char *info_name) { int row = _checked_constraint_index(constraint); double retval; int num = 1; int error; switch (constraint.type) { case ConstraintType::Linear: error = copt::COPT_GetRowInfo(m_model.get(), info_name, num, &row, &retval); break; case ConstraintType::Quadratic: error = copt::COPT_GetQConstrInfo(m_model.get(), info_name, num, &row, &retval); break; case ConstraintType::NL: error = copt::COPT_GetNLConstrInfo(m_model.get(), info_name, num, &row, &retval); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); return retval; } std::string COPTModel::get_constraint_name(const ConstraintIndex &constraint) { int row = _checked_constraint_index(constraint); int error; int reqsize; switch (constraint.type) { case ConstraintType::Linear: error = copt::COPT_GetRowName(m_model.get(), row, NULL, 0, &reqsize); break; case ConstraintType::Quadratic: error = copt::COPT_GetQConstrName(m_model.get(), row, NULL, 0, &reqsize); break; case ConstraintType::NL: error = copt::COPT_GetNLConstrName(m_model.get(), row, NULL, 0, &reqsize); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); std::string retval(reqsize - 1, '\0'); switch (constraint.type) { case ConstraintType::Linear: error = copt::COPT_GetRowName(m_model.get(), row, retval.data(), reqsize, &reqsize); break; case ConstraintType::Quadratic: error = copt::COPT_GetQConstrName(m_model.get(), row, retval.data(), reqsize, &reqsize); break; } check_error(error); return retval; } void COPTModel::set_constraint_name(const ConstraintIndex &constraint, const char *name) { int row = _checked_constraint_index(constraint); const char *names[] = {name}; int error; switch (constraint.type) { case ConstraintType::Linear: error = copt::COPT_SetRowNames(m_model.get(), 1, &row, names); break; case ConstraintType::Quadratic: error = copt::COPT_SetQConstrNames(m_model.get(), 1, &row, names); break; case ConstraintType::NL: error = copt::COPT_SetNLConstrNames(m_model.get(), 1, &row, names); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); } void COPTModel::set_obj_sense(ObjectiveSense sense) { int obj_sense = copt_obj_sense(sense); int error = copt::COPT_SetObjSense(m_model.get(), obj_sense); check_error(error); } void COPTModel::add_mip_start(const Vector &variables, const Vector &values) { if (variables.size() != values.size()) { throw std::runtime_error("Number of variables and values do not match"); } int numnz = variables.size(); if (numnz == 0) return; std::vector ind_v(numnz); for (int i = 0; i < numnz; i++) { ind_v[i] = _variable_index(variables[i].index); } int *ind = ind_v.data(); double *val = (double *)values.data(); int error = copt::COPT_AddMipStart(m_model.get(), numnz, ind, val); check_error(error); } void COPTModel::add_nl_start(const Vector &variables, const Vector &values) { if (variables.size() != values.size()) { throw std::runtime_error("Number of variables and values do not match"); } int numnz = variables.size(); if (numnz == 0) return; std::vector ind_v(numnz); for (int i = 0; i < numnz; i++) { ind_v[i] = _variable_index(variables[i].index); } int *ind = ind_v.data(); double *val = (double *)values.data(); int error = copt::COPT_SetNLPrimalStart(m_model.get(), numnz, ind, val); check_error(error); } double COPTModel::get_normalized_rhs(const ConstraintIndex &constraint) { int row = _checked_constraint_index(constraint); int num = 1; int error; switch (constraint.type) { case ConstraintType::Linear: { double lb, ub; error = copt::COPT_GetRowInfo(m_model.get(), COPT_DBLINFO_LB, num, &row, &lb); check_error(error); error = copt::COPT_GetRowInfo(m_model.get(), COPT_DBLINFO_UB, num, &row, &ub); check_error(error); bool lb_inf = lb < -COPT_INFINITY + 1.0; bool ub_inf = ub > COPT_INFINITY - 1.0; if (!lb_inf) return lb; if (!ub_inf) return ub; throw std::runtime_error("Constraint has no finite bound"); } break; case ConstraintType::Quadratic: { double rhs; error = copt::COPT_GetQConstrRhs(m_model.get(), num, &row, &rhs); check_error(error); return rhs; } break; default: throw std::runtime_error("Unknown constraint type to get_normalized_rhs"); } } void COPTModel::set_normalized_rhs(const ConstraintIndex &constraint, double value) { int row = _checked_constraint_index(constraint); int num = 1; int error; switch (constraint.type) { case ConstraintType::Linear: { double lb, ub; error = copt::COPT_GetRowInfo(m_model.get(), COPT_DBLINFO_LB, num, &row, &lb); check_error(error); error = copt::COPT_GetRowInfo(m_model.get(), COPT_DBLINFO_UB, num, &row, &ub); check_error(error); bool lb_inf = lb < -COPT_INFINITY + 1.0; bool ub_inf = ub > COPT_INFINITY - 1.0; if (!lb_inf) { error = copt::COPT_SetRowLower(m_model.get(), num, &row, &value); check_error(error); } if (!ub_inf) { error = copt::COPT_SetRowUpper(m_model.get(), num, &row, &value); check_error(error); } if (lb_inf && ub_inf) { throw std::runtime_error("Constraint has no finite bound"); } } break; case ConstraintType::Quadratic: { error = copt::COPT_SetQConstrRhs(m_model.get(), num, &row, &value); check_error(error); } break; default: throw std::runtime_error("Unknown constraint type to set_normalized_rhs"); } } double COPTModel::get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable) { if (constraint.type != ConstraintType::Linear) { throw std::runtime_error("Only linear constraint supports get_normalized_coefficient"); } int row = _checked_constraint_index(constraint); int col = _checked_variable_index(variable); double retval; int error = copt::COPT_GetElem(m_model.get(), col, row, &retval); check_error(error); return retval; } void COPTModel::set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value) { if (constraint.type != ConstraintType::Linear) { throw std::runtime_error("Only linear constraint supports set_normalized_coefficient"); } int row = _checked_constraint_index(constraint); int col = _checked_variable_index(variable); int error = copt::COPT_SetElem(m_model.get(), col, row, value); check_error(error); } double COPTModel::get_objective_coefficient(const VariableIndex &variable) { return get_variable_info(variable, COPT_DBLINFO_OBJ); } void COPTModel::set_objective_coefficient(const VariableIndex &variable, double value) { auto column = _checked_variable_index(variable); int error = copt::COPT_SetColObj(m_model.get(), 1, &column, &value); check_error(error); } int COPTModel::_variable_index(const VariableIndex &variable) { return m_variable_index.get_index(variable.index); } int COPTModel::_checked_variable_index(const VariableIndex &variable) { int column = _variable_index(variable); if (column < 0) { throw std::runtime_error("Variable does not exist"); } return column; } int COPTModel::_constraint_index(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: return m_linear_constraint_index.get_index(constraint.index); case ConstraintType::Quadratic: return m_quadratic_constraint_index.get_index(constraint.index); case ConstraintType::SOS: return m_sos_constraint_index.get_index(constraint.index); case ConstraintType::SecondOrderCone: return m_cone_constraint_index.get_index(constraint.index); case ConstraintType::ExponentialCone: return m_exp_cone_constraint_index.get_index(constraint.index); case ConstraintType::NL: return m_nl_constraint_index.get_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } int COPTModel::_checked_constraint_index(const ConstraintIndex &constraint) { int row = _constraint_index(constraint); if (row < 0) { throw std::runtime_error("Constraint does not exist"); } return row; } void *COPTModel::get_raw_model() { return m_model.get(); } std::string COPTModel::version_string() { char buffer[COPT_BUFFSIZE]; copt::COPT_GetBanner(buffer, COPT_BUFFSIZE); return buffer; } COPTEnv::COPTEnv() { if (!copt::is_library_loaded()) { throw std::runtime_error("COPT library is not loaded"); } int error = copt::COPT_CreateEnv(&m_env); check_error(error); } COPTEnv::COPTEnv(COPTEnvConfig &config) { if (!copt::is_library_loaded()) { throw std::runtime_error("COPT library is not loaded"); } int error = copt::COPT_CreateEnvWithConfig(config.m_config, &m_env); check_error(error); } COPTEnv::~COPTEnv() { int error = copt::COPT_DeleteEnv(&m_env); check_error(error); } void COPTEnv::close() { if (m_env != nullptr) { copt::COPT_DeleteEnv(&m_env); } m_env = nullptr; } COPTEnvConfig::COPTEnvConfig() { if (!copt::is_library_loaded()) { throw std::runtime_error("COPT library is not loaded"); } int error = copt::COPT_CreateEnvConfig(&m_config); check_error(error); } COPTEnvConfig::~COPTEnvConfig() { int error = copt::COPT_DeleteEnvConfig(&m_config); check_error(error); } void COPTEnvConfig::set(const char *param_name, const char *value) { int error = copt::COPT_SetEnvConfig(m_config, param_name, value); check_error(error); } // Logging callback static void RealLoggingCallbackFunction(char *msg, void *logdata) { auto real_logdata = static_cast(logdata); auto &callback = real_logdata->callback; callback(msg); } void COPTModel::set_logging(const COPTLoggingCallback &callback) { m_logging_callback_userdata.callback = callback; int error = copt::COPT_SetLogCallback(m_model.get(), &RealLoggingCallbackFunction, &m_logging_callback_userdata); check_error(error); } // Callback static int RealCOPTCallbackFunction(copt_prob *prob, void *cbdata, int cbctx, void *userdata) { auto real_userdata = static_cast(userdata); auto model = static_cast(real_userdata->model); auto &callback = real_userdata->callback; model->m_cbdata = cbdata; model->m_callback_userdata.where = cbctx; model->m_callback_userdata.cb_get_mipsol_called = false; model->m_callback_userdata.cb_get_mipnoderel_called = false; model->m_callback_userdata.cb_get_mipincumbent_called = false; model->m_callback_userdata.cb_set_solution_called = false; model->m_callback_userdata.cb_requires_submit_solution = false; callback(model, cbctx); if (model->m_callback_userdata.cb_requires_submit_solution) { model->cb_submit_solution(); } return COPT_RETCODE_OK; } void COPTModel::set_callback(const COPTCallback &callback, int cbctx) { m_callback_userdata.model = this; m_callback_userdata.callback = callback; int error = copt::COPT_SetCallback(m_model.get(), RealCOPTCallbackFunction, cbctx, &m_callback_userdata); check_error(error); has_callback = true; } int COPTModel::cb_get_info_int(const std::string &what) { int retval; int error = copt::COPT_GetCallbackInfo(m_cbdata, what.c_str(), &retval); check_error(error); return retval; } double COPTModel::cb_get_info_double(const std::string &what) { double retval; int error = copt::COPT_GetCallbackInfo(m_cbdata, what.c_str(), &retval); check_error(error); return retval; } void COPTModel::cb_get_info_doublearray(const std::string &what) { int n_vars = m_callback_userdata.n_variables; double *val = nullptr; if (what == COPT_CBINFO_MIPCANDIDATE) { m_callback_userdata.mipsol.resize(n_vars); val = m_callback_userdata.mipsol.data(); } else if (what == COPT_CBINFO_RELAXSOLUTION) { m_callback_userdata.mipnoderel.resize(n_vars); val = m_callback_userdata.mipnoderel.data(); } else if (what == COPT_CBINFO_INCUMBENT) { m_callback_userdata.mipincumbent.resize(n_vars); val = m_callback_userdata.mipincumbent.data(); } else { throw std::runtime_error("Invalid what for cb_get_info_doublearray"); } int error = copt::COPT_GetCallbackInfo(m_cbdata, what.c_str(), val); check_error(error); } double COPTModel::cb_get_solution(const VariableIndex &variable) { auto &userdata = m_callback_userdata; if (!userdata.cb_get_mipsol_called) { cb_get_info_doublearray(COPT_CBINFO_MIPCANDIDATE); userdata.cb_get_mipsol_called = true; } auto index = _variable_index(variable); return userdata.mipsol[index]; } double COPTModel::cb_get_relaxation(const VariableIndex &variable) { auto &userdata = m_callback_userdata; if (!userdata.cb_get_mipnoderel_called) { cb_get_info_doublearray(COPT_CBINFO_RELAXSOLUTION); userdata.cb_get_mipnoderel_called = true; } auto index = _variable_index(variable); return userdata.mipnoderel[index]; } double COPTModel::cb_get_incumbent(const VariableIndex &variable) { auto &userdata = m_callback_userdata; if (!userdata.cb_get_mipincumbent_called) { cb_get_info_doublearray(COPT_CBINFO_INCUMBENT); userdata.cb_get_mipincumbent_called = true; } auto index = _variable_index(variable); return userdata.mipincumbent[index]; } void COPTModel::cb_set_solution(const VariableIndex &variable, double value) { auto &userdata = m_callback_userdata; if (!userdata.cb_set_solution_called) { userdata.heuristic_solution.resize(userdata.n_variables, COPT_UNDEFINED); userdata.cb_set_solution_called = true; } userdata.heuristic_solution[_variable_index(variable)] = value; m_callback_userdata.cb_requires_submit_solution = true; } double COPTModel::cb_submit_solution() { if (!m_callback_userdata.cb_set_solution_called) { throw std::runtime_error("No solution is set in the callback!"); } double obj; int error = copt::COPT_AddCallbackSolution(m_cbdata, m_callback_userdata.heuristic_solution.data(), &obj); check_error(error); m_callback_userdata.cb_requires_submit_solution = false; return obj; } void COPTModel::cb_exit() { int error = copt::COPT_Interrupt(m_model.get()); check_error(error); } void COPTModel::cb_add_lazy_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; char g_sense = copt_con_sense(sense); double g_rhs = rhs - function.constant.value_or(0.0); int error = copt::COPT_AddCallbackLazyConstr(m_cbdata, numnz, cind, cval, g_sense, g_rhs); check_error(error); } void COPTModel::cb_add_lazy_constraint(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs) { ScalarAffineFunction f(function); cb_add_lazy_constraint(f, sense, rhs); } void COPTModel::cb_add_user_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; char g_sense = copt_con_sense(sense); double g_rhs = rhs - function.constant.value_or(0.0); int error = copt::COPT_AddCallbackUserCut(m_cbdata, numnz, cind, cval, g_sense, g_rhs); check_error(error); } void COPTModel::cb_add_user_cut(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs) { ScalarAffineFunction f(function); cb_add_user_cut(f, sense, rhs); } void COPTModel::computeIIS() { int error = copt::COPT_ComputeIIS(m_model.get()); check_error(error); } int COPTModel::_get_variable_upperbound_IIS(const VariableIndex &variable) { auto column = _checked_variable_index(variable); int retval; int error = copt::COPT_GetColUpperIIS(m_model.get(), 1, &column, &retval); check_error(error); return retval; } int COPTModel::_get_variable_lowerbound_IIS(const VariableIndex &variable) { auto column = _checked_variable_index(variable); int retval; int error = copt::COPT_GetColLowerIIS(m_model.get(), 1, &column, &retval); check_error(error); return retval; } int COPTModel::_get_constraint_IIS(const ConstraintIndex &constraint) { int row = _checked_constraint_index(constraint); int num = 1; int error; switch (constraint.type) { case ConstraintType::Linear: { int lb_iis, ub_iis; error = copt::COPT_GetRowLowerIIS(m_model.get(), num, &row, &lb_iis); check_error(error); error = copt::COPT_GetRowUpperIIS(m_model.get(), num, &row, &ub_iis); check_error(error); return lb_iis + ub_iis; } break; case ConstraintType::SOS: { int iis; error = copt::COPT_GetSOSIIS(m_model.get(), num, &row, &iis); check_error(error); return iis; } break; default: throw std::runtime_error("Unknown constraint type to get IIS state"); } } ================================================ FILE: lib/copt_model_ext.cpp ================================================ #include #include #include #include #include #include "pyoptinterface/copt_model.hpp" namespace nb = nanobind; extern void bind_copt_constants(nb::module_ &m); NB_MODULE(copt_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.def("is_library_loaded", &copt::is_library_loaded); m.def("load_library", &copt::load_library); bind_copt_constants(m); nb::class_(m, "EnvConfig").def(nb::init<>()).def("set", &COPTEnvConfig::set); nb::class_(m, "Env") .def(nb::init<>()) .def(nb::init()) .def("close", &COPTEnv::close); #define BIND_F(f) .def(#f, &COPTModel::f) nb::class_(m, "RawModel") .def(nb::init<>()) .def(nb::init()) // clang-format off BIND_F(init) BIND_F(write) BIND_F(close) // clang-format on .def("add_variable", &COPTModel::add_variable, nb::arg("domain") = VariableDomain::Continuous, nb::arg("lb") = -COPT_INFINITY, nb::arg("ub") = COPT_INFINITY, nb::arg("name") = "") // clang-format off BIND_F(delete_variable) BIND_F(delete_variables) BIND_F(is_variable_active) // clang-format on .def("set_variable_bounds", &COPTModel::set_variable_bounds, nb::arg("variable"), nb::arg("lb"), nb::arg("ub")) .def("get_value", nb::overload_cast(&COPTModel::get_variable_value)) .def("get_value", nb::overload_cast(&COPTModel::get_expression_value)) .def("get_value", nb::overload_cast(&COPTModel::get_expression_value)) .def("get_value", nb::overload_cast(&COPTModel::get_expression_value)) .def("pprint", &COPTModel::pprint_variable) .def("pprint", nb::overload_cast(&COPTModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&COPTModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&COPTModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("_add_linear_constraint", nb::overload_cast( &COPTModel::add_linear_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", nb::overload_cast &, const char *>(&COPTModel::add_linear_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &COPTModel::add_linear_constraint_from_var, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &COPTModel::add_linear_interval_constraint_from_var, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &COPTModel::add_linear_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &COPTModel::add_linear_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_quadratic_constraint", &COPTModel::add_quadratic_constraint, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", &COPTModel::add_quadratic_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("add_second_order_cone_constraint", &COPTModel::add_second_order_cone_constraint, nb::arg("variables"), nb::arg("name") = "", nb::arg("rotated") = false) .def("add_exp_cone_constraint", &COPTModel::add_exp_cone_constraint, nb::arg("variables"), nb::arg("name") = "", nb::arg("dual") = false) .def("add_sos_constraint", nb::overload_cast &, SOSType>( &COPTModel::add_sos_constraint)) .def("add_sos_constraint", nb::overload_cast &, SOSType, const Vector &>( &COPTModel::add_sos_constraint)) .def("_add_single_nl_constraint", &COPTModel::add_single_nl_constraint, nb::arg("graph"), nb::arg("result"), nb::arg("interval"), nb::arg("name") = "") .def("_add_single_nl_constraint", &COPTModel::add_single_nl_constraint_sense_rhs, nb::arg("graph"), nb::arg("result"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_single_nl_constraint", &COPTModel::add_single_nl_constraint_from_comparison, nb::arg("graph"), nb::arg("expr"), nb::arg("name") = "") // clang-format off BIND_F(delete_constraint) BIND_F(is_constraint_active) // clang-format on .def("set_objective", nb::overload_cast( &COPTModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &COPTModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&COPTModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", &COPTModel::set_objective_as_variable, nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", &COPTModel::set_objective_as_constant, nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("_add_single_nl_objective", &COPTModel::add_single_nl_objective) .def("cb_add_lazy_constraint", nb::overload_cast( &COPTModel::cb_add_lazy_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("cb_add_lazy_constraint", nb::overload_cast( &COPTModel::cb_add_lazy_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("cb_add_user_cut", nb::overload_cast( &COPTModel::cb_add_user_cut), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("cb_add_user_cut", nb::overload_cast( &COPTModel::cb_add_user_cut), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("optimize", &COPTModel::optimize, nb::call_guard()) // clang-format off BIND_F(version_string) BIND_F(get_raw_model) BIND_F(set_logging) BIND_F(set_callback) BIND_F(cb_get_info_int) BIND_F(cb_get_info_double) BIND_F(cb_get_solution) BIND_F(cb_get_relaxation) BIND_F(cb_get_incumbent) BIND_F(cb_set_solution) BIND_F(cb_submit_solution) BIND_F(cb_exit) BIND_F(raw_parameter_attribute_type) BIND_F(set_raw_parameter_int) BIND_F(set_raw_parameter_double) BIND_F(get_raw_parameter_int) BIND_F(get_raw_parameter_double) BIND_F(get_raw_attribute_int) BIND_F(get_raw_attribute_double) BIND_F(get_variable_info) BIND_F(set_variable_name) BIND_F(get_variable_name) BIND_F(set_variable_type) BIND_F(get_variable_type) BIND_F(set_variable_lower_bound) BIND_F(set_variable_upper_bound) BIND_F(get_constraint_info) BIND_F(set_constraint_name) BIND_F(get_constraint_name) BIND_F(set_obj_sense) BIND_F(add_mip_start) BIND_F(add_nl_start) BIND_F(get_normalized_rhs) BIND_F(set_normalized_rhs) BIND_F(get_normalized_coefficient) BIND_F(set_normalized_coefficient) BIND_F(get_objective_coefficient) BIND_F(set_objective_coefficient) BIND_F(computeIIS) BIND_F(_get_variable_upperbound_IIS) BIND_F(_get_variable_lowerbound_IIS) BIND_F(_get_constraint_IIS) // clang-format on ; } ================================================ FILE: lib/copt_model_ext_constants.cpp ================================================ #include namespace nb = nanobind; void bind_copt_constants(nb::module_ &m) { nb::module_ COPT = m.def_submodule("COPT"); COPT.attr("BASIS_BASIC") = 1; COPT.attr("BASIS_FIXED") = 4; COPT.attr("BASIS_LOWER") = 0; COPT.attr("BASIS_SUPERBASIC") = 3; COPT.attr("BASIS_UPPER") = 2; COPT.attr("BINARY") = "B"; COPT.attr("CBCONTEXT_INCUMBENT") = 8; COPT.attr("CBCONTEXT_MIPNODE") = 4; COPT.attr("CBCONTEXT_MIPRELAX") = 1; COPT.attr("CBCONTEXT_MIPSOL") = 2; COPT.attr("CLIENT_CAFILE") = "CaFile"; COPT.attr("CLIENT_CERTFILE") = "CertFile"; COPT.attr("CLIENT_CERTKEYFILE") = "CertKeyFile"; COPT.attr("CLIENT_CLUSTER") = "Cluster"; COPT.attr("CLIENT_FLOATING") = "Floating"; COPT.attr("CLIENT_PASSWORD") = "PassWord"; COPT.attr("CLIENT_PORT") = "Port"; COPT.attr("CLIENT_PRIORITY") = "Priority"; COPT.attr("CLIENT_WAITTIME") = "WaitTime"; COPT.attr("CLIENT_WEBACESSKEY") = "WebAccessKey"; COPT.attr("CLIENT_WEBLICENSEID") = "WebLicenseId"; COPT.attr("CLIENT_WEBSERVER") = "WebServer"; COPT.attr("CLIENT_WEBTOKENDURATION") = "WebTokenDuration"; COPT.attr("CONE_QUAD") = 1; COPT.attr("CONE_RQUAD") = 2; COPT.attr("CONTINUOUS") = "C"; COPT.attr("EQUAL") = "E"; COPT.attr("EXPCONE_DUAL") = 4; COPT.attr("EXPCONE_PRIMAL") = 3; COPT.attr("FREE") = "N"; COPT.attr("GENCONSTR_ABS") = 1; COPT.attr("GENCONSTR_AND") = 2; COPT.attr("GENCONSTR_MAX") = 3; COPT.attr("GENCONSTR_MIN") = 4; COPT.attr("GENCONSTR_OR") = 5; COPT.attr("GENCONSTR_PWL") = 6; COPT.attr("GREATER_EQUAL") = "G"; COPT.attr("IMPRECISE") = 7; COPT.attr("INDICATOR_IF") = 1; COPT.attr("INDICATOR_IFANDONLYIF") = 3; COPT.attr("INDICATOR_ONLYIF") = 2; COPT.attr("INFEASIBLE") = 2; COPT.attr("INFINITY") = 1e+30; COPT.attr("INF_OR_UNB") = 4; COPT.attr("INTEGER") = "I"; COPT.attr("INTERRUPTED") = 10; COPT.attr("ITERLIMIT") = 11; COPT.attr("LESS_EQUAL") = "L"; COPT.attr("MAXIMIZE") = -1; COPT.attr("MINIMIZE") = 1; COPT.attr("NODELIMIT") = 6; COPT.attr("NUMERICAL") = 5; COPT.attr("OPTIMAL") = 1; COPT.attr("RANGE") = "R"; COPT.attr("SOS_TYPE1") = 1; COPT.attr("SOS_TYPE2") = 2; COPT.attr("TIMEOUT") = 8; COPT.attr("UNBOUNDED") = 3; COPT.attr("UNDEFINED") = 1e+40; COPT.attr("UNFINISHED") = 9; COPT.attr("UNSTARTED") = 0; COPT.attr("VERSION_MAJOR") = 8; COPT.attr("VERSION_MINOR") = 0; COPT.attr("VERSION_TECHNICAL") = 1; nb::module_ Attr = COPT.def_submodule("Attr"); Attr.attr("AffineCones") = "AffineCones"; Attr.attr("BarrierIter") = "BarrierIter"; Attr.attr("BestBnd") = "BestBnd"; Attr.attr("BestGap") = "BestGap"; Attr.attr("BestObj") = "BestObj"; Attr.attr("Bins") = "Bins"; Attr.attr("Cols") = "Cols"; Attr.attr("Cones") = "Cones"; Attr.attr("Elems") = "Elems"; Attr.attr("ExpCones") = "ExpCones"; Attr.attr("FeasRelaxObj") = "FeasRelaxObj"; Attr.attr("HasBasis") = "HasBasis"; Attr.attr("HasBranchFactor") = "HasBranchFactor"; Attr.attr("HasDualFarkas") = "HasDualFarkas"; Attr.attr("HasFeasRelaxSol") = "HasFeasRelaxSol"; Attr.attr("HasIIS") = "HasIIS"; Attr.attr("HasLpSol") = "HasLpSol"; Attr.attr("HasMipSol") = "HasMipSol"; Attr.attr("HasNLObj") = "HasNLObj"; Attr.attr("HasPrimalRay") = "HasPrimalRay"; Attr.attr("HasPsdObj") = "HasPSDObj"; Attr.attr("HasQObj") = "HasQObj"; Attr.attr("HasSensitivity") = "HasSensitivity"; Attr.attr("IISCols") = "IISCols"; Attr.attr("IISIndicators") = "IISIndicators"; Attr.attr("IISRows") = "IISRows"; Attr.attr("IISSOSs") = "IISSOSs"; Attr.attr("Indicators") = "Indicators"; Attr.attr("Ints") = "Ints"; Attr.attr("IsMIP") = "IsMIP"; Attr.attr("IsMinIIS") = "IsMinIIS"; Attr.attr("LmiConstrs") = "LMIConstrs"; Attr.attr("LpObjVal") = "LpObjVal"; Attr.attr("LpStatus") = "LpStatus"; Attr.attr("MipStatus") = "MipStatus"; Attr.attr("MultiObjs") = "MultiObjs"; Attr.attr("NLConstrs") = "NLConstrs"; Attr.attr("NLElems") = "NLElems"; Attr.attr("NodeCnt") = "NodeCnt"; Attr.attr("ObjConst") = "ObjConst"; Attr.attr("ObjSense") = "ObjSense"; Attr.attr("PDLPIter") = "PDLPIter"; Attr.attr("PoolSols") = "PoolSols"; Attr.attr("PsdCols") = "PSDCols"; Attr.attr("PsdConstrs") = "PSDConstrs"; Attr.attr("PsdElems") = "PSDElems"; Attr.attr("QConstrs") = "QConstrs"; Attr.attr("QElems") = "QElems"; Attr.attr("Rows") = "Rows"; Attr.attr("SimplexIter") = "SimplexIter"; Attr.attr("SolvingTime") = "SolvingTime"; Attr.attr("Soss") = "Soss"; Attr.attr("SymMats") = "SymMats"; Attr.attr("TuneResults") = "TuneResults"; nb::module_ Param = COPT.def_submodule("Param"); Param.attr("AbsGap") = "AbsGap"; Param.attr("BarHomogeneous") = "BarHomogeneous"; Param.attr("BarIterLimit") = "BarIterLimit"; Param.attr("BarOrder") = "BarOrder"; Param.attr("BarStart") = "BarStart"; Param.attr("BarThreads") = "BarThreads"; Param.attr("ConflictAnalysis") = "ConflictAnalysis"; Param.attr("Crossover") = "Crossover"; Param.attr("CrossoverThreads") = "CrossoverThreads"; Param.attr("CutLevel") = "CutLevel"; Param.attr("DivingHeurLevel") = "DivingHeurLevel"; Param.attr("DualPerturb") = "DualPerturb"; Param.attr("DualPrice") = "DualPrice"; Param.attr("DualTol") = "DualTol"; Param.attr("Dualize") = "Dualize"; Param.attr("FAPHeurLevel") = "FAPHeurLevel"; Param.attr("FeasRelaxMode") = "FeasRelaxMode"; Param.attr("FeasTol") = "FeasTol"; Param.attr("GPUDevice") = "GPUDevice"; Param.attr("GPUMode") = "GPUMode"; Param.attr("HeurLevel") = "HeurLevel"; Param.attr("IISMethod") = "IISMethod"; Param.attr("IntTol") = "IntTol"; Param.attr("LazyConstraints") = "LazyConstraints"; Param.attr("LinearizeIndicators") = "LinearizeIndicators"; Param.attr("LinearizeSos") = "LinearizeSos"; Param.attr("LogToConsole") = "LogToConsole"; Param.attr("Logging") = "Logging"; Param.attr("LpMethod") = "LpMethod"; Param.attr("MatrixTol") = "MatrixTol"; Param.attr("MipStartMode") = "MipStartMode"; Param.attr("MipStartNodeLimit") = "MipStartNodeLimit"; Param.attr("MipTasks") = "MipTasks"; Param.attr("MultiObjAbsTol") = "MultiObjAbsTol"; Param.attr("MultiObjParamMode") = "MultiObjParamMode"; Param.attr("MultiObjPriority") = "MultiObjPriority"; Param.attr("MultiObjRelTol") = "MultiObjRelTol"; Param.attr("MultiObjTimeLimit") = "MultiObjTimeLimit"; Param.attr("MultiObjWeight") = "MultiObjWeight"; Param.attr("NLPIterLimit") = "NLPIterLimit"; Param.attr("NLPLinScale") = "NLPLinScale"; Param.attr("NLPMuUpdate") = "NLPMuUpdate"; Param.attr("NLPTol") = "NLPTol"; Param.attr("NodeCutRounds") = "NodeCutRounds"; Param.attr("NodeLimit") = "NodeLimit"; Param.attr("NonConvex") = "NonConvex"; Param.attr("PDLPTol") = "PDLPTol"; Param.attr("Presolve") = "Presolve"; Param.attr("RelGap") = "RelGap"; Param.attr("ReqFarkasRay") = "ReqFarkasRay"; Param.attr("ReqSensitivity") = "ReqSensitivity"; Param.attr("RootCutLevel") = "RootCutLevel"; Param.attr("RootCutRounds") = "RootCutRounds"; Param.attr("RoundingHeurLevel") = "RoundingHeurLevel"; Param.attr("SDPMethod") = "SDPMethod"; Param.attr("Scaling") = "Scaling"; Param.attr("SimplexThreads") = "SimplexThreads"; Param.attr("SolTimeLimit") = "SolTimeLimit"; Param.attr("StrongBranching") = "StrongBranching"; Param.attr("SubMipHeurLevel") = "SubMipHeurLevel"; Param.attr("Threads") = "Threads"; Param.attr("TimeLimit") = "TimeLimit"; Param.attr("TreeCutLevel") = "TreeCutLevel"; Param.attr("TuneMeasure") = "TuneMeasure"; Param.attr("TuneMethod") = "TuneMethod"; Param.attr("TuneMode") = "TuneMode"; Param.attr("TuneOutputLevel") = "TuneOutputLevel"; Param.attr("TunePermutes") = "TunePermutes"; Param.attr("TuneTargetRelGap") = "TuneTargetRelGap"; Param.attr("TuneTargetTime") = "TuneTargetTime"; Param.attr("TuneTimeLimit") = "TuneTimeLimit"; nb::module_ Info = COPT.def_submodule("Info"); Info.attr("BranchFactor") = "BranchFactor"; Info.attr("Dual") = "Dual"; Info.attr("DualFarkas") = "DualFarkas"; Info.attr("LB") = "LB"; Info.attr("Obj") = "Obj"; Info.attr("PrimalRay") = "PrimalRay"; Info.attr("RedCost") = "RedCost"; Info.attr("RelaxLB") = "RelaxLB"; Info.attr("RelaxUB") = "RelaxUB"; Info.attr("RelaxValue") = "RelaxValue"; Info.attr("SALBLow") = "SALBLow"; Info.attr("SALBUp") = "SALBUp"; Info.attr("SAObjLow") = "SAObjLow"; Info.attr("SAObjUp") = "SAObjUp"; Info.attr("SAUBLow") = "SAUBLow"; Info.attr("SAUBUp") = "SAUBUp"; Info.attr("Slack") = "Slack"; Info.attr("UB") = "UB"; Info.attr("Value") = "Value"; nb::module_ CbInfo = COPT.def_submodule("CbInfo"); CbInfo.attr("BestBnd") = "BestBnd"; CbInfo.attr("BestObj") = "BestObj"; CbInfo.attr("HasIncumbent") = "HasIncumbent"; CbInfo.attr("Incumbent") = "Incumbent"; CbInfo.attr("MipCandObj") = "MipCandObj"; CbInfo.attr("MipCandidate") = "MipCandidate"; CbInfo.attr("NodeStatus") = "NodeStatus"; CbInfo.attr("RelaxSolObj") = "RelaxSolObj"; CbInfo.attr("RelaxSolution") = "RelaxSolution"; } ================================================ FILE: lib/core.cpp ================================================ #include "pyoptinterface/core.hpp" #include #include #include "fmt/core.h" VariableIndex::VariableIndex(IndexT v) : index(v) { } ScalarAffineFunction::ScalarAffineFunction(CoeffT c) : constant(c) { } ScalarAffineFunction::ScalarAffineFunction(const VariableIndex &v) : coefficients({1.0}), variables({v.index}) { } ScalarAffineFunction::ScalarAffineFunction(const VariableIndex &v, CoeffT c) : coefficients({c}), variables({v.index}) { } ScalarAffineFunction::ScalarAffineFunction(const VariableIndex &v, CoeffT c1, CoeffT c2) : coefficients({c1}), variables({v.index}), constant(c2) { } ScalarAffineFunction::ScalarAffineFunction(const Vector &a, const Vector &b) : coefficients(a), variables(b) { } ScalarAffineFunction::ScalarAffineFunction(const Vector &a, const Vector &b, const std::optional &c) : coefficients(a), variables(b), constant(c) { } ScalarAffineFunction::ScalarAffineFunction(const ExprBuilder &t) { const auto &affine_terms = t.affine_terms; auto N = affine_terms.size(); coefficients.reserve(N); variables.reserve(N); for (auto &[v, c] : affine_terms) { coefficients.push_back(c); variables.push_back(v); } if (t.constant_term) { constant = t.constant_term.value(); } } size_t ScalarAffineFunction::size() const { return coefficients.size(); } void ScalarAffineFunction::canonicalize(CoeffT threshold) { ExprBuilder t(*this); t.clean_nearzero_terms(threshold); // *this = ScalarAffineFunction(t); // sort coefficients and variable lock by lock with std::views::zip // std::ranges::sort(std::views::zip(variables, coefficients)); variables.clear(); variables.reserve(t.affine_terms.size()); for (const auto &it : t.affine_terms) { variables.push_back(it.first); } std::sort(variables.begin(), variables.end()); coefficients.clear(); coefficients.reserve(variables.size()); for (const auto &it : variables) { coefficients.push_back(t.affine_terms[it]); } constant = t.constant_term; } void ScalarAffineFunction::reserve(size_t n) { coefficients.reserve(n); variables.reserve(n); } void ScalarAffineFunction::add_term(const VariableIndex &v, CoeffT c) { coefficients.push_back(c); variables.push_back(v.index); } void ScalarAffineFunction::add_constant(CoeffT c) { constant = constant.value_or(0.0) + c; } ScalarQuadraticFunction::ScalarQuadraticFunction(const Vector &c, const Vector &v1, const Vector &v2) : coefficients(c), variable_1s(v1), variable_2s(v2) { } ScalarQuadraticFunction::ScalarQuadraticFunction(const Vector &c, const Vector &v1, const Vector &v2, const std::optional &a) : coefficients(c), variable_1s(v1), variable_2s(v2), affine_part(a) { } ScalarQuadraticFunction::ScalarQuadraticFunction(const ExprBuilder &t) { const auto &quadratic_terms = t.quadratic_terms; auto N = quadratic_terms.size(); coefficients.reserve(N); variable_1s.reserve(N); variable_2s.reserve(N); for (auto &[vp, c] : quadratic_terms) { coefficients.push_back(c); variable_1s.push_back(vp.var_1); variable_2s.push_back(vp.var_2); } if (!t.affine_terms.empty() || t.constant_term) { affine_part = ScalarAffineFunction(t); } } size_t ScalarQuadraticFunction::size() const { return coefficients.size(); } void ScalarQuadraticFunction::canonicalize(CoeffT threshold) { ExprBuilder t(*this); t.clean_nearzero_terms(threshold); // *this = ScalarQuadraticFunction(t); // std::ranges::sort(std::views::zip(variable_1s, variable_2s, coefficients)); auto N = t.quadratic_terms.size(); std::vector varpairs; varpairs.reserve(N); for (const auto &it : t.quadratic_terms) { varpairs.emplace_back(it.first); } std::sort(varpairs.begin(), varpairs.end()); variable_1s.clear(); variable_1s.reserve(N); variable_2s.clear(); variable_2s.reserve(N); coefficients.clear(); coefficients.reserve(N); for (const auto &it : varpairs) { variable_1s.push_back(it.var_1); variable_2s.push_back(it.var_2); coefficients.push_back(t.quadratic_terms[it]); } if (affine_part) { auto &affine_function = affine_part.value(); affine_function.canonicalize(threshold); } } void ScalarQuadraticFunction::reserve_quadratic(size_t n) { coefficients.reserve(n); variable_1s.reserve(n); variable_2s.reserve(n); } void ScalarQuadraticFunction::reserve_affine(size_t n) { if (n > 0) { if (!affine_part) { affine_part = ScalarAffineFunction(); } affine_part.value().reserve(n); } } void ScalarQuadraticFunction::add_quadratic_term(const VariableIndex &v1, const VariableIndex &v2, CoeffT c) { coefficients.push_back(c); variable_1s.push_back(v1.index); variable_2s.push_back(v2.index); } void ScalarQuadraticFunction::add_affine_term(const VariableIndex &v, CoeffT c) { if (!affine_part) { affine_part = ScalarAffineFunction(); } affine_part.value().add_term(v, c); } void ScalarQuadraticFunction::add_constant(CoeffT c) { if (!affine_part) { affine_part = ScalarAffineFunction(c); } else { affine_part.value().add_constant(c); } } bool VariablePair::operator==(const VariablePair &x) const { return var_1 == x.var_1 && var_2 == x.var_2; } bool VariablePair::operator<(const VariablePair &x) const { if (var_1 != x.var_1) return var_1 < x.var_1; else return var_2 < x.var_2; } ExprBuilder::ExprBuilder(CoeffT c) { operator+=(c); } ExprBuilder::ExprBuilder(const VariableIndex &v) { operator+=(v); } ExprBuilder::ExprBuilder(const ScalarAffineFunction &a) { auto N = a.coefficients.size(); affine_terms.reserve(N); operator+=(a); } ExprBuilder::ExprBuilder(const ScalarQuadraticFunction &q) { if (q.affine_part) { affine_terms.reserve(q.affine_part.value().coefficients.size()); } auto N = q.coefficients.size(); quadratic_terms.reserve(N); operator+=(q); } bool ExprBuilder::empty() const { return quadratic_terms.empty() && affine_terms.empty() && !constant_term; } int ExprBuilder::degree() const { if (!quadratic_terms.empty()) { return 2; } if (!affine_terms.empty()) { return 1; } return 0; } void ExprBuilder::reserve_quadratic(size_t n) { quadratic_terms.reserve(n); } void ExprBuilder::reserve_affine(size_t n) { affine_terms.reserve(n); } void ExprBuilder::clear() { quadratic_terms.clear(); affine_terms.clear(); constant_term.reset(); } void ExprBuilder::clean_nearzero_terms(CoeffT threshold) { // clear all coefficients if abs(coefficient) < threshold for (auto it = quadratic_terms.begin(); it != quadratic_terms.end();) { if (std::abs(it->second) < threshold) { it = quadratic_terms.erase(it); } else { ++it; } } for (auto it = affine_terms.begin(); it != affine_terms.end();) { if (std::abs(it->second) < threshold) { it = affine_terms.erase(it); } else { ++it; } } if (constant_term && std::abs(*constant_term) < threshold) { constant_term.reset(); } } void ExprBuilder::_add_quadratic_term(IndexT i, IndexT j, CoeffT coeff) { if (i > j) { std::swap(i, j); } VariablePair vp{i, j}; auto ret = quadratic_terms.emplace(vp, coeff); if (!ret.second) { auto &iter = ret.first; iter->second += coeff; } } void ExprBuilder::_set_quadratic_coef(IndexT i, IndexT j, CoeffT coeff) { if (i > j) { std::swap(i, j); } VariablePair vp{i, j}; auto ret = quadratic_terms.emplace(vp, coeff); if (!ret.second) { auto &iter = ret.first; iter->second = coeff; } } void ExprBuilder::add_quadratic_term(const VariableIndex &i, const VariableIndex &j, CoeffT coeff) { _add_quadratic_term(i.index, j.index, coeff); } void ExprBuilder::set_quadratic_coef(const VariableIndex &i, const VariableIndex &j, CoeffT coeff) { _set_quadratic_coef(i.index, j.index, coeff); } void ExprBuilder::_add_affine_term(IndexT i, CoeffT coeff) { auto ret = affine_terms.emplace(i, coeff); if (!ret.second) { auto &iter = ret.first; iter->second += coeff; } } void ExprBuilder::_set_affine_coef(IndexT i, CoeffT coeff) { auto ret = affine_terms.emplace(i, coeff); if (!ret.second) { auto &iter = ret.first; iter->second = coeff; } } void ExprBuilder::add_affine_term(const VariableIndex &i, CoeffT coeff) { _add_affine_term(i.index, coeff); } void ExprBuilder::set_affine_coef(const VariableIndex &i, CoeffT coeff) { _set_affine_coef(i.index, coeff); } ExprBuilder &ExprBuilder::operator+=(CoeffT c) { constant_term = constant_term.value_or(0.0) + c; return *this; } ExprBuilder &ExprBuilder::operator+=(const VariableIndex &v) { _add_affine_term(v.index, 1.0); return *this; } ExprBuilder &ExprBuilder::operator+=(const ScalarAffineFunction &a) { auto N = a.coefficients.size(); for (auto i = 0; i < N; i++) { _add_affine_term(a.variables[i], a.coefficients[i]); } if (a.constant) { constant_term = constant_term.value_or(0.0) + a.constant.value(); } return *this; } ExprBuilder &ExprBuilder::operator+=(const ScalarQuadraticFunction &q) { if (q.affine_part) { operator+=(q.affine_part.value()); } auto N = q.coefficients.size(); for (auto i = 0; i < N; i++) { _add_quadratic_term(q.variable_1s[i], q.variable_2s[i], q.coefficients[i]); } return *this; } ExprBuilder &ExprBuilder::operator+=(const ExprBuilder &t) { if (this == &t) { for (auto &[varpair, c] : quadratic_terms) { c *= 2.0; } for (auto &[v, c] : affine_terms) { c *= 2.0; } if (constant_term) { constant_term = constant_term.value() * 2.0; } } else { for (const auto &[varpair, c] : t.quadratic_terms) { _add_quadratic_term(varpair.var_1, varpair.var_2, c); } for (const auto &[v, c] : t.affine_terms) { _add_affine_term(v, c); } if (t.constant_term) { constant_term = constant_term.value_or(0.0) + t.constant_term.value(); } } return *this; } ExprBuilder &ExprBuilder::operator-=(CoeffT c) { constant_term = constant_term.value_or(0.0) - c; return *this; } ExprBuilder &ExprBuilder::operator-=(const VariableIndex &v) { _add_affine_term(v.index, -1.0); return *this; } ExprBuilder &ExprBuilder::operator-=(const ScalarAffineFunction &a) { auto N = a.coefficients.size(); for (auto i = 0; i < N; i++) { _add_affine_term(a.variables[i], -a.coefficients[i]); } if (a.constant) { constant_term = constant_term.value_or(0.0) - a.constant.value(); } return *this; } ExprBuilder &ExprBuilder::operator-=(const ScalarQuadraticFunction &q) { if (q.affine_part) { operator-=(q.affine_part.value()); } auto N = q.coefficients.size(); for (auto i = 0; i < N; i++) { _add_quadratic_term(q.variable_1s[i], q.variable_2s[i], -q.coefficients[i]); } return *this; } ExprBuilder &ExprBuilder::operator-=(const ExprBuilder &t) { if (this == &t) { quadratic_terms.clear(); affine_terms.clear(); constant_term.reset(); } else { for (const auto &[varpair, c] : t.quadratic_terms) { _add_quadratic_term(varpair.var_1, varpair.var_2, -c); } for (const auto &[v, c] : t.affine_terms) { _add_affine_term(v, -c); } if (t.constant_term) { constant_term = constant_term.value_or(0.0) - t.constant_term.value(); } } return *this; } ExprBuilder &ExprBuilder::operator*=(CoeffT c) { for (auto &[varpair, cc] : quadratic_terms) { cc *= c; } for (auto &[v, cc] : affine_terms) { cc *= c; } if (constant_term) { constant_term = constant_term.value() * c; } return *this; } ExprBuilder &ExprBuilder::operator*=(const VariableIndex &v) { auto deg = degree(); if (deg > 1) { throw std::logic_error( fmt::format("ExprBuilder with degree {} cannot multiply with VariableIndex", deg)); } auto N = affine_terms.size(); quadratic_terms.reserve(N); for (const auto &[var2, c] : affine_terms) { _add_quadratic_term(v.index, var2, c); } if (constant_term) { affine_terms = {{v.index, constant_term.value()}}; constant_term.reset(); } else { affine_terms.clear(); } return *this; } ExprBuilder &ExprBuilder::operator*=(const ScalarAffineFunction &a) { auto deg = degree(); if (deg > 1) { throw std::logic_error(fmt::format( "ExprBuilder with degree {} cannot multiply with ScalarAffineFunction", deg)); } auto N1 = affine_terms.size(); auto N2 = a.size(); quadratic_terms.reserve(N1 * N2 / 2); for (const auto &[xi, ci] : affine_terms) { for (int j = 0; j < a.size(); j++) { auto dj = a.coefficients[j]; auto xj = a.variables[j]; _add_quadratic_term(xi, xj, ci * dj); } } if (a.constant) { auto d0 = a.constant.value(); for (auto &[_, ci] : affine_terms) { ci *= d0; } } else { affine_terms.clear(); } if (constant_term) { auto c0 = constant_term.value(); for (int j = 0; j < a.size(); j++) { auto dj = a.coefficients[j]; auto xj = a.variables[j]; _add_affine_term(xj, c0 * dj); } } if (a.constant && constant_term) { constant_term = a.constant.value() * constant_term.value(); } else { constant_term.reset(); } return *this; } ExprBuilder &ExprBuilder::operator*=(const ScalarQuadraticFunction &q) { auto deg = degree(); if (deg > 0) { throw std::logic_error(fmt::format( "ExprBuilder with degree {} cannot multiply with ScalarQuadraticFunction", deg)); } if (!constant_term) return *this; auto c = constant_term.value(); { auto N = q.coefficients.size(); quadratic_terms.reserve(N); for (auto i = 0; i < N; i++) { _add_quadratic_term(q.variable_1s[i], q.variable_2s[i], c * q.coefficients[i]); } } if (q.affine_part) { auto &affine_part = q.affine_part.value(); auto N = affine_part.coefficients.size(); affine_terms.reserve(N); for (auto i = 0; i < N; i++) { _add_affine_term(affine_part.variables[i], c * affine_part.coefficients[i]); } if (affine_part.constant) { constant_term = c * affine_part.constant.value(); } else { constant_term.reset(); } } else { constant_term.reset(); } return *this; } ExprBuilder &ExprBuilder::operator*=(const ExprBuilder &t) { auto deg1 = degree(); auto deg2 = t.degree(); if (deg1 + deg2 > 2) { throw std::logic_error(fmt::format( "ExprBuilder with degree {} cannot multiply with ExprBuilder with degree {}", deg1, deg2)); } if (this == &t) { auto deg = deg1; if (deg == 0) { if (constant_term) { auto c = constant_term.value(); constant_term = c * c; } } else // deg = 1 { auto N = affine_terms.size(); quadratic_terms.reserve(N * N / 2); for (const auto &[xi, ci] : affine_terms) { for (const auto &[xj, cj] : affine_terms) { _add_quadratic_term(xi, xj, ci * cj); } } if (constant_term) { auto d0 = constant_term.value(); for (auto &[_, ci] : affine_terms) { ci *= 2.0 * d0; } constant_term = d0 * d0; } else { affine_terms.clear(); } } } else { if (deg1 == 0) { if (constant_term) { auto c = constant_term.value(); auto N = t.quadratic_terms.size(); quadratic_terms.reserve(N); for (const auto &[varpair, c2] : t.quadratic_terms) { _add_quadratic_term(varpair.var_1, varpair.var_2, c * c2); } N = t.affine_terms.size(); affine_terms.reserve(N); for (const auto &[v, c1] : t.affine_terms) { _add_affine_term(v, c * c1); } if (t.constant_term) { constant_term = c * t.constant_term.value(); } else { constant_term.reset(); } } } else if (deg1 == 1) { if (deg2 == 1) { auto N1 = affine_terms.size(); auto N2 = t.affine_terms.size(); quadratic_terms.reserve(N1 * N2 / 2); for (const auto &[xi, ci] : affine_terms) { for (const auto &[xj, dj] : t.affine_terms) { _add_quadratic_term(xi, xj, ci * dj); } } } if (t.constant_term) { auto d0 = t.constant_term.value(); for (auto &[_, ci] : affine_terms) { ci *= d0; } } else { affine_terms.clear(); } if (constant_term) { auto c0 = constant_term.value(); for (const auto &[xj, dj] : t.affine_terms) { _add_affine_term(xj, c0 * dj); } } if (t.constant_term && constant_term) { constant_term = t.constant_term.value() * constant_term.value(); } else { constant_term.reset(); } } else if (deg1 == 2) { if (t.constant_term) { auto c = t.constant_term.value(); for (auto &[_, c2] : quadratic_terms) { c2 *= c; } for (auto &[_, c1] : affine_terms) { c1 *= c; } if (constant_term) { constant_term = c * constant_term.value(); } } else { clear(); } } } return *this; } ExprBuilder &ExprBuilder::operator/=(CoeffT c) { for (auto &[varpair, cc] : quadratic_terms) { cc /= c; } for (auto &[v, cc] : affine_terms) { cc /= c; } if (constant_term) { constant_term = constant_term.value() / c; } return *this; } // Operator overloading functions auto operator+(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction { return ScalarAffineFunction(a, 1.0, b); } auto operator+(CoeffT a, const VariableIndex &b) -> ScalarAffineFunction { return b + a; } auto operator+(const VariableIndex &a, const VariableIndex &b) -> ScalarAffineFunction { return ScalarAffineFunction({1.0, 1.0}, {a.index, b.index}); } auto operator+(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction { CoeffT new_constant = a.constant.value_or(0.0) + b; return ScalarAffineFunction(a.coefficients, a.variables, new_constant); } auto operator+(CoeffT a, const ScalarAffineFunction &b) -> ScalarAffineFunction { return b + a; } auto operator+(const ScalarAffineFunction &a, const VariableIndex &b) -> ScalarAffineFunction { auto coefficients = a.coefficients; auto variables = a.variables; coefficients.push_back(1.0); variables.push_back(b.index); return ScalarAffineFunction(coefficients, variables, a.constant); } auto operator+(const VariableIndex &a, const ScalarAffineFunction &b) -> ScalarAffineFunction { return b + a; } auto operator+(const ScalarAffineFunction &a, const ScalarAffineFunction &b) -> ScalarAffineFunction { ExprBuilder t(a); t.operator+=(b); return ScalarAffineFunction(t); } auto operator+(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction { ScalarAffineFunction affine_part; if (a.affine_part) { affine_part = operator+(a.affine_part.value(), b); } else { affine_part = ScalarAffineFunction(b); } return ScalarQuadraticFunction(a.coefficients, a.variable_1s, a.variable_2s, affine_part); } auto operator+(CoeffT a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return b + a; } auto operator+(const ScalarQuadraticFunction &a, const VariableIndex &b) -> ScalarQuadraticFunction { ScalarAffineFunction affine_part; if (a.affine_part) { affine_part = operator+(a.affine_part.value(), b); } else { affine_part = ScalarAffineFunction(b); } return ScalarQuadraticFunction(a.coefficients, a.variable_1s, a.variable_2s, affine_part); } auto operator+(const VariableIndex &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return b + a; } auto operator+(const ScalarQuadraticFunction &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction { ScalarAffineFunction affine_part; if (a.affine_part) { affine_part = operator+(a.affine_part.value(), b); } else { affine_part = b; } return ScalarQuadraticFunction(a.coefficients, a.variable_1s, a.variable_2s, affine_part); } auto operator+(const ScalarAffineFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return b + a; } auto operator+(const ScalarQuadraticFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { ExprBuilder t(a); t.operator+=(b); return ScalarQuadraticFunction(t); } auto operator-(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction { return ScalarAffineFunction(a, 1.0, -b); } auto operator-(CoeffT a, const VariableIndex &b) -> ScalarAffineFunction { return ScalarAffineFunction(b, -1.0, a); } auto operator-(const VariableIndex &a, const VariableIndex &b) -> ScalarAffineFunction { return ScalarAffineFunction({1.0, -1.0}, {a.index, b.index}); } auto operator-(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction { return operator+(a, -b); } auto operator-(CoeffT a, const ScalarAffineFunction &b) -> ScalarAffineFunction { return operator+(operator*(b, -1.0), a); } auto operator-(const ScalarAffineFunction &a, const VariableIndex &b) -> ScalarAffineFunction { auto coefficients = a.coefficients; auto variables = a.variables; coefficients.push_back(-1.0); variables.push_back(b.index); return ScalarAffineFunction(coefficients, variables, a.constant); } auto operator-(const VariableIndex &a, const ScalarAffineFunction &b) -> ScalarAffineFunction { return operator+(operator*(b, -1.0), a); } auto operator-(const ScalarAffineFunction &a, const ScalarAffineFunction &b) -> ScalarAffineFunction { return operator+(a, operator*(b, -1.0)); } auto operator-(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction { return operator+(a, -b); } auto operator-(CoeffT a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return operator+(operator*(b, -1.0), a); } auto operator-(const ScalarQuadraticFunction &a, const VariableIndex &b) -> ScalarQuadraticFunction { return operator+(a, ScalarAffineFunction(b, -1.0)); } auto operator-(const VariableIndex &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return operator+(operator*(b, -1.0), a); } auto operator-(const ScalarQuadraticFunction &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction { return operator+(a, operator*(b, -1.0)); } auto operator-(const ScalarAffineFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return operator+(operator*(b, -1.0), a); } auto operator-(const ScalarQuadraticFunction &a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return operator+(a, operator*(b, -1.0)); } auto operator*(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction { return ScalarAffineFunction(a, b); } auto operator*(CoeffT a, const VariableIndex &b) -> ScalarAffineFunction { return b * a; } auto operator*(const VariableIndex &a, const VariableIndex &b) -> ScalarQuadraticFunction { return ScalarQuadraticFunction({1.0}, {a.index}, {b.index}); } auto operator*(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction { ScalarAffineFunction aa = a; for (auto &c : aa.coefficients) { c *= b; } if (aa.constant) { aa.constant = aa.constant.value() * b; } return aa; } auto operator*(CoeffT a, const ScalarAffineFunction &b) -> ScalarAffineFunction { return b * a; } auto operator*(const ScalarAffineFunction &a, const VariableIndex &b) -> ScalarQuadraticFunction { auto &coefficients = a.coefficients; auto &variable_1s = a.variables; auto variable_2s = Vector(variable_1s.size(), b.index); std::optional affine_part; if (a.constant) { affine_part = operator*(b, a.constant.value()); } return ScalarQuadraticFunction(coefficients, variable_1s, variable_2s, affine_part); } auto operator*(const VariableIndex &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction { return b * a; } auto operator*(const ScalarAffineFunction &a, const ScalarAffineFunction &b) -> ScalarQuadraticFunction { auto t = ExprBuilder(); for (int i = 0; i < a.coefficients.size(); i++) { auto ci = a.coefficients[i]; auto xi = a.variables[i]; for (int j = 0; j < b.coefficients.size(); j++) { auto dj = b.coefficients[j]; auto xj = b.variables[j]; t._add_quadratic_term(xi, xj, ci * dj); } } if (b.constant) { auto d0 = b.constant.value(); for (int i = 0; i < a.coefficients.size(); i++) { auto ci = a.coefficients[i]; auto xi = a.variables[i]; t._add_affine_term(xi, ci * d0); } } if (a.constant) { auto c0 = a.constant.value(); for (int j = 0; j < b.coefficients.size(); j++) { auto dj = b.coefficients[j]; auto xj = b.variables[j]; t._add_affine_term(xj, c0 * dj); } } if (a.constant && b.constant) { t.constant_term = a.constant.value() * b.constant.value(); } return ScalarQuadraticFunction(t); } auto operator*(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction { ScalarQuadraticFunction aa = a; for (auto &c : aa.coefficients) { c *= b; } if (aa.affine_part) { aa.affine_part = operator*(aa.affine_part.value(), b); } return aa; } auto operator*(CoeffT a, const ScalarQuadraticFunction &b) -> ScalarQuadraticFunction { return b * a; } auto operator/(const VariableIndex &a, CoeffT b) -> ScalarAffineFunction { return a * (1.0 / b); } auto operator/(const ScalarAffineFunction &a, CoeffT b) -> ScalarAffineFunction { return a * (1.0 / b); } auto operator/(const ScalarQuadraticFunction &a, CoeffT b) -> ScalarQuadraticFunction { return a * (1.0 / b); } auto operator-(const VariableIndex &a) -> ScalarAffineFunction { return a * -1.0; } auto operator-(const ScalarAffineFunction &a) -> ScalarAffineFunction { return a * -1.0; } auto operator-(const ScalarQuadraticFunction &a) -> ScalarQuadraticFunction { return a * -1.0; } auto operator-(const ExprBuilder &a) -> ExprBuilder { return a * -1.0; } auto operator+(const ExprBuilder &a, CoeffT b) -> ExprBuilder { ExprBuilder e = a; e.operator+=(b); return e; } auto operator+(CoeffT b, const ExprBuilder &a) -> ExprBuilder { return a + b; } auto operator+(const ExprBuilder &a, const VariableIndex &b) -> ExprBuilder { ExprBuilder e = a; e.operator+=(b); return e; } auto operator+(const VariableIndex &b, const ExprBuilder &a) -> ExprBuilder { return a + b; } auto operator+(const ExprBuilder &a, const ScalarAffineFunction &b) -> ExprBuilder { ExprBuilder e = a; e.operator+=(b); return e; } auto operator+(const ScalarAffineFunction &b, const ExprBuilder &a) -> ExprBuilder { return a + b; } auto operator+(const ExprBuilder &a, const ScalarQuadraticFunction &b) -> ExprBuilder { ExprBuilder e = a; e.operator+=(b); return e; } auto operator+(const ScalarQuadraticFunction &b, const ExprBuilder &a) -> ExprBuilder { return a + b; } auto operator+(const ExprBuilder &a, const ExprBuilder &b) -> ExprBuilder { ExprBuilder e = a; e.operator+=(b); return e; } auto operator-(const ExprBuilder &a, CoeffT b) -> ExprBuilder { ExprBuilder e = a; e.operator-=(b); return e; } auto operator-(CoeffT b, const ExprBuilder &a) -> ExprBuilder { ExprBuilder e = a - b; e.operator*=(-1.0); return e; } auto operator-(const ExprBuilder &a, const VariableIndex &b) -> ExprBuilder { ExprBuilder e = a; e.operator-=(b); return e; } auto operator-(const VariableIndex &b, const ExprBuilder &a) -> ExprBuilder { ExprBuilder e = a - b; e.operator*=(-1.0); return e; } auto operator-(const ExprBuilder &a, const ScalarAffineFunction &b) -> ExprBuilder { ExprBuilder e = a; e.operator-=(b); return e; } auto operator-(const ScalarAffineFunction &b, const ExprBuilder &a) -> ExprBuilder { ExprBuilder e = a - b; e.operator*=(-1.0); return e; } auto operator-(const ExprBuilder &a, const ScalarQuadraticFunction &b) -> ExprBuilder { ExprBuilder e = a; e.operator-=(b); return e; } auto operator-(const ScalarQuadraticFunction &b, const ExprBuilder &a) -> ExprBuilder { ExprBuilder e = a - b; e.operator*=(-1.0); return e; } auto operator-(const ExprBuilder &a, const ExprBuilder &b) -> ExprBuilder { ExprBuilder e = a; e.operator-=(b); return e; } auto operator*(const ExprBuilder &a, CoeffT b) -> ExprBuilder { ExprBuilder e = a; e.operator*=(b); return e; } auto operator*(CoeffT b, const ExprBuilder &a) -> ExprBuilder { return a * b; } auto operator*(const ExprBuilder &a, const VariableIndex &b) -> ExprBuilder { ExprBuilder e = a; e.operator*=(b); return e; } auto operator*(const VariableIndex &b, const ExprBuilder &a) -> ExprBuilder { return a * b; } auto operator*(const ExprBuilder &a, const ScalarAffineFunction &b) -> ExprBuilder { ExprBuilder e = a; e.operator*=(b); return e; } auto operator*(const ScalarAffineFunction &b, const ExprBuilder &a) -> ExprBuilder { return a * b; } auto operator*(const ExprBuilder &a, const ScalarQuadraticFunction &b) -> ExprBuilder { ExprBuilder e = a; e.operator*=(b); return e; } auto operator*(const ScalarQuadraticFunction &b, const ExprBuilder &a) -> ExprBuilder { return a * b; } auto operator*(const ExprBuilder &a, const ExprBuilder &b) -> ExprBuilder { ExprBuilder e = a; e.operator*=(b); return e; } auto operator/(const ExprBuilder &a, CoeffT b) -> ExprBuilder { ExprBuilder e = a; e.operator/=(b); return e; } ================================================ FILE: lib/core_ext.cpp ================================================ #include #include #include #include #include #include "pyoptinterface/core.hpp" #include "pyoptinterface/container.hpp" #include #include namespace nb = nanobind; using CoeffNdarrayT = nb::ndarray, nb::any_contig>; using IndexNdarrayT = nb::ndarray, nb::any_contig>; NB_MODULE(core_ext, m) { nb::set_leak_warnings(false); // VariableDomain nb::enum_(m, "VariableDomain", nb::is_arithmetic()) .value("Continuous", VariableDomain::Continuous) .value("Integer", VariableDomain::Integer) .value("Binary", VariableDomain::Binary) .value("SemiContinuous", VariableDomain::SemiContinuous); // ConstraintSense nb::enum_(m, "ConstraintSense") .value("LessEqual", ConstraintSense::LessEqual) .value("Equal", ConstraintSense::Equal) .value("GreaterEqual", ConstraintSense::GreaterEqual); // ConstraintType nb::enum_(m, "ConstraintType") .value("Linear", ConstraintType::Linear) .value("Quadratic", ConstraintType::Quadratic) .value("SOS", ConstraintType::SOS) .value("SecondOrderCone", ConstraintType::SecondOrderCone) .value("ExponentialCone", ConstraintType::ExponentialCone); nb::enum_(m, "SOSType").value("SOS1", SOSType::SOS1).value("SOS2", SOSType::SOS2); // ObjectiveSense nb::enum_(m, "ObjectiveSense") .value("Minimize", ObjectiveSense::Minimize) .value("Maximize", ObjectiveSense::Maximize); nb::class_(m, "VariableIndex") .def(nb::init()) .def_ro("index", &VariableIndex::index) .def(-nb::self) .def(nb::self + CoeffT()) .def(CoeffT() + nb::self) .def(nb::self + VariableIndex()) .def(nb::self + ScalarAffineFunction()) .def(nb::self + ScalarQuadraticFunction()) .def(nb::self - CoeffT()) .def(CoeffT() - nb::self) .def(nb::self - VariableIndex()) .def(nb::self - ScalarAffineFunction()) .def(nb::self - ScalarQuadraticFunction()) .def(nb::self * CoeffT()) .def(CoeffT() * nb::self) .def(nb::self * VariableIndex()) .def(nb::self * ScalarAffineFunction()) .def(nb::self / CoeffT()); nb::class_(m, "ConstraintIndex") .def(nb::init()) .def_ro("type", &ConstraintIndex::type) .def_ro("index", &ConstraintIndex::index); nb::class_(m, "ScalarAffineFunction") .def(nb::init<>()) .def(nb::init()) .def(nb::init()) .def(nb::init()) .def(nb::init()) .def(nb::init &, const Vector &>(), nb::arg("coefficients"), nb::arg("variables")) .def(nb::init &, const Vector &, CoeffT>(), nb::arg("coefficients"), nb::arg("variables"), nb::arg("constant")) // ndarray constructor .def_static( "from_numpy", [](CoeffNdarrayT coefficients, IndexNdarrayT variables) { auto *expr = new ScalarAffineFunction(); auto n = coefficients.size(); expr->coefficients.resize(n); std::span coeffs(coefficients.data(), n); std::ranges::copy(coeffs, expr->coefficients.begin()); n = variables.size(); expr->variables.resize(n); std::span vars(variables.data(), n); std::ranges::copy(vars, expr->variables.begin()); expr->constant.reset(); return expr; }, nb::arg("coefficients"), nb::arg("variables"), nb::rv_policy::take_ownership) .def_static( "from_numpy", [](CoeffNdarrayT coefficients, IndexNdarrayT variables, CoeffT constant) { auto *expr = new ScalarAffineFunction(); auto n = coefficients.size(); expr->coefficients.resize(n); std::span coeffs(coefficients.data(), n); std::ranges::copy(coeffs, expr->coefficients.begin()); n = variables.size(); expr->variables.resize(n); std::span vars(variables.data(), n); std::ranges::copy(vars, expr->variables.begin()); expr->constant = constant; return expr; }, nb::arg("coefficients"), nb::arg("variables"), nb::arg("constant"), nb::rv_policy::take_ownership) .def(nb::init()) .def_ro("coefficients", &ScalarAffineFunction::coefficients) .def_ro("variables", &ScalarAffineFunction::variables) .def_ro("constant", &ScalarAffineFunction::constant) .def("size", &ScalarAffineFunction::size) .def("canonicalize", &ScalarAffineFunction::canonicalize, nb::arg("threshold") = COEFTHRESHOLD) .def("reserve", &ScalarAffineFunction::reserve) .def("add_term", &ScalarAffineFunction::add_term) .def("add_constant", &ScalarAffineFunction::add_constant) .def(-nb::self) .def(nb::self + CoeffT()) .def(CoeffT() + nb::self) .def(nb::self + VariableIndex()) .def(nb::self + ScalarAffineFunction()) .def(nb::self + ScalarQuadraticFunction()) .def(nb::self - CoeffT()) .def(CoeffT() - nb::self) .def(nb::self - VariableIndex()) .def(nb::self - ScalarAffineFunction()) .def(nb::self - ScalarQuadraticFunction()) .def(nb::self * CoeffT()) .def(CoeffT() * nb::self) .def(nb::self * VariableIndex()) .def(nb::self * ScalarAffineFunction()) .def(nb::self / CoeffT()); nb::class_(m, "ScalarQuadraticFunction") .def(nb::init<>()) .def(nb::init &, const Vector &, const Vector &>(), nb::arg("coefficients"), nb::arg("var1s"), nb::arg("var2s")) .def(nb::init &, const Vector &, const Vector &, const ScalarAffineFunction &>(), nb::arg("coefficients"), nb::arg("var1s"), nb::arg("var2s"), nb::arg("affine_part")) .def(nb::init()) .def_ro("coefficients", &ScalarQuadraticFunction::coefficients) .def_ro("variable_1s", &ScalarQuadraticFunction::variable_1s) .def_ro("variable_2s", &ScalarQuadraticFunction::variable_2s) .def_ro("affine_part", &ScalarQuadraticFunction::affine_part) .def("size", &ScalarQuadraticFunction::size) .def("canonicalize", &ScalarQuadraticFunction::canonicalize, nb::arg("threshold") = COEFTHRESHOLD) .def("reserve_quadratic", &ScalarQuadraticFunction::reserve_quadratic) .def("reserve_affine", &ScalarQuadraticFunction::reserve_affine) .def("add_quadratic_term", &ScalarQuadraticFunction::add_quadratic_term) .def("add_affine_term", &ScalarQuadraticFunction::add_affine_term) .def("add_constant", &ScalarQuadraticFunction::add_constant) .def(-nb::self) .def(nb::self + CoeffT()) .def(CoeffT() + nb::self) .def(nb::self + VariableIndex()) .def(nb::self + ScalarAffineFunction()) .def(nb::self + ScalarQuadraticFunction()) .def(nb::self - CoeffT()) .def(CoeffT() - nb::self) .def(nb::self - VariableIndex()) .def(nb::self - ScalarAffineFunction()) .def(nb::self - ScalarQuadraticFunction()) .def(nb::self * CoeffT()) .def(CoeffT() * nb::self) .def(nb::self / CoeffT()); nb::class_(m, "VariablePair").def(nb::init()); nb::class_(m, "ExprBuilder") .def(nb::init<>()) .def(nb::init()) .def(nb::init()) .def(nb::init()) .def(nb::init()) .def("empty", &ExprBuilder::empty) .def("degree", &ExprBuilder::degree) .def("reserve_quadratic", &ExprBuilder::reserve_quadratic) .def("reserve_affine", &ExprBuilder::reserve_affine) .def("clear", &ExprBuilder::clear) .def("clean_nearzero_terms", &ExprBuilder::clean_nearzero_terms, nb::arg("threshold") = COEFTHRESHOLD) .def("add_quadratic_term", &ExprBuilder::add_quadratic_term) .def("set_quadratic_coef", &ExprBuilder::set_quadratic_coef) .def("add_affine_term", &ExprBuilder::add_affine_term) .def("set_affine_coef", &ExprBuilder::set_affine_coef) .def(-nb::self) .def(nb::self += CoeffT(), nb::rv_policy::none) .def(nb::self += VariableIndex(), nb::rv_policy::none) .def(nb::self += ScalarAffineFunction(), nb::rv_policy::none) .def(nb::self += ScalarQuadraticFunction(), nb::rv_policy::none) .def(nb::self += ExprBuilder(), nb::rv_policy::none) .def(nb::self -= CoeffT(), nb::rv_policy::none) .def(nb::self -= VariableIndex(), nb::rv_policy::none) .def(nb::self -= ScalarAffineFunction(), nb::rv_policy::none) .def(nb::self -= ScalarQuadraticFunction(), nb::rv_policy::none) .def(nb::self -= ExprBuilder(), nb::rv_policy::none) .def(nb::self *= CoeffT(), nb::rv_policy::none) .def(nb::self *= VariableIndex(), nb::rv_policy::none) .def(nb::self *= ScalarAffineFunction(), nb::rv_policy::none) .def(nb::self *= ScalarQuadraticFunction(), nb::rv_policy::none) .def(nb::self *= ExprBuilder(), nb::rv_policy::none) .def(nb::self /= CoeffT(), nb::rv_policy::none) .def(nb::self + CoeffT()) .def(CoeffT() + nb::self) .def(nb::self + VariableIndex()) .def(nb::self + ScalarAffineFunction()) .def(nb::self + ScalarQuadraticFunction()) .def(nb::self + ExprBuilder()) .def(VariableIndex() + nb::self) .def(ScalarAffineFunction() + nb::self) .def(ScalarQuadraticFunction() + nb::self) .def(nb::self - CoeffT()) .def(CoeffT() - nb::self) .def(nb::self - VariableIndex()) .def(nb::self - ScalarAffineFunction()) .def(nb::self - ScalarQuadraticFunction()) .def(nb::self - ExprBuilder()) .def(VariableIndex() - nb::self) .def(ScalarAffineFunction() - nb::self) .def(ScalarQuadraticFunction() - nb::self) .def(nb::self * CoeffT()) .def(CoeffT() * nb::self) .def(nb::self * VariableIndex()) .def(nb::self * ScalarAffineFunction()) .def(nb::self * ScalarQuadraticFunction()) .def(nb::self * ExprBuilder()) .def(VariableIndex() * nb::self) .def(ScalarAffineFunction() * nb::self) .def(ScalarQuadraticFunction() * nb::self) .def(nb::self / CoeffT()); // We need to test the functionality of MonotoneIndexer using IntMonotoneIndexer = MonotoneIndexer; nb::class_(m, "IntMonotoneIndexer") .def(nb::init<>()) .def("add_index", &IntMonotoneIndexer::add_index) .def("get_index", &IntMonotoneIndexer::get_index) .def("delete_index", &IntMonotoneIndexer::delete_index); } ================================================ FILE: lib/cppad_interface.cpp ================================================ #include "pyoptinterface/cppad_interface.hpp" #include "fmt/core.h" static const std::string opt_options = "no_compare_op no_conditional_skip no_cumulative_sum_op"; ADFunDouble dense_jacobian(const ADFunDouble &f) { using CppAD::AD; using CppAD::ADFun; using CppAD::Independent; using Base = double; size_t nx = f.Domain(); size_t ny = f.Range(); size_t np = f.size_dyn_ind(); std::vector> apx(np + nx), ax(nx), ap(np), aj(nx * ny); ADFun, Base> af = f.base2ad(); Independent(apx); for (size_t i = 0; i < np; i++) ap[i] = apx[i]; for (size_t i = 0; i < nx; i++) ax[i] = apx[np + i]; af.new_dynamic(ap); aj = af.Jacobian(ax); ADFun jac; jac.Dependent(apx, aj); jac.optimize(opt_options); return jac; } JacobianHessianSparsityPattern jacobian_hessian_sparsity(ADFunDouble &f, HessianSparsityType hessian_sparsity) { using s_vector = std::vector; using CppAD::sparse_rc; size_t nx = f.Domain(); size_t ny = f.Range(); size_t np = f.size_dyn_ind(); JacobianHessianSparsityPattern jachess; // We must compute the jacobian sparsity first { // sparsity pattern for the identity matrix size_t nr = nx; size_t nc = nx; size_t nnz_in = nx; sparsity_pattern_t pattern_jac_in(nr, nc, nnz_in); for (size_t k = 0; k < nnz_in; k++) { size_t r = k; size_t c = k; pattern_jac_in.set(k, r, c); } // compute sparsity pattern for J(x) = F'(x) const bool transpose = false; const bool dependency = false; const bool internal_bool = true; sparsity_pattern_t pattern_jac; f.for_jac_sparsity(pattern_jac_in, transpose, dependency, internal_bool, pattern_jac); jachess.jacobian = pattern_jac; } std::vector select_range(ny, true); const bool transpose = false; const bool internal_bool = true; sparsity_pattern_t pattern_hes; f.rev_hes_sparsity(select_range, transpose, internal_bool, pattern_hes); jachess.hessian = pattern_hes; // Filter the sparsity pattern sparsity_pattern_t pattern_hes_partial; pattern_hes_partial.resize(nx, nx, 0); if (hessian_sparsity == HessianSparsityType::Upper) { for (int i = 0; i < pattern_hes.nnz(); i++) { auto r = pattern_hes.row()[i]; auto c = pattern_hes.col()[i]; if (r <= c) { pattern_hes_partial.push_back(r, c); } } } else if (hessian_sparsity == HessianSparsityType::Lower) { for (int i = 0; i < pattern_hes.nnz(); i++) { auto r = pattern_hes.row()[i]; auto c = pattern_hes.col()[i]; if (r >= c) { pattern_hes_partial.push_back(r, c); } } } jachess.reduced_hessian = pattern_hes_partial; return jachess; } ADFunDouble sparse_jacobian(const ADFunDouble &f, const sparsity_pattern_t &pattern_jac, const std::vector &x_values, const std::vector &p_values) { using CppAD::AD; using CppAD::ADFun; using CppAD::Independent; using Base = double; size_t nx = f.Domain(); size_t ny = f.Range(); size_t np = f.size_dyn_ind(); std::vector> apx(np + nx), ax(nx), ap(np); for (size_t i = 0; i < np; i++) { apx[i] = p_values[i]; } for (size_t i = 0; i < nx; i++) { apx[np + i] = x_values[i]; } ADFun, Base> af = f.base2ad(); Independent(apx); for (size_t i = 0; i < np; i++) { ap[i] = apx[i]; } for (size_t i = 0; i < nx; i++) { ax[i] = apx[np + i]; } af.new_dynamic(ap); CppAD::sparse_rcv, std::vector>> subset(pattern_jac); CppAD::sparse_jac_work work; std::string coloring = "cppad"; size_t n_color = af.sparse_jac_rev(ax, subset, pattern_jac, coloring, work); ADFun jacobian; jacobian.Dependent(apx, subset.val()); jacobian.optimize(opt_options); return jacobian; } ADFunDouble sparse_hessian(const ADFunDouble &f, const sparsity_pattern_t &pattern_hes, const sparsity_pattern_t &pattern_subset, const std::vector &x_values, const std::vector &p_values) { using CppAD::AD; using CppAD::ADFun; using CppAD::Independent; using Base = double; size_t nx = f.Domain(); size_t ny = f.Range(); size_t np = f.size_dyn_ind(); size_t nw = ny; std::vector> apwx(np + nw + nx), ax(nx), ap(np), aw(nw); for (size_t i = 0; i < np; i++) { apwx[i] = p_values[i]; } for (size_t i = 0; i < nw; i++) { apwx[np + i] = 1.0; } for (size_t i = 0; i < nx; i++) { apwx[np + nw + i] = x_values[i]; } ADFun, Base> af = f.base2ad(); Independent(apwx); for (size_t i = 0; i < np; i++) { ap[i] = apwx[i]; } for (size_t i = 0; i < nw; i++) { aw[i] = apwx[np + i]; } for (size_t i = 0; i < nx; i++) { ax[i] = apwx[np + nw + i]; } af.new_dynamic(ap); CppAD::sparse_rcv, std::vector>> subset(pattern_subset); CppAD::sparse_hes_work work; std::string coloring = "cppad.symmetric"; size_t n_sweep = af.sparse_hes(ax, aw, subset, pattern_hes, coloring, work); ADFun hessian; hessian.Dependent(apwx, subset.val()); hessian.optimize(opt_options); return hessian; } CppAD::AD cppad_build_unary_expression(UnaryOperator op, const CppAD::AD &operand) { switch (op) { case UnaryOperator::Neg: { return -operand; } case UnaryOperator::Sin: { return CppAD::sin(operand); } case UnaryOperator::Cos: { return CppAD::cos(operand); } case UnaryOperator::Tan: { return CppAD::tan(operand); } case UnaryOperator::Asin: { return CppAD::asin(operand); } case UnaryOperator::Acos: { return CppAD::acos(operand); } case UnaryOperator::Atan: { return CppAD::atan(operand); } case UnaryOperator::Abs: { return CppAD::abs(operand); } case UnaryOperator::Sqrt: { return CppAD::sqrt(operand); } case UnaryOperator::Exp: { return CppAD::exp(operand); } case UnaryOperator::Log: { return CppAD::log(operand); } case UnaryOperator::Log10: { return CppAD::log10(operand); } default: { auto msg = "Invalid unary operator " + unary_operator_to_string(op); throw std::runtime_error(msg); } } } CppAD::AD cppad_build_binary_expression(BinaryOperator op, const CppAD::AD &left, const CppAD::AD &right) { switch (op) { case BinaryOperator::Sub: { return left - right; } case BinaryOperator::Div: { return left / right; } case BinaryOperator::Pow: { return CppAD::pow(left, right); } case BinaryOperator::LessThan: case BinaryOperator::LessEqual: case BinaryOperator::Equal: case BinaryOperator::NotEqual: case BinaryOperator::GreaterEqual: case BinaryOperator::GreaterThan: { throw std::runtime_error("Currently comparision operator can only be used with ifelse " "function and cannot be evaluated as value"); } default: { auto msg = "Invalid binary operator " + binary_operator_to_string(op); throw std::runtime_error(msg); } } } CppAD::AD cppad_build_ternary_expression(BinaryOperator compare_op, const CppAD::AD &compare_left, const CppAD::AD &compare_right, const CppAD::AD &then_result, const CppAD::AD &else_result) { switch (compare_op) { case BinaryOperator::LessThan: { return CppAD::CondExpLt(compare_left, compare_right, then_result, else_result); } case BinaryOperator::LessEqual: { return CppAD::CondExpLe(compare_left, compare_right, then_result, else_result); } case BinaryOperator::Equal: { return CppAD::CondExpEq(compare_left, compare_right, then_result, else_result); } case BinaryOperator::NotEqual: { return CppAD::CondExpEq(compare_left, compare_right, else_result, then_result); } case BinaryOperator::GreaterEqual: { return CppAD::CondExpGe(compare_left, compare_right, then_result, else_result); } case BinaryOperator::GreaterThan: { return CppAD::CondExpGt(compare_left, compare_right, then_result, else_result); } default: { auto msg = "Invalid compare operator " + binary_operator_to_string(compare_op); throw std::runtime_error(msg); } } } CppAD::AD cppad_build_nary_expression(NaryOperator op, const std::vector> &operands) { if (operands.size() == 0) return CppAD::AD(0.0); CppAD::AD result = operands[0]; switch (op) { case NaryOperator::Add: { for (auto i = 1; i < operands.size(); i++) { result += operands[i]; } break; } case NaryOperator::Mul: { for (auto i = 1; i < operands.size(); i++) { result *= operands[i]; } break; } default: { auto msg = "Invalid nary operator " + nary_operator_to_string(op); throw std::runtime_error(msg); } } return result; } CppAD::AD cppad_trace_expression( const ExpressionGraph &graph, const ExpressionHandle &expression, const std::vector> &x, const std::vector> &p, ankerl::unordered_dense::map> &seen_expressions) { auto it = seen_expressions.find(expression); if (it != seen_expressions.end()) { return it->second; } CppAD::AD result; auto id = expression.id; switch (expression.array) { case ArrayType::Variable: { result = x[id]; break; } case ArrayType::Constant: { result = p[id]; break; } case ArrayType::Parameter: { throw std::runtime_error("Parameter is not supported in cppad_trace_expression"); break; } case ArrayType::Unary: { auto &unary = graph.m_unaries[id]; auto operand = cppad_trace_expression(graph, unary.operand, x, p, seen_expressions); result = cppad_build_unary_expression(unary.op, operand); seen_expressions[expression] = result; break; } case ArrayType::Binary: { auto &binary = graph.m_binaries[id]; auto left = cppad_trace_expression(graph, binary.left, x, p, seen_expressions); auto right = cppad_trace_expression(graph, binary.right, x, p, seen_expressions); result = cppad_build_binary_expression(binary.op, left, right); seen_expressions[expression] = result; break; } case ArrayType::Ternary: { auto &ifelsethen_body = graph.m_ternaries[id]; auto &condition = ifelsethen_body.left; assert(condition.array == ArrayType::Binary); auto &condition_body = graph.m_binaries[condition.id]; BinaryOperator compare_op = condition_body.op; assert(is_binary_compare_op(compare_op)); CppAD::AD compare_left = cppad_trace_expression(graph, condition_body.left, x, p, seen_expressions); CppAD::AD compare_right = cppad_trace_expression(graph, condition_body.right, x, p, seen_expressions); CppAD::AD then_result = cppad_trace_expression(graph, ifelsethen_body.middle, x, p, seen_expressions); CppAD::AD else_result = cppad_trace_expression(graph, ifelsethen_body.right, x, p, seen_expressions); result = cppad_build_ternary_expression(compare_op, compare_left, compare_right, then_result, else_result); seen_expressions[expression] = result; break; } case ArrayType::Nary: { auto &nary = graph.m_naries[id]; std::vector> operand_values; for (auto &operand : nary.operands) { auto operand_value = cppad_trace_expression(graph, operand, x, p, seen_expressions); operand_values.push_back(operand_value); } result = cppad_build_nary_expression(nary.op, operand_values); seen_expressions[expression] = result; break; } default: { throw std::runtime_error("Invalid array type"); } } return result; } ADFunDouble cppad_trace_graph_constraints(const ExpressionGraph &graph, const std::vector &selected) { ankerl::unordered_dense::map> seen_expressions; auto N_inputs = graph.n_variables(); std::vector> x(N_inputs); auto N_parameters = graph.n_constants(); std::vector> p(N_parameters); // Must assign value to parameter, otherwise p will be zero // and CppAD::pow(x, p) will be 0 for (size_t i = 0; i < N_parameters; i++) { p[i] = graph.m_constants[i]; } if (N_parameters > 0) { CppAD::Independent(x, p); } else { CppAD::Independent(x); } auto &outputs = graph.m_constraint_outputs; std::vector indices; if (selected.empty()) { indices.reserve(outputs.size()); for (size_t i = 0; i < outputs.size(); i++) { indices.push_back(i); } } else { indices = selected; } auto N_outputs = indices.size(); std::vector> y(N_outputs); // Trace the selected outputs for (size_t i = 0; i < N_outputs; i++) { auto idx = indices[i]; auto &output = outputs[idx]; y[i] = cppad_trace_expression(graph, output, x, p, seen_expressions); } ADFunDouble f; f.Dependent(x, y); return f; } ADFunDouble cppad_trace_graph_objective(const ExpressionGraph &graph, const std::vector &selected, bool aggregate) { ankerl::unordered_dense::map> seen_expressions; auto N_inputs = graph.n_variables(); std::vector> x(N_inputs); auto N_parameters = graph.n_constants(); std::vector> p(N_parameters); for (size_t i = 0; i < N_parameters; i++) { p[i] = graph.m_constants[i]; } if (N_parameters > 0) { CppAD::Independent(x, p); } else { CppAD::Independent(x); } auto &outputs = graph.m_objective_outputs; std::vector indices; if (selected.empty()) { indices.reserve(outputs.size()); for (size_t i = 0; i < outputs.size(); i++) { indices.push_back(i); } } else { indices = selected; } auto N_outputs = indices.size(); std::vector> y(N_outputs); for (size_t i = 0; i < N_outputs; i++) { auto idx = indices[i]; auto &output = outputs[idx]; y[i] = cppad_trace_expression(graph, output, x, p, seen_expressions); } ADFunDouble f; if (aggregate) { CppAD::AD y_sum = 0.0; for (size_t i = 0; i < N_outputs; i++) { y_sum += y[i]; } f.Dependent(x, {y_sum}); } else { f.Dependent(x, y); } return f; } void cppad_autodiff(ADFunDouble &f, AutodiffSymbolicStructure &structure, CppADAutodiffGraph &graph, const std::vector &x_values, const std::vector &p_values) { auto nx = f.Domain(); auto np = f.size_dyn_ind(); assert(x_values.size() == nx); assert(p_values.size() == np); auto ny = f.Range(); structure.nx = nx; structure.np = np; structure.ny = ny; structure.has_parameter = np > 0; f.to_graph(graph.f_graph); auto sparsity = jacobian_hessian_sparsity(f, HessianSparsityType::Upper); { auto &pattern = sparsity.jacobian; auto &m_jacobian_rows = structure.m_jacobian_rows; auto &m_jacobian_cols = structure.m_jacobian_cols; auto &m_jacobian_nnz = structure.m_jacobian_nnz; for (int i = 0; i < pattern.nnz(); i++) { auto r = pattern.row()[i]; auto c = pattern.col()[i]; m_jacobian_rows.push_back(r); m_jacobian_cols.push_back(c); } m_jacobian_nnz = pattern.nnz(); } { auto &pattern = sparsity.reduced_hessian; auto &m_hessian_rows = structure.m_hessian_rows; auto &m_hessian_cols = structure.m_hessian_cols; auto &m_hessian_nnz = structure.m_hessian_nnz; for (int i = 0; i < pattern.nnz(); i++) { auto r = pattern.row()[i]; auto c = pattern.col()[i]; m_hessian_rows.push_back(r); m_hessian_cols.push_back(c); } m_hessian_nnz = pattern.nnz(); } if (structure.m_jacobian_nnz > 0) { structure.has_jacobian = true; ADFunDouble jacobian = sparse_jacobian(f, sparsity.jacobian, x_values, p_values); jacobian.to_graph(graph.jacobian_graph); } if (structure.m_hessian_nnz > 0) { structure.has_hessian = true; ADFunDouble hessian = sparse_hessian(f, sparsity.hessian, sparsity.reduced_hessian, x_values, p_values); hessian.to_graph(graph.hessian_graph); } } ================================================ FILE: lib/cppad_interface_ext.cpp ================================================ #include #include #include #include #include namespace nb = nanobind; #include "pyoptinterface/cppad_interface.hpp" using graph_op_enum = CppAD::graph::graph_op_enum; struct cpp_graph_cursor { size_t op_index = 0; size_t arg_index = 0; }; graph_op_enum cursor_op(CppAD::cpp_graph &graph, cpp_graph_cursor &cursor) { return graph.operator_vec_get(cursor.op_index); } size_t cursor_n_arg(CppAD::cpp_graph &graph, cpp_graph_cursor &cursor) { auto op = cursor_op(graph, cursor); size_t n_arg = 0; switch (op) { // unary operators case graph_op_enum::abs_graph_op: case graph_op_enum::acos_graph_op: case graph_op_enum::acosh_graph_op: case graph_op_enum::asin_graph_op: case graph_op_enum::asinh_graph_op: case graph_op_enum::atan_graph_op: case graph_op_enum::atanh_graph_op: case graph_op_enum::cos_graph_op: case graph_op_enum::cosh_graph_op: case graph_op_enum::erf_graph_op: case graph_op_enum::erfc_graph_op: case graph_op_enum::exp_graph_op: case graph_op_enum::expm1_graph_op: case graph_op_enum::log1p_graph_op: case graph_op_enum::log_graph_op: case graph_op_enum::neg_graph_op: case graph_op_enum::sign_graph_op: case graph_op_enum::sin_graph_op: case graph_op_enum::sinh_graph_op: case graph_op_enum::sqrt_graph_op: case graph_op_enum::tan_graph_op: case graph_op_enum::tanh_graph_op: n_arg = 1; break; // binary operators case graph_op_enum::add_graph_op: case graph_op_enum::azmul_graph_op: case graph_op_enum::div_graph_op: case graph_op_enum::mul_graph_op: case graph_op_enum::pow_graph_op: case graph_op_enum::sub_graph_op: n_arg = 2; break; // conditional operators case graph_op_enum::cexp_eq_graph_op: case graph_op_enum::cexp_le_graph_op: case graph_op_enum::cexp_lt_graph_op: n_arg = 4; break; default: { std::string op_name = CppAD::local::graph::op_enum2name[op]; auto message = "Unknown graph_op: " + op_name; throw std::runtime_error(message); } break; } return n_arg; } void advance_graph_cursor(CppAD::cpp_graph &graph, cpp_graph_cursor &cursor) { auto n_arg = cursor_n_arg(graph, cursor); cursor.arg_index += n_arg; cursor.op_index++; } NB_MODULE(cppad_interface_ext, m) { nb::enum_(m, "graph_op") .value("abs", graph_op_enum::abs_graph_op) .value("acos", graph_op_enum::acos_graph_op) .value("acosh", graph_op_enum::acosh_graph_op) .value("add", graph_op_enum::add_graph_op) .value("asin", graph_op_enum::asin_graph_op) .value("asinh", graph_op_enum::asinh_graph_op) .value("atan", graph_op_enum::atan_graph_op) .value("atanh", graph_op_enum::atanh_graph_op) .value("atom4", graph_op_enum::atom4_graph_op) .value("atom", graph_op_enum::atom_graph_op) .value("azmul", graph_op_enum::azmul_graph_op) .value("cexp_eq", graph_op_enum::cexp_eq_graph_op) .value("cexp_le", graph_op_enum::cexp_le_graph_op) .value("cexp_lt", graph_op_enum::cexp_lt_graph_op) .value("comp_eq", graph_op_enum::comp_eq_graph_op) .value("comp_le", graph_op_enum::comp_le_graph_op) .value("comp_lt", graph_op_enum::comp_lt_graph_op) .value("comp_ne", graph_op_enum::comp_ne_graph_op) .value("cos", graph_op_enum::cos_graph_op) .value("cosh", graph_op_enum::cosh_graph_op) .value("discrete", graph_op_enum::discrete_graph_op) .value("div", graph_op_enum::div_graph_op) .value("erf", graph_op_enum::erf_graph_op) .value("erfc", graph_op_enum::erfc_graph_op) .value("exp", graph_op_enum::exp_graph_op) .value("expm1", graph_op_enum::expm1_graph_op) .value("log1p", graph_op_enum::log1p_graph_op) .value("log", graph_op_enum::log_graph_op) .value("mul", graph_op_enum::mul_graph_op) .value("neg", graph_op_enum::neg_graph_op) .value("pow", graph_op_enum::pow_graph_op) .value("print", graph_op_enum::print_graph_op) .value("sign", graph_op_enum::sign_graph_op) .value("sin", graph_op_enum::sin_graph_op) .value("sinh", graph_op_enum::sinh_graph_op) .value("sqrt", graph_op_enum::sqrt_graph_op) .value("sub", graph_op_enum::sub_graph_op) .value("sum", graph_op_enum::sum_graph_op) .value("tan", graph_op_enum::tan_graph_op) .value("tanh", graph_op_enum::tanh_graph_op); nb::class_(m, "cpp_graph_cursor") .def(nb::init<>()) .def_ro("op_index", &cpp_graph_cursor::op_index) .def_ro("arg_index", &cpp_graph_cursor::arg_index); nb::class_(m, "cpp_graph") .def(nb::init<>()) .def_prop_ro("n_dynamic_ind", [](CppAD::cpp_graph &g) { return g.n_dynamic_ind_get(); }) .def_prop_ro("n_variable_ind", [](CppAD::cpp_graph &g) { return g.n_variable_ind_get(); }) .def_prop_ro("n_constant", [](CppAD::cpp_graph &g) { return g.constant_vec_size(); }) .def_prop_ro("n_dependent", [](CppAD::cpp_graph &g) { return g.dependent_vec_size(); }) .def_prop_ro("n_operator", [](CppAD::cpp_graph &g) { return g.operator_vec_size(); }) .def_prop_ro("n_operator_arg", [](CppAD::cpp_graph &g) { return g.operator_arg_size(); }) .def("constant_vec_get", &CppAD::cpp_graph::constant_vec_get) .def("dependent_vec_get", &CppAD::cpp_graph::dependent_vec_get) .def("__str__", [](CppAD::cpp_graph &g) { std::ostringstream oss; g.print(oss); return oss.str(); }) .def("get_cursor_op", &cursor_op) .def("get_cursor_n_arg", &cursor_n_arg) .def("get_cursor_args", [](CppAD::cpp_graph &graph, cpp_graph_cursor &cursor) { auto n_arg = cursor_n_arg(graph, cursor); nb::list args; for (size_t i = 0; i < n_arg; i++) { auto arg = graph.operator_arg_get(cursor.arg_index + i); args.append(nb::int_(arg)); } return args; }) .def("next_cursor", &advance_graph_cursor); nb::class_(m, "ADFunDouble") .def(nb::init<>()) .def_prop_ro("nx", [](const ADFunDouble &f) { return f.Domain(); }) .def_prop_ro("ny", [](const ADFunDouble &f) { return f.Range(); }) .def_prop_ro("np", [](const ADFunDouble &f) { return f.size_dyn_ind(); }) .def("to_graph", &ADFunDouble::to_graph); nb::class_(m, "CppADAutodiffGraph") .def(nb::init<>()) .def_ro("f", &CppADAutodiffGraph::f_graph) .def_ro("jacobian", &CppADAutodiffGraph::jacobian_graph) .def_ro("hessian", &CppADAutodiffGraph::hessian_graph); m.def("cppad_trace_graph_constraints", cppad_trace_graph_constraints, nb::arg("graph"), nb::arg("selected") = std::vector{}); m.def("cppad_trace_graph_objective", cppad_trace_graph_objective, nb::arg("graph"), nb::arg("selected") = std::vector{}, nb::arg("aggregate") = true); m.def("cppad_autodiff", &cppad_autodiff); } ================================================ FILE: lib/gurobi_model.cpp ================================================ #include "pyoptinterface/gurobi_model.hpp" #include "fmt/core.h" #include extern "C" { int __stdcall GRBloadenvinternal(GRBenv **envP, const char *logfilename, int major, int minor, int tech); int __stdcall GRBemptyenvinternal(GRBenv **envP, int major, int minor, int tech); int __stdcall GRBloadenv_1200(GRBenv **envP, const char *logfile); int __stdcall GRBemptyenv_1200(GRBenv **envP); } namespace gurobi { #define B DYLIB_DECLARE APILIST #undef B // Gurobi 12.0.0 workarounds DYLIB_DECLARE(GRBloadenvinternal); DYLIB_DECLARE(GRBemptyenvinternal); static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B // We have to deal with Gurobi 12.0.0 where some functions are absent from the dynamic library { int major, minor, techinical; auto GRBversion_p = reinterpret_cast(_function_pointers["GRBversion"]); if (GRBversion_p != nullptr) { GRBversion_p(&major, &minor, &techinical); if (major == 12 && minor == 0 && techinical == 0) { DYLIB_LOAD_FUNCTION(GRBloadenvinternal); DYLIB_LOAD_FUNCTION(GRBemptyenvinternal); _function_pointers["GRBloadenv"] = reinterpret_cast(&GRBloadenv_1200); _function_pointers["GRBemptyenv"] = reinterpret_cast(&GRBemptyenv_1200); // Now check there is no nullptr in _function_pointers _load_success = true; for (auto &pair : _function_pointers) { if (pair.second == nullptr) { fmt::print("function {} is not loaded correctly\n", pair.first); _load_success = false; } } if (_load_success) { DYLIB_SAVE_FUNCTION(GRBloadenvinternal); DYLIB_SAVE_FUNCTION(GRBemptyenvinternal); } } } } if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } } // namespace gurobi int GRBloadenv_1200(GRBenv **envP, const char *logfile) { return gurobi::GRBloadenvinternal(envP, logfile, 12, 0, 0); } int GRBemptyenv_1200(GRBenv **envP) { return gurobi::GRBemptyenvinternal(envP, 12, 0, 0); } static char gurobi_con_sense(ConstraintSense sense) { switch (sense) { case ConstraintSense::LessEqual: return GRB_LESS_EQUAL; case ConstraintSense::Equal: return GRB_EQUAL; case ConstraintSense::GreaterEqual: return GRB_GREATER_EQUAL; default: throw std::runtime_error("Unknown constraint sense"); } } static int gurobi_obj_sense(ObjectiveSense sense) { switch (sense) { case ObjectiveSense::Minimize: return GRB_MINIMIZE; case ObjectiveSense::Maximize: return GRB_MAXIMIZE; default: throw std::runtime_error("Unknown objective sense"); } } static char gurobi_vtype(VariableDomain domain) { switch (domain) { case VariableDomain::Continuous: return GRB_CONTINUOUS; case VariableDomain::Integer: return GRB_INTEGER; case VariableDomain::Binary: return GRB_BINARY; case VariableDomain::SemiContinuous: return GRB_SEMICONT; default: throw std::runtime_error("Unknown variable domain"); } } static int gurobi_sostype(SOSType type) { switch (type) { case SOSType::SOS1: return GRB_SOS_TYPE1; case SOSType::SOS2: return GRB_SOS_TYPE2; default: throw std::runtime_error("Unknown SOS type"); } } GurobiModel::GurobiModel(const GurobiEnv &env) { init(env); } void GurobiModel::init(const GurobiEnv &env) { if (!gurobi::is_library_loaded()) { throw std::runtime_error("Gurobi library is not loaded"); } GRBmodel *model; int error = gurobi::GRBnewmodel(env.m_env, &model, NULL, 0, NULL, NULL, NULL, NULL, NULL); check_error(error); m_env = gurobi::GRBgetenv(model); m_model = std::unique_ptr(model); } void GurobiModel::close() { m_model.reset(); } void GurobiModel::_reset(int clearall) { int error = gurobi::GRBreset(m_model.get(), clearall); check_error(error); } double GurobiModel::get_infinity() const { return GRB_INFINITY; } void GurobiModel::write(const std::string &filename) { int error = gurobi::GRBwrite(m_model.get(), filename.c_str()); check_error(error); } VariableIndex GurobiModel::add_variable(VariableDomain domain, double lb, double ub, const char *name) { if (name != nullptr && name[0] == '\0') { name = nullptr; } IndexT index = m_variable_index.add_index(); VariableIndex variable(index); // Create a new Gurobi variable char vtype = gurobi_vtype(domain); int error = gurobi::GRBaddvar(m_model.get(), 0, NULL, NULL, 0.0, lb, ub, vtype, name); check_error(error); m_update_flag |= m_variable_creation; return variable; } void GurobiModel::delete_variable(const VariableIndex &variable) { if (!is_variable_active(variable)) { throw std::runtime_error("Variable does not exist"); } // Delete the corresponding Gurobi variable int variable_column = _variable_index(variable); int error = gurobi::GRBdelvars(m_model.get(), 1, &variable_column); check_error(error); m_variable_index.delete_index(variable.index); m_update_flag |= m_variable_deletion; } void GurobiModel::delete_variables(const Vector &variables) { int n_variables = variables.size(); if (n_variables == 0) return; std::vector columns; columns.reserve(n_variables); for (int i = 0; i < n_variables; i++) { if (!is_variable_active(variables[i])) { continue; } auto column = _variable_index(variables[i]); columns.push_back(column); } int error = gurobi::GRBdelvars(m_model.get(), columns.size(), columns.data()); check_error(error); for (int i = 0; i < n_variables; i++) { m_variable_index.delete_index(variables[i].index); } m_update_flag |= m_variable_deletion; } bool GurobiModel::is_variable_active(const VariableIndex &variable) { return m_variable_index.has_index(variable.index); } double GurobiModel::get_variable_value(const VariableIndex &variable) { return get_variable_raw_attribute_double(variable, GRB_DBL_ATTR_X); } std::string GurobiModel::pprint_variable(const VariableIndex &variable) { return get_variable_raw_attribute_string(variable, GRB_STR_ATTR_VARNAME); } void GurobiModel::set_variable_bounds(const VariableIndex &variable, double lb, double ub) { auto column = _checked_variable_index(variable); int error; error = gurobi::GRBsetdblattrelement(m_model.get(), GRB_DBL_ATTR_LB, column, lb); check_error(error); error = gurobi::GRBsetdblattrelement(m_model.get(), GRB_DBL_ATTR_UB, column, ub); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_variable_name(const VariableIndex &variable, const char *name) { set_variable_raw_attribute_string(variable, GRB_STR_ATTR_VARNAME, name); } void GurobiModel::set_constraint_name(const ConstraintIndex &constraint, const char *name) { const char *attr_name; switch (constraint.type) { case ConstraintType::Linear: attr_name = GRB_STR_ATTR_CONSTRNAME; break; case ConstraintType::Quadratic: attr_name = GRB_STR_ATTR_QCNAME; break; default: throw std::runtime_error("Unknown constraint type to set name!"); } set_constraint_raw_attribute_string(constraint, attr_name, name); } ConstraintIndex GurobiModel::add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { IndexT index = m_linear_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); // Create a new Gurobi linear constraint AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; char g_sense = gurobi_con_sense(sense); double g_rhs = rhs - function.constant.value_or(0.0); if (name != nullptr && name[0] == '\0') { name = nullptr; } int error = gurobi::GRBaddconstr(m_model.get(), numnz, cind, cval, g_sense, g_rhs, name); check_error(error); // _require_update(); m_update_flag |= m_linear_constraint_creation; return constraint_index; } ConstraintIndex GurobiModel::add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { IndexT index = m_quadratic_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Quadratic, index); // Create a new Gurobi quadratic constraint const auto &affine_part = function.affine_part; int numlnz = 0; int *lind = NULL; double *lval = NULL; AffineFunctionPtrForm affine_ptr_form; if (affine_part.has_value()) { const auto &affine_function = affine_part.value(); affine_ptr_form.make(this, affine_function); numlnz = affine_ptr_form.numnz; lind = affine_ptr_form.index; lval = affine_ptr_form.value; } QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); int numqnz = ptr_form.numnz; int *qrow = ptr_form.row; int *qcol = ptr_form.col; double *qval = ptr_form.value; char g_sense = gurobi_con_sense(sense); double g_rhs = rhs; if (affine_part) g_rhs -= affine_part->constant.value_or(0.0); if (name != nullptr && name[0] == '\0') { name = nullptr; } int error = gurobi::GRBaddqconstr(m_model.get(), numlnz, lind, lval, numqnz, qrow, qcol, qval, g_sense, g_rhs, name); check_error(error); m_update_flag |= m_quadratic_constraint_creation; return constraint_index; } ConstraintIndex GurobiModel::add_sos_constraint(const Vector &variables, SOSType sos_type) { Vector weights(variables.size(), 1.0); return add_sos_constraint(variables, sos_type, weights); } ConstraintIndex GurobiModel::add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights) { IndexT index = m_sos_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::SOS, index); // Create a new Gurobi SOS constraint int numsos = 1; int nummembers = variables.size(); int types = gurobi_sostype(sos_type); int beg[] = {0, nummembers}; std::vector ind_v(nummembers); for (int i = 0; i < nummembers; i++) { ind_v[i] = _variable_index(variables[i].index); } int *ind = ind_v.data(); double *weight = (double *)weights.data(); int error = gurobi::GRBaddsos(m_model.get(), numsos, nummembers, &types, beg, ind, weight); check_error(error); m_update_flag |= m_sos_constraint_creation; return constraint_index; } int unary_opcode(const UnaryOperator &op) { switch (op) { case UnaryOperator::Neg: return GRB_OPCODE_UMINUS; case UnaryOperator::Sin: return GRB_OPCODE_SIN; case UnaryOperator::Cos: return GRB_OPCODE_COS; case UnaryOperator::Tan: return GRB_OPCODE_TAN; case UnaryOperator::Sqrt: return GRB_OPCODE_SQRT; case UnaryOperator::Exp: return GRB_OPCODE_EXP; case UnaryOperator::Log: return GRB_OPCODE_LOG; case UnaryOperator::Log10: return GRB_OPCODE_LOG10; default: { auto opname = unary_operator_to_string(op); auto msg = fmt::format("Unknown unary operator for Gurobi: {}", opname); throw std::runtime_error(msg); } } } int binary_opcode(const BinaryOperator &op) { switch (op) { case BinaryOperator::Sub: return GRB_OPCODE_MINUS; case BinaryOperator::Div: return GRB_OPCODE_DIVIDE; case BinaryOperator::Pow: return GRB_OPCODE_POW; default: { auto opname = binary_operator_to_string(op); auto msg = fmt::format("Unknown binary operator for Gurobi: {}", opname); throw std::runtime_error(msg); } } } int nary_opcode(const NaryOperator &op) { switch (op) { case NaryOperator::Add: return GRB_OPCODE_PLUS; case NaryOperator::Mul: return GRB_OPCODE_MULTIPLY; default: { auto opname = nary_operator_to_string(op); auto msg = fmt::format("Unknown n-ary operator for Gurobi: {}", opname); throw std::runtime_error(msg); } } } void GurobiModel::information_of_expr(const ExpressionGraph &graph, const ExpressionHandle &expr, int &opcode, double &data) { auto array_type = expr.array; auto index = expr.id; switch (array_type) { case ArrayType::Constant: { opcode = GRB_OPCODE_CONSTANT; data = graph.m_constants[index]; break; } case ArrayType::Variable: { opcode = GRB_OPCODE_VARIABLE; data = _checked_variable_index(graph.m_variables[index]); break; } case ArrayType::Parameter: { throw std::runtime_error("Parameter is not supported in Gurobi"); break; } case ArrayType::Unary: { auto &unary = graph.m_unaries[index]; opcode = unary_opcode(unary.op); data = 0.0; break; } case ArrayType::Binary: { auto &binary = graph.m_binaries[index]; opcode = binary_opcode(binary.op); data = 0.0; break; } case ArrayType::Ternary: { throw std::runtime_error("Ternary operator is not supported in Gurobi"); break; } case ArrayType::Nary: { auto &nary = graph.m_naries[index]; opcode = nary_opcode(nary.op); data = 0.0; break; } } } void GurobiModel::decode_graph(const ExpressionGraph &graph, const ExpressionHandle &result, std::vector &opcodes, std::vector &parents, std::vector &datas) { std::stack expr_stack; std::stack parent_stack; // init stack expr_stack.push(result); parent_stack.push(-1); while (!expr_stack.empty()) { auto expr = expr_stack.top(); expr_stack.pop(); auto parent = parent_stack.top(); parent_stack.pop(); int opcode; double data; information_of_expr(graph, expr, opcode, data); auto current_parent = opcodes.size(); opcodes.push_back(opcode); parents.push_back(parent); datas.push_back(data); auto array_type = expr.array; auto index = expr.id; switch (array_type) { case ArrayType::Unary: { auto &unary = graph.m_unaries[index]; expr_stack.push(unary.operand); parent_stack.push(current_parent); break; } case ArrayType::Binary: { auto &binary = graph.m_binaries[index]; expr_stack.push(binary.right); expr_stack.push(binary.left); parent_stack.push(current_parent); parent_stack.push(current_parent); break; } case ArrayType::Nary: { auto &nary = graph.m_naries[index]; for (int i = nary.operands.size() - 1; i >= 0; i--) { expr_stack.push(nary.operands[i]); parent_stack.push(current_parent); } break; } default: break; } } } ConstraintIndex GurobiModel::add_single_nl_constraint(const ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name) { double lb = std::get<0>(interval); double ub = std::get<1>(interval); std::vector opcodes, parents; std::vector datas; decode_graph(graph, result, opcodes, parents, datas); if (name != nullptr && name[0] == '\0') { name = nullptr; } // add a slack variable VariableIndex resvar = add_variable(VariableDomain::Continuous, lb, ub, name); auto resvar_column = _variable_index(resvar); // add NL constraint int error = gurobi::GRBaddgenconstrNL(m_model.get(), name, resvar_column, opcodes.size(), opcodes.data(), datas.data(), parents.data()); check_error(error); IndexT constraint_index = m_general_constraint_index.add_index(); ConstraintIndex constraint(ConstraintType::NL, constraint_index); m_nlcon_resvar_map.emplace(constraint_index, resvar.index); m_update_flag |= m_general_constraint_creation; return constraint; } void GurobiModel::delete_constraint(const ConstraintIndex &constraint) { // Delete the corresponding Gurobi constraint int error = 0; int constraint_row = _constraint_index(constraint); if (constraint_row >= 0) { switch (constraint.type) { case ConstraintType::Linear: m_linear_constraint_index.delete_index(constraint.index); error = gurobi::GRBdelconstrs(m_model.get(), 1, &constraint_row); m_update_flag |= m_linear_constraint_deletion; break; case ConstraintType::Quadratic: m_quadratic_constraint_index.delete_index(constraint.index); error = gurobi::GRBdelqconstrs(m_model.get(), 1, &constraint_row); m_update_flag |= m_quadratic_constraint_deletion; break; case ConstraintType::SOS: m_sos_constraint_index.delete_index(constraint.index); error = gurobi::GRBdelsos(m_model.get(), 1, &constraint_row); m_update_flag |= m_sos_constraint_deletion; break; case ConstraintType::NL: { m_general_constraint_index.delete_index(constraint.index); error = gurobi::GRBdelgenconstrs(m_model.get(), 1, &constraint_row); // delete the corresponding resvar variable as well auto resvar = m_nlcon_resvar_map.at(constraint.index); delete_variable(VariableIndex(resvar)); m_update_flag |= m_general_constraint_deletion; break; } default: throw std::runtime_error("Unknown constraint type"); } } check_error(error); } bool GurobiModel::is_constraint_active(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: return m_linear_constraint_index.has_index(constraint.index); case ConstraintType::Quadratic: return m_quadratic_constraint_index.has_index(constraint.index); case ConstraintType::SOS: return m_sos_constraint_index.has_index(constraint.index); case ConstraintType::NL: return m_general_constraint_index.has_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } void GurobiModel::_set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic) { int error = 0; if (clear_quadratic) { // First delete all quadratic terms error = gurobi::GRBdelq(m_model.get()); check_error(error); } // Set Obj attribute of each variable int n_variables = get_model_raw_attribute_int(GRB_INT_ATTR_NUMVARS); std::vector obj_v(n_variables, 0.0); int numnz = function.size(); for (int i = 0; i < numnz; i++) { auto column = _variable_index(function.variables[i]); if (column < 0) { throw std::runtime_error("Variable does not exist"); } obj_v[column] = function.coefficients[i]; } error = gurobi::GRBsetdblattrarray(m_model.get(), "Obj", 0, n_variables, obj_v.data()); check_error(error); error = gurobi::GRBsetdblattr(m_model.get(), "ObjCon", function.constant.value_or(0.0)); check_error(error); int obj_sense = gurobi_obj_sense(sense); set_model_raw_attribute_int("ModelSense", obj_sense); m_update_flag |= m_objective_update; } void GurobiModel::set_objective(const ScalarAffineFunction &function, ObjectiveSense sense) { _set_affine_objective(function, sense, true); } void GurobiModel::set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense) { int error = 0; // First delete all quadratic terms error = gurobi::GRBdelq(m_model.get()); check_error(error); // Add quadratic term int numqnz = function.size(); if (numqnz > 0) { QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); int numqnz = ptr_form.numnz; int *qrow = ptr_form.row; int *qcol = ptr_form.col; double *qval = ptr_form.value; error = gurobi::GRBaddqpterms(m_model.get(), numqnz, qrow, qcol, qval); check_error(error); } // Affine part const auto &affine_part = function.affine_part; if (affine_part) { const auto &affine_function = affine_part.value(); _set_affine_objective(affine_function, sense, false); } else { ScalarAffineFunction zero; _set_affine_objective(zero, sense, false); } } void GurobiModel::set_objective(const ExprBuilder &function, ObjectiveSense sense) { auto deg = function.degree(); if (deg <= 1) { ScalarAffineFunction f(function); set_objective(f, sense); } else if (deg == 2) { ScalarQuadraticFunction f(function); set_objective(f, sense); } else { throw std::runtime_error("Objective must be linear or quadratic"); } } void GurobiModel::add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result) { std::vector opcodes, parents; std::vector datas; decode_graph(graph, result, opcodes, parents, datas); // add a slack variable VariableIndex resvar = add_variable(VariableDomain::Continuous); auto resvar_column = _variable_index(resvar); // add NL constraint int error = gurobi::GRBaddgenconstrNL(m_model.get(), nullptr, resvar_column, opcodes.size(), opcodes.data(), datas.data(), parents.data()); check_error(error); IndexT constraint_index = m_general_constraint_index.add_index(); m_nlobj_num += 1; m_nlobj_con_indices.push_back(constraint_index); m_nlobj_resvar_indices.push_back(resvar.index); m_update_flag |= m_general_constraint_creation; } void GurobiModel::set_nl_objective() { if (m_nlobj_num > 0) { std::vector resvar_columns(m_nlobj_num); std::vector resvar_objcoefs(m_nlobj_num); std::fill(resvar_objcoefs.begin(), resvar_objcoefs.end(), 1.0); for (int i = 0; i < m_nlobj_num; i++) { auto resvar = m_nlobj_resvar_indices[i]; resvar_columns[i] = _variable_index(resvar); } int error = gurobi::GRBsetdblattrlist(m_model.get(), GRB_DBL_ATTR_OBJ, m_nlobj_num, resvar_columns.data(), resvar_objcoefs.data()); check_error(error); } } void GurobiModel::optimize() { if (has_callback) { // Store the number of variables for the callback m_callback_userdata.n_variables = get_model_raw_attribute_int(GRB_INT_ATTR_NUMVARS); } set_nl_objective(); m_update_flag = 0; int error = gurobi::GRBoptimize(m_model.get()); check_error(error); } void GurobiModel::update() { int error = gurobi::GRBupdatemodel(m_model.get()); check_error(error); m_update_flag = 0; } int GurobiModel::raw_parameter_type(const char *param_name) { return gurobi::GRBgetparamtype(m_env, param_name); } void GurobiModel::set_raw_parameter_int(const char *param_name, int value) { int error = gurobi::GRBsetintparam(m_env, param_name, value); check_error(error); } void GurobiModel::set_raw_parameter_double(const char *param_name, double value) { int error = gurobi::GRBsetdblparam(m_env, param_name, value); check_error(error); } void GurobiModel::set_raw_parameter_string(const char *param_name, const char *value) { int error = gurobi::GRBsetstrparam(m_env, param_name, value); check_error(error); } int GurobiModel::get_raw_parameter_int(const char *param_name) { int retval; int error = gurobi::GRBgetintparam(m_env, param_name, &retval); check_error(error); return retval; } double GurobiModel::get_raw_parameter_double(const char *param_name) { double retval; int error = gurobi::GRBgetdblparam(m_env, param_name, &retval); check_error(error); return retval; } std::string GurobiModel::get_raw_parameter_string(const char *param_name) { char retval[GRB_MAX_STRLEN]; int error = gurobi::GRBgetstrparam(m_env, param_name, retval); check_error(error); return std::string(retval); } int GurobiModel::raw_attribute_type(const char *attr_name) { int datatypeP; int error = gurobi::GRBgetattrinfo(m_model.get(), attr_name, &datatypeP, NULL, NULL); check_error(error); return datatypeP; } void GurobiModel::set_model_raw_attribute_int(const char *attr_name, int value) { int error = gurobi::GRBsetintattr(m_model.get(), attr_name, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_model_raw_attribute_double(const char *attr_name, double value) { int error = gurobi::GRBsetdblattr(m_model.get(), attr_name, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_model_raw_attribute_string(const char *attr_name, const char *value) { int error = gurobi::GRBsetstrattr(m_model.get(), attr_name, value); check_error(error); m_update_flag |= m_attribute_update; } int GurobiModel::get_model_raw_attribute_int(const char *attr_name) { _update_for_information(); int retval; int error = gurobi::GRBgetintattr(m_model.get(), attr_name, &retval); check_error(error); return retval; } double GurobiModel::get_model_raw_attribute_double(const char *attr_name) { _update_for_information(); double retval; int error = gurobi::GRBgetdblattr(m_model.get(), attr_name, &retval); check_error(error); return retval; } std::string GurobiModel::get_model_raw_attribute_string(const char *attr_name) { _update_for_information(); char *retval; int error = gurobi::GRBgetstrattr(m_model.get(), attr_name, &retval); check_error(error); return std::string(retval); } std::vector GurobiModel::get_model_raw_attribute_vector_double(const char *attr_name, int start, int len) { _update_for_information(); std::vector retval(len); int error = gurobi::GRBgetdblattrarray(m_model.get(), attr_name, start, len, retval.data()); check_error(error); return retval; } std::vector GurobiModel::get_model_raw_attribute_list_double(const char *attr_name, const std::vector &ind) { _update_for_information(); std::vector retval(ind.size()); int error = gurobi::GRBgetdblattrlist(m_model.get(), attr_name, ind.size(), (int *)ind.data(), retval.data()); check_error(error); return retval; } void GurobiModel::set_variable_raw_attribute_int(const VariableIndex &variable, const char *attr_name, int value) { auto column = _checked_variable_index(variable); int error = gurobi::GRBsetintattrelement(m_model.get(), attr_name, column, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_variable_raw_attribute_char(const VariableIndex &variable, const char *attr_name, char value) { auto column = _checked_variable_index(variable); int error = gurobi::GRBsetcharattrelement(m_model.get(), attr_name, column, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_variable_raw_attribute_double(const VariableIndex &variable, const char *attr_name, double value) { auto column = _checked_variable_index(variable); int error = gurobi::GRBsetdblattrelement(m_model.get(), attr_name, column, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_variable_raw_attribute_string(const VariableIndex &variable, const char *attr_name, const char *value) { auto column = _checked_variable_index(variable); int error = gurobi::GRBsetstrattrelement(m_model.get(), attr_name, column, value); check_error(error); m_update_flag |= m_attribute_update; } int GurobiModel::get_variable_raw_attribute_int(const VariableIndex &variable, const char *attr_name) { _update_for_information(); auto column = _checked_variable_index(variable); int retval; int error = gurobi::GRBgetintattrelement(m_model.get(), attr_name, column, &retval); check_error(error); return retval; } char GurobiModel::get_variable_raw_attribute_char(const VariableIndex &variable, const char *attr_name) { _update_for_information(); auto column = _checked_variable_index(variable); char retval; int error = gurobi::GRBgetcharattrelement(m_model.get(), attr_name, column, &retval); check_error(error); return retval; } double GurobiModel::get_variable_raw_attribute_double(const VariableIndex &variable, const char *attr_name) { _update_for_information(); auto column = _checked_variable_index(variable); double retval; int error = gurobi::GRBgetdblattrelement(m_model.get(), attr_name, column, &retval); check_error(error); return retval; } std::string GurobiModel::get_variable_raw_attribute_string(const VariableIndex &variable, const char *attr_name) { _update_for_information(); auto column = _checked_variable_index(variable); char *retval; int error = gurobi::GRBgetstrattrelement(m_model.get(), attr_name, column, &retval); check_error(error); return std::string(retval); } int GurobiModel::_variable_index(const VariableIndex &variable) { _update_for_variable_index(); return m_variable_index.get_index(variable.index); } int GurobiModel::_checked_variable_index(const VariableIndex &variable) { int column = _variable_index(variable); if (column < 0) { throw std::runtime_error("Variable does not exist"); } return column; } void GurobiModel::set_constraint_raw_attribute_int(const ConstraintIndex &constraint, const char *attr_name, int value) { int row = _checked_constraint_index(constraint); int error = gurobi::GRBsetintattrelement(m_model.get(), attr_name, row, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_constraint_raw_attribute_char(const ConstraintIndex &constraint, const char *attr_name, char value) { int row = _checked_constraint_index(constraint); int error = gurobi::GRBsetcharattrelement(m_model.get(), attr_name, row, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_constraint_raw_attribute_double(const ConstraintIndex &constraint, const char *attr_name, double value) { int row = _checked_constraint_index(constraint); int error = gurobi::GRBsetdblattrelement(m_model.get(), attr_name, row, value); check_error(error); m_update_flag |= m_attribute_update; } void GurobiModel::set_constraint_raw_attribute_string(const ConstraintIndex &constraint, const char *attr_name, const char *value) { int row = _checked_constraint_index(constraint); int error = gurobi::GRBsetstrattrelement(m_model.get(), attr_name, row, value); check_error(error); m_update_flag |= m_attribute_update; } int GurobiModel::get_constraint_raw_attribute_int(const ConstraintIndex &constraint, const char *attr_name) { _update_for_information(); int row = _checked_constraint_index(constraint); int retval; int error = gurobi::GRBgetintattrelement(m_model.get(), attr_name, row, &retval); check_error(error); return retval; } char GurobiModel::get_constraint_raw_attribute_char(const ConstraintIndex &constraint, const char *attr_name) { _update_for_information(); int row = _checked_constraint_index(constraint); char retval; int error = gurobi::GRBgetcharattrelement(m_model.get(), attr_name, row, &retval); check_error(error); return retval; } double GurobiModel::get_constraint_raw_attribute_double(const ConstraintIndex &constraint, const char *attr_name) { _update_for_information(); int row = _checked_constraint_index(constraint); double retval; int error = gurobi::GRBgetdblattrelement(m_model.get(), attr_name, row, &retval); check_error(error); return retval; } std::string GurobiModel::get_constraint_raw_attribute_string(const ConstraintIndex &constraint, const char *attr_name) { _update_for_information(); int row = _checked_constraint_index(constraint); char *retval; int error = gurobi::GRBgetstrattrelement(m_model.get(), attr_name, row, &retval); check_error(error); return std::string(retval); } double GurobiModel::get_normalized_rhs(const ConstraintIndex &constraint) { const char *name; switch (constraint.type) { case ConstraintType::Linear: name = GRB_DBL_ATTR_RHS; break; case ConstraintType::Quadratic: name = GRB_DBL_ATTR_QCRHS; break; default: throw std::runtime_error("Unknown constraint type to get_normalized_rhs"); } return get_constraint_raw_attribute_double(constraint, name); } void GurobiModel::set_normalized_rhs(const ConstraintIndex &constraint, double value) { const char *name; switch (constraint.type) { case ConstraintType::Linear: name = GRB_DBL_ATTR_RHS; break; case ConstraintType::Quadratic: name = GRB_DBL_ATTR_QCRHS; break; default: throw std::runtime_error("Unknown constraint type to set_normalized_rhs"); } set_constraint_raw_attribute_double(constraint, name, value); } double GurobiModel::get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable) { if (constraint.type != ConstraintType::Linear) { throw std::runtime_error("Only linear constraint supports get_normalized_coefficient"); } _update_for_information(); int row = _checked_constraint_index(constraint); int col = _checked_variable_index(variable); double retval; int error = gurobi::GRBgetcoeff(m_model.get(), row, col, &retval); check_error(error); return retval; } void GurobiModel::set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value) { if (constraint.type != ConstraintType::Linear) { throw std::runtime_error("Only linear constraint supports set_normalized_coefficient"); } int row = _checked_constraint_index(constraint); int col = _checked_variable_index(variable); int error = gurobi::GRBchgcoeffs(m_model.get(), 1, &row, &col, &value); check_error(error); m_update_flag |= m_constraint_coefficient_update; } double GurobiModel::get_objective_coefficient(const VariableIndex &variable) { return get_variable_raw_attribute_double(variable, GRB_DBL_ATTR_OBJ); } void GurobiModel::set_objective_coefficient(const VariableIndex &variable, double value) { set_variable_raw_attribute_double(variable, GRB_DBL_ATTR_OBJ, value); } void GurobiModel::_converttofixed() { int error = gurobi::GRBconverttofixed(m_model.get()); check_error(error); } void GurobiModel::computeIIS() { int error = gurobi::GRBcomputeIIS(m_model.get()); check_error(error); } int GurobiModel::_constraint_index(const ConstraintIndex &constraint) { _update_for_constraint_index(constraint.type); switch (constraint.type) { case ConstraintType::Linear: return m_linear_constraint_index.get_index(constraint.index); case ConstraintType::Quadratic: return m_quadratic_constraint_index.get_index(constraint.index); case ConstraintType::SOS: return m_sos_constraint_index.get_index(constraint.index); case ConstraintType::NL: return m_general_constraint_index.get_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } int GurobiModel::_checked_constraint_index(const ConstraintIndex &constraint) { int row = _constraint_index(constraint); if (row < 0) { throw std::runtime_error("Variable does not exist"); } return row; } void GurobiModel::check_error(int error) { if (error) { throw std::runtime_error(gurobi::GRBgeterrormsg(m_env)); } } void GurobiModel::_update_for_information() { if (m_update_flag) { update(); } } void GurobiModel::_update_for_variable_index() { if (m_update_flag & m_variable_deletion) { update(); } } void GurobiModel::_update_for_constraint_index(ConstraintType type) { bool need_update = false; switch (type) { case ConstraintType::Linear: // Adding new linear constraints does not need to update the model need_update = m_update_flag & m_linear_constraint_deletion; break; case ConstraintType::Quadratic: need_update = m_update_flag & (m_quadratic_constraint_creation | m_quadratic_constraint_deletion); break; case ConstraintType::SOS: need_update = m_update_flag & (m_sos_constraint_creation | m_sos_constraint_deletion); break; case ConstraintType::NL: need_update = m_update_flag & (m_general_constraint_creation | m_general_constraint_deletion); break; } if (need_update) { update(); } } void *GurobiModel::get_raw_model() { return m_model.get(); } std::string GurobiModel::version_string() { int major, minor, techinical; gurobi::GRBversion(&major, &minor, &techinical); std::string version = fmt::format("v{}.{}.{}", major, minor, techinical); return version; } GurobiEnv::GurobiEnv(bool empty) { if (!gurobi::is_library_loaded()) { throw std::runtime_error("Gurobi library is not loaded"); } int error = 0; if (empty) { error = gurobi::GRBemptyenv(&m_env); } else { error = gurobi::GRBloadenv(&m_env, NULL); } check_error(error); } GurobiEnv::~GurobiEnv() { close(); } int GurobiEnv::raw_parameter_type(const char *param_name) { return gurobi::GRBgetparamtype(m_env, param_name); } void GurobiEnv::set_raw_parameter_int(const char *param_name, int value) { int error = gurobi::GRBsetintparam(m_env, param_name, value); check_error(error); } void GurobiEnv::set_raw_parameter_double(const char *param_name, double value) { int error = gurobi::GRBsetdblparam(m_env, param_name, value); check_error(error); } void GurobiEnv::set_raw_parameter_string(const char *param_name, const char *value) { int error = gurobi::GRBsetstrparam(m_env, param_name, value); check_error(error); } void GurobiEnv::start() { int error = gurobi::GRBstartenv(m_env); check_error(error); } void GurobiEnv::close() { if (m_env != nullptr) { gurobi::GRBfreeenv(m_env); } m_env = nullptr; } void GurobiEnv::check_error(int error) { if (error) { throw std::runtime_error(gurobi::GRBgeterrormsg(m_env)); } } // Logging callback static int RealLoggingCallbackFunction(char *msg, void *logdata) { auto real_logdata = static_cast(logdata); auto &callback = real_logdata->callback; callback(msg); return 0; } void GurobiModel::set_logging(const GurobiLoggingCallback &callback) { m_logging_callback_userdata.callback = callback; int error = gurobi::GRBsetlogcallbackfunc(m_model.get(), &RealLoggingCallbackFunction, &m_logging_callback_userdata); check_error(error); } // Callback static int RealGurobiCallbackFunction(GRBmodel *, void *cbdata, int where, void *usrdata) { auto real_userdata = static_cast(usrdata); auto model = static_cast(real_userdata->model); auto &callback = real_userdata->callback; model->m_cbdata = cbdata; model->m_callback_userdata.where = where; model->m_callback_userdata.cb_get_mipsol_called = false; model->m_callback_userdata.cb_get_mipnoderel_called = false; model->m_callback_userdata.cb_set_solution_called = false; model->m_callback_userdata.cb_requires_submit_solution = false; callback(model, where); if (model->m_callback_userdata.cb_requires_submit_solution) { model->cb_submit_solution(); } return 0; } void GurobiModel::set_callback(const GurobiCallback &callback) { m_callback_userdata.model = this; m_callback_userdata.callback = callback; int error = gurobi::GRBsetcallbackfunc(m_model.get(), RealGurobiCallbackFunction, &m_callback_userdata); check_error(error); has_callback = true; } int GurobiModel::cb_get_info_int(int what) { int retval; int error = gurobi::GRBcbget(m_cbdata, m_callback_userdata.where, what, &retval); check_error(error); return retval; } double GurobiModel::cb_get_info_double(int what) { double retval; int error = gurobi::GRBcbget(m_cbdata, m_callback_userdata.where, what, &retval); check_error(error); return retval; } void GurobiModel::cb_get_info_doublearray(int what) { int n_vars = m_callback_userdata.n_variables; double *val = nullptr; if (what == GRB_CB_MIPSOL_SOL) { m_callback_userdata.mipsol.resize(n_vars); val = m_callback_userdata.mipsol.data(); } else if (what == GRB_CB_MIPNODE_REL) { m_callback_userdata.mipnoderel.resize(n_vars); val = m_callback_userdata.mipnoderel.data(); } else { throw std::runtime_error("Invalid what for cb_get_info_doublearray"); } int error = gurobi::GRBcbget(m_cbdata, m_callback_userdata.where, what, val); check_error(error); } double GurobiModel::cb_get_solution(const VariableIndex &variable) { auto &userdata = m_callback_userdata; if (!userdata.cb_get_mipsol_called) { cb_get_info_doublearray(GRB_CB_MIPSOL_SOL); userdata.cb_get_mipsol_called = true; } auto index = _variable_index(variable); return userdata.mipsol[index]; } double GurobiModel::cb_get_relaxation(const VariableIndex &variable) { auto &userdata = m_callback_userdata; if (!userdata.cb_get_mipnoderel_called) { cb_get_info_doublearray(GRB_CB_MIPNODE_REL); userdata.cb_get_mipnoderel_called = true; } auto index = _variable_index(variable); return userdata.mipnoderel[index]; } void GurobiModel::cb_set_solution(const VariableIndex &variable, double value) { auto &userdata = m_callback_userdata; if (!userdata.cb_set_solution_called) { userdata.heuristic_solution.resize(userdata.n_variables, GRB_UNDEFINED); userdata.cb_set_solution_called = true; } userdata.heuristic_solution[_variable_index(variable)] = value; m_callback_userdata.cb_requires_submit_solution = true; } double GurobiModel::cb_submit_solution() { if (!m_callback_userdata.cb_set_solution_called) { throw std::runtime_error("No solution is set in the callback!"); } double obj; int error = gurobi::GRBcbsolution(m_cbdata, m_callback_userdata.heuristic_solution.data(), &obj); check_error(error); m_callback_userdata.cb_requires_submit_solution = false; return obj; } void GurobiModel::cb_add_lazy_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; char g_sense = gurobi_con_sense(sense); double g_rhs = rhs - function.constant.value_or(0.0); int error = gurobi::GRBcblazy(m_cbdata, numnz, cind, cval, g_sense, g_rhs); check_error(error); } void GurobiModel::cb_exit() { gurobi::GRBterminate(m_model.get()); } void GurobiModel::cb_add_lazy_constraint(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs) { ScalarAffineFunction f(function); cb_add_lazy_constraint(f, sense, rhs); } void GurobiModel::cb_add_user_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; int *cind = ptr_form.index; double *cval = ptr_form.value; char g_sense = gurobi_con_sense(sense); double g_rhs = rhs - function.constant.value_or(0.0); int error = gurobi::GRBcbcut(m_cbdata, numnz, cind, cval, g_sense, g_rhs); check_error(error); } void GurobiModel::cb_add_user_cut(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs) { ScalarAffineFunction f(function); cb_add_user_cut(f, sense, rhs); } ================================================ FILE: lib/gurobi_model_ext.cpp ================================================ #include #include #include #include #include #include "pyoptinterface/gurobi_model.hpp" namespace nb = nanobind; extern void bind_gurobi_constants(nb::module_ &m); NB_MODULE(gurobi_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.def("is_library_loaded", &gurobi::is_library_loaded); m.def("load_library", &gurobi::load_library); bind_gurobi_constants(m); #define BIND_F(f) .def(#f, &GurobiEnv::f) nb::class_(m, "RawEnv") .def(nb::init(), nb::arg("empty") = false) // clang-format off BIND_F(start) BIND_F(close) BIND_F(raw_parameter_type) BIND_F(set_raw_parameter_int) BIND_F(set_raw_parameter_double) BIND_F(set_raw_parameter_string) // clang-format on ; #undef BIND_F #define BIND_F(f) .def(#f, &GurobiModel::f) nb::class_(m, "RawModel") .def(nb::init<>()) .def(nb::init()) // clang-format off BIND_F(init) BIND_F(close) BIND_F(write) // clang-format on .def("_reset", &GurobiModel::_reset, nb::arg("clearall") = 0) .def("add_variable", &GurobiModel::add_variable, nb::arg("domain") = VariableDomain::Continuous, nb::arg("lb") = -GRB_INFINITY, nb::arg("ub") = GRB_INFINITY, nb::arg("name") = "") // clang-format off BIND_F(delete_variable) BIND_F(delete_variables) BIND_F(is_variable_active) // clang-format on .def("set_variable_bounds", &GurobiModel::set_variable_bounds, nb::arg("variable"), nb::arg("lb"), nb::arg("ub")) .def("get_value", &GurobiModel::get_variable_value) .def("get_value", nb::overload_cast(&GurobiModel::get_expression_value)) .def("get_value", nb::overload_cast(&GurobiModel::get_expression_value)) .def("get_value", nb::overload_cast(&GurobiModel::get_expression_value)) .def("pprint", &GurobiModel::pprint_variable) .def("pprint", nb::overload_cast(&GurobiModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast( &GurobiModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&GurobiModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) // clang-format off BIND_F(set_variable_name) BIND_F(set_constraint_name) // clang-format on .def("_add_linear_constraint", &GurobiModel::add_linear_constraint, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &GurobiModel::add_linear_constraint_from_var, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &GurobiModel::add_linear_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", &GurobiModel::add_quadratic_constraint, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", &GurobiModel::add_quadratic_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("add_sos_constraint", nb::overload_cast &, SOSType>( &GurobiModel::add_sos_constraint)) .def("add_sos_constraint", nb::overload_cast &, SOSType, const Vector &>( &GurobiModel::add_sos_constraint)) .def("_add_single_nl_constraint", &GurobiModel::add_single_nl_constraint, nb::arg("graph"), nb::arg("result"), nb::arg("interval"), nb::arg("name") = "") .def("_add_single_nl_constraint", &GurobiModel::add_single_nl_constraint_sense_rhs, nb::arg("graph"), nb::arg("result"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_single_nl_constraint", &GurobiModel::add_single_nl_constraint_from_comparison, nb::arg("graph"), nb::arg("expr"), nb::arg("name") = "") // clang-format off BIND_F(delete_constraint) BIND_F(is_constraint_active) // clang-format on .def("set_objective", nb::overload_cast( &GurobiModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &GurobiModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&GurobiModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &GurobiModel::set_objective_as_variable), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&GurobiModel::set_objective_as_constant), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("_add_single_nl_objective", &GurobiModel::add_single_nl_objective) .def("cb_add_lazy_constraint", nb::overload_cast( &GurobiModel::cb_add_lazy_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("cb_add_lazy_constraint", nb::overload_cast( &GurobiModel::cb_add_lazy_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("cb_add_user_cut", nb::overload_cast( &GurobiModel::cb_add_user_cut), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("cb_add_user_cut", nb::overload_cast( &GurobiModel::cb_add_user_cut), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs")) .def("optimize", &GurobiModel::optimize, nb::call_guard()) // clang-format off BIND_F(update) BIND_F(version_string) BIND_F(get_raw_model) BIND_F(set_logging) BIND_F(set_callback) BIND_F(cb_get_info_int) BIND_F(cb_get_info_double) BIND_F(cb_get_solution) BIND_F(cb_get_relaxation) BIND_F(cb_set_solution) BIND_F(cb_submit_solution) BIND_F(cb_exit) BIND_F(raw_parameter_type) BIND_F(set_raw_parameter_int) BIND_F(set_raw_parameter_double) BIND_F(set_raw_parameter_string) BIND_F(get_raw_parameter_int) BIND_F(get_raw_parameter_double) BIND_F(get_raw_parameter_string) BIND_F(raw_attribute_type) BIND_F(set_model_raw_attribute_int) BIND_F(set_model_raw_attribute_double) BIND_F(set_model_raw_attribute_string) BIND_F(get_model_raw_attribute_int) BIND_F(get_model_raw_attribute_double) BIND_F(get_model_raw_attribute_string) BIND_F(get_model_raw_attribute_vector_double) BIND_F(get_model_raw_attribute_list_double) BIND_F(set_variable_raw_attribute_int) BIND_F(set_variable_raw_attribute_char) BIND_F(set_variable_raw_attribute_double) BIND_F(set_variable_raw_attribute_string) BIND_F(get_variable_raw_attribute_int) BIND_F(get_variable_raw_attribute_char) BIND_F(get_variable_raw_attribute_double) BIND_F(get_variable_raw_attribute_string) BIND_F(set_constraint_raw_attribute_int) BIND_F(set_constraint_raw_attribute_char) BIND_F(set_constraint_raw_attribute_double) BIND_F(set_constraint_raw_attribute_string) BIND_F(get_constraint_raw_attribute_int) BIND_F(get_constraint_raw_attribute_char) BIND_F(get_constraint_raw_attribute_double) BIND_F(get_constraint_raw_attribute_string) BIND_F(get_normalized_rhs) BIND_F(set_normalized_rhs) BIND_F(get_normalized_coefficient) BIND_F(set_normalized_coefficient) BIND_F(get_objective_coefficient) BIND_F(set_objective_coefficient) BIND_F(_converttofixed) BIND_F(computeIIS) // clang-format on ; } ================================================ FILE: lib/gurobi_model_ext_constants.cpp ================================================ #include namespace nb = nanobind; void bind_gurobi_constants(nb::module_ &m) { nb::module_ GRB = m.def_submodule("GRB"); GRB.attr("BARHOMOGENEOUS_AUTO") = -1; GRB.attr("BARHOMOGENEOUS_OFF") = 0; GRB.attr("BARHOMOGENEOUS_ON") = 1; GRB.attr("BARORDER_AMD") = 0; GRB.attr("BARORDER_AUTOMATIC") = -1; GRB.attr("BARORDER_NESTEDDISSECTION") = 1; GRB.attr("BASIC") = 0; GRB.attr("BATCH_ABORTED") = 3; GRB.attr("BATCH_COMPLETED") = 5; GRB.attr("BATCH_CREATED") = 1; GRB.attr("BATCH_FAILED") = 4; GRB.attr("BATCH_SUBMITTED") = 2; GRB.attr("BINARY") = "B"; GRB.attr("CONTINUOUS") = "C"; GRB.attr("CUTOFF") = 6; GRB.attr("CUTS_AGGRESSIVE") = 2; GRB.attr("CUTS_AUTO") = -1; GRB.attr("CUTS_CONSERVATIVE") = 1; GRB.attr("CUTS_OFF") = 0; GRB.attr("CUTS_VERYAGGRESSIVE") = 3; GRB.attr("DEFAULT_CS_PORT") = 61000; GRB.attr("EQUAL") = "="; GRB.attr("ERROR_CALLBACK") = 10011; GRB.attr("ERROR_CLOUD") = 10028; GRB.attr("ERROR_CSWORKER") = 10030; GRB.attr("ERROR_DATA_NOT_AVAILABLE") = 10005; GRB.attr("ERROR_DUPLICATES") = 10018; GRB.attr("ERROR_EXCEED_2B_NONZEROS") = 10025; GRB.attr("ERROR_FAILED_TO_CREATE_MODEL") = 20002; GRB.attr("ERROR_FILE_READ") = 10012; GRB.attr("ERROR_FILE_WRITE") = 10013; GRB.attr("ERROR_IIS_NOT_INFEASIBLE") = 10015; GRB.attr("ERROR_INDEX_OUT_OF_RANGE") = 10006; GRB.attr("ERROR_INTERNAL") = 20003; GRB.attr("ERROR_INVALID_ARGUMENT") = 10003; GRB.attr("ERROR_INVALID_PIECEWISE_OBJ") = 10026; GRB.attr("ERROR_JOB_REJECTED") = 10023; GRB.attr("ERROR_MODEL_MODIFICATION") = 10029; GRB.attr("ERROR_NETWORK") = 10022; GRB.attr("ERROR_NODEFILE") = 10019; GRB.attr("ERROR_NOT_FOR_MIP") = 10016; GRB.attr("ERROR_NOT_IN_MODEL") = 20001; GRB.attr("ERROR_NOT_SUPPORTED") = 10024; GRB.attr("ERROR_NO_LICENSE") = 10009; GRB.attr("ERROR_NULL_ARGUMENT") = 10002; GRB.attr("ERROR_NUMERIC") = 10014; GRB.attr("ERROR_OPTIMIZATION_IN_PROGRESS") = 10017; GRB.attr("ERROR_OUT_OF_MEMORY") = 10001; GRB.attr("ERROR_QCP_EQUALITY_CONSTRAINT") = 10021; GRB.attr("ERROR_Q_NOT_PSD") = 10020; GRB.attr("ERROR_SECURITY") = 10032; GRB.attr("ERROR_SIZE_LIMIT_EXCEEDED") = 10010; GRB.attr("ERROR_TUNE_MODEL_TYPES") = 10031; GRB.attr("ERROR_UNKNOWN_ATTRIBUTE") = 10004; GRB.attr("ERROR_UNKNOWN_PARAMETER") = 10007; GRB.attr("ERROR_UPDATEMODE_CHANGE") = 10027; GRB.attr("ERROR_VALUE_OUT_OF_RANGE") = 10008; GRB.attr("FEASRELAX_CARDINALITY") = 2; GRB.attr("FEASRELAX_LINEAR") = 0; GRB.attr("FEASRELAX_QUADRATIC") = 1; GRB.attr("GENCONSTR_ABS") = 2; GRB.attr("GENCONSTR_AND") = 3; GRB.attr("GENCONSTR_COS") = 16; GRB.attr("GENCONSTR_EXP") = 10; GRB.attr("GENCONSTR_EXPA") = 11; GRB.attr("GENCONSTR_INDICATOR") = 7; GRB.attr("GENCONSTR_LOG") = 12; GRB.attr("GENCONSTR_LOGA") = 13; GRB.attr("GENCONSTR_LOGISTIC") = 18; GRB.attr("GENCONSTR_MAX") = 0; GRB.attr("GENCONSTR_MIN") = 1; GRB.attr("GENCONSTR_NL") = 6; GRB.attr("GENCONSTR_NORM") = 5; GRB.attr("GENCONSTR_OR") = 4; GRB.attr("GENCONSTR_POLY") = 9; GRB.attr("GENCONSTR_POW") = 14; GRB.attr("GENCONSTR_PWL") = 8; GRB.attr("GENCONSTR_SIN") = 15; GRB.attr("GENCONSTR_TAN") = 17; GRB.attr("GREATER_EQUAL") = ">"; GRB.attr("INFEASIBLE") = 3; GRB.attr("INFINITY") = 1e+100; GRB.attr("INF_OR_UNBD") = 4; GRB.attr("INPROGRESS") = 14; GRB.attr("INTEGER") = "I"; GRB.attr("INTERRUPTED") = 11; GRB.attr("ITERATION_LIMIT") = 7; GRB.attr("LESS_EQUAL") = "<"; GRB.attr("LOADED") = 1; GRB.attr("LOCALLY_INFEASIBLE") = 19; GRB.attr("LOCALLY_OPTIMAL") = 18; GRB.attr("MAXIMIZE") = -1; GRB.attr("MAXINT") = 2000000000; GRB.attr("MAX_CONCURRENT") = 64; GRB.attr("MAX_NAMELEN") = 255; GRB.attr("MAX_STRLEN") = 512; GRB.attr("MAX_TAGLEN") = 10240; GRB.attr("MEM_LIMIT") = 17; GRB.attr("METHOD_AUTO") = -1; GRB.attr("METHOD_BARRIER") = 2; GRB.attr("METHOD_CONCURRENT") = 3; GRB.attr("METHOD_DETERMINISTIC_CONCURRENT") = 4; GRB.attr("METHOD_DETERMINISTIC_CONCURRENT_SIMPLEX") = 5; GRB.attr("METHOD_DUAL") = 1; GRB.attr("METHOD_NONE") = -1; GRB.attr("METHOD_PDHG") = 6; GRB.attr("METHOD_PRIMAL") = 0; GRB.attr("MINIMIZE") = 1; GRB.attr("MIPFOCUS_BALANCED") = 0; GRB.attr("MIPFOCUS_BESTBOUND") = 3; GRB.attr("MIPFOCUS_FEASIBILITY") = 1; GRB.attr("MIPFOCUS_OPTIMALITY") = 2; GRB.attr("NODE_LIMIT") = 8; GRB.attr("NONBASIC_LOWER") = -1; GRB.attr("NONBASIC_UPPER") = -2; GRB.attr("NUMERIC") = 12; GRB.attr("OPCODE_CONSTANT") = 0; GRB.attr("OPCODE_COS") = 10; GRB.attr("OPCODE_DIVIDE") = 5; GRB.attr("OPCODE_EXP") = 13; GRB.attr("OPCODE_LOG") = 14; GRB.attr("OPCODE_LOG10") = 16; GRB.attr("OPCODE_LOG2") = 15; GRB.attr("OPCODE_LOGISTIC") = 17; GRB.attr("OPCODE_MINUS") = 3; GRB.attr("OPCODE_MULTIPLY") = 4; GRB.attr("OPCODE_PLUS") = 2; GRB.attr("OPCODE_POW") = 12; GRB.attr("OPCODE_SIGNPOW") = 19; GRB.attr("OPCODE_SIN") = 9; GRB.attr("OPCODE_SQRT") = 8; GRB.attr("OPCODE_SQUARE") = 7; GRB.attr("OPCODE_TAN") = 11; GRB.attr("OPCODE_TANH") = 18; GRB.attr("OPCODE_UMINUS") = 6; GRB.attr("OPCODE_VARIABLE") = 1; GRB.attr("OPTIMAL") = 2; GRB.attr("PARTITION_CLEANUP") = 1; GRB.attr("PARTITION_EARLY") = 16; GRB.attr("PARTITION_NODES") = 2; GRB.attr("PARTITION_ROOTEND") = 4; GRB.attr("PARTITION_ROOTSTART") = 8; GRB.attr("PHASE_MIP_IMPROVE") = 2; GRB.attr("PHASE_MIP_NOREL") = 0; GRB.attr("PHASE_MIP_SEARCH") = 1; GRB.attr("PRESOLVE_AGGRESSIVE") = 2; GRB.attr("PRESOLVE_AUTO") = -1; GRB.attr("PRESOLVE_CONSERVATIVE") = 1; GRB.attr("PRESOLVE_OFF") = 0; GRB.attr("SEMICONT") = "S"; GRB.attr("SEMIINT") = "N"; GRB.attr("SIMPLEXPRICING_AUTO") = -1; GRB.attr("SIMPLEXPRICING_DEVEX") = 2; GRB.attr("SIMPLEXPRICING_PARTIAL") = 0; GRB.attr("SIMPLEXPRICING_STEEPEST_EDGE") = 1; GRB.attr("SIMPLEXPRICING_STEEPEST_QUICK") = 3; GRB.attr("SOLUTION_LIMIT") = 10; GRB.attr("SOS_TYPE1") = 1; GRB.attr("SOS_TYPE2") = 2; GRB.attr("SUBOPTIMAL") = 13; GRB.attr("SUPERBASIC") = -3; GRB.attr("TIME_LIMIT") = 9; GRB.attr("UNBOUNDED") = 5; GRB.attr("UNDEFINED") = 1e+101; GRB.attr("USER_OBJ_LIMIT") = 15; GRB.attr("VARBRANCH_AUTO") = -1; GRB.attr("VARBRANCH_MAX_INFEAS") = 2; GRB.attr("VARBRANCH_PSEUDO_REDUCED") = 0; GRB.attr("VARBRANCH_PSEUDO_SHADOW") = 1; GRB.attr("VARBRANCH_STRONG") = 3; GRB.attr("VERSION_MAJOR") = 13; GRB.attr("VERSION_MINOR") = 0; GRB.attr("VERSION_TECHNICAL") = 0; GRB.attr("WORK_LIMIT") = 16; nb::module_ Attr = GRB.def_submodule("Attr"); Attr.attr("BarIterCount") = "BarIterCount"; Attr.attr("BarPi") = "BarPi"; Attr.attr("BarStatus") = "BarStatus"; Attr.attr("BarX") = "BarX"; Attr.attr("BatchErrorCode") = "BatchErrorCode"; Attr.attr("BatchErrorMessage") = "BatchErrorMessage"; Attr.attr("BatchID") = "BatchID"; Attr.attr("BatchStatus") = "BatchStatus"; Attr.attr("BoundSVio") = "BoundSVio"; Attr.attr("BoundSVioIndex") = "BoundSVioIndex"; Attr.attr("BoundSVioSum") = "BoundSVioSum"; Attr.attr("BoundVio") = "BoundVio"; Attr.attr("BoundVioIndex") = "BoundVioIndex"; Attr.attr("BoundVioSum") = "BoundVioSum"; Attr.attr("BranchPriority") = "BranchPriority"; Attr.attr("CBasis") = "CBasis"; Attr.attr("CTag") = "CTag"; Attr.attr("ComplVio") = "ComplVio"; Attr.attr("ComplVioIndex") = "ComplVioIndex"; Attr.attr("ComplVioSum") = "ComplVioSum"; Attr.attr("ConcurrentWinMethod") = "ConcurrentWinMethod"; Attr.attr("ConstrName") = "ConstrName"; Attr.attr("ConstrResidual") = "ConstrResidual"; Attr.attr("ConstrResidualIndex") = "ConstrResidualIndex"; Attr.attr("ConstrResidualSum") = "ConstrResidualSum"; Attr.attr("ConstrSResidual") = "ConstrSResidual"; Attr.attr("ConstrSResidualIndex") = "ConstrSResidualIndex"; Attr.attr("ConstrSResidualSum") = "ConstrSResidualSum"; Attr.attr("ConstrSVio") = "ConstrSVio"; Attr.attr("ConstrSVioIndex") = "ConstrSVioIndex"; Attr.attr("ConstrSVioSum") = "ConstrSVioSum"; Attr.attr("ConstrVio") = "ConstrVio"; Attr.attr("ConstrVioIndex") = "ConstrVioIndex"; Attr.attr("ConstrVioSum") = "ConstrVioSum"; Attr.attr("DNumNZs") = "DNumNZs"; Attr.attr("DStart") = "DStart"; Attr.attr("DualResidual") = "DualResidual"; Attr.attr("DualResidualIndex") = "DualResidualIndex"; Attr.attr("DualResidualSum") = "DualResidualSum"; Attr.attr("DualSResidual") = "DualSResidual"; Attr.attr("DualSResidualIndex") = "DualSResidualIndex"; Attr.attr("DualSResidualSum") = "DualSResidualSum"; Attr.attr("DualSVio") = "DualSVio"; Attr.attr("DualSVioIndex") = "DualSVioIndex"; Attr.attr("DualSVioSum") = "DualSVioSum"; Attr.attr("DualVio") = "DualVio"; Attr.attr("DualVioIndex") = "DualVioIndex"; Attr.attr("DualVioSum") = "DualVioSum"; Attr.attr("FarkasDual") = "FarkasDual"; Attr.attr("FarkasProof") = "FarkasProof"; Attr.attr("Fingerprint") = "Fingerprint"; Attr.attr("FuncNonlinear") = "FuncNonlinear"; Attr.attr("FuncPieceError") = "FuncPieceError"; Attr.attr("FuncPieceLength") = "FuncPieceLength"; Attr.attr("FuncPieceRatio") = "FuncPieceRatio"; Attr.attr("FuncPieces") = "FuncPieces"; Attr.attr("GenConstrName") = "GenConstrName"; Attr.attr("GenConstrType") = "GenConstrType"; Attr.attr("IISConstr") = "IISConstr"; Attr.attr("IISConstrForce") = "IISConstrForce"; Attr.attr("IISGenConstr") = "IISGenConstr"; Attr.attr("IISGenConstrForce") = "IISGenConstrForce"; Attr.attr("IISLB") = "IISLB"; Attr.attr("IISLBForce") = "IISLBForce"; Attr.attr("IISMinimal") = "IISMinimal"; Attr.attr("IISQConstr") = "IISQConstr"; Attr.attr("IISQConstrForce") = "IISQConstrForce"; Attr.attr("IISSOS") = "IISSOS"; Attr.attr("IISSOSForce") = "IISSOSForce"; Attr.attr("IISUB") = "IISUB"; Attr.attr("IISUBForce") = "IISUBForce"; Attr.attr("IntVio") = "IntVio"; Attr.attr("IntVioIndex") = "IntVioIndex"; Attr.attr("IntVioSum") = "IntVioSum"; Attr.attr("IsMIP") = "IsMIP"; Attr.attr("IsMultiObj") = "IsMultiObj"; Attr.attr("IsQCP") = "IsQCP"; Attr.attr("IsQP") = "IsQP"; Attr.attr("IterCount") = "IterCount"; Attr.attr("Kappa") = "Kappa"; Attr.attr("KappaExact") = "KappaExact"; Attr.attr("LB") = "LB"; Attr.attr("Lazy") = "Lazy"; Attr.attr("LicenseExpiration") = "LicenseExpiration"; Attr.attr("MIPGap") = "MIPGap"; Attr.attr("MaxBound") = "MaxBound"; Attr.attr("MaxCoeff") = "MaxCoeff"; Attr.attr("MaxMemUsed") = "MaxMemUsed"; Attr.attr("MaxObjCoeff") = "MaxObjCoeff"; Attr.attr("MaxQCCoeff") = "MaxQCCoeff"; Attr.attr("MaxQCLCoeff") = "MaxQCLCoeff"; Attr.attr("MaxQCRHS") = "MaxQCRHS"; Attr.attr("MaxQObjCoeff") = "MaxQObjCoeff"; Attr.attr("MaxRHS") = "MaxRHS"; Attr.attr("MaxVio") = "MaxVio"; Attr.attr("MemUsed") = "MemUsed"; Attr.attr("MinBound") = "MinBound"; Attr.attr("MinCoeff") = "MinCoeff"; Attr.attr("MinObjCoeff") = "MinObjCoeff"; Attr.attr("MinQCCoeff") = "MinQCCoeff"; Attr.attr("MinQCLCoeff") = "MinQCLCoeff"; Attr.attr("MinQCRHS") = "MinQCRHS"; Attr.attr("MinQObjCoeff") = "MinQObjCoeff"; Attr.attr("MinRHS") = "MinRHS"; Attr.attr("ModelName") = "ModelName"; Attr.attr("ModelSense") = "ModelSense"; Attr.attr("NLBarIterCount") = "NLBarIterCount"; Attr.attr("NodeCount") = "NodeCount"; Attr.attr("NumBinVars") = "NumBinVars"; Attr.attr("NumConstrs") = "NumConstrs"; Attr.attr("NumGenConstrs") = "NumGenConstrs"; Attr.attr("NumIntVars") = "NumIntVars"; Attr.attr("NumNZs") = "NumNZs"; Attr.attr("NumObj") = "NumObj"; Attr.attr("NumObjPasses") = "NumObjPasses"; Attr.attr("NumPWLObjVars") = "NumPWLObjVars"; Attr.attr("NumQCNZs") = "NumQCNZs"; Attr.attr("NumQConstrs") = "NumQConstrs"; Attr.attr("NumQNZs") = "NumQNZs"; Attr.attr("NumSOS") = "NumSOS"; Attr.attr("NumScenarios") = "NumScenarios"; Attr.attr("NumStart") = "NumStart"; Attr.attr("NumVars") = "NumVars"; Attr.attr("Obj") = "Obj"; Attr.attr("ObjBound") = "ObjBound"; Attr.attr("ObjBoundC") = "ObjBoundC"; Attr.attr("ObjCon") = "ObjCon"; Attr.attr("ObjN") = "ObjN"; Attr.attr("ObjNAbsTol") = "ObjNAbsTol"; Attr.attr("ObjNCon") = "ObjNCon"; Attr.attr("ObjNName") = "ObjNName"; Attr.attr("ObjNPass") = "ObjNPass"; Attr.attr("ObjNPriority") = "ObjNPriority"; Attr.attr("ObjNRelTol") = "ObjNRelTol"; Attr.attr("ObjNVal") = "ObjNVal"; Attr.attr("ObjNWeight") = "ObjNWeight"; Attr.attr("ObjPassNIterCount") = "ObjPassNIterCount"; Attr.attr("ObjPassNMIPGap") = "ObjPassNMIPGap"; Attr.attr("ObjPassNNodeCount") = "ObjPassNNodeCount"; Attr.attr("ObjPassNObjBound") = "ObjPassNObjBound"; Attr.attr("ObjPassNObjVal") = "ObjPassNObjVal"; Attr.attr("ObjPassNOpenNodeCount") = "ObjPassNOpenNodeCount"; Attr.attr("ObjPassNRuntime") = "ObjPassNRuntime"; Attr.attr("ObjPassNStatus") = "ObjPassNStatus"; Attr.attr("ObjPassNWork") = "ObjPassNWork"; Attr.attr("ObjVal") = "ObjVal"; Attr.attr("PDHGIterCount") = "PDHGIterCount"; Attr.attr("PStart") = "PStart"; Attr.attr("PWLObjCvx") = "PWLObjCvx"; Attr.attr("Partition") = "Partition"; Attr.attr("Pi") = "Pi"; Attr.attr("PoolIgnore") = "PoolIgnore"; Attr.attr("PoolNBoundVio") = "PoolNBoundVio"; Attr.attr("PoolNBoundVioIndex") = "PoolNBoundVioIndex"; Attr.attr("PoolNBoundVioSum") = "PoolNBoundVioSum"; Attr.attr("PoolNConstrVio") = "PoolNConstrVio"; Attr.attr("PoolNConstrVioIndex") = "PoolNConstrVioIndex"; Attr.attr("PoolNConstrVioSum") = "PoolNConstrVioSum"; Attr.attr("PoolNIntVio") = "PoolNIntVio"; Attr.attr("PoolNIntVioIndex") = "PoolNIntVioIndex"; Attr.attr("PoolNIntVioSum") = "PoolNIntVioSum"; Attr.attr("PoolNMaxVio") = "PoolNMaxVio"; Attr.attr("PoolNObjVal") = "PoolNObjVal"; Attr.attr("PoolNX") = "PoolNX"; Attr.attr("PoolObjBound") = "PoolObjBound"; Attr.attr("PoolObjVal") = "PoolObjVal"; Attr.attr("PreFixVal") = "PreFixVal"; Attr.attr("QCName") = "QCName"; Attr.attr("QCPi") = "QCPi"; Attr.attr("QCRHS") = "QCRHS"; Attr.attr("QCSense") = "QCSense"; Attr.attr("QCSlack") = "QCSlack"; Attr.attr("QCTag") = "QCTag"; Attr.attr("RC") = "RC"; Attr.attr("RHS") = "RHS"; Attr.attr("Runtime") = "Runtime"; Attr.attr("SALBLow") = "SALBLow"; Attr.attr("SALBUp") = "SALBUp"; Attr.attr("SAObjLow") = "SAObjLow"; Attr.attr("SAObjUp") = "SAObjUp"; Attr.attr("SARHSLow") = "SARHSLow"; Attr.attr("SARHSUp") = "SARHSUp"; Attr.attr("SAUBLow") = "SAUBLow"; Attr.attr("SAUBUp") = "SAUBUp"; Attr.attr("ScenNLB") = "ScenNLB"; Attr.attr("ScenNName") = "ScenNName"; Attr.attr("ScenNObj") = "ScenNObj"; Attr.attr("ScenNObjBound") = "ScenNObjBound"; Attr.attr("ScenNObjVal") = "ScenNObjVal"; Attr.attr("ScenNRHS") = "ScenNRHS"; Attr.attr("ScenNUB") = "ScenNUB"; Attr.attr("ScenNX") = "ScenNX"; Attr.attr("Sense") = "Sense"; Attr.attr("Slack") = "Slack"; Attr.attr("SolCount") = "SolCount"; Attr.attr("Start") = "Start"; Attr.attr("Status") = "Status"; Attr.attr("TuneResultCount") = "TuneResultCount"; Attr.attr("UB") = "UB"; Attr.attr("UnbdRay") = "UnbdRay"; Attr.attr("VBasis") = "VBasis"; Attr.attr("VTag") = "VTag"; Attr.attr("VType") = "VType"; Attr.attr("VarHintPri") = "VarHintPri"; Attr.attr("VarHintVal") = "VarHintVal"; Attr.attr("VarName") = "VarName"; Attr.attr("VarPreStat") = "VarPreStat"; Attr.attr("Work") = "Work"; Attr.attr("X") = "X"; Attr.attr("Xn") = "Xn"; nb::module_ Param = GRB.def_submodule("Param"); Param.attr("AggFill") = "AggFill"; Param.attr("Aggregate") = "Aggregate"; Param.attr("BQPCuts") = "BQPCuts"; Param.attr("BarConvTol") = "BarConvTol"; Param.attr("BarCorrectors") = "BarCorrectors"; Param.attr("BarHomogeneous") = "BarHomogeneous"; Param.attr("BarIterLimit") = "BarIterLimit"; Param.attr("BarOrder") = "BarOrder"; Param.attr("BarQCPConvTol") = "BarQCPConvTol"; Param.attr("BestBdStop") = "BestBdStop"; Param.attr("BestObjStop") = "BestObjStop"; Param.attr("BranchDir") = "BranchDir"; Param.attr("CSAPIAccessID") = "CSAPIAccessID"; Param.attr("CSAPISecret") = "CSAPISecret"; Param.attr("CSAppName") = "CSAppName"; Param.attr("CSAuthToken") = "CSAuthToken"; Param.attr("CSBatchMode") = "CSBatchMode"; Param.attr("CSClientLog") = "CSClientLog"; Param.attr("CSGroup") = "CSGroup"; Param.attr("CSIdleTimeout") = "CSIdleTimeout"; Param.attr("CSManager") = "CSManager"; Param.attr("CSPriority") = "CSPriority"; Param.attr("CSQueueTimeout") = "CSQueueTimeout"; Param.attr("CSRouter") = "CSRouter"; Param.attr("CSTLSInsecure") = "CSTLSInsecure"; Param.attr("CliqueCuts") = "CliqueCuts"; Param.attr("CloudAccessID") = "CloudAccessID"; Param.attr("CloudHost") = "CloudHost"; Param.attr("CloudPool") = "CloudPool"; Param.attr("CloudSecretKey") = "CloudSecretKey"; Param.attr("ComputeServer") = "ComputeServer"; Param.attr("ConcurrentJobs") = "ConcurrentJobs"; Param.attr("ConcurrentMIP") = "ConcurrentMIP"; Param.attr("ConcurrentMethod") = "ConcurrentMethod"; Param.attr("CoverCuts") = "CoverCuts"; Param.attr("Crossover") = "Crossover"; Param.attr("CrossoverBasis") = "CrossoverBasis"; Param.attr("CutAggPasses") = "CutAggPasses"; Param.attr("CutPasses") = "CutPasses"; Param.attr("Cutoff") = "Cutoff"; Param.attr("Cuts") = "Cuts"; Param.attr("DegenMoves") = "DegenMoves"; Param.attr("Disconnected") = "Disconnected"; Param.attr("DisplayInterval") = "DisplayInterval"; Param.attr("DistributedMIPJobs") = "DistributedMIPJobs"; Param.attr("DualImpliedCuts") = "DualImpliedCuts"; Param.attr("DualReductions") = "DualReductions"; Param.attr("FeasRelaxBigM") = "FeasRelaxBigM"; Param.attr("FeasibilityTol") = "FeasibilityTol"; Param.attr("FixVarsInIndicators") = "FixVarsInIndicators"; Param.attr("FlowCoverCuts") = "FlowCoverCuts"; Param.attr("FlowPathCuts") = "FlowPathCuts"; Param.attr("FuncMaxVal") = "FuncMaxVal"; Param.attr("FuncNonlinear") = "FuncNonlinear"; Param.attr("FuncPieceError") = "FuncPieceError"; Param.attr("FuncPieceLength") = "FuncPieceLength"; Param.attr("FuncPieceRatio") = "FuncPieceRatio"; Param.attr("FuncPieces") = "FuncPieces"; Param.attr("GUBCoverCuts") = "GUBCoverCuts"; Param.attr("GomoryPasses") = "GomoryPasses"; Param.attr("Heuristics") = "Heuristics"; Param.attr("IISMethod") = "IISMethod"; Param.attr("IgnoreNames") = "IgnoreNames"; Param.attr("ImpliedCuts") = "ImpliedCuts"; Param.attr("ImproveStartGap") = "ImproveStartGap"; Param.attr("ImproveStartNodes") = "ImproveStartNodes"; Param.attr("ImproveStartTime") = "ImproveStartTime"; Param.attr("ImproveStartWork") = "ImproveStartWork"; Param.attr("InfProofCuts") = "InfProofCuts"; Param.attr("InfUnbdInfo") = "InfUnbdInfo"; Param.attr("InheritParams") = "InheritParams"; Param.attr("IntFeasTol") = "IntFeasTol"; Param.attr("IntegralityFocus") = "IntegralityFocus"; Param.attr("IterationLimit") = "IterationLimit"; Param.attr("JSONSolDetail") = "JSONSolDetail"; Param.attr("JobID") = "JobID"; Param.attr("LPWarmStart") = "LPWarmStart"; Param.attr("LazyConstraints") = "LazyConstraints"; Param.attr("LicenseID") = "LicenseID"; Param.attr("LiftProjectCuts") = "LiftProjectCuts"; Param.attr("LogFile") = "LogFile"; Param.attr("LogToConsole") = "LogToConsole"; Param.attr("MIPFocus") = "MIPFocus"; Param.attr("MIPGap") = "MIPGap"; Param.attr("MIPGapAbs") = "MIPGapAbs"; Param.attr("MIPSepCuts") = "MIPSepCuts"; Param.attr("MIQCPMethod") = "MIQCPMethod"; Param.attr("MIRCuts") = "MIRCuts"; Param.attr("MarkowitzTol") = "MarkowitzTol"; Param.attr("MasterKnapsackCuts") = "MasterKnapsackCuts"; Param.attr("MemLimit") = "MemLimit"; Param.attr("Method") = "Method"; Param.attr("MinRelNodes") = "MinRelNodes"; Param.attr("MixingCuts") = "MixingCuts"; Param.attr("ModKCuts") = "ModKCuts"; Param.attr("MultiObjMethod") = "MultiObjMethod"; Param.attr("MultiObjPre") = "MultiObjPre"; Param.attr("NLBarCFeasTol") = "NLBarCFeasTol"; Param.attr("NLBarDFeasTol") = "NLBarDFeasTol"; Param.attr("NLBarIterLimit") = "NLBarIterLimit"; Param.attr("NLBarPFeasTol") = "NLBarPFeasTol"; Param.attr("NLPHeur") = "NLPHeur"; Param.attr("NetworkAlg") = "NetworkAlg"; Param.attr("NetworkCuts") = "NetworkCuts"; Param.attr("NoRelHeurSolutions") = "NoRelHeurSolutions"; Param.attr("NoRelHeurTime") = "NoRelHeurTime"; Param.attr("NoRelHeurWork") = "NoRelHeurWork"; Param.attr("NodeLimit") = "NodeLimit"; Param.attr("NodeMethod") = "NodeMethod"; Param.attr("NodefileDir") = "NodefileDir"; Param.attr("NodefileStart") = "NodefileStart"; Param.attr("NonConvex") = "NonConvex"; Param.attr("NormAdjust") = "NormAdjust"; Param.attr("NumericFocus") = "NumericFocus"; Param.attr("OBBT") = "OBBT"; Param.attr("ObjNumber") = "ObjNumber"; Param.attr("ObjPassNumber") = "ObjPassNumber"; Param.attr("ObjScale") = "ObjScale"; Param.attr("OptimalityTarget") = "OptimalityTarget"; Param.attr("OptimalityTol") = "OptimalityTol"; Param.attr("OutputFlag") = "OutputFlag"; Param.attr("PDHGAbsTol") = "PDHGAbsTol"; Param.attr("PDHGConvTol") = "PDHGConvTol"; Param.attr("PDHGGPU") = "PDHGGPU"; Param.attr("PDHGIterLimit") = "PDHGIterLimit"; Param.attr("PDHGRelTol") = "PDHGRelTol"; Param.attr("PSDCuts") = "PSDCuts"; Param.attr("PSDTol") = "PSDTol"; Param.attr("PartitionPlace") = "PartitionPlace"; Param.attr("PerturbValue") = "PerturbValue"; Param.attr("PoolGap") = "PoolGap"; Param.attr("PoolGapAbs") = "PoolGapAbs"; Param.attr("PoolSearchMode") = "PoolSearchMode"; Param.attr("PoolSolutions") = "PoolSolutions"; Param.attr("PreCrush") = "PreCrush"; Param.attr("PreDepRow") = "PreDepRow"; Param.attr("PreDual") = "PreDual"; Param.attr("PreMIQCPForm") = "PreMIQCPForm"; Param.attr("PrePasses") = "PrePasses"; Param.attr("PreQLinearize") = "PreQLinearize"; Param.attr("PreSOS1BigM") = "PreSOS1BigM"; Param.attr("PreSOS1Encoding") = "PreSOS1Encoding"; Param.attr("PreSOS2BigM") = "PreSOS2BigM"; Param.attr("PreSOS2Encoding") = "PreSOS2Encoding"; Param.attr("PreSparsify") = "PreSparsify"; Param.attr("Presolve") = "Presolve"; Param.attr("ProjImpliedCuts") = "ProjImpliedCuts"; Param.attr("PumpPasses") = "PumpPasses"; Param.attr("QCPDual") = "QCPDual"; Param.attr("Quad") = "Quad"; Param.attr("RINS") = "RINS"; Param.attr("RLTCuts") = "RLTCuts"; Param.attr("Record") = "Record"; Param.attr("RelaxLiftCuts") = "RelaxLiftCuts"; Param.attr("ResultFile") = "ResultFile"; Param.attr("ScaleFlag") = "ScaleFlag"; Param.attr("ScenarioNumber") = "ScenarioNumber"; Param.attr("Seed") = "Seed"; Param.attr("ServerPassword") = "ServerPassword"; Param.attr("ServerTimeout") = "ServerTimeout"; Param.attr("SiftMethod") = "SiftMethod"; Param.attr("Sifting") = "Sifting"; Param.attr("SimplexPricing") = "SimplexPricing"; Param.attr("SoftMemLimit") = "SoftMemLimit"; Param.attr("SolFiles") = "SolFiles"; Param.attr("SolutionLimit") = "SolutionLimit"; Param.attr("SolutionNumber") = "SolutionNumber"; Param.attr("SolutionTarget") = "SolutionTarget"; Param.attr("StartNodeLimit") = "StartNodeLimit"; Param.attr("StartNumber") = "StartNumber"; Param.attr("StartTimeLimit") = "StartTimeLimit"; Param.attr("StartWorkLimit") = "StartWorkLimit"; Param.attr("StrongCGCuts") = "StrongCGCuts"; Param.attr("SubMIPCuts") = "SubMIPCuts"; Param.attr("SubMIPNodes") = "SubMIPNodes"; Param.attr("Symmetry") = "Symmetry"; Param.attr("TSPort") = "TSPort"; Param.attr("ThreadLimit") = "ThreadLimit"; Param.attr("Threads") = "Threads"; Param.attr("TimeLimit") = "TimeLimit"; Param.attr("TokenServer") = "TokenServer"; Param.attr("TuneCleanup") = "TuneCleanup"; Param.attr("TuneCriterion") = "TuneCriterion"; Param.attr("TuneDynamicJobs") = "TuneDynamicJobs"; Param.attr("TuneJobs") = "TuneJobs"; Param.attr("TuneMetric") = "TuneMetric"; Param.attr("TuneOutput") = "TuneOutput"; Param.attr("TuneResults") = "TuneResults"; Param.attr("TuneTargetMIPGap") = "TuneTargetMIPGap"; Param.attr("TuneTargetTime") = "TuneTargetTime"; Param.attr("TuneTimeLimit") = "TuneTimeLimit"; Param.attr("TuneTrials") = "TuneTrials"; Param.attr("UpdateMode") = "UpdateMode"; Param.attr("UserName") = "UserName"; Param.attr("VarBranch") = "VarBranch"; Param.attr("WLSAccessID") = "WLSAccessID"; Param.attr("WLSConfig") = "WLSConfig"; Param.attr("WLSProxy") = "WLSProxy"; Param.attr("WLSSecret") = "WLSSecret"; Param.attr("WLSToken") = "WLSToken"; Param.attr("WLSTokenDuration") = "WLSTokenDuration"; Param.attr("WLSTokenRefresh") = "WLSTokenRefresh"; Param.attr("WorkLimit") = "WorkLimit"; Param.attr("WorkerPassword") = "WorkerPassword"; Param.attr("WorkerPool") = "WorkerPool"; Param.attr("ZeroHalfCuts") = "ZeroHalfCuts"; Param.attr("ZeroObjNodes") = "ZeroObjNodes"; nb::module_ Callback = GRB.def_submodule("Callback"); Callback.attr("BARRIER") = 7; Callback.attr("BARRIER_COMPL") = 7006; Callback.attr("BARRIER_DUALINF") = 7005; Callback.attr("BARRIER_DUALOBJ") = 7003; Callback.attr("BARRIER_ITRCNT") = 7001; Callback.attr("BARRIER_PRIMINF") = 7004; Callback.attr("BARRIER_PRIMOBJ") = 7002; Callback.attr("IIS") = 9; Callback.attr("IIS_BOUNDGUESS") = 9006; Callback.attr("IIS_BOUNDMAX") = 9005; Callback.attr("IIS_BOUNDMIN") = 9004; Callback.attr("IIS_CONSTRGUESS") = 9003; Callback.attr("IIS_CONSTRMAX") = 9002; Callback.attr("IIS_CONSTRMIN") = 9001; Callback.attr("MAXMEMUSED") = 6005; Callback.attr("MEMUSED") = 6004; Callback.attr("MESSAGE") = 6; Callback.attr("MIP") = 3; Callback.attr("MIPNODE") = 5; Callback.attr("MIPNODE_BRVAR") = 5007; Callback.attr("MIPNODE_NODCNT") = 5005; Callback.attr("MIPNODE_OBJBND") = 5004; Callback.attr("MIPNODE_OBJBST") = 5003; Callback.attr("MIPNODE_OPENSCENARIOS") = 5008; Callback.attr("MIPNODE_PHASE") = 5009; Callback.attr("MIPNODE_REL") = 5002; Callback.attr("MIPNODE_SOLCNT") = 5006; Callback.attr("MIPNODE_STATUS") = 5001; Callback.attr("MIPSOL") = 4; Callback.attr("MIPSOL_NODCNT") = 4005; Callback.attr("MIPSOL_OBJ") = 4002; Callback.attr("MIPSOL_OBJBND") = 4004; Callback.attr("MIPSOL_OBJBST") = 4003; Callback.attr("MIPSOL_OPENSCENARIOS") = 4007; Callback.attr("MIPSOL_PHASE") = 4008; Callback.attr("MIPSOL_SOL") = 4001; Callback.attr("MIPSOL_SOLCNT") = 4006; Callback.attr("MIP_CUTCNT") = 3004; Callback.attr("MIP_ITRCNT") = 3006; Callback.attr("MIP_NODCNT") = 3002; Callback.attr("MIP_NODLFT") = 3005; Callback.attr("MIP_OBJBND") = 3001; Callback.attr("MIP_OBJBST") = 3000; Callback.attr("MIP_OPENSCENARIOS") = 3007; Callback.attr("MIP_PHASE") = 3008; Callback.attr("MIP_SOLCNT") = 3003; Callback.attr("MSG_STRING") = 6001; Callback.attr("MULTIOBJ") = 8; Callback.attr("MULTIOBJ_ITRCNT") = 8004; Callback.attr("MULTIOBJ_MIPGAP") = 8008; Callback.attr("MULTIOBJ_NODCNT") = 8009; Callback.attr("MULTIOBJ_NODLFT") = 8010; Callback.attr("MULTIOBJ_OBJBND") = 8006; Callback.attr("MULTIOBJ_OBJBST") = 8005; Callback.attr("MULTIOBJ_OBJCNT") = 8001; Callback.attr("MULTIOBJ_RUNTIME") = 8011; Callback.attr("MULTIOBJ_SOL") = 8003; Callback.attr("MULTIOBJ_SOLCNT") = 8002; Callback.attr("MULTIOBJ_STATUS") = 8007; Callback.attr("MULTIOBJ_WORK") = 8012; Callback.attr("NLBAR") = 11; Callback.attr("NLBAR_COMPL") = 11005; Callback.attr("NLBAR_DUALINF") = 11004; Callback.attr("NLBAR_ITRCNT") = 11001; Callback.attr("NLBAR_PRIMINF") = 11003; Callback.attr("NLBAR_PRIMOBJ") = 11002; Callback.attr("PDHG") = 10; Callback.attr("PDHG_COMPL") = 10006; Callback.attr("PDHG_DUALINF") = 10005; Callback.attr("PDHG_DUALOBJ") = 10003; Callback.attr("PDHG_ITRCNT") = 10001; Callback.attr("PDHG_PRIMINF") = 10004; Callback.attr("PDHG_PRIMOBJ") = 10002; Callback.attr("POLLING") = 0; Callback.attr("PRESOLVE") = 1; Callback.attr("PRE_BNDCHG") = 1003; Callback.attr("PRE_COECHG") = 1004; Callback.attr("PRE_COLDEL") = 1000; Callback.attr("PRE_ROWDEL") = 1001; Callback.attr("PRE_SENCHG") = 1002; Callback.attr("RUNTIME") = 6002; Callback.attr("SIMPLEX") = 2; Callback.attr("SPX_DUALINF") = 2003; Callback.attr("SPX_ISPERT") = 2004; Callback.attr("SPX_ITRCNT") = 2000; Callback.attr("SPX_OBJVAL") = 2001; Callback.attr("SPX_PRIMINF") = 2002; Callback.attr("WORK") = 6003; } ================================================ FILE: lib/highs_model.cpp ================================================ #include "pyoptinterface/highs_model.hpp" #include "fmt/core.h" namespace highs { #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } } // namespace highs static void check_error(HighsInt error) { if (error == kHighsStatusError) { std::string errmsg = fmt::format( "Encountered an error in HiGHS (Status {}). Check the log for details.", error); throw std::runtime_error(errmsg); } } static HighsInt highs_vtype(VariableDomain domain) { switch (domain) { case VariableDomain::Continuous: return kHighsVarTypeContinuous; case VariableDomain::Integer: case VariableDomain::Binary: return kHighsVarTypeInteger; case VariableDomain::SemiContinuous: return kHighsVarTypeSemiContinuous; default: throw std::runtime_error("Unknown variable domain"); } } static VariableDomain highs_vtype_to_domain(HighsInt vtype) { switch (vtype) { case kHighsVarTypeContinuous: return VariableDomain::Continuous; case kHighsVarTypeInteger: return VariableDomain::Integer; case kHighsVarTypeSemiContinuous: return VariableDomain::SemiContinuous; default: throw std::runtime_error("Unknown variable domain"); } } static HighsInt highs_obj_sense(ObjectiveSense sense) { switch (sense) { case ObjectiveSense::Minimize: return kHighsObjSenseMinimize; case ObjectiveSense::Maximize: return kHighsObjSenseMaximize; default: throw std::runtime_error("Unknown objective sense"); } } POIHighsModel::POIHighsModel() { init(); } void POIHighsModel::init() { if (!highs::is_library_loaded()) { throw std::runtime_error("HiGHS library is not loaded"); } void *model = highs::Highs_create(); m_model = std::unique_ptr(model); } void POIHighsModel::close() { m_model.reset(); } void POIHighsModel::write(const std::string &filename, bool pretty) { bool is_solution = false; if (filename.ends_with(".sol")) { is_solution = true; } HighsInt error; if (is_solution) { if (pretty) error = highs::Highs_writeSolutionPretty(m_model.get(), filename.c_str()); else error = highs::Highs_writeSolution(m_model.get(), filename.c_str()); } else { error = highs::Highs_writeModel(m_model.get(), filename.c_str()); } check_error(error); } VariableIndex POIHighsModel::add_variable(VariableDomain domain, double lb, double ub, const char *name) { IndexT index = m_variable_index.add_index(); VariableIndex variable(index); if (domain == VariableDomain::Binary) { lb = 0.0; ub = 1.0; } auto error = highs::Highs_addCol(m_model.get(), 0.0, lb, ub, 0, nullptr, nullptr); check_error(error); auto column = m_n_variables; if (domain != VariableDomain::Continuous) { auto vtype = highs_vtype(domain); if (domain == VariableDomain::Binary) { binary_variables.insert(index); } error = highs::Highs_changeColIntegrality(m_model.get(), column, vtype); check_error(error); } if (name != nullptr && name[0] == '\0') { name = nullptr; } if (name) { error = highs::Highs_passColName(m_model.get(), column, name); check_error(error); } m_n_variables++; return variable; } void POIHighsModel::delete_variable(const VariableIndex &variable) { if (!is_variable_active(variable)) { throw std::runtime_error("Variable does not exist"); } int variable_column = _variable_index(variable); auto error = highs::Highs_deleteColsBySet(m_model.get(), 1, &variable_column); check_error(error); m_variable_index.delete_index(variable.index); binary_variables.erase(variable.index); m_n_variables--; } void POIHighsModel::delete_variables(const Vector &variables) { int n_variables = variables.size(); if (n_variables == 0) return; std::vector columns; columns.reserve(n_variables); for (int i = 0; i < n_variables; i++) { if (!is_variable_active(variables[i])) { continue; } auto column = _variable_index(variables[i]); columns.push_back(column); } int error = highs::Highs_deleteColsBySet(m_model.get(), columns.size(), columns.data()); check_error(error); for (int i = 0; i < n_variables; i++) { m_variable_index.delete_index(variables[i].index); } m_n_variables -= columns.size(); } bool POIHighsModel::is_variable_active(const VariableIndex &variable) { return m_variable_index.has_index(variable.index); } double POIHighsModel::get_variable_value(const VariableIndex &variable) { auto column = _checked_variable_index(variable); if (m_solution.primal_solution_status != kHighsSolutionStatusNone) { return m_solution.colvalue[column]; } throw std::runtime_error("No solution available"); } std::string POIHighsModel::pprint_variable(const VariableIndex &variable) { return get_variable_name(variable); } void POIHighsModel::set_variable_bounds(const VariableIndex &variable, double lb, double ub) { auto column = _checked_variable_index(variable); auto error = highs::Highs_changeColsBoundsBySet(m_model.get(), 1, &column, &lb, &ub); check_error(error); } ConstraintIndex POIHighsModel::add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { double lb = -kHighsInf; double ub = kHighsInf; switch (sense) { case ConstraintSense::LessEqual: ub = rhs; break; case ConstraintSense::GreaterEqual: lb = rhs; break; case ConstraintSense::Equal: lb = rhs; ub = rhs; break; } auto con = add_linear_constraint(function, std::make_tuple(lb, ub), name); return con; } ConstraintIndex POIHighsModel::add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name) { IndexT index = m_linear_constraint_index.add_index(); ConstraintIndex constraint(ConstraintType::Linear, index); AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); HighsInt numnz = ptr_form.numnz; HighsInt *cind = ptr_form.index; double *cval = ptr_form.value; double lb = std::get<0>(interval); double ub = std::get<1>(interval); if (function.constant.has_value()) { lb -= function.constant.value(); ub -= function.constant.value(); } auto error = highs::Highs_addRow(m_model.get(), lb, ub, numnz, cind, cval); check_error(error); HighsInt row = m_n_constraints; if (name != nullptr && name[0] == '\0') { name = nullptr; } if (name) { error = highs::Highs_passRowName(m_model.get(), row, name); check_error(error); } m_n_constraints++; return constraint; } ConstraintIndex POIHighsModel::add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { throw std::runtime_error("HIGHS does not support quadratic constraint!"); } void POIHighsModel::delete_constraint(const ConstraintIndex &constraint) { if (!is_constraint_active(constraint)) { throw std::runtime_error("Constraint does not exist"); } int constraint_row = _constraint_index(constraint); auto error = highs::Highs_deleteRowsBySet(m_model.get(), 1, &constraint_row); check_error(error); m_linear_constraint_index.delete_index(constraint.index); m_n_constraints--; } bool POIHighsModel::is_constraint_active(const ConstraintIndex &constraint) { return m_linear_constraint_index.has_index(constraint.index); } void POIHighsModel::_set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic) { HighsInt error; HighsInt n_variables = m_n_variables; if (clear_quadratic) { // First delete all quadratic terms std::vector colstarts(n_variables, 0); error = highs::Highs_passHessian(m_model.get(), n_variables, 0, kHighsHessianFormatTriangular, colstarts.data(), nullptr, nullptr); check_error(error); } // Set Obj attribute of each variable std::vector obj_v(n_variables, 0.0); int numnz = function.size(); for (int i = 0; i < numnz; i++) { auto column = _variable_index(function.variables[i]); if (column < 0) { throw std::runtime_error("Variable does not exist"); } obj_v[column] = function.coefficients[i]; } error = highs::Highs_changeColsCostByRange(m_model.get(), 0, n_variables - 1, obj_v.data()); check_error(error); error = highs::Highs_changeObjectiveOffset(m_model.get(), function.constant.value_or(0.0)); check_error(error); HighsInt obj_sense = highs_obj_sense(sense); error = highs::Highs_changeObjectiveSense(m_model.get(), obj_sense); check_error(error); } void POIHighsModel::set_objective(const ScalarAffineFunction &function, ObjectiveSense sense) { _set_affine_objective(function, sense, true); } void POIHighsModel::set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense) { HighsInt error; // Add quadratic term int numqnz = function.size(); HighsInt n_variables = m_n_variables; if (numqnz > 0) { CSCMatrix csc; csc.make(this, function, n_variables, HessianTriangular::Lower); // Highs optimizes 0.5 * x' * Q * x // so the coefficient must be multiplied by 2.0 for (auto &v : csc.values_CSC) { v *= 2.0; } error = highs::Highs_passHessian(m_model.get(), n_variables, numqnz, kHighsHessianFormatTriangular, csc.colStarts_CSC.data(), csc.rows_CSC.data(), csc.values_CSC.data()); check_error(error); } else { std::vector colstarts(n_variables, 0); error = highs::Highs_passHessian(m_model.get(), n_variables, 0, kHighsHessianFormatTriangular, colstarts.data(), nullptr, nullptr); check_error(error); } // Affine part const auto &affine_part = function.affine_part; if (affine_part) { const auto &affine_function = affine_part.value(); _set_affine_objective(affine_function, sense, false); } else { ScalarAffineFunction zero; _set_affine_objective(zero, sense, false); } } void POIHighsModel::set_objective(const ExprBuilder &function, ObjectiveSense sense) { auto deg = function.degree(); if (deg <= 1) { ScalarAffineFunction f(function); set_objective(f, sense); } else if (deg == 2) { ScalarQuadraticFunction f(function); set_objective(f, sense); } else { throw std::runtime_error("Objective must be linear or quadratic"); } } void POIHighsModel::optimize() { HighsInt error = highs::Highs_run(m_model.get()); POIHighsSolution &x = m_solution; x.status = error == kHighsStatusError ? HighsSolutionStatus::OPTIMIZE_ERROR : HighsSolutionStatus::OPTIMIZE_OK; void *model = m_model.get(); x.primal_solution_status = kHighsSolutionStatusNone; x.dual_solution_status = kHighsSolutionStatusNone; x.has_dual_ray = false; x.has_primal_ray = false; auto numCols = m_n_variables; auto numRows = m_n_constraints; x.model_status = highs::Highs_getModelStatus(model); HighsInt status; HighsInt *statusP = &status; if (x.model_status == kHighsModelStatusInfeasible) { x.dual_ray.resize(numRows); error = highs::Highs_getDualRay(model, statusP, x.dual_ray.data()); x.has_dual_ray = (error == kHighsStatusOk) && (*statusP == 1); } else if (x.model_status == kHighsModelStatusUnbounded) { x.primal_ray.resize(numCols); error = highs::Highs_getPrimalRay(model, statusP, x.primal_ray.data()); x.has_primal_ray = (error == kHighsStatusOk) && (*statusP == 1); } else { highs::Highs_getIntInfoValue(model, "primal_solution_status", statusP); x.primal_solution_status = *statusP; highs::Highs_getIntInfoValue(model, "dual_solution_status", statusP); x.dual_solution_status = *statusP; if (x.primal_solution_status != kHighsSolutionStatusNone) { x.colvalue.resize(numCols); x.coldual.resize(numCols); x.rowvalue.resize(numRows); x.rowdual.resize(numRows); highs::Highs_getSolution(model, x.colvalue.data(), x.coldual.data(), x.rowvalue.data(), x.rowdual.data()); // HighsModel &h_model = ((Highs *)m_model.get())->model_; // auto &hessian = h_model.hessian_; auto hessian_nz = highs::Highs_getHessianNumNz(model); if (hessian_nz == 0) { // No basis is present in a QP. x.colstatus.resize(numCols); x.rowstatus.resize(numRows); highs::Highs_getBasis(model, x.colstatus.data(), x.rowstatus.data()); } } } } void *POIHighsModel::get_raw_model() { return m_model.get(); } std::string POIHighsModel::version_string() { auto version = highs::Highs_version(); return version; } double POIHighsModel::getruntime() { double runtime = highs::Highs_getRunTime(m_model.get()); return runtime; } int POIHighsModel::getnumrow() { return highs::Highs_getNumRow(m_model.get()); } int POIHighsModel::getnumcol() { return highs::Highs_getNumCol(m_model.get()); } int POIHighsModel::raw_option_type(const char *param_name) { HighsInt retval; auto error = highs::Highs_getOptionType(m_model.get(), param_name, &retval); check_error(error); return retval; } void POIHighsModel::set_raw_option_bool(const char *param_name, bool value) { auto error = highs::Highs_setBoolOptionValue(m_model.get(), param_name, value); check_error(error); } void POIHighsModel::set_raw_option_int(const char *param_name, int value) { auto error = highs::Highs_setIntOptionValue(m_model.get(), param_name, value); check_error(error); } void POIHighsModel::set_raw_option_double(const char *param_name, double value) { auto error = highs::Highs_setDoubleOptionValue(m_model.get(), param_name, value); check_error(error); } void POIHighsModel::set_raw_option_string(const char *param_name, const char *value) { auto error = highs::Highs_setStringOptionValue(m_model.get(), param_name, value); check_error(error); } bool POIHighsModel::get_raw_option_bool(const char *param_name) { HighsInt retval; auto error = highs::Highs_getBoolOptionValue(m_model.get(), param_name, &retval); check_error(error); return retval; } int POIHighsModel::get_raw_option_int(const char *param_name) { HighsInt retval; auto error = highs::Highs_getIntOptionValue(m_model.get(), param_name, &retval); check_error(error); return retval; } double POIHighsModel::get_raw_option_double(const char *param_name) { double retval; auto error = highs::Highs_getDoubleOptionValue(m_model.get(), param_name, &retval); check_error(error); return retval; } std::string POIHighsModel::get_raw_option_string(const char *param_name) { char retval[kHighsMaximumStringLength]; auto error = highs::Highs_getStringOptionValue(m_model.get(), param_name, retval); check_error(error); return std::string(retval); } int POIHighsModel::raw_info_type(const char *info_name) { HighsInt retval; auto error = highs::Highs_getInfoType(m_model.get(), info_name, &retval); check_error(error); return retval; } int POIHighsModel::get_raw_info_int(const char *info_name) { HighsInt retval; auto error = highs::Highs_getIntInfoValue(m_model.get(), info_name, &retval); check_error(error); return retval; } std::int64_t POIHighsModel::get_raw_info_int64(const char *info_name) { int64_t retval; auto error = highs::Highs_getInt64InfoValue(m_model.get(), info_name, &retval); check_error(error); return retval; } double POIHighsModel::get_raw_info_double(const char *info_name) { double retval; auto error = highs::Highs_getDoubleInfoValue(m_model.get(), info_name, &retval); check_error(error); return retval; } std::string POIHighsModel::get_variable_name(const VariableIndex &variable) { auto column = _checked_variable_index(variable); char name[kHighsMaximumStringLength]; auto error = highs::Highs_getColName(m_model.get(), column, name); check_error(error); return std::string(name); } void POIHighsModel::set_variable_name(const VariableIndex &variable, const char *name) { auto column = _checked_variable_index(variable); auto error = highs::Highs_passColName(m_model.get(), column, name); check_error(error); } VariableDomain POIHighsModel::get_variable_type(const VariableIndex &variable) { if (binary_variables.contains(variable.index)) { return VariableDomain::Binary; } auto column = _checked_variable_index(variable); HighsInt vtype; auto error = highs::Highs_getColIntegrality(m_model.get(), column, &vtype); check_error(error); return highs_vtype_to_domain(vtype); } void POIHighsModel::set_variable_type(const VariableIndex &variable, VariableDomain domain) { auto vtype = highs_vtype(domain); auto column = _checked_variable_index(variable); auto error = highs::Highs_changeColIntegrality(m_model.get(), column, vtype); check_error(error); if (domain == VariableDomain::Binary) { double lb = 0.0; double ub = 1.0; binary_variables.insert(variable.index); error = highs::Highs_changeColsBoundsBySet(m_model.get(), 1, &column, &lb, &ub); check_error(error); } else { binary_variables.erase(variable.index); } } double POIHighsModel::get_variable_lower_bound(const VariableIndex &variable) { auto column = _checked_variable_index(variable); HighsInt numcol, nz; double c, lb, ub; auto error = highs::Highs_getColsBySet(m_model.get(), 1, &column, &numcol, &c, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); return lb; } double POIHighsModel::get_variable_upper_bound(const VariableIndex &variable) { auto column = _checked_variable_index(variable); HighsInt numcol, nz; double c, lb, ub; auto error = highs::Highs_getColsBySet(m_model.get(), 1, &column, &numcol, &c, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); return ub; } void POIHighsModel::set_variable_lower_bound(const VariableIndex &variable, double lb) { double new_lb = lb; auto column = _checked_variable_index(variable); HighsInt numcol, nz; double c, ub; auto error = highs::Highs_getColsBySet(m_model.get(), 1, &column, &numcol, &c, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); error = highs::Highs_changeColsBoundsBySet(m_model.get(), 1, &column, &new_lb, &ub); check_error(error); } void POIHighsModel::set_variable_upper_bound(const VariableIndex &variable, double ub) { double new_ub = ub; auto column = _checked_variable_index(variable); HighsInt numcol, nz; double c, lb; auto error = highs::Highs_getColsBySet(m_model.get(), 1, &column, &numcol, &c, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); error = highs::Highs_changeColsBoundsBySet(m_model.get(), 1, &column, &lb, &new_ub); check_error(error); } std::string POIHighsModel::get_constraint_name(const ConstraintIndex &constraint) { auto row = _checked_constraint_index(constraint); char name[kHighsMaximumStringLength]; auto error = highs::Highs_getRowName(m_model.get(), row, name); check_error(error); return std::string(name); } void POIHighsModel::set_constraint_name(const ConstraintIndex &constraint, const char *name) { auto row = _checked_constraint_index(constraint); auto error = highs::Highs_passRowName(m_model.get(), row, name); check_error(error); } double POIHighsModel::get_constraint_primal(const ConstraintIndex &constraint) { auto row = _checked_constraint_index(constraint); if (m_solution.primal_solution_status != kHighsSolutionStatusNone) { return m_solution.rowvalue[row]; } throw std::runtime_error("No solution available"); } double POIHighsModel::get_constraint_dual(const ConstraintIndex &constraint) { auto row = _checked_constraint_index(constraint); if (m_solution.primal_solution_status != kHighsSolutionStatusNone) { return m_solution.rowdual[row]; } throw std::runtime_error("No solution available"); } double POIHighsModel::get_variable_dual(const VariableIndex &variable) { auto col = _checked_variable_index(variable); if (m_solution.primal_solution_status != kHighsSolutionStatusNone) { return m_solution.coldual[col]; } throw std::runtime_error("No solution available"); } ObjectiveSense POIHighsModel::get_obj_sense() { HighsInt obj_sense; auto error = highs::Highs_getObjectiveSense(m_model.get(), &obj_sense); check_error(error); return obj_sense == kHighsObjSenseMinimize ? ObjectiveSense::Minimize : ObjectiveSense::Maximize; } void POIHighsModel::set_obj_sense(ObjectiveSense sense) { auto obj_sense = highs_obj_sense(sense); auto error = highs::Highs_changeObjectiveSense(m_model.get(), obj_sense); check_error(error); } double POIHighsModel::get_obj_value() { double obj = highs::Highs_getObjectiveValue(m_model.get()); return obj; } HighsInt POIHighsModel::_variable_index(const VariableIndex &variable) { return m_variable_index.get_index(variable.index); } HighsInt POIHighsModel::_checked_variable_index(const VariableIndex &variable) { HighsInt column = _variable_index(variable); if (column < 0) { throw std::runtime_error("Variable does not exist"); } return column; } HighsInt POIHighsModel::_constraint_index(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: return m_linear_constraint_index.get_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } HighsInt POIHighsModel::_checked_constraint_index(const ConstraintIndex &constraint) { HighsInt row = _constraint_index(constraint); if (row < 0) { throw std::runtime_error("Constraint does not exist"); } return row; } void POIHighsModel::set_primal_start(const Vector &variables, const Vector &values) { if (variables.size() != values.size()) { throw std::runtime_error("Number of variables and values do not match"); } int numnz = variables.size(); if (numnz == 0) return; auto numcol = m_n_variables; if (numcol == 0) return; HighsInt _numcol, nz; std::vector c(numcol), lb(numcol), ub(numcol); auto error = highs::Highs_getColsByRange(m_model.get(), 0, numcol - 1, &_numcol, c.data(), lb.data(), ub.data(), &nz, nullptr, nullptr, nullptr); std::vector vals(numcol); for (int i = 0; i < numcol; i++) { double L = lb[i]; double U = ub[i]; bool L_inf = L < -kHighsInf + 1.0; bool U_inf = U > kHighsInf - 1.0; double initial = L; if (L_inf) { if (U_inf) { initial = 0.0; } else { initial = U; } } vals[i] = initial; } for (auto i = 0; i < variables.size(); i++) { auto column = _checked_variable_index(variables[i]); vals[column] = values[i]; } error = highs::Highs_setSolution(m_model.get(), vals.data(), nullptr, nullptr, nullptr); check_error(error); } double POIHighsModel::get_normalized_rhs(const ConstraintIndex &constraint) { auto row = _checked_constraint_index(constraint); HighsInt numrow, nz; double ub, lb; auto error = highs::Highs_getRowsBySet(m_model.get(), 1, &row, &numrow, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); bool lb_inf = lb < -kHighsInf + 1.0; bool ub_inf = ub > kHighsInf - 1.0; if (!lb_inf) return lb; if (!ub_inf) return ub; throw std::runtime_error("Constraint has no finite bound"); } void POIHighsModel::set_normalized_rhs(const ConstraintIndex &constraint, double value) { auto row = _checked_constraint_index(constraint); HighsInt numrow, nz; double ub, lb; auto error = highs::Highs_getRowsBySet(m_model.get(), 1, &row, &numrow, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); bool lb_inf = lb < -kHighsInf + 1.0; bool ub_inf = ub > kHighsInf - 1.0; if (!lb_inf) lb = value; if (!ub_inf) ub = value; if (lb_inf && ub_inf) { throw std::runtime_error("Constraint has no finite bound"); } error = highs::Highs_changeRowsBoundsBySet(m_model.get(), 1, &row, &lb, &ub); check_error(error); } double POIHighsModel::get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable) { auto row = _checked_constraint_index(constraint); HighsInt numrow, nz; double ub, lb; auto error = highs::Highs_getRowsBySet(m_model.get(), 1, &row, &numrow, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); std::vector matrix_start(nz); std::vector matrix_index(nz); std::vector matrix_value(nz); error = highs::Highs_getRowsBySet(m_model.get(), 1, &row, &numrow, &lb, &ub, &nz, matrix_start.data(), matrix_index.data(), matrix_value.data()); check_error(error); double coef = 0.0; auto col = _checked_variable_index(variable); for (auto i = 0; i < nz; i++) { if (matrix_index[i] == col) { coef = matrix_value[i]; break; } } return coef; } void POIHighsModel::set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value) { auto row = _checked_constraint_index(constraint); auto col = _checked_variable_index(variable); auto error = highs::Highs_changeCoeff(m_model.get(), row, col, value); check_error(error); } double POIHighsModel::get_objective_coefficient(const VariableIndex &variable) { auto column = _checked_variable_index(variable); HighsInt numcol, nz; double c, lb, ub; auto error = highs::Highs_getColsBySet(m_model.get(), 1, &column, &numcol, &c, &lb, &ub, &nz, nullptr, nullptr, nullptr); check_error(error); return c; } void POIHighsModel::set_objective_coefficient(const VariableIndex &variable, double value) { auto column = _checked_variable_index(variable); auto error = highs::Highs_changeColCost(m_model.get(), column, value); check_error(error); } ================================================ FILE: lib/highs_model_ext.cpp ================================================ #include #include #include #include #include "pyoptinterface/highs_model.hpp" namespace nb = nanobind; extern void bind_highs_constants(nb::module_ &m); NB_MODULE(highs_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.def("is_library_loaded", &highs::is_library_loaded); m.def("load_library", &highs::load_library); bind_highs_constants(m); nb::enum_(m, "HighsSolutionStatus") .value("OPTIMIZE_NOT_CALLED", HighsSolutionStatus::OPTIMIZE_NOT_CALLED) .value("OPTIMIZE_OK", HighsSolutionStatus::OPTIMIZE_OK) .value("OPTIMIZE_ERROR", HighsSolutionStatus::OPTIMIZE_ERROR); nb::class_(m, "HighsSolution") .def_ro("status", &POIHighsSolution::status) .def_ro("model_status", &POIHighsSolution::model_status) .def_ro("primal_solution_status", &POIHighsSolution::primal_solution_status) .def_ro("dual_solution_status", &POIHighsSolution::dual_solution_status) .def_ro("has_primal_ray", &POIHighsSolution::has_primal_ray) .def_ro("has_dual_ray", &POIHighsSolution::has_dual_ray); using HighsModel = POIHighsModel; #define BIND_F(f) .def(#f, &HighsModel::f) nb::class_(m, "RawModel") .def(nb::init<>()) .def_ro("m_n_variables", &HighsModel::m_n_variables) .def_ro("m_n_constraints", &HighsModel::m_n_constraints) // clang-format off BIND_F(init) BIND_F(close) // clang-format on .def("write", &HighsModel::write, nb::arg("filename"), nb::arg("pretty") = false) .def_ro("solution", &HighsModel::m_solution) .def("add_variable", &HighsModel::add_variable, nb::arg("domain") = VariableDomain::Continuous, nb::arg("lb") = -kHighsInf, nb::arg("ub") = kHighsInf, nb::arg("name") = "") // clang-format off BIND_F(delete_variable) BIND_F(delete_variables) BIND_F(is_variable_active) // clang-format on .def("set_variable_bounds", &HighsModel::set_variable_bounds, nb::arg("variable"), nb::arg("lb"), nb::arg("ub")) .def("get_value", nb::overload_cast(&HighsModel::get_variable_value)) .def("get_value", nb::overload_cast(&HighsModel::get_expression_value)) .def("get_value", nb::overload_cast(&HighsModel::get_expression_value)) .def("get_value", nb::overload_cast(&HighsModel::get_expression_value)) .def("pprint", &HighsModel::pprint_variable) .def("pprint", nb::overload_cast(&HighsModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def( "pprint", nb::overload_cast(&HighsModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&HighsModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("_add_linear_constraint", nb::overload_cast( &HighsModel::add_linear_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", nb::overload_cast &, const char *>(&HighsModel::add_linear_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &HighsModel::add_linear_constraint_from_var, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &HighsModel::add_linear_interval_constraint_from_var, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &HighsModel::add_linear_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &HighsModel::add_linear_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("delete_constraint", &HighsModel::delete_constraint) .def("is_constraint_active", &HighsModel::is_constraint_active) .def("set_objective", nb::overload_cast( &HighsModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &HighsModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&HighsModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &HighsModel::set_objective_as_variable), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&HighsModel::set_objective_as_constant), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("optimize", &HighsModel::optimize, nb::call_guard()) // clang-format off BIND_F(version_string) BIND_F(get_raw_model) BIND_F(getruntime) BIND_F(getnumrow) BIND_F(getnumcol) BIND_F(raw_option_type) BIND_F(set_raw_option_bool) BIND_F(set_raw_option_int) BIND_F(set_raw_option_double) BIND_F(set_raw_option_string) BIND_F(get_raw_option_bool) BIND_F(get_raw_option_int) BIND_F(get_raw_option_double) BIND_F(get_raw_option_string) BIND_F(raw_info_type) BIND_F(get_raw_info_int) BIND_F(get_raw_info_int64) BIND_F(get_raw_info_double) BIND_F(set_variable_name) BIND_F(get_variable_name) BIND_F(set_variable_type) BIND_F(get_variable_type) BIND_F(set_variable_lower_bound) BIND_F(set_variable_upper_bound) BIND_F(get_variable_lower_bound) BIND_F(get_variable_upper_bound) BIND_F(get_variable_dual) BIND_F(get_constraint_primal) BIND_F(get_constraint_dual) BIND_F(get_constraint_name) BIND_F(set_constraint_name) BIND_F(set_obj_sense) BIND_F(get_obj_sense) BIND_F(get_obj_value) BIND_F(set_primal_start) BIND_F(get_normalized_rhs) BIND_F(set_normalized_rhs) BIND_F(get_normalized_coefficient) BIND_F(set_normalized_coefficient) BIND_F(get_objective_coefficient) BIND_F(set_objective_coefficient) // clang-format on ; } ================================================ FILE: lib/highs_model_ext_constants.cpp ================================================ #include #include "interfaces/highs_c_api.h" namespace nb = nanobind; void bind_highs_constants(nb::module_ &m) { nb::module_ Enum = m.def_submodule("Enum"); #define BIND_C(x) Enum.attr(#x) = x BIND_C(kHighsOptionTypeBool); BIND_C(kHighsOptionTypeInt); BIND_C(kHighsOptionTypeDouble); BIND_C(kHighsOptionTypeString); BIND_C(kHighsInfoTypeInt64); BIND_C(kHighsInfoTypeInt); BIND_C(kHighsInfoTypeDouble); BIND_C(kHighsSolutionStatusNone); BIND_C(kHighsSolutionStatusInfeasible); BIND_C(kHighsSolutionStatusFeasible); BIND_C(kHighsModelStatusNotset); BIND_C(kHighsModelStatusLoadError); BIND_C(kHighsModelStatusModelError); BIND_C(kHighsModelStatusPresolveError); BIND_C(kHighsModelStatusSolveError); BIND_C(kHighsModelStatusPostsolveError); BIND_C(kHighsModelStatusModelEmpty); BIND_C(kHighsModelStatusOptimal); BIND_C(kHighsModelStatusInfeasible); BIND_C(kHighsModelStatusUnboundedOrInfeasible); BIND_C(kHighsModelStatusUnbounded); BIND_C(kHighsModelStatusObjectiveBound); BIND_C(kHighsModelStatusObjectiveTarget); BIND_C(kHighsModelStatusTimeLimit); BIND_C(kHighsModelStatusIterationLimit); BIND_C(kHighsModelStatusUnknown); BIND_C(kHighsModelStatusSolutionLimit); BIND_C(kHighsModelStatusInterrupt); } ================================================ FILE: lib/ipopt_model.cpp ================================================ #include "pyoptinterface/ipopt_model.hpp" #include "pyoptinterface/solver_common.hpp" #include "fmt/core.h" #include "fmt/ranges.h" #include "pyoptinterface/dylib.hpp" #include static bool is_name_empty(const char *name) { return name == nullptr || name[0] == '\0'; } namespace ipopt { #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } } // namespace ipopt IpoptModel::IpoptModel() { if (!ipopt::is_library_loaded()) { throw std::runtime_error("IPOPT library is not loaded"); } } void IpoptModel::close() { m_problem.reset(); } VariableIndex IpoptModel::add_variable(double lb, double ub, double start, const char *name) { VariableIndex vi(n_variables); m_var_lb.push_back(lb); m_var_ub.push_back(ub); m_var_init.push_back(start); n_variables += 1; if (!is_name_empty(name)) { m_var_names.emplace(vi.index, name); } m_is_dirty = true; return vi; } double IpoptModel::get_variable_lb(const VariableIndex &variable) { return m_var_lb[variable.index]; } double IpoptModel::get_variable_ub(const VariableIndex &variable) { return m_var_ub[variable.index]; } void IpoptModel::set_variable_lb(const VariableIndex &variable, double lb) { m_var_lb[variable.index] = lb; } void IpoptModel::set_variable_ub(const VariableIndex &variable, double ub) { m_var_ub[variable.index] = ub; } void IpoptModel::set_variable_bounds(const VariableIndex &variable, double lb, double ub) { m_var_lb[variable.index] = lb; m_var_ub[variable.index] = ub; } double IpoptModel::get_variable_start(const VariableIndex &variable) { return m_var_init[variable.index]; } void IpoptModel::set_variable_start(const VariableIndex &variable, double start) { m_var_init[variable.index] = start; } double IpoptModel::get_variable_value(const VariableIndex &variable) { if (m_is_dirty) { throw std::runtime_error( "Variable value is not available before optimization. Call optimize() first."); } return m_result.x[variable.index]; } std::string IpoptModel::get_variable_name(const VariableIndex &variable) { auto iter = m_var_names.find(variable.index); if (iter != m_var_names.end()) { return iter->second; } else { return fmt::format("x{}", variable.index); } } void IpoptModel::set_variable_name(const VariableIndex &variable, const std::string &name) { m_var_names[variable.index] = name; } std::string IpoptModel::pprint_variable(const VariableIndex &variable) { return get_variable_name(variable); } double IpoptModel::get_obj_value() { if (m_is_dirty) { throw std::runtime_error( "Objective value is not available before optimization. Call optimize() first."); } return m_result.obj_val; } int IpoptModel::_constraint_internal_index(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: return constraint.index; case ConstraintType::Quadratic: return m_linear_con_evaluator.n_constraints + constraint.index; case ConstraintType::NL: { auto base = m_linear_con_evaluator.n_constraints + m_quadratic_con_evaluator.n_constraints; auto internal_nl_index = nl_constraint_map_ext2int[constraint.index]; return base + internal_nl_index; } default: throw std::runtime_error("Invalid constraint type"); } } double IpoptModel::get_constraint_primal(const ConstraintIndex &constraint) { if (m_is_dirty) { throw std::runtime_error( "Constraint primal value is not available before optimization. Call optimize() first."); } int index = _constraint_internal_index(constraint); return m_result.g[index]; } double IpoptModel::get_constraint_dual(const ConstraintIndex &constraint) { if (m_is_dirty) { throw std::runtime_error( "Constraint dual value is not available before optimization. Call optimize() first."); } int index = _constraint_internal_index(constraint); auto dual = -m_result.mult_g[index]; return dual; } ConstraintIndex IpoptModel::add_linear_constraint(const ScalarAffineFunction &f, ConstraintSense sense, double rhs, const char *name) { double lb = -INFINITY; double ub = INFINITY; if (sense == ConstraintSense::LessEqual) { ub = rhs; } else if (sense == ConstraintSense::GreaterEqual) { lb = rhs; } else if (sense == ConstraintSense::Equal) { lb = rhs; ub = rhs; } return add_linear_constraint(f, {lb, ub}, name); } ConstraintIndex IpoptModel::add_linear_constraint(const ScalarAffineFunction &f, const std::tuple &interval, const char *name) { ConstraintIndex con(ConstraintType::Linear, m_linear_con_evaluator.n_constraints); m_linear_con_evaluator.add_row(f); auto lb = std::get<0>(interval); auto ub = std::get<1>(interval); m_linear_con_lb.push_back(lb); m_linear_con_ub.push_back(ub); m_is_dirty = true; return con; } ConstraintIndex IpoptModel::add_quadratic_constraint(const ScalarQuadraticFunction &f, ConstraintSense sense, double rhs, const char *name) { double lb = -INFINITY; double ub = INFINITY; if (sense == ConstraintSense::LessEqual) { ub = rhs; } else if (sense == ConstraintSense::GreaterEqual) { lb = rhs; } else if (sense == ConstraintSense::Equal) { lb = rhs; ub = rhs; } else { throw std::runtime_error("'Within' constraint sense must have both LB and UB"); } return add_quadratic_constraint(f, {lb, ub}, name); } ConstraintIndex IpoptModel::add_quadratic_constraint(const ScalarQuadraticFunction &f, const std::tuple &interval, const char *name) { ConstraintIndex con(ConstraintType::Quadratic, m_quadratic_con_evaluator.n_constraints); m_quadratic_con_evaluator.add_row(f); auto lb = std::get<0>(interval); auto ub = std::get<1>(interval); m_quadratic_con_lb.push_back(lb); m_quadratic_con_ub.push_back(ub); m_is_dirty = true; return con; } void IpoptModel::set_objective(const ScalarAffineFunction &expr, ObjectiveSense sense) { _set_linear_objective(expr); } void IpoptModel::set_objective(const ScalarQuadraticFunction &expr, ObjectiveSense sense) { _set_quadratic_objective(expr); } void IpoptModel::set_objective(const ExprBuilder &expr, ObjectiveSense sense) { auto degree = expr.degree(); if (degree <= 1) { _set_linear_objective(expr); } else if (degree == 2) { _set_quadratic_objective(expr); } else { throw std::runtime_error("Only linear and quadratic objective is supported"); } m_is_dirty = true; } void IpoptModel::_set_linear_objective(const ScalarAffineFunction &expr) { LinearEvaluator evaluator; evaluator.add_row(expr); m_linear_obj_evaluator = evaluator; m_quadratic_obj_evaluator.reset(); } void IpoptModel::_set_quadratic_objective(const ScalarQuadraticFunction &expr) { QuadraticEvaluator evaluator; evaluator.add_row(expr); m_linear_obj_evaluator.reset(); m_quadratic_obj_evaluator = evaluator; } int IpoptModel::add_graph_index() { return m_nl_evaluator.add_graph_instance(); } void IpoptModel::finalize_graph_instance(size_t graph_index, const ExpressionGraph &graph) { m_nl_evaluator.finalize_graph_instance(graph_index, graph); } int IpoptModel::aggregate_nl_constraint_groups() { return m_nl_evaluator.aggregate_constraint_groups(); } int IpoptModel::get_nl_constraint_group_representative(int group_index) const { return m_nl_evaluator.get_constraint_group_representative(group_index); } int IpoptModel::aggregate_nl_objective_groups() { return m_nl_evaluator.aggregate_objective_groups(); } int IpoptModel::get_nl_objective_group_representative(int group_index) const { return m_nl_evaluator.get_objective_group_representative(group_index); } void IpoptModel::assign_nl_constraint_group_autodiff_structure( int group_index, const AutodiffSymbolicStructure &structure) { m_nl_evaluator.assign_constraint_group_autodiff_structure(group_index, structure); } void IpoptModel::assign_nl_constraint_group_autodiff_evaluator( int group_index, const ConstraintAutodiffEvaluator &evaluator) { m_nl_evaluator.assign_constraint_group_autodiff_evaluator(group_index, evaluator); } void IpoptModel::assign_nl_objective_group_autodiff_structure( int group_index, const AutodiffSymbolicStructure &structure) { m_nl_evaluator.assign_objective_group_autodiff_structure(group_index, structure); } void IpoptModel::assign_nl_objective_group_autodiff_evaluator( int group_index, const ObjectiveAutodiffEvaluator &evaluator) { m_nl_evaluator.assign_objective_group_autodiff_evaluator(group_index, evaluator); } ConstraintIndex IpoptModel::add_single_nl_constraint(size_t graph_index, const ExpressionGraph &graph, double lb, double ub) { m_nl_con_lb.push_back(lb); m_nl_con_ub.push_back(ub); auto constraint_index = n_nl_constraints; n_nl_constraints += 1; nl_constraint_graph_memberships.push_back(ConstraintGraphMembership{ .graph = (int)graph_index, .rank = (int)graph.m_constraint_outputs.size() - 1}); m_is_dirty = true; return ConstraintIndex(ConstraintType::NL, constraint_index); } static bool eval_f(ipindex n, ipnumber *x, bool new_x, ipnumber *obj_value, UserDataPtr user_data) { IpoptModel &model = *static_cast(user_data); *obj_value = 0.0; // fmt::print("Before linear and quad objective, obj_value: {}\n", *obj_value); if (model.m_linear_obj_evaluator) { model.m_linear_obj_evaluator->eval_function(x, obj_value); } else if (model.m_quadratic_obj_evaluator) { model.m_quadratic_obj_evaluator->eval_function(x, obj_value); } // fmt::print("After linear and quad objective, obj_value: {}\n", *obj_value); // nonlinear part double nl_obj = model.m_nl_evaluator.eval_objective(x); *obj_value += nl_obj; return true; } static bool eval_grad_f(ipindex n, ipnumber *x, bool new_x, ipnumber *grad_f, UserDataPtr user_data) { IpoptModel &model = *static_cast(user_data); std::fill(grad_f, grad_f + n, 0.0); // fmt::print("Enters eval_grad_f\n"); // fill sparse_gradient_values auto &sparse_gradient_values = model.sparse_gradient_values; std::fill(sparse_gradient_values.begin(), sparse_gradient_values.end(), 0.0); // analytical part if (model.m_linear_obj_evaluator) { model.m_linear_obj_evaluator->eval_jacobian(x, sparse_gradient_values.data()); } else if (model.m_quadratic_obj_evaluator) { model.m_quadratic_obj_evaluator->eval_jacobian(x, sparse_gradient_values.data()); } // nonlinear part model.m_nl_evaluator.eval_objective_gradient(x, sparse_gradient_values.data()); // copy to grad_f for (size_t i = 0; i < model.sparse_gradient_indices.size(); i++) { auto index = model.sparse_gradient_indices[i]; auto value = sparse_gradient_values[i]; grad_f[index] += value; } // debug /*fmt::print("Current x: {}\n", std::vector(x, x + n)); fmt::print("Current gradient: {}\n", std::vector(grad_f, grad_f + n));*/ return true; } static bool eval_g(ipindex n, ipnumber *x, bool new_x, ipindex m, ipnumber *g, UserDataPtr user_data) { IpoptModel &model = *static_cast(user_data); // std::fill(g, g + m, 0.0); // fmt::print("Enters eval_g\n"); auto original_g = g; // linear part model.m_linear_con_evaluator.eval_function(x, g); // quadratic part g += model.m_linear_con_evaluator.n_constraints; model.m_quadratic_con_evaluator.eval_function(x, g); // nonlinear part g += model.m_quadratic_con_evaluator.n_constraints; model.m_nl_evaluator.eval_constraints(x, g); // debug /*fmt::print("Current x: {}\n", std::vector(x, x + n)); fmt::print("Current g: {}\n", std::vector(original_g, original_g + m));*/ return true; } static bool eval_jac_g(ipindex n, ipnumber *x, bool new_x, ipindex m, ipindex nele_jac, ipindex *iRow, ipindex *jCol, ipnumber *values, UserDataPtr user_data) { IpoptModel &model = *static_cast(user_data); // fmt::print("Enters eval_jac_g\n"); if (iRow != nullptr) { auto &rows = model.m_jacobian_rows; auto &cols = model.m_jacobian_cols; std::copy(rows.begin(), rows.end(), iRow); std::copy(cols.begin(), cols.end(), jCol); } else { // std::fill(values, values + nele_jac, 0.0); auto original_jacobian = values; // fmt::print("Initial jacobian: {}\n", std::vector(values, values + nele_jac)); // linear part model.m_linear_con_evaluator.eval_jacobian(x, values); // quadratic part /*fmt::print("jacobian forwards {} for linear part\n", model.m_linear_con_evaluator.coefs.size());*/ values += model.m_linear_con_evaluator.coefs.size(); model.m_quadratic_con_evaluator.eval_jacobian(x, values); // nonlinear part /*fmt::print("jacobian forwards {} for quadratic part\n", model.m_quadratic_con_evaluator.jacobian_nnz);*/ values += model.m_quadratic_con_evaluator.jacobian_nnz; model.m_nl_evaluator.eval_constraints_jacobian(x, values); // debug /*fmt::print("Current x: {}\n", std::vector(x, x + n)); fmt::print("Current jacobian: {}\n", std::vector(original_jacobian, original_jacobian + nele_jac));*/ } return true; } static bool eval_h(ipindex n, ipnumber *x, bool new_x, ipnumber obj_factor, ipindex m, ipnumber *lambda, bool new_lambda, ipindex nele_hess, ipindex *iRow, ipindex *jCol, ipnumber *values, UserDataPtr user_data) { IpoptModel &model = *static_cast(user_data); // fmt::print("Enters eval_h\n"); if (iRow != nullptr) { auto &rows = model.m_hessian_rows; auto &cols = model.m_hessian_cols; std::copy(rows.begin(), rows.end(), iRow); std::copy(cols.begin(), cols.end(), jCol); } else { std::fill(values, values + nele_hess, 0.0); // objective // quadratic part if (model.m_quadratic_obj_evaluator) { model.m_quadratic_obj_evaluator->eval_lagrangian_hessian(&obj_factor, values); } // constraint // quadratic part lambda += model.m_linear_con_evaluator.n_constraints; model.m_quadratic_con_evaluator.eval_lagrangian_hessian(lambda, values); // nonlinear part lambda += model.m_quadratic_con_evaluator.n_constraints; model.m_nl_evaluator.eval_lagrangian_hessian(x, lambda, obj_factor, values); // debug /*fmt::print("Current x: {}\n", std::vector(x, x + n)); fmt::print("Current obj_factor: {}\n", obj_factor); fmt::print("Current lambda: {}\n", std::vector(lambda, lambda + m)); fmt::print("Current hessian: {}\n", std::vector(values, values + nele_hess));*/ } return true; } void IpoptModel::analyze_structure() { // init variables m_jacobian_nnz = 0; m_jacobian_rows.clear(); m_jacobian_cols.clear(); m_hessian_nnz = 0; m_hessian_rows.clear(); m_hessian_cols.clear(); m_hessian_index_map.clear(); // constraints // analyze linear part m_linear_con_evaluator.analyze_jacobian_structure(m_jacobian_nnz, m_jacobian_rows, m_jacobian_cols); // analyze quadratic part m_quadratic_con_evaluator.analyze_jacobian_structure( m_linear_con_evaluator.n_constraints, m_jacobian_nnz, m_jacobian_rows, m_jacobian_cols); m_quadratic_con_evaluator.analyze_hessian_structure(m_hessian_nnz, m_hessian_rows, m_hessian_cols, m_hessian_index_map, HessianSparsityType::Lower); // objective sparse_gradient_indices.clear(); sparse_gradient_values.clear(); Hashmap sparse_gradient_map; // linear and quadratic objective if (m_linear_obj_evaluator) { auto &evaluator = m_linear_obj_evaluator.value(); sparse_gradient_indices.insert(sparse_gradient_indices.end(), evaluator.indices.begin(), evaluator.indices.end()); } else if (m_quadratic_obj_evaluator) { auto &evaluator = m_quadratic_obj_evaluator.value(); sparse_gradient_indices.insert(sparse_gradient_indices.end(), evaluator.jacobian_variable_indices.begin(), evaluator.jacobian_variable_indices.end()); evaluator.analyze_hessian_structure(m_hessian_nnz, m_hessian_rows, m_hessian_cols, m_hessian_index_map, HessianSparsityType::Lower); } // update map for (int i = 0; i < sparse_gradient_indices.size(); i++) { auto index = sparse_gradient_indices[i]; sparse_gradient_map.emplace(index, i); } // analyze nonlinear constraints and objectives { auto &evaluator = m_nl_evaluator; auto constraint_counter = m_linear_con_evaluator.n_constraints + m_quadratic_con_evaluator.n_constraints; evaluator.analyze_constraints_jacobian_structure(constraint_counter, m_jacobian_nnz, m_jacobian_rows, m_jacobian_cols); evaluator.analyze_objective_gradient_structure(sparse_gradient_indices, sparse_gradient_map); evaluator.analyze_constraints_hessian_structure(m_hessian_nnz, m_hessian_rows, m_hessian_cols, m_hessian_index_map, HessianSparsityType::Lower); evaluator.analyze_objective_hessian_structure(m_hessian_nnz, m_hessian_rows, m_hessian_cols, m_hessian_index_map, HessianSparsityType::Lower); } sparse_gradient_values.resize(sparse_gradient_indices.size()); // update the mapping of nl constraint nl_constraint_map_ext2int.resize(n_nl_constraints); m_nl_evaluator.calculate_constraint_graph_instances_offset(); for (int i = 0; i < n_nl_constraints; i++) { auto i_nl_con = i; auto i_graph_instance = nl_constraint_graph_memberships[i].graph; auto i_graph_rank = nl_constraint_graph_memberships[i].rank; auto index_base = m_nl_evaluator.constraint_indices_offsets[i_graph_instance]; nl_constraint_map_ext2int[i_nl_con] = index_base + i_graph_rank; } // construct the lower bound and upper bound of the constraints auto n_constraints = m_linear_con_evaluator.n_constraints + m_quadratic_con_evaluator.n_constraints + n_nl_constraints; m_con_lb.resize(n_constraints); m_con_ub.resize(n_constraints); std::copy(m_linear_con_lb.begin(), m_linear_con_lb.end(), m_con_lb.begin()); std::copy(m_linear_con_ub.begin(), m_linear_con_ub.end(), m_con_ub.begin()); std::copy(m_quadratic_con_lb.begin(), m_quadratic_con_lb.end(), m_con_lb.begin() + m_linear_con_evaluator.n_constraints); std::copy(m_quadratic_con_ub.begin(), m_quadratic_con_ub.end(), m_con_ub.begin() + m_linear_con_evaluator.n_constraints); // nonlinear parts need mapping auto nl_constraint_start = m_linear_con_evaluator.n_constraints + m_quadratic_con_evaluator.n_constraints; for (int i = 0; i < n_nl_constraints; i++) { auto index = nl_constraint_map_ext2int[i]; m_con_lb[nl_constraint_start + index] = m_nl_con_lb[i]; m_con_ub[nl_constraint_start + index] = m_nl_con_ub[i]; } } void IpoptModel::optimize() { analyze_structure(); auto n_constraints = m_linear_con_evaluator.n_constraints + m_quadratic_con_evaluator.n_constraints + n_nl_constraints; /*fmt::print("Problem has {} variables and {} constraints.\n", n_variables, n_constraints); fmt::print("Variable LB: {}\n", m_var_lb); fmt::print("Variable UB: {}\n", m_var_ub); fmt::print("Constraint LB: {}\n", m_con_lb); fmt::print("Constraint UB: {}\n", m_con_ub); fmt::print("Jacobian has {} nonzeros\n", m_jacobian_nnz); fmt::print("Jacobian rows : {}\n", m_jacobian_rows); fmt::print("Jacobian cols : {}\n", m_jacobian_cols); fmt::print("Hessian has {} nonzeros\n", m_hessian_nnz); fmt::print("Hessian rows : {}\n", m_hessian_rows); fmt::print("Hessian cols : {}\n", m_hessian_cols);*/ /*if (m_quadratic_obj_evaluator) { auto &evaluator = m_quadratic_obj_evaluator.value(); fmt::print("Diag coefs : {}\n", evaluator.diag_coefs); fmt::print("Diag indices : {}\n", evaluator.diag_indices); fmt::print("Diag intervals : {}\n", evaluator.diag_intervals); fmt::print("OffDiag coefs : {}\n", evaluator.offdiag_coefs); fmt::print("OffDiag rows : {}\n", evaluator.offdiag_rows); fmt::print("OffDiag cols : {}\n", evaluator.offdiag_cols); fmt::print("OffDiag intervals : {}\n", evaluator.offdiag_intervals); fmt::print("hessian_diag_indices : {}\n", evaluator.hessian_diag_indices); fmt::print("hessian_offdiag_indices : {}\n", evaluator.hessian_offdiag_indices); } { auto &evaluator = m_quadratic_con_evaluator; fmt::print("Diag coefs : {}\n", evaluator.diag_coefs); fmt::print("Diag indices : {}\n", evaluator.diag_indices); fmt::print("Diag intervals : {}\n", evaluator.diag_intervals); fmt::print("OffDiag coefs : {}\n", evaluator.offdiag_coefs); fmt::print("OffDiag rows : {}\n", evaluator.offdiag_rows); fmt::print("OffDiag cols : {}\n", evaluator.offdiag_cols); fmt::print("OffDiag intervals : {}\n", evaluator.offdiag_intervals); fmt::print("hessian_diag_indices : {}\n", evaluator.hessian_diag_indices); fmt::print("hessian_offdiag_indices : {}\n", evaluator.hessian_offdiag_indices); }*/ auto problem_ptr = ipopt::CreateIpoptProblem(n_variables, m_var_lb.data(), m_var_ub.data(), n_constraints, m_con_lb.data(), m_con_ub.data(), m_jacobian_nnz, m_hessian_nnz, 0, &eval_f, &eval_g, &eval_grad_f, &eval_jac_g, &eval_h); m_problem = std::unique_ptr(problem_ptr); // set options for (auto &[key, value] : m_options_int) { bool ret = ipopt::AddIpoptIntOption(problem_ptr, (char *)key.c_str(), value); if (!ret) { fmt::print("Failed to set integer option {}\n", key); } } for (auto &[key, value] : m_options_num) { bool ret = ipopt::AddIpoptNumOption(problem_ptr, (char *)key.c_str(), value); if (!ret) { fmt::print("Failed to set number option {}\n", key); } } for (auto &[key, value] : m_options_str) { bool ret = ipopt::AddIpoptStrOption(problem_ptr, (char *)key.c_str(), (char *)value.c_str()); if (!ret) { fmt::print("Failed to set string option {}\n", key); } } // initialize the solution m_result.x.resize(n_variables); std::copy(m_var_init.begin(), m_var_init.end(), m_result.x.begin()); m_result.mult_x_L.resize(n_variables); m_result.mult_x_U.resize(n_variables); m_result.g.resize(n_constraints); m_result.mult_g.resize(n_constraints); m_status = ipopt::IpoptSolve(problem_ptr, m_result.x.data(), m_result.g.data(), &m_result.obj_val, m_result.mult_g.data(), m_result.mult_x_L.data(), m_result.mult_x_U.data(), (void *)this); m_result.is_valid = true; m_is_dirty = false; } void IpoptModel::load_current_solution() { if (!m_result.is_valid) { throw std::runtime_error("No valid solution to load"); } std::copy(m_result.x.begin(), m_result.x.end(), m_var_init.begin()); } void IpoptModel::set_raw_option_int(const std::string &name, int value) { m_options_int[name] = value; } void IpoptModel::set_raw_option_double(const std::string &name, double value) { m_options_num[name] = value; } void IpoptModel::set_raw_option_string(const std::string &name, const std::string &value) { m_options_str[name] = value; } ================================================ FILE: lib/ipopt_model_ext.cpp ================================================ #include #include #include #include namespace nb = nanobind; #include "pyoptinterface/ipopt_model.hpp" NB_MODULE(ipopt_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.import_("pyoptinterface._src.nleval_ext"); m.def("is_library_loaded", &ipopt::is_library_loaded); m.def("load_library", &ipopt::load_library); nb::enum_(m, "ApplicationReturnStatus") .value("Solve_Succeeded", ApplicationReturnStatus::Solve_Succeeded) .value("Solved_To_Acceptable_Level", ApplicationReturnStatus::Solved_To_Acceptable_Level) .value("Infeasible_Problem_Detected", ApplicationReturnStatus::Infeasible_Problem_Detected) .value("Search_Direction_Becomes_Too_Small", ApplicationReturnStatus::Search_Direction_Becomes_Too_Small) .value("Diverging_Iterates", ApplicationReturnStatus::Diverging_Iterates) .value("User_Requested_Stop", ApplicationReturnStatus::User_Requested_Stop) .value("Feasible_Point_Found", ApplicationReturnStatus::Feasible_Point_Found) .value("Maximum_Iterations_Exceeded", ApplicationReturnStatus::Maximum_Iterations_Exceeded) .value("Restoration_Failed", ApplicationReturnStatus::Restoration_Failed) .value("Error_In_Step_Computation", ApplicationReturnStatus::Error_In_Step_Computation) .value("Maximum_CpuTime_Exceeded", ApplicationReturnStatus::Maximum_CpuTime_Exceeded) .value("Maximum_WallTime_Exceeded", ApplicationReturnStatus::Maximum_WallTime_Exceeded) .value("Not_Enough_Degrees_Of_Freedom", ApplicationReturnStatus::Not_Enough_Degrees_Of_Freedom) .value("Invalid_Problem_Definition", ApplicationReturnStatus::Invalid_Problem_Definition) .value("Invalid_Option", ApplicationReturnStatus::Invalid_Option) .value("Invalid_Number_Detected", ApplicationReturnStatus::Invalid_Number_Detected) .value("Unrecoverable_Exception", ApplicationReturnStatus::Unrecoverable_Exception) .value("NonIpopt_Exception_Thrown", ApplicationReturnStatus::NonIpopt_Exception_Thrown) .value("Insufficient_Memory", ApplicationReturnStatus::Insufficient_Memory) .value("Internal_Error", ApplicationReturnStatus::Internal_Error); nb::class_(m, "RawModel") .def(nb::init<>()) .def("close", &IpoptModel::close) .def_ro("m_status", &IpoptModel::m_status) .def_rw("m_is_dirty", &IpoptModel::m_is_dirty) .def("add_variable", &IpoptModel::add_variable, nb::arg("lb") = -INFINITY, nb::arg("ub") = INFINITY, nb::arg("start") = 0.0, nb::arg("name") = "") .def("get_variable_lb", &IpoptModel::get_variable_lb) .def("get_variable_ub", &IpoptModel::get_variable_ub) .def("set_variable_lb", &IpoptModel::set_variable_lb) .def("set_variable_ub", &IpoptModel::set_variable_ub) .def("set_variable_bounds", &IpoptModel::set_variable_bounds, nb::arg("variable"), nb::arg("lb"), nb::arg("ub")) .def("get_variable_start", &IpoptModel::get_variable_start) .def("set_variable_start", &IpoptModel::set_variable_start) .def("get_variable_name", &IpoptModel::get_variable_name) .def("set_variable_name", &IpoptModel::set_variable_name) .def("get_value", nb::overload_cast(&IpoptModel::get_variable_value)) .def("get_value", nb::overload_cast(&IpoptModel::get_expression_value)) .def("get_value", nb::overload_cast(&IpoptModel::get_expression_value)) .def("get_value", nb::overload_cast(&IpoptModel::get_expression_value)) .def("pprint", &IpoptModel::pprint_variable) .def("pprint", nb::overload_cast(&IpoptModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def( "pprint", nb::overload_cast(&IpoptModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&IpoptModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("get_obj_value", &IpoptModel::get_obj_value) .def("get_constraint_primal", &IpoptModel::get_constraint_primal) .def("get_constraint_dual", &IpoptModel::get_constraint_dual) .def("_add_linear_constraint", nb::overload_cast( &IpoptModel::add_linear_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", nb::overload_cast &, const char *>(&IpoptModel::add_linear_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &IpoptModel::add_linear_constraint_from_var, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &IpoptModel::add_linear_interval_constraint_from_var, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &IpoptModel::add_linear_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &IpoptModel::add_linear_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_quadratic_constraint", nb::overload_cast(&IpoptModel::add_quadratic_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", nb::overload_cast &, const char *>(&IpoptModel::add_quadratic_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_quadratic_constraint", &IpoptModel::add_quadratic_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", &IpoptModel::add_quadratic_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("set_objective", &IpoptModel::set_objective_as_constant, nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", &IpoptModel::set_objective_as_variable, nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &IpoptModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &IpoptModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&IpoptModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) // New API .def("_add_graph_index", &IpoptModel::add_graph_index) .def("_finalize_graph_instance", &IpoptModel::finalize_graph_instance) .def("_aggregate_nl_constraint_groups", &IpoptModel::aggregate_nl_constraint_groups) .def("_get_nl_constraint_group_representative", &IpoptModel::get_nl_constraint_group_representative) .def("_aggregate_nl_objective_groups", &IpoptModel::aggregate_nl_objective_groups) .def("_get_nl_objective_group_representative", &IpoptModel::get_nl_objective_group_representative) .def("_assign_nl_constraint_group_autodiff_structure", &IpoptModel::assign_nl_constraint_group_autodiff_structure) .def("_assign_nl_constraint_group_autodiff_evaluator", &IpoptModel::assign_nl_constraint_group_autodiff_evaluator) .def("_assign_nl_objective_group_autodiff_structure", &IpoptModel::assign_nl_objective_group_autodiff_structure) .def("_assign_nl_objective_group_autodiff_evaluator", &IpoptModel::assign_nl_objective_group_autodiff_evaluator) .def("_add_single_nl_constraint", &IpoptModel::add_single_nl_constraint) .def("_optimize", &IpoptModel::optimize, nb::call_guard()) .def("load_current_solution", &IpoptModel::load_current_solution) .def("set_raw_option_int", &IpoptModel::set_raw_option_int) .def("set_raw_option_double", &IpoptModel::set_raw_option_double) .def("set_raw_option_string", &IpoptModel::set_raw_option_string); } ================================================ FILE: lib/knitro_model.cpp ================================================ #include "pyoptinterface/knitro_model.hpp" #include "pyoptinterface/solver_common.hpp" #include "fmt/core.h" #include "fmt/ranges.h" #include #include #include namespace knitro { #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } bool has_valid_license() { if (!is_library_loaded()) { throw std::runtime_error("KNITRO library not loaded"); } LM_context *lm = nullptr; int error = KN_checkout_license(&lm); if (error == 0) { KN_release_license(&lm); return true; } else { return false; } } } // namespace knitro void ensure_library_loaded() { if (!knitro::is_library_loaded()) { throw std::runtime_error("KNITRO library not loaded"); } } KNITROEnv::KNITROEnv(bool empty) { if (!empty) { start(); } } void KNITROEnv::start() { if (!empty()) { return; } ensure_library_loaded(); LM_context *lm = nullptr; int error = knitro::KN_checkout_license(&lm); _check_error(error); m_lm = std::shared_ptr(lm, KNITROFreeLicenseT()); } bool KNITROEnv::empty() const { return m_lm == nullptr; } std::shared_ptr KNITROEnv::get_lm() const { return m_lm; } void KNITROEnv::close() { m_lm.reset(); } void KNITROEnv::_check_error(int code) const { knitro_throw(code); } KNITROModel::KNITROModel() { init(); } KNITROModel::KNITROModel(const KNITROEnv &env) { init(env); } void KNITROModel::init() { m_lm.reset(); _init(); } void KNITROModel::init(const KNITROEnv &env) { if (env.empty()) { throw std::runtime_error("Empty environment provided. Call start()..."); } m_lm = env.get_lm(); _init(); } void KNITROModel::close() { _reset_state(); m_lm.reset(); } void KNITROModel::_check_error(int code) const { knitro_throw(code); } // Model information double KNITROModel::get_infinity() const { return KN_INFINITY; } std::string KNITROModel::get_solver_name() const { return std::string("KNITRO"); } std::string KNITROModel::get_release() const { constexpr int buf_size = 20; char release[buf_size]; int error = knitro::KN_get_release(buf_size, release); _check_error(error); return std::string(release); } // Variable functions VariableIndex KNITROModel::add_variable(VariableDomain domain, double lb, double ub, const char *name) { KNINT indexVar; int error = knitro::KN_add_var(m_kc.get(), &indexVar); _check_error(error); VariableIndex variable(indexVar); int var_type = knitro_var_type(domain); _set_value(knitro::KN_set_var_type, indexVar, var_type); if (var_type == KN_VARTYPE_BINARY) { lb = (lb < 0.0) ? 0.0 : lb; ub = (ub > 1.0) ? 1.0 : ub; } _set_value(knitro::KN_set_var_lobnd, indexVar, lb); _set_value(knitro::KN_set_var_upbnd, indexVar, ub); if (!is_name_empty(name)) { _set_value(knitro::KN_set_var_name, indexVar, name); } m_n_vars++; _mark_dirty(); return variable; } double KNITROModel::get_variable_lb(const VariableIndex &variable) const { KNINT indexVar = _variable_index(variable); return _get_value(knitro::KN_get_var_lobnd, indexVar); } double KNITROModel::get_variable_ub(const VariableIndex &variable) const { KNINT indexVar = _variable_index(variable); return _get_value(knitro::KN_get_var_upbnd, indexVar); } void KNITROModel::set_variable_lb(const VariableIndex &variable, double lb) { KNINT indexVar = _variable_index(variable); _set_value(knitro::KN_set_var_lobnd, indexVar, lb); } void KNITROModel::set_variable_ub(const VariableIndex &variable, double ub) { KNINT indexVar = _variable_index(variable); _set_value(knitro::KN_set_var_upbnd, indexVar, ub); } void KNITROModel::set_variable_bounds(const VariableIndex &variable, double lb, double ub) { set_variable_lb(variable, lb); set_variable_ub(variable, ub); } double KNITROModel::get_variable_value(const VariableIndex &variable) const { _check_dirty(); KNINT indexVar = _variable_index(variable); return _get_value(knitro::KN_get_var_primal_value, indexVar); } void KNITROModel::set_variable_start(const VariableIndex &variable, double start) { KNINT indexVar = _variable_index(variable); _set_value(knitro::KN_set_var_primal_init_value, indexVar, start); } std::string KNITROModel::get_variable_name(const VariableIndex &variable) const { KNINT indexVar = _variable_index(variable); return _get_name(knitro::KN_get_var_name, indexVar, "x"); } void KNITROModel::set_variable_name(const VariableIndex &variable, const std::string &name) { KNINT indexVar = _variable_index(variable); _set_value(knitro::KN_set_var_name, indexVar, name.c_str()); } void KNITROModel::set_variable_domain(const VariableIndex &variable, VariableDomain domain) { KNINT indexVar = _variable_index(variable); int var_type = knitro_var_type(domain); double lb = -get_infinity(); double ub = get_infinity(); if (var_type == KN_VARTYPE_BINARY) { lb = _get_value(knitro::KN_get_var_lobnd, indexVar); ub = _get_value(knitro::KN_get_var_upbnd, indexVar); } _set_value(knitro::KN_set_var_type, indexVar, var_type); if (var_type == KN_VARTYPE_BINARY) { lb = (lb < 0.0) ? 0.0 : lb; ub = (ub > 1.0) ? 1.0 : ub; _set_value(knitro::KN_set_var_lobnd, indexVar, lb); _set_value(knitro::KN_set_var_upbnd, indexVar, ub); } _mark_dirty(); } VariableDomain KNITROModel::get_variable_domain(const VariableIndex &variable) const { KNINT indexVar = _variable_index(variable); int var_type = _get_value(knitro::KN_get_var_type, indexVar); return knitro_variable_domain(var_type); } double KNITROModel::get_variable_rc(const VariableIndex &variable) const { _check_dirty(); KNINT indexVar = _variable_index(variable); double dual = _get_value(knitro::KN_get_var_dual_value, indexVar); return -dual; } void KNITROModel::delete_variable(const VariableIndex &variable) { KNINT indexVar = _variable_index(variable); _set_value(knitro::KN_set_var_type, indexVar, KN_VARTYPE_CONTINUOUS); _set_value(knitro::KN_set_var_lobnd, indexVar, -get_infinity()); _set_value(knitro::KN_set_var_upbnd, indexVar, get_infinity()); m_n_vars--; _mark_dirty(); } std::string KNITROModel::pprint_variable(const VariableIndex &variable) const { return get_variable_name(variable); } // Constraint functions ConstraintIndex KNITROModel::add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, double rhs, const char *name) { auto add = [this](const ScalarAffineFunction &f, const std::tuple &interval, const char *n) { return add_linear_constraint(f, interval, n); }; return _add_constraint_with_sense(function, sense, rhs, name, add); } ConstraintIndex KNITROModel::add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name) { auto setter = [this, &function](const ConstraintIndex &constraint) { _set_linear_constraint(constraint, function); }; return _add_constraint_impl(ConstraintType::Linear, interval, name, setter); } ConstraintIndex KNITROModel::add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, double rhs, const char *name) { auto add = [this](const ScalarQuadraticFunction &f, const std::tuple &interval, const char *n) { return add_quadratic_constraint(f, interval, n); }; return _add_constraint_with_sense(function, sense, rhs, name, add); } ConstraintIndex KNITROModel::add_quadratic_constraint(const ScalarQuadraticFunction &function, const std::tuple &interval, const char *name) { auto setter = [this, &function](const ConstraintIndex &constraint) { _set_quadratic_constraint(constraint, function); }; return _add_constraint_impl(ConstraintType::Quadratic, interval, name, setter); } ConstraintIndex KNITROModel::add_second_order_cone_constraint( const Vector &variables, const char *name, bool rotated) { if (rotated) { auto setter = [this, &variables](const ConstraintIndex &constraint) { _set_second_order_cone_constraint_rotated(constraint, variables); }; std::pair interval = {0.0, get_infinity()}; return _add_constraint_impl(ConstraintType::SecondOrderCone, interval, name, setter); } else { auto setter = [this, &variables](const ConstraintIndex &constraint) { _set_second_order_cone_constraint(constraint, variables); }; std::pair interval = {0.0, get_infinity()}; return _add_constraint_impl(ConstraintType::SecondOrderCone, interval, name, setter); } } ConstraintIndex KNITROModel::add_single_nl_constraint(ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name) { _add_graph(graph); graph.add_constraint_output(result); size_t i = graph.m_constraint_outputs.size() - 1; m_pending_outputs[&graph].constraint_outputs.push_back(i); auto setter = [this, &graph](const ConstraintIndex &constraint) { m_pending_outputs[&graph].constraints.push_back(constraint); m_has_pending_callbacks = true; }; return _add_constraint_impl(ConstraintType::NL, interval, name, setter); } ConstraintIndex KNITROModel::add_single_nl_constraint_sense_rhs(ExpressionGraph &graph, const ExpressionHandle &result, ConstraintSense sense, double rhs, const char *name) { auto add = [this, &graph, &result](void *, const std::tuple &interval, const char *n) { return add_single_nl_constraint(graph, result, interval, n); }; return _add_constraint_with_sense(nullptr, sense, rhs, name, add); } std::tuple KNITROModel::_sense_to_interval(ConstraintSense sense, double rhs) { switch (sense) { case ConstraintSense::LessEqual: return {-get_infinity(), rhs}; case ConstraintSense::Equal: return {rhs, rhs}; case ConstraintSense::GreaterEqual: return {rhs, get_infinity()}; default: throw std::runtime_error("Unknown constraint sense"); } } void KNITROModel::_update_con_sense_flags(const ConstraintIndex &constraint, ConstraintSense sense) { KNINT indexCon = _constraint_index(constraint); switch (sense) { case ConstraintSense::Equal: m_con_sense_flags[indexCon] |= CON_LOBND; break; case ConstraintSense::GreaterEqual: m_con_sense_flags[indexCon] = CON_LOBND; break; default: break; } } void KNITROModel::_set_second_order_cone_constraint(const ConstraintIndex &constraint, const Vector &variables) { KNINT indexCon = _constraint_index(constraint); KNINT indexCon0; int error = knitro::KN_add_con(m_kc.get(), &indexCon0); _check_error(error); KNINT indexVar0 = _variable_index(variables[0]); error = knitro::KN_add_con_linear_term(m_kc.get(), indexCon0, indexVar0, 1.0); _check_error(error); _set_value(knitro::KN_set_con_lobnd, indexCon0, 0.0); _set_value(knitro::KN_set_con_upbnd, indexCon0, KN_INFINITY); error = knitro::KN_add_con_quadratic_term(m_kc.get(), indexCon, indexVar0, indexVar0, 1.0); _check_error(error); size_t nnz = variables.size() - 1; std::vector indexCons(nnz, indexCon); std::vector indexVars(nnz); for (size_t i = 0; i < nnz; ++i) { indexVars[i] = _variable_index(variables[i + 1]); } std::vector coefs(nnz, -1.0); error = knitro::KN_add_con_quadratic_struct(m_kc.get(), nnz, indexCons.data(), indexVars.data(), indexVars.data(), coefs.data()); _check_error(error); m_soc_aux_cons[indexCon] = indexCon0; } void KNITROModel::_set_second_order_cone_constraint_rotated(const ConstraintIndex &constraint, const Vector &variables) { KNINT indexCon = _constraint_index(constraint); KNINT indexCon0, indexCon1; int error = knitro::KN_add_con(m_kc.get(), &indexCon0); _check_error(error); error = knitro::KN_add_con(m_kc.get(), &indexCon1); _check_error(error); KNINT indexVar0 = _variable_index(variables[0]); KNINT indexVar1 = _variable_index(variables[1]); error = knitro::KN_add_con_linear_term(m_kc.get(), indexCon0, indexVar0, 1.0); _check_error(error); error = knitro::KN_add_con_linear_term(m_kc.get(), indexCon1, indexVar1, 1.0); _check_error(error); _set_value(knitro::KN_set_con_lobnd, indexCon0, 0.0); _set_value(knitro::KN_set_con_upbnd, indexCon0, KN_INFINITY); _set_value(knitro::KN_set_con_lobnd, indexCon1, 0.0); _set_value(knitro::KN_set_con_upbnd, indexCon1, KN_INFINITY); size_t nnz = variables.size() - 2; std::vector indexCons(nnz, indexCon); std::vector indexVars(nnz); for (size_t i = 0; i < nnz; ++i) { indexVars[i] = _variable_index(variables[i + 2]); } std::vector coefs(nnz, -1.0); error = knitro::KN_add_con_quadratic_struct(m_kc.get(), nnz, indexCons.data(), indexVars.data(), indexVars.data(), coefs.data()); _check_error(error); error = knitro::KN_add_con_quadratic_term(m_kc.get(), indexCon, indexVar0, indexVar1, 2.0); _check_error(error); m_soc_aux_cons[indexCon] = std::make_pair(indexCon0, indexCon1); } void KNITROModel::delete_constraint(const ConstraintIndex &constraint) { KNINT indexCon = _constraint_index(constraint); _set_value(knitro::KN_set_con_lobnd, indexCon, -get_infinity()); _set_value(knitro::KN_set_con_upbnd, indexCon, get_infinity()); m_n_cons_map[constraint.type]--; auto it = m_soc_aux_cons.find(indexCon); if (it != m_soc_aux_cons.end()) { std::vector aux_cons; if (std::holds_alternative(it->second)) { aux_cons.push_back(std::get(it->second)); } else if (std::holds_alternative>(it->second)) { auto p = std::get>(it->second); aux_cons.push_back(p.first); aux_cons.push_back(p.second); } for (auto const &con : aux_cons) { _set_value(knitro::KN_set_con_lobnd, con, -get_infinity()); _set_value(knitro::KN_set_con_upbnd, con, get_infinity()); } m_soc_aux_cons.erase(it); } _mark_dirty(); } void KNITROModel::set_constraint_name(const ConstraintIndex &constraint, const std::string &name) { KNINT indexCon = _constraint_index(constraint); _set_value(knitro::KN_set_con_name, indexCon, name.c_str()); } std::string KNITROModel::get_constraint_name(const ConstraintIndex &constraint) const { KNINT indexCon = _constraint_index(constraint); return _get_name(knitro::KN_get_con_name, indexCon, "c"); } double KNITROModel::get_constraint_primal(const ConstraintIndex &constraint) const { _check_dirty(); KNINT indexCon = _constraint_index(constraint); return _get_value(knitro::KN_get_con_value, indexCon); } double KNITROModel::get_constraint_dual(const ConstraintIndex &constraint) const { _check_dirty(); KNINT indexCon = _constraint_index(constraint); double dual = _get_value(knitro::KN_get_con_dual_value, indexCon); return -dual; } void KNITROModel::set_normalized_rhs(const ConstraintIndex &constraint, double rhs) { KNINT indexCon = _constraint_index(constraint); auto flag = m_con_sense_flags[indexCon]; if (flag & CON_LOBND) { _set_value(knitro::KN_set_con_lobnd, indexCon, rhs); } if (flag & CON_UPBND) { _set_value(knitro::KN_set_con_upbnd, indexCon, rhs); } _mark_dirty(); } double KNITROModel::get_normalized_rhs(const ConstraintIndex &constraint) const { KNINT indexCon = _constraint_index(constraint); auto it = m_con_sense_flags.find(indexCon); uint8_t flag = (it != m_con_sense_flags.end()) ? it->second : CON_UPBND; double rhs; if (flag & CON_UPBND) { rhs = _get_value(knitro::KN_get_con_upbnd, indexCon); } else { rhs = _get_value(knitro::KN_get_con_lobnd, indexCon); } return rhs; } void KNITROModel::set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double coefficient) { KNINT indexCon = _constraint_index(constraint); KNINT indexVar = _variable_index(variable); // NOTE: To make sure the coefficient is updated correctly, // we need to call KN_update before changing the linear term _update(); int error = knitro::KN_chg_con_linear_term(m_kc.get(), indexCon, indexVar, coefficient); _check_error(error); _mark_dirty(); } void KNITROModel::_set_linear_constraint(const ConstraintIndex &constraint, const ScalarAffineFunction &function) { KNINT indexCon = _constraint_index(constraint); if (function.constant.has_value()) { double constant = function.constant.value(); if (constant != 0.0) { int error = knitro::KN_add_con_constant(m_kc.get(), indexCon, constant); _check_error(error); } } AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); KNLONG nnz = ptr_form.numnz; if (nnz > 0) { std::vector indexCons(nnz, indexCon); KNINT *indexVars = ptr_form.index; double *coefs = ptr_form.value; int error = knitro::KN_add_con_linear_struct(m_kc.get(), nnz, indexCons.data(), indexVars, coefs); _check_error(error); } } void KNITROModel::_set_quadratic_constraint(const ConstraintIndex &constraint, const ScalarQuadraticFunction &function) { KNINT indexCon = _constraint_index(constraint); if (function.affine_part.has_value()) { _set_linear_constraint(constraint, function.affine_part.value()); } QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); KNLONG nnz = ptr_form.numnz; if (nnz > 0) { std::vector indexCons(nnz, indexCon); KNINT *indexVars1 = ptr_form.row; KNINT *indexVars2 = ptr_form.col; double *coefs = ptr_form.value; int error = knitro::KN_add_con_quadratic_struct(m_kc.get(), nnz, indexCons.data(), indexVars1, indexVars2, coefs); _check_error(error); } } // Objective functions void KNITROModel::set_objective(const ScalarAffineFunction &function, ObjectiveSense sense) { _set_objective_impl(sense, [this, &function]() { _set_linear_objective(function); }); } void KNITROModel::set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense) { _set_objective_impl(sense, [this, &function]() { _set_quadratic_objective(function); }); } void KNITROModel::set_objective(const ExprBuilder &expr, ObjectiveSense sense) { auto degree = expr.degree(); if (degree <= 1) { ScalarAffineFunction linear(expr); set_objective(linear, sense); } else if (degree == 2) { ScalarQuadraticFunction quadratic(expr); set_objective(quadratic, sense); } else { throw std::runtime_error("Objective must be linear or quadratic"); } } void KNITROModel::set_objective_coefficient(const VariableIndex &variable, double coefficient) { KNINT indexVar = _variable_index(variable); // NOTE: To make sure the coefficient is updated correctly, // we need to call KN_update before changing the linear term _update(); int error = knitro::KN_chg_obj_linear_term(m_kc.get(), indexVar, coefficient); _check_error(error); _mark_dirty(); } void KNITROModel::add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result) { _add_graph(graph); graph.add_objective_output(result); size_t i = graph.m_objective_outputs.size() - 1; m_pending_outputs[&graph].objective_outputs.push_back(i); m_has_pending_callbacks = true; m_obj_flag |= OBJ_NONLINEAR; _mark_dirty(); } void KNITROModel::set_obj_sense(ObjectiveSense sense) { int goal = knitro_obj_goal(sense); _set_value(knitro::KN_set_obj_goal, goal); } ObjectiveSense KNITROModel::get_obj_sense() const { int goal = _get_value(knitro::KN_get_obj_goal); return knitro_obj_sense(goal); } void KNITROModel::_add_graph(ExpressionGraph &graph) { if (m_pending_outputs.find(&graph) == m_pending_outputs.end()) { m_pending_outputs[&graph] = Outputs(); } } void KNITROModel::_reset_objective() { if (m_obj_flag & OBJ_CONSTANT) { int error = knitro::KN_del_obj_constant(m_kc.get()); _check_error(error); } if (m_obj_flag & OBJ_LINEAR) { int error = knitro::KN_del_obj_linear_struct_all(m_kc.get()); _check_error(error); } if (m_obj_flag & OBJ_QUADRATIC) { int error = knitro::KN_del_obj_quadratic_struct_all(m_kc.get()); _check_error(error); } if (m_obj_flag & OBJ_NONLINEAR) { int error = knitro::KN_del_obj_eval_callback_all(m_kc.get()); _check_error(error); for (auto &[graph, outputs] : m_pending_outputs) { outputs.objective_outputs.clear(); } } m_obj_flag = 0; _update(); } void KNITROModel::_set_linear_objective(const ScalarAffineFunction &function) { if (function.constant.has_value()) { double constant = function.constant.value(); if (constant != 0.0) { int error = knitro::KN_add_obj_constant(m_kc.get(), constant); _check_error(error); m_obj_flag |= OBJ_CONSTANT; } } AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); KNLONG nnz = ptr_form.numnz; if (nnz > 0) { KNINT *indexVars = ptr_form.index; double *coefs = ptr_form.value; int error = knitro::KN_add_obj_linear_struct(m_kc.get(), nnz, indexVars, coefs); _check_error(error); m_obj_flag |= OBJ_LINEAR; } } void KNITROModel::_set_quadratic_objective(const ScalarQuadraticFunction &function) { if (function.affine_part.has_value()) { _set_linear_objective(function.affine_part.value()); } QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); KNLONG nnz = ptr_form.numnz; if (nnz > 0) { KNINT *indexVars1 = ptr_form.row; KNINT *indexVars2 = ptr_form.col; double *coefs = ptr_form.value; int error = knitro::KN_add_obj_quadratic_struct(m_kc.get(), nnz, indexVars1, indexVars2, coefs); _check_error(error); m_obj_flag |= OBJ_QUADRATIC; } } double KNITROModel::get_obj_value() const { _check_dirty(); return _get_value(knitro::KN_get_obj_value); } void KNITROModel::_register_callback(Evaluator *evaluator) { auto f = [](KN_context *, CB_context *cb, KN_eval_request *req, KN_eval_result *res, void *data) -> int { auto evaluator = static_cast(data); if (evaluator->is_objective()) { evaluator->eval_fun(req->x, res->obj); } else { evaluator->eval_fun(req->x, res->c); } return 0; }; auto g = [](KN_context *, CB_context *cb, KN_eval_request *req, KN_eval_result *res, void *data) -> int { auto evaluator = static_cast(data); if (evaluator->is_objective()) { evaluator->eval_jac(req->x, res->objGrad); } else { evaluator->eval_jac(req->x, res->jac); } return 0; }; auto h = [](KN_context *, CB_context *cb, KN_eval_request *req, KN_eval_result *res, void *data) -> int { auto evaluator = static_cast(data); if (evaluator->is_objective()) { evaluator->eval_hess(req->x, req->sigma, res->hess); } else { evaluator->eval_hess(req->x, req->lambda, res->hess); } return 0; }; CB_context *cb = nullptr; auto p = evaluator->get_callback_pattern(); int error; error = knitro::KN_add_eval_callback(m_kc.get(), p.indexCons.empty(), p.indexCons.size(), p.indexCons.data(), f, &cb); _check_error(error); error = knitro::KN_set_cb_user_params(m_kc.get(), cb, evaluator); _check_error(error); error = knitro::KN_set_cb_grad(m_kc.get(), cb, p.objGradIndexVars.size(), p.objGradIndexVars.data(), p.jacIndexCons.size(), p.jacIndexCons.data(), p.jacIndexVars.data(), g); _check_error(error); error = knitro::KN_set_cb_hess(m_kc.get(), cb, p.hessIndexVars1.size(), p.hessIndexVars1.data(), p.hessIndexVars2.data(), h); _check_error(error); } void KNITROModel::_add_callback(const ExpressionGraph &graph, const std::vector &outputs, const std::vector &constraints) { auto evaluator_ptr = std::make_unique(); auto *evaluator = evaluator_ptr.get(); evaluator->indexVars.resize(graph.n_variables()); for (size_t i = 0; i < graph.n_variables(); i++) { evaluator->indexVars[i] = _variable_index(graph.m_variables[i]); } evaluator->indexCons.resize(constraints.size()); for (size_t i = 0; i < constraints.size(); i++) { evaluator->indexCons[i] = _constraint_index(constraints[i]); } if (evaluator->is_objective()) { evaluator->fun = cppad_trace_graph_objective(graph, outputs, true); } else { evaluator->fun = cppad_trace_graph_constraints(graph, outputs); } evaluator->setup(); _register_callback(evaluator); m_evaluators.push_back(std::move(evaluator_ptr)); } void KNITROModel::_add_callbacks(const ExpressionGraph &graph, const Outputs &outputs) { if (graph.has_constraint_output() && !outputs.constraint_outputs.empty()) { _add_callback(graph, outputs.constraint_outputs, outputs.constraints); } if (graph.has_objective_output() && !outputs.objective_outputs.empty()) { _add_callback(graph, outputs.objective_outputs, {}); } } void KNITROModel::_add_pending_callbacks() { if (!m_has_pending_callbacks) { return; } for (const auto &[graph, outputs] : m_pending_outputs) { _add_callbacks(*graph, outputs); } m_pending_outputs.clear(); m_has_pending_callbacks = false; } // Solve functions void KNITROModel::_update() { _add_pending_callbacks(); int error = knitro::KN_update(m_kc.get()); _check_error(error); } void KNITROModel::_pre_solve() { _add_pending_callbacks(); } void KNITROModel::_solve() { m_solve_status = knitro::KN_solve(m_kc.get()); } void KNITROModel::_post_solve() { } void KNITROModel::optimize() { _pre_solve(); _solve(); _post_solve(); _unmark_dirty(); } // Solve information size_t KNITROModel::get_number_iterations() const { _check_dirty(); int iters = _get_value(knitro::KN_get_number_iters); return static_cast(iters); } size_t KNITROModel::get_mip_node_count() const { _check_dirty(); int nodes = _get_value(knitro::KN_get_mip_number_nodes); return static_cast(nodes); } double KNITROModel::get_obj_bound() const { _check_dirty(); return _get_value(knitro::KN_get_mip_relaxation_bnd); } double KNITROModel::get_mip_relative_gap() const { _check_dirty(); return _get_value(knitro::KN_get_mip_rel_gap); } double KNITROModel::get_solve_time() const { _check_dirty(); return _get_value(knitro::KN_get_solve_time_real); } // Dirty state management void KNITROModel::_mark_dirty() { m_is_dirty = true; } void KNITROModel::_unmark_dirty() { m_is_dirty = false; } bool KNITROModel::dirty() const { return m_is_dirty; } // Model state bool KNITROModel::empty() const { return m_kc == nullptr; } size_t KNITROModel::get_num_vars() const { return m_n_vars; } size_t KNITROModel::get_num_cons(std::optional type) const { if (!type.has_value()) { size_t total = 0; for (const auto &[_, count] : m_n_cons_map) { total += count; } return total; } else { auto it = m_n_cons_map.find(type.value()); return it != m_n_cons_map.end() ? it->second : 0; } } int KNITROModel::get_solve_status() const { _check_dirty(); return m_solve_status; } // Internal helpers void KNITROModel::_check_dirty() const { if (dirty()) { throw std::runtime_error("Model has been modified since last solve. Call optimize()..."); } } void KNITROModel::_reset_state() { m_kc.reset(); m_n_vars = 0; m_n_cons_map.clear(); m_soc_aux_cons.clear(); m_con_sense_flags.clear(); m_obj_flag = 0; m_pending_outputs.clear(); m_evaluators.clear(); m_has_pending_callbacks = false; _mark_dirty(); m_solve_status = 0; } void KNITROModel::_init() { ensure_library_loaded(); _reset_state(); // Create new KNITRO problem KN_context *kc = nullptr; int error = m_lm ? knitro::KN_new_lm(m_lm.get(), &kc) : knitro::KN_new(&kc); knitro_throw(error); m_kc = std::unique_ptr(kc); } KNINT KNITROModel::_variable_index(const VariableIndex &variable) const { return _get_index(variable); } KNINT KNITROModel::_constraint_index(const ConstraintIndex &constraint) const { return _get_index(constraint); } ================================================ FILE: lib/knitro_model_ext.cpp ================================================ #include #include #include #include #include #include "pyoptinterface/knitro_model.hpp" namespace nb = nanobind; extern void bind_knitro_constants(nb::module_ &m); NB_MODULE(knitro_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.def("is_library_loaded", &knitro::is_library_loaded); m.def("load_library", &knitro::load_library); m.def("has_valid_license", &knitro::has_valid_license); bind_knitro_constants(m); nb::class_(m, "RawEnv") .def(nb::init(), nb::arg("empty") = false) .def("start", &KNITROEnv::start) .def("empty", &KNITROEnv::empty) .def("close", &KNITROEnv::close); #define BIND_F(f) .def(#f, &KNITROModel::f) nb::class_(m, "RawModel") .def(nb::init<>()) .def(nb::init()) .def("init", nb::overload_cast<>(&KNITROModel::init)) .def("init", nb::overload_cast(&KNITROModel::init)) // clang-format off BIND_F(close) BIND_F(get_infinity) BIND_F(get_number_iterations) BIND_F(get_mip_node_count) BIND_F(get_obj_bound) BIND_F(get_mip_relative_gap) BIND_F(get_solve_time) BIND_F(get_solver_name) BIND_F(get_release) // clang-format on .def("number_of_variables", &KNITROModel::get_num_vars) .def("number_of_constraints", &KNITROModel::get_num_cons, nb::arg("type") = nb::none()) .def("add_variable", &KNITROModel::add_variable, nb::arg("domain") = VariableDomain::Continuous, nb::arg("lb") = -KN_INFINITY, nb::arg("ub") = KN_INFINITY, nb::arg("name") = "") // clang-format off BIND_F(get_variable_lb) BIND_F(get_variable_ub) BIND_F(set_variable_lb) BIND_F(set_variable_ub) BIND_F(set_variable_bounds) BIND_F(set_variable_start) BIND_F(get_variable_name) BIND_F(set_variable_name) BIND_F(set_variable_domain) BIND_F(get_variable_domain) BIND_F(get_variable_rc) BIND_F(delete_variable) // clang-format on .def("get_value", &KNITROModel::get_variable_value) .def("get_value", nb::overload_cast(&KNITROModel::get_expression_value)) .def("get_value", nb::overload_cast(&KNITROModel::get_expression_value)) .def("get_value", nb::overload_cast(&KNITROModel::get_expression_value)) .def("pprint", &KNITROModel::pprint_variable) .def("pprint", nb::overload_cast(&KNITROModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast( &KNITROModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&KNITROModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) // clang-format off BIND_F(set_constraint_name) BIND_F(get_constraint_name) BIND_F(get_constraint_primal) BIND_F(get_constraint_dual) BIND_F(set_normalized_rhs) BIND_F(get_normalized_rhs) BIND_F(set_normalized_coefficient) // clang-format on .def("_add_linear_constraint", nb::overload_cast( &KNITROModel::add_linear_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", nb::overload_cast &, const char *>(&KNITROModel::add_linear_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &KNITROModel::add_linear_constraint_from_var, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &KNITROModel::add_linear_interval_constraint_from_var, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &KNITROModel::add_linear_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &KNITROModel::add_linear_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_quadratic_constraint", nb::overload_cast(&KNITROModel::add_quadratic_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", nb::overload_cast &, const char *>(&KNITROModel::add_quadratic_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_quadratic_constraint", &KNITROModel::add_quadratic_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", &KNITROModel::add_quadratic_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("add_second_order_cone_constraint", &KNITROModel::add_second_order_cone_constraint, nb::arg("variables"), nb::arg("name") = "", nb::arg("rotated") = false) .def("_add_single_nl_constraint", &KNITROModel::add_single_nl_constraint, nb::arg("graph"), nb::arg("result"), nb::arg("interval"), nb::arg("name") = "") .def("_add_single_nl_constraint", &KNITROModel::add_single_nl_constraint_sense_rhs, nb::arg("graph"), nb::arg("result"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_single_nl_constraint", &KNITROModel::add_single_nl_constraint_from_comparison, nb::arg("graph"), nb::arg("expr"), nb::arg("name") = "") // clang-format off BIND_F(delete_constraint) // clang-format on .def("set_objective", nb::overload_cast( &KNITROModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &KNITROModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&KNITROModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &KNITROModel::set_objective_as_variable), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&KNITROModel::set_objective_as_constant), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("_add_single_nl_objective", &KNITROModel::add_single_nl_objective, nb::arg("graph"), nb::arg("result")) // clang-format off BIND_F(set_objective_coefficient) BIND_F(get_obj_value) BIND_F(set_obj_sense) BIND_F(get_obj_sense) // clang-format on .def("optimize", &KNITROModel::optimize, nb::call_guard()) .def( "set_raw_parameter", [](KNITROModel &m, const std::string &name, int value) { m.set_raw_parameter(name, value); }, nb::arg("name"), nb::arg("value")) .def( "set_raw_parameter", [](KNITROModel &m, const std::string &name, double value) { m.set_raw_parameter(name, value); }, nb::arg("name"), nb::arg("value")) .def( "set_raw_parameter", [](KNITROModel &m, const std::string &name, const std::string &value) { m.set_raw_parameter(name, value); }, nb::arg("name"), nb::arg("value")) .def( "set_raw_parameter", [](KNITROModel &m, int param_id, int value) { m.set_raw_parameter(param_id, value); }, nb::arg("param_id"), nb::arg("value")) .def( "set_raw_parameter", [](KNITROModel &m, int param_id, double value) { m.set_raw_parameter(param_id, value); }, nb::arg("param_id"), nb::arg("value")) .def( "set_raw_parameter", [](KNITROModel &m, int param_id, const std::string &value) { m.set_raw_parameter(param_id, value); }, nb::arg("param_id"), nb::arg("value")) .def( "get_raw_parameter", [](KNITROModel &m, const std::string &name) -> int { return m.get_raw_parameter(name); }, nb::arg("name")) .def( "get_raw_parameter", [](KNITROModel &m, const std::string &name) -> double { return m.get_raw_parameter(name); }, nb::arg("name")) .def( "get_raw_parameter", [](KNITROModel &m, int param_id) -> int { return m.get_raw_parameter(param_id); }, nb::arg("param_id")) .def( "get_raw_parameter", [](KNITROModel &m, int param_id) -> double { return m.get_raw_parameter(param_id); }, nb::arg("param_id")) // clang-format off BIND_F(dirty) BIND_F(empty) // clang-format on // clang-format off BIND_F(get_solve_status) // clang-format on ; #undef BIND_F } ================================================ FILE: lib/knitro_model_ext_constants.cpp ================================================ #include #include "pyoptinterface/knitro_model.hpp" namespace nb = nanobind; void bind_knitro_constants(nb::module_ &m) { nb::module_ KN = m.def_submodule("KN"); // Objective goal KN.attr("OBJGOAL_MINIMIZE") = KN_OBJGOAL_MINIMIZE; KN.attr("OBJGOAL_MAXIMIZE") = KN_OBJGOAL_MAXIMIZE; // Variable types KN.attr("VARTYPE_CONTINUOUS") = KN_VARTYPE_CONTINUOUS; KN.attr("VARTYPE_INTEGER") = KN_VARTYPE_INTEGER; KN.attr("VARTYPE_BINARY") = KN_VARTYPE_BINARY; // Infinity KN.attr("INFINITY") = KN_INFINITY; // Return codes - Optimal/Satisfactory KN.attr("RC_OPTIMAL_OR_SATISFACTORY") = KN_RC_OPTIMAL_OR_SATISFACTORY; KN.attr("RC_OPTIMAL") = KN_RC_OPTIMAL; // Return codes - Feasible KN.attr("RC_NEAR_OPT") = KN_RC_NEAR_OPT; KN.attr("RC_FEAS_XTOL") = KN_RC_FEAS_XTOL; KN.attr("RC_FEAS_NO_IMPROVE") = KN_RC_FEAS_NO_IMPROVE; KN.attr("RC_FEAS_FTOL") = KN_RC_FEAS_FTOL; KN.attr("RC_FEAS_BEST") = KN_RC_FEAS_BEST; KN.attr("RC_FEAS_MULTISTART") = KN_RC_FEAS_MULTISTART; // Return codes - Infeasible KN.attr("RC_INFEASIBLE") = KN_RC_INFEASIBLE; KN.attr("RC_INFEAS_XTOL") = KN_RC_INFEAS_XTOL; KN.attr("RC_INFEAS_NO_IMPROVE") = KN_RC_INFEAS_NO_IMPROVE; KN.attr("RC_INFEAS_MULTISTART") = KN_RC_INFEAS_MULTISTART; KN.attr("RC_INFEAS_CON_BOUNDS") = KN_RC_INFEAS_CON_BOUNDS; KN.attr("RC_INFEAS_VAR_BOUNDS") = KN_RC_INFEAS_VAR_BOUNDS; // Return codes - Unbounded KN.attr("RC_UNBOUNDED") = KN_RC_UNBOUNDED; KN.attr("RC_UNBOUNDED_OR_INFEAS") = KN_RC_UNBOUNDED_OR_INFEAS; // Return codes - Limit exceeded (feasible) KN.attr("RC_ITER_LIMIT_FEAS") = KN_RC_ITER_LIMIT_FEAS; KN.attr("RC_TIME_LIMIT_FEAS") = KN_RC_TIME_LIMIT_FEAS; KN.attr("RC_FEVAL_LIMIT_FEAS") = KN_RC_FEVAL_LIMIT_FEAS; KN.attr("RC_MIP_EXH_FEAS") = KN_RC_MIP_EXH_FEAS; KN.attr("RC_MIP_TERM_FEAS") = KN_RC_MIP_TERM_FEAS; KN.attr("RC_MIP_SOLVE_LIMIT_FEAS") = KN_RC_MIP_SOLVE_LIMIT_FEAS; KN.attr("RC_MIP_NODE_LIMIT_FEAS") = KN_RC_MIP_NODE_LIMIT_FEAS; // Return codes - Limit exceeded (infeasible) KN.attr("RC_ITER_LIMIT_INFEAS") = KN_RC_ITER_LIMIT_INFEAS; KN.attr("RC_TIME_LIMIT_INFEAS") = KN_RC_TIME_LIMIT_INFEAS; KN.attr("RC_FEVAL_LIMIT_INFEAS") = KN_RC_FEVAL_LIMIT_INFEAS; KN.attr("RC_MIP_EXH_INFEAS") = KN_RC_MIP_EXH_INFEAS; KN.attr("RC_MIP_SOLVE_LIMIT_INFEAS") = KN_RC_MIP_SOLVE_LIMIT_INFEAS; KN.attr("RC_MIP_NODE_LIMIT_INFEAS") = KN_RC_MIP_NODE_LIMIT_INFEAS; // Return codes - Other failures KN.attr("RC_CALLBACK_ERR") = KN_RC_CALLBACK_ERR; KN.attr("RC_LP_SOLVER_ERR") = KN_RC_LP_SOLVER_ERR; KN.attr("RC_EVAL_ERR") = KN_RC_EVAL_ERR; KN.attr("RC_OUT_OF_MEMORY") = KN_RC_OUT_OF_MEMORY; KN.attr("RC_USER_TERMINATION") = KN_RC_USER_TERMINATION; KN.attr("RC_OPEN_FILE_ERR") = KN_RC_OPEN_FILE_ERR; // Return codes - Problem definition errors KN.attr("RC_BAD_N_OR_F") = KN_RC_BAD_N_OR_F; KN.attr("RC_BAD_CONSTRAINT") = KN_RC_BAD_CONSTRAINT; KN.attr("RC_BAD_JACOBIAN") = KN_RC_BAD_JACOBIAN; KN.attr("RC_BAD_HESSIAN") = KN_RC_BAD_HESSIAN; KN.attr("RC_BAD_CON_INDEX") = KN_RC_BAD_CON_INDEX; KN.attr("RC_BAD_JAC_INDEX") = KN_RC_BAD_JAC_INDEX; KN.attr("RC_BAD_HESS_INDEX") = KN_RC_BAD_HESS_INDEX; KN.attr("RC_BAD_CON_BOUNDS") = KN_RC_BAD_CON_BOUNDS; KN.attr("RC_BAD_VAR_BOUNDS") = KN_RC_BAD_VAR_BOUNDS; KN.attr("RC_ILLEGAL_CALL") = KN_RC_ILLEGAL_CALL; KN.attr("RC_BAD_KCPTR") = KN_RC_BAD_KCPTR; KN.attr("RC_NULL_POINTER") = KN_RC_NULL_POINTER; KN.attr("RC_BAD_INIT_VALUE") = KN_RC_BAD_INIT_VALUE; KN.attr("RC_LICENSE_ERROR") = KN_RC_LICENSE_ERROR; KN.attr("RC_BAD_PARAMINPUT") = KN_RC_BAD_PARAMINPUT; KN.attr("RC_LINEAR_SOLVER_ERR") = KN_RC_LINEAR_SOLVER_ERR; KN.attr("RC_DERIV_CHECK_FAILED") = KN_RC_DERIV_CHECK_FAILED; KN.attr("RC_DERIV_CHECK_TERMINATE") = KN_RC_DERIV_CHECK_TERMINATE; KN.attr("RC_OVERFLOW_ERR") = KN_RC_OVERFLOW_ERR; KN.attr("RC_BAD_SIZE") = KN_RC_BAD_SIZE; KN.attr("RC_BAD_VARIABLE") = KN_RC_BAD_VARIABLE; KN.attr("RC_BAD_VAR_INDEX") = KN_RC_BAD_VAR_INDEX; KN.attr("RC_BAD_OBJECTIVE") = KN_RC_BAD_OBJECTIVE; KN.attr("RC_BAD_OBJ_INDEX") = KN_RC_BAD_OBJ_INDEX; KN.attr("RC_BAD_RESIDUAL") = KN_RC_BAD_RESIDUAL; KN.attr("RC_BAD_RSD_INDEX") = KN_RC_BAD_RSD_INDEX; KN.attr("RC_INTERNAL_ERROR") = KN_RC_INTERNAL_ERROR; // Callback request codes KN.attr("RC_EVALFC") = KN_RC_EVALFC; KN.attr("RC_EVALGA") = KN_RC_EVALGA; KN.attr("RC_EVALH") = KN_RC_EVALH; KN.attr("RC_EVALHV") = KN_RC_EVALHV; KN.attr("RC_EVALH_NO_F") = KN_RC_EVALH_NO_F; KN.attr("RC_EVALHV_NO_F") = KN_RC_EVALHV_NO_F; KN.attr("RC_EVALR") = KN_RC_EVALR; KN.attr("RC_EVALRJ") = KN_RC_EVALRJ; KN.attr("RC_EVALFCGA") = KN_RC_EVALFCGA; // Parameter types KN.attr("PARAMTYPE_INTEGER") = KN_PARAMTYPE_INTEGER; KN.attr("PARAMTYPE_FLOAT") = KN_PARAMTYPE_FLOAT; KN.attr("PARAMTYPE_STRING") = KN_PARAMTYPE_STRING; // Parameter IDs KN.attr("PARAM_NEWPOINT") = KN_PARAM_NEWPOINT; KN.attr("PARAM_HONORBNDS") = KN_PARAM_HONORBNDS; KN.attr("PARAM_ALGORITHM") = KN_PARAM_ALGORITHM; KN.attr("PARAM_ALG") = KN_PARAM_ALG; KN.attr("PARAM_NLP_ALGORITHM") = KN_PARAM_NLP_ALGORITHM; KN.attr("PARAM_BAR_MURULE") = KN_PARAM_BAR_MURULE; KN.attr("PARAM_BAR_FEASIBLE") = KN_PARAM_BAR_FEASIBLE; KN.attr("PARAM_GRADOPT") = KN_PARAM_GRADOPT; KN.attr("PARAM_HESSOPT") = KN_PARAM_HESSOPT; KN.attr("PARAM_BAR_INITPT") = KN_PARAM_BAR_INITPT; KN.attr("PARAM_ACT_LPSOLVER") = KN_PARAM_ACT_LPSOLVER; KN.attr("PARAM_CG_MAXIT") = KN_PARAM_CG_MAXIT; KN.attr("PARAM_MAXIT") = KN_PARAM_MAXIT; KN.attr("PARAM_OUTLEV") = KN_PARAM_OUTLEV; KN.attr("PARAM_OUTMODE") = KN_PARAM_OUTMODE; KN.attr("PARAM_SCALE") = KN_PARAM_SCALE; KN.attr("PARAM_SOC") = KN_PARAM_SOC; KN.attr("PARAM_DELTA") = KN_PARAM_DELTA; KN.attr("PARAM_BAR_FEASMODETOL") = KN_PARAM_BAR_FEASMODETOL; KN.attr("PARAM_FEASTOL") = KN_PARAM_FEASTOL; KN.attr("PARAM_FEASTOLABS") = KN_PARAM_FEASTOLABS; KN.attr("PARAM_MAXTIMECPU") = KN_PARAM_MAXTIMECPU; KN.attr("PARAM_BAR_INITMU") = KN_PARAM_BAR_INITMU; KN.attr("PARAM_OBJRANGE") = KN_PARAM_OBJRANGE; KN.attr("PARAM_OPTTOL") = KN_PARAM_OPTTOL; KN.attr("PARAM_OPTTOLABS") = KN_PARAM_OPTTOLABS; KN.attr("PARAM_LINSOLVER_PIVOTTOL") = KN_PARAM_LINSOLVER_PIVOTTOL; KN.attr("PARAM_XTOL") = KN_PARAM_XTOL; KN.attr("PARAM_DEBUG") = KN_PARAM_DEBUG; KN.attr("PARAM_MULTISTART") = KN_PARAM_MULTISTART; KN.attr("PARAM_MS_ENABLE") = KN_PARAM_MS_ENABLE; KN.attr("PARAM_MS_MAXSOLVES") = KN_PARAM_MS_MAXSOLVES; KN.attr("PARAM_MS_MAXBNDRANGE") = KN_PARAM_MS_MAXBNDRANGE; KN.attr("PARAM_MS_MAXTIMECPU") = KN_PARAM_MS_MAXTIMECPU; KN.attr("PARAM_MS_MAXTIMEREAL") = KN_PARAM_MS_MAXTIMEREAL; KN.attr("PARAM_LMSIZE") = KN_PARAM_LMSIZE; KN.attr("PARAM_BAR_MAXCROSSIT") = KN_PARAM_BAR_MAXCROSSIT; KN.attr("PARAM_MAXTIMEREAL") = KN_PARAM_MAXTIMEREAL; KN.attr("PARAM_CG_PRECOND") = KN_PARAM_CG_PRECOND; KN.attr("PARAM_BLASOPTION") = KN_PARAM_BLASOPTION; KN.attr("PARAM_BAR_MAXREFACTOR") = KN_PARAM_BAR_MAXREFACTOR; KN.attr("PARAM_LINESEARCH_MAXTRIALS") = KN_PARAM_LINESEARCH_MAXTRIALS; KN.attr("PARAM_BLASOPTIONLIB") = KN_PARAM_BLASOPTIONLIB; KN.attr("PARAM_OUTAPPEND") = KN_PARAM_OUTAPPEND; KN.attr("PARAM_OUTDIR") = KN_PARAM_OUTDIR; KN.attr("PARAM_CPLEXLIB") = KN_PARAM_CPLEXLIB; KN.attr("PARAM_BAR_PENRULE") = KN_PARAM_BAR_PENRULE; KN.attr("PARAM_BAR_PENCONS") = KN_PARAM_BAR_PENCONS; KN.attr("PARAM_MS_NUMTOSAVE") = KN_PARAM_MS_NUMTOSAVE; KN.attr("PARAM_MS_SAVETOL") = KN_PARAM_MS_SAVETOL; KN.attr("PARAM_MS_TERMINATE") = KN_PARAM_MS_TERMINATE; KN.attr("PARAM_MS_STARTPTRANGE") = KN_PARAM_MS_STARTPTRANGE; KN.attr("PARAM_INFEASTOL") = KN_PARAM_INFEASTOL; KN.attr("PARAM_LINSOLVER") = KN_PARAM_LINSOLVER; KN.attr("PARAM_BAR_DIRECTINTERVAL") = KN_PARAM_BAR_DIRECTINTERVAL; KN.attr("PARAM_PRESOLVE") = KN_PARAM_PRESOLVE; KN.attr("PARAM_PRESOLVE_TOL") = KN_PARAM_PRESOLVE_TOL; KN.attr("PARAM_BAR_SWITCHRULE") = KN_PARAM_BAR_SWITCHRULE; KN.attr("PARAM_HESSIAN_NO_F") = KN_PARAM_HESSIAN_NO_F; KN.attr("PARAM_MA_TERMINATE") = KN_PARAM_MA_TERMINATE; KN.attr("PARAM_MA_MAXTIMECPU") = KN_PARAM_MA_MAXTIMECPU; KN.attr("PARAM_MA_MAXTIMEREAL") = KN_PARAM_MA_MAXTIMEREAL; KN.attr("PARAM_MS_SEED") = KN_PARAM_MS_SEED; KN.attr("PARAM_MA_OUTSUB") = KN_PARAM_MA_OUTSUB; KN.attr("PARAM_MS_OUTSUB") = KN_PARAM_MS_OUTSUB; KN.attr("PARAM_XPRESSLIB") = KN_PARAM_XPRESSLIB; KN.attr("PARAM_TUNER") = KN_PARAM_TUNER; KN.attr("PARAM_TUNER_OPTIONSFILE") = KN_PARAM_TUNER_OPTIONSFILE; KN.attr("PARAM_TUNER_MAXTIMECPU") = KN_PARAM_TUNER_MAXTIMECPU; KN.attr("PARAM_TUNER_MAXTIMEREAL") = KN_PARAM_TUNER_MAXTIMEREAL; KN.attr("PARAM_TUNER_OUTSUB") = KN_PARAM_TUNER_OUTSUB; KN.attr("PARAM_TUNER_TERMINATE") = KN_PARAM_TUNER_TERMINATE; KN.attr("PARAM_LINSOLVER_OOC") = KN_PARAM_LINSOLVER_OOC; KN.attr("PARAM_BAR_RELAXCONS") = KN_PARAM_BAR_RELAXCONS; KN.attr("PARAM_MS_DETERMINISTIC") = KN_PARAM_MS_DETERMINISTIC; KN.attr("PARAM_BAR_REFINEMENT") = KN_PARAM_BAR_REFINEMENT; KN.attr("PARAM_DERIVCHECK") = KN_PARAM_DERIVCHECK; KN.attr("PARAM_DERIVCHECK_TYPE") = KN_PARAM_DERIVCHECK_TYPE; KN.attr("PARAM_DERIVCHECK_TOL") = KN_PARAM_DERIVCHECK_TOL; KN.attr("PARAM_MAXFEVALS") = KN_PARAM_MAXFEVALS; KN.attr("PARAM_FSTOPVAL") = KN_PARAM_FSTOPVAL; KN.attr("PARAM_DATACHECK") = KN_PARAM_DATACHECK; KN.attr("PARAM_DERIVCHECK_TERMINATE") = KN_PARAM_DERIVCHECK_TERMINATE; KN.attr("PARAM_BAR_WATCHDOG") = KN_PARAM_BAR_WATCHDOG; KN.attr("PARAM_FTOL") = KN_PARAM_FTOL; KN.attr("PARAM_FTOL_ITERS") = KN_PARAM_FTOL_ITERS; KN.attr("PARAM_ACT_QPALG") = KN_PARAM_ACT_QPALG; KN.attr("PARAM_BAR_INITPI_MPEC") = KN_PARAM_BAR_INITPI_MPEC; KN.attr("PARAM_XTOL_ITERS") = KN_PARAM_XTOL_ITERS; KN.attr("PARAM_LINESEARCH") = KN_PARAM_LINESEARCH; KN.attr("PARAM_OUT_CSVINFO") = KN_PARAM_OUT_CSVINFO; KN.attr("PARAM_INITPENALTY") = KN_PARAM_INITPENALTY; KN.attr("PARAM_ACT_LPFEASTOL") = KN_PARAM_ACT_LPFEASTOL; KN.attr("PARAM_CG_STOPTOL") = KN_PARAM_CG_STOPTOL; KN.attr("PARAM_RESTARTS") = KN_PARAM_RESTARTS; KN.attr("PARAM_RESTARTS_MAXIT") = KN_PARAM_RESTARTS_MAXIT; KN.attr("PARAM_BAR_SLACKBOUNDPUSH") = KN_PARAM_BAR_SLACKBOUNDPUSH; KN.attr("PARAM_CG_PMEM") = KN_PARAM_CG_PMEM; KN.attr("PARAM_BAR_SWITCHOBJ") = KN_PARAM_BAR_SWITCHOBJ; KN.attr("PARAM_OUTNAME") = KN_PARAM_OUTNAME; KN.attr("PARAM_OUT_CSVNAME") = KN_PARAM_OUT_CSVNAME; KN.attr("PARAM_ACT_PARAMETRIC") = KN_PARAM_ACT_PARAMETRIC; KN.attr("PARAM_ACT_LPDUMPMPS") = KN_PARAM_ACT_LPDUMPMPS; KN.attr("PARAM_ACT_LPALG") = KN_PARAM_ACT_LPALG; KN.attr("PARAM_ACT_LPPRESOLVE") = KN_PARAM_ACT_LPPRESOLVE; KN.attr("PARAM_ACT_LPPENALTY") = KN_PARAM_ACT_LPPENALTY; KN.attr("PARAM_BNDRANGE") = KN_PARAM_BNDRANGE; KN.attr("PARAM_BAR_CONIC_ENABLE") = KN_PARAM_BAR_CONIC_ENABLE; KN.attr("PARAM_CONVEX") = KN_PARAM_CONVEX; KN.attr("PARAM_OUT_HINTS") = KN_PARAM_OUT_HINTS; KN.attr("PARAM_EVAL_FCGA") = KN_PARAM_EVAL_FCGA; KN.attr("PARAM_BAR_MAXCORRECTORS") = KN_PARAM_BAR_MAXCORRECTORS; KN.attr("PARAM_STRAT_WARM_START") = KN_PARAM_STRAT_WARM_START; KN.attr("PARAM_FINDIFF_TERMINATE") = KN_PARAM_FINDIFF_TERMINATE; KN.attr("PARAM_CPUPLATFORM") = KN_PARAM_CPUPLATFORM; KN.attr("PARAM_PRESOLVE_PASSES") = KN_PARAM_PRESOLVE_PASSES; KN.attr("PARAM_PRESOLVE_LEVEL") = KN_PARAM_PRESOLVE_LEVEL; KN.attr("PARAM_FINDIFF_RELSTEPSIZE") = KN_PARAM_FINDIFF_RELSTEPSIZE; KN.attr("PARAM_INFEASTOL_ITERS") = KN_PARAM_INFEASTOL_ITERS; KN.attr("PARAM_PRESOLVEOP_TIGHTEN") = KN_PARAM_PRESOLVEOP_TIGHTEN; KN.attr("PARAM_BAR_LINSYS") = KN_PARAM_BAR_LINSYS; KN.attr("PARAM_PRESOLVE_INITPT") = KN_PARAM_PRESOLVE_INITPT; KN.attr("PARAM_ACT_QPPENALTY") = KN_PARAM_ACT_QPPENALTY; KN.attr("PARAM_BAR_LINSYS_STORAGE") = KN_PARAM_BAR_LINSYS_STORAGE; KN.attr("PARAM_LINSOLVER_MAXITREF") = KN_PARAM_LINSOLVER_MAXITREF; KN.attr("PARAM_BFGS_SCALING") = KN_PARAM_BFGS_SCALING; KN.attr("PARAM_BAR_INITSHIFTTOL") = KN_PARAM_BAR_INITSHIFTTOL; KN.attr("PARAM_NUMTHREADS") = KN_PARAM_NUMTHREADS; KN.attr("PARAM_CONCURRENT_EVALS") = KN_PARAM_CONCURRENT_EVALS; KN.attr("PARAM_BLAS_NUMTHREADS") = KN_PARAM_BLAS_NUMTHREADS; KN.attr("PARAM_LINSOLVER_NUMTHREADS") = KN_PARAM_LINSOLVER_NUMTHREADS; KN.attr("PARAM_MS_NUMTHREADS") = KN_PARAM_MS_NUMTHREADS; KN.attr("PARAM_CONIC_NUMTHREADS") = KN_PARAM_CONIC_NUMTHREADS; KN.attr("PARAM_NCVX_QCQP_INIT") = KN_PARAM_NCVX_QCQP_INIT; KN.attr("PARAM_FINDIFF_ESTNOISE") = KN_PARAM_FINDIFF_ESTNOISE; KN.attr("PARAM_FINDIFF_NUMTHREADS") = KN_PARAM_FINDIFF_NUMTHREADS; KN.attr("PARAM_BAR_MPEC_HEURISTIC") = KN_PARAM_BAR_MPEC_HEURISTIC; KN.attr("PARAM_PRESOLVEOP_REDUNDANT") = KN_PARAM_PRESOLVEOP_REDUNDANT; KN.attr("PARAM_LINSOLVER_ORDERING") = KN_PARAM_LINSOLVER_ORDERING; KN.attr("PARAM_LINSOLVER_NODEAMALG") = KN_PARAM_LINSOLVER_NODEAMALG; KN.attr("PARAM_PRESOLVEOP_SUBSTITUTION") = KN_PARAM_PRESOLVEOP_SUBSTITUTION; KN.attr("PARAM_PRESOLVEOP_SUBSTITUTION_TOL") = KN_PARAM_PRESOLVEOP_SUBSTITUTION_TOL; KN.attr("PARAM_MS_INITPT_CLUSTER") = KN_PARAM_MS_INITPT_CLUSTER; KN.attr("PARAM_SCALE_VARS") = KN_PARAM_SCALE_VARS; KN.attr("PARAM_BAR_MAXMU") = KN_PARAM_BAR_MAXMU; KN.attr("PARAM_BAR_GLOBALIZE") = KN_PARAM_BAR_GLOBALIZE; KN.attr("PARAM_LINSOLVER_SCALING") = KN_PARAM_LINSOLVER_SCALING; KN.attr("PARAM_INITPT_STRATEGY") = KN_PARAM_INITPT_STRATEGY; KN.attr("PARAM_EVAL_COST") = KN_PARAM_EVAL_COST; KN.attr("PARAM_MS_TERMINATERULE_TOL") = KN_PARAM_MS_TERMINATERULE_TOL; KN.attr("PARAM_SOLTYPE") = KN_PARAM_SOLTYPE; KN.attr("PARAM_MAXTIME") = KN_PARAM_MAXTIME; KN.attr("PARAM_LP_ALGORITHM") = KN_PARAM_LP_ALGORITHM; KN.attr("PARAM_LP_ALG") = KN_PARAM_LP_ALG; KN.attr("PARAM_AL_INITPENALTY") = KN_PARAM_AL_INITPENALTY; KN.attr("PARAM_AL_MAXPENALTY") = KN_PARAM_AL_MAXPENALTY; KN.attr("PARAM_PRESOLVEOP_PROBING") = KN_PARAM_PRESOLVEOP_PROBING; // Algorithm values KN.attr("ALG_AUTOMATIC") = KN_ALG_AUTOMATIC; KN.attr("ALG_AUTO") = KN_ALG_AUTO; KN.attr("ALG_BAR_DIRECT") = KN_ALG_BAR_DIRECT; KN.attr("ALG_BAR_CG") = KN_ALG_BAR_CG; KN.attr("ALG_ACT_CG") = KN_ALG_ACT_CG; KN.attr("ALG_ACT_SQP") = KN_ALG_ACT_SQP; KN.attr("ALG_MULTI") = KN_ALG_MULTI; KN.attr("ALG_AL") = KN_ALG_AL; // NLP Algorithm values KN.attr("NLP_ALG_AUTOMATIC") = KN_NLP_ALG_AUTOMATIC; KN.attr("NLP_ALG_AUTO") = KN_NLP_ALG_AUTO; KN.attr("NLP_ALG_BAR_DIRECT") = KN_NLP_ALG_BAR_DIRECT; KN.attr("NLP_ALG_BAR_CG") = KN_NLP_ALG_BAR_CG; KN.attr("NLP_ALG_ACT_CG") = KN_NLP_ALG_ACT_CG; KN.attr("NLP_ALG_ACT_SQP") = KN_NLP_ALG_ACT_SQP; KN.attr("NLP_ALG_MULTI") = KN_NLP_ALG_MULTI; KN.attr("NLP_ALG_AL") = KN_NLP_ALG_AL; // Bar murule values KN.attr("BAR_MURULE_AUTOMATIC") = KN_BAR_MURULE_AUTOMATIC; KN.attr("BAR_MURULE_AUTO") = KN_BAR_MURULE_AUTO; KN.attr("BAR_MURULE_MONOTONE") = KN_BAR_MURULE_MONOTONE; KN.attr("BAR_MURULE_ADAPTIVE") = KN_BAR_MURULE_ADAPTIVE; KN.attr("BAR_MURULE_PROBING") = KN_BAR_MURULE_PROBING; KN.attr("BAR_MURULE_DAMPMPC") = KN_BAR_MURULE_DAMPMPC; KN.attr("BAR_MURULE_FULLMPC") = KN_BAR_MURULE_FULLMPC; KN.attr("BAR_MURULE_QUALITY") = KN_BAR_MURULE_QUALITY; // Gradient options KN.attr("GRADOPT_EXACT") = KN_GRADOPT_EXACT; KN.attr("GRADOPT_FORWARD") = KN_GRADOPT_FORWARD; KN.attr("GRADOPT_CENTRAL") = KN_GRADOPT_CENTRAL; KN.attr("GRADOPT_USER_FORWARD") = KN_GRADOPT_USER_FORWARD; KN.attr("GRADOPT_USER_CENTRAL") = KN_GRADOPT_USER_CENTRAL; // Hessian options KN.attr("HESSOPT_AUTO") = KN_HESSOPT_AUTO; KN.attr("HESSOPT_EXACT") = KN_HESSOPT_EXACT; KN.attr("HESSOPT_BFGS") = KN_HESSOPT_BFGS; KN.attr("HESSOPT_SR1") = KN_HESSOPT_SR1; KN.attr("HESSOPT_PRODUCT_FINDIFF") = KN_HESSOPT_PRODUCT_FINDIFF; KN.attr("HESSOPT_PRODUCT") = KN_HESSOPT_PRODUCT; KN.attr("HESSOPT_LBFGS") = KN_HESSOPT_LBFGS; KN.attr("HESSOPT_GAUSS_NEWTON") = KN_HESSOPT_GAUSS_NEWTON; // Output level values KN.attr("OUTLEV_NONE") = KN_OUTLEV_NONE; KN.attr("OUTLEV_SUMMARY") = KN_OUTLEV_SUMMARY; KN.attr("OUTLEV_ITER_10") = KN_OUTLEV_ITER_10; KN.attr("OUTLEV_ITER") = KN_OUTLEV_ITER; KN.attr("OUTLEV_ITER_VERBOSE") = KN_OUTLEV_ITER_VERBOSE; KN.attr("OUTLEV_ITER_X") = KN_OUTLEV_ITER_X; KN.attr("OUTLEV_ALL") = KN_OUTLEV_ALL; // Linear solver values KN.attr("LINSOLVER_AUTO") = KN_LINSOLVER_AUTO; KN.attr("LINSOLVER_INTERNAL") = KN_LINSOLVER_INTERNAL; KN.attr("LINSOLVER_HYBRID") = KN_LINSOLVER_HYBRID; KN.attr("LINSOLVER_DENSEQR") = KN_LINSOLVER_DENSEQR; KN.attr("LINSOLVER_MA27") = KN_LINSOLVER_MA27; KN.attr("LINSOLVER_MA57") = KN_LINSOLVER_MA57; KN.attr("LINSOLVER_MKLPARDISO") = KN_LINSOLVER_MKLPARDISO; KN.attr("LINSOLVER_MA97") = KN_LINSOLVER_MA97; KN.attr("LINSOLVER_MA86") = KN_LINSOLVER_MA86; KN.attr("LINSOLVER_APPLE") = KN_LINSOLVER_APPLE; // Presolve values KN.attr("PRESOLVE_NO") = KN_PRESOLVE_NO; KN.attr("PRESOLVE_YES") = KN_PRESOLVE_YES; // Derivative check values KN.attr("DERIVCHECK_NONE") = KN_DERIVCHECK_NONE; KN.attr("DERIVCHECK_FIRST") = KN_DERIVCHECK_FIRST; KN.attr("DERIVCHECK_SECOND") = KN_DERIVCHECK_SECOND; KN.attr("DERIVCHECK_ALL") = KN_DERIVCHECK_ALL; // Linesearch values KN.attr("LINESEARCH_AUTO") = KN_LINESEARCH_AUTO; KN.attr("LINESEARCH_BACKTRACK") = KN_LINESEARCH_BACKTRACK; KN.attr("LINESEARCH_INTERPOLATE") = KN_LINESEARCH_INTERPOLATE; KN.attr("LINESEARCH_WEAKWOLFE") = KN_LINESEARCH_WEAKWOLFE; // LP Algorithm values KN.attr("LP_ALG_AUTO") = KN_LP_ALG_AUTO; KN.attr("LP_ALG_NLPALGORITHM") = KN_LP_ALG_NLPALGORITHM; KN.attr("LP_ALG_PRIMALSIMPLEX") = KN_LP_ALG_PRIMALSIMPLEX; KN.attr("LP_ALG_DUALSIMPLEX") = KN_LP_ALG_DUALSIMPLEX; KN.attr("LP_ALG_BARRIER") = KN_LP_ALG_BARRIER; KN.attr("LP_ALG_PDLP") = KN_LP_ALG_PDLP; // MIP parameter IDs KN.attr("PARAM_MIP_METHOD") = KN_PARAM_MIP_METHOD; KN.attr("PARAM_MIP_BRANCHRULE") = KN_PARAM_MIP_BRANCHRULE; KN.attr("PARAM_MIP_SELECTRULE") = KN_PARAM_MIP_SELECTRULE; KN.attr("PARAM_MIP_OPTGAPABS") = KN_PARAM_MIP_OPTGAPABS; KN.attr("PARAM_MIP_OPTGAPREL") = KN_PARAM_MIP_OPTGAPREL; KN.attr("PARAM_MIP_MAXTIMECPU") = KN_PARAM_MIP_MAXTIMECPU; KN.attr("PARAM_MIP_MAXTIMEREAL") = KN_PARAM_MIP_MAXTIMEREAL; KN.attr("PARAM_MIP_MAXSOLVES") = KN_PARAM_MIP_MAXSOLVES; KN.attr("PARAM_MIP_INTEGERTOL") = KN_PARAM_MIP_INTEGERTOL; KN.attr("PARAM_MIP_OUTLEVEL") = KN_PARAM_MIP_OUTLEVEL; KN.attr("PARAM_MIP_OUTINTERVAL") = KN_PARAM_MIP_OUTINTERVAL; KN.attr("PARAM_MIP_OUTSUB") = KN_PARAM_MIP_OUTSUB; KN.attr("PARAM_MIP_DEBUG") = KN_PARAM_MIP_DEBUG; KN.attr("PARAM_MIP_IMPLICATIONS") = KN_PARAM_MIP_IMPLICATIONS; KN.attr("PARAM_MIP_GUB_BRANCH") = KN_PARAM_MIP_GUB_BRANCH; KN.attr("PARAM_MIP_KNAPSACK") = KN_PARAM_MIP_KNAPSACK; KN.attr("PARAM_MIP_ROUNDING") = KN_PARAM_MIP_ROUNDING; KN.attr("PARAM_MIP_ROOT_NLPALG") = KN_PARAM_MIP_ROOT_NLPALG; KN.attr("PARAM_MIP_TERMINATE") = KN_PARAM_MIP_TERMINATE; KN.attr("PARAM_MIP_MAXNODES") = KN_PARAM_MIP_MAXNODES; KN.attr("PARAM_MIP_HEUR_MAXIT") = KN_PARAM_MIP_HEUR_MAXIT; KN.attr("PARAM_MIP_PSEUDOINIT") = KN_PARAM_MIP_PSEUDOINIT; KN.attr("PARAM_MIP_STRONG_MAXIT") = KN_PARAM_MIP_STRONG_MAXIT; KN.attr("PARAM_MIP_STRONG_CANDLIM") = KN_PARAM_MIP_STRONG_CANDLIM; KN.attr("PARAM_MIP_STRONG_LEVEL") = KN_PARAM_MIP_STRONG_LEVEL; KN.attr("PARAM_MIP_INTVAR_STRATEGY") = KN_PARAM_MIP_INTVAR_STRATEGY; KN.attr("PARAM_MIP_RELAXABLE") = KN_PARAM_MIP_RELAXABLE; KN.attr("PARAM_MIP_NODE_NLPALG") = KN_PARAM_MIP_NODE_NLPALG; KN.attr("PARAM_MIP_HEUR_TERMINATE") = KN_PARAM_MIP_HEUR_TERMINATE; KN.attr("PARAM_MIP_SELECTDIR") = KN_PARAM_MIP_SELECTDIR; KN.attr("PARAM_MIP_CUTFACTOR") = KN_PARAM_MIP_CUTFACTOR; KN.attr("PARAM_MIP_ZEROHALF") = KN_PARAM_MIP_ZEROHALF; KN.attr("PARAM_MIP_MIR") = KN_PARAM_MIP_MIR; KN.attr("PARAM_MIP_CLIQUE") = KN_PARAM_MIP_CLIQUE; KN.attr("PARAM_MIP_HEUR_STRATEGY") = KN_PARAM_MIP_HEUR_STRATEGY; KN.attr("PARAM_MIP_HEUR_FEASPUMP") = KN_PARAM_MIP_HEUR_FEASPUMP; KN.attr("PARAM_MIP_HEUR_MPEC") = KN_PARAM_MIP_HEUR_MPEC; KN.attr("PARAM_MIP_HEUR_DIVING") = KN_PARAM_MIP_HEUR_DIVING; KN.attr("PARAM_MIP_CUTTINGPLANE") = KN_PARAM_MIP_CUTTINGPLANE; KN.attr("PARAM_MIP_CUTOFF") = KN_PARAM_MIP_CUTOFF; KN.attr("PARAM_MIP_HEUR_LNS") = KN_PARAM_MIP_HEUR_LNS; KN.attr("PARAM_MIP_MULTISTART") = KN_PARAM_MIP_MULTISTART; KN.attr("PARAM_MIP_LIFTPROJECT") = KN_PARAM_MIP_LIFTPROJECT; KN.attr("PARAM_MIP_NUMTHREADS") = KN_PARAM_MIP_NUMTHREADS; KN.attr("PARAM_MIP_HEUR_MISQP") = KN_PARAM_MIP_HEUR_MISQP; KN.attr("PARAM_MIP_RESTART") = KN_PARAM_MIP_RESTART; KN.attr("PARAM_MIP_GOMORY") = KN_PARAM_MIP_GOMORY; KN.attr("PARAM_MIP_CUT_PROBING") = KN_PARAM_MIP_CUT_PROBING; KN.attr("PARAM_MIP_CUT_FLOWCOVER") = KN_PARAM_MIP_CUT_FLOWCOVER; KN.attr("PARAM_MIP_HEUR_LOCALSEARCH") = KN_PARAM_MIP_HEUR_LOCALSEARCH; KN.attr("PARAM_MIP_ROOT_LPALG") = KN_PARAM_MIP_ROOT_LPALG; KN.attr("PARAM_MIP_NODE_LPALG") = KN_PARAM_MIP_NODE_LPALG; // MIP method values KN.attr("MIP_METHOD_AUTO") = KN_MIP_METHOD_AUTO; KN.attr("MIP_METHOD_BB") = KN_MIP_METHOD_BB; KN.attr("MIP_METHOD_HQG") = KN_MIP_METHOD_HQG; KN.attr("MIP_METHOD_MISQP") = KN_MIP_METHOD_MISQP; // MIP branch rule values KN.attr("MIP_BRANCH_AUTO") = KN_MIP_BRANCH_AUTO; KN.attr("MIP_BRANCH_MOSTFRAC") = KN_MIP_BRANCH_MOSTFRAC; KN.attr("MIP_BRANCH_PSEUDOCOST") = KN_MIP_BRANCH_PSEUDOCOST; KN.attr("MIP_BRANCH_STRONG") = KN_MIP_BRANCH_STRONG; // MIP select rule values KN.attr("MIP_SEL_AUTO") = KN_MIP_SEL_AUTO; KN.attr("MIP_SEL_DEPTHFIRST") = KN_MIP_SEL_DEPTHFIRST; KN.attr("MIP_SEL_BESTBOUND") = KN_MIP_SEL_BESTBOUND; KN.attr("MIP_SEL_COMBO_1") = KN_MIP_SEL_COMBO_1; // MIP terminate values KN.attr("MIP_TERMINATE_OPTIMAL") = KN_MIP_TERMINATE_OPTIMAL; KN.attr("MIP_TERMINATE_FEASIBLE") = KN_MIP_TERMINATE_FEASIBLE; // MIP output level values KN.attr("MIP_OUTLEVEL_NONE") = KN_MIP_OUTLEVEL_NONE; KN.attr("MIP_OUTLEVEL_ITERS") = KN_MIP_OUTLEVEL_ITERS; KN.attr("MIP_OUTLEVEL_ITERSTIME") = KN_MIP_OUTLEVEL_ITERSTIME; KN.attr("MIP_OUTLEVEL_ROOT") = KN_MIP_OUTLEVEL_ROOT; // MIP rounding values KN.attr("MIP_ROUND_AUTO") = KN_MIP_ROUND_AUTO; KN.attr("MIP_ROUND_NONE") = KN_MIP_ROUND_NONE; KN.attr("MIP_ROUND_HEURISTIC") = KN_MIP_ROUND_HEURISTIC; KN.attr("MIP_ROUND_NLP_SOME") = KN_MIP_ROUND_NLP_SOME; KN.attr("MIP_ROUND_NLP_ALWAYS") = KN_MIP_ROUND_NLP_ALWAYS; // MIP heuristic strategy values KN.attr("MIP_HEUR_STRATEGY_AUTO") = KN_MIP_HEUR_STRATEGY_AUTO; KN.attr("MIP_HEUR_STRATEGY_NONE") = KN_MIP_HEUR_STRATEGY_NONE; KN.attr("MIP_HEUR_STRATEGY_BASIC") = KN_MIP_HEUR_STRATEGY_BASIC; KN.attr("MIP_HEUR_STRATEGY_ADVANCED") = KN_MIP_HEUR_STRATEGY_ADVANCED; KN.attr("MIP_HEUR_STRATEGY_EXTENSIVE") = KN_MIP_HEUR_STRATEGY_EXTENSIVE; } ================================================ FILE: lib/main.cpp ================================================ #include "pyoptinterface/nleval.hpp" #include "pyoptinterface/cppad_interface.hpp" void test_linear_eval() { LinearEvaluator evaluator; { ScalarAffineFunction f; f.add_constant(1.0); f.add_term(0, 2.0); evaluator.add_row(f); } { ScalarAffineFunction f; f.add_term(1, 3.0); f.add_term(0, 4.0); evaluator.add_row(f); } std::vector x = {1.0, 2.0}; std::vector f(2); evaluator.eval_function(x.data(), f.data()); size_t global_jabobian_nnz = 0; std::vector global_jacobian_rows, global_jacobian_cols; evaluator.analyze_jacobian_structure(global_jabobian_nnz, global_jacobian_rows, global_jacobian_cols); std::vector jacobian(global_jabobian_nnz); evaluator.eval_jacobian(x.data(), jacobian.data()); } void test_quadratic_eval() { QuadraticEvaluator evaluator; { ScalarQuadraticFunction f; f.add_constant(1.0); f.add_quadratic_term(0, 1, 4.0); f.add_quadratic_term(0, 0, 1.0); f.add_affine_term(0, 4.0); evaluator.add_row(f); } std::vector x = {1.0, 2.0}; std::vector f(1); evaluator.eval_function(x.data(), f.data()); size_t global_jabobian_nnz = 0; std::vector global_jacobian_rows, global_jacobian_cols; evaluator.analyze_jacobian_structure(0, global_jabobian_nnz, global_jacobian_rows, global_jacobian_cols); std::vector jacobian(global_jabobian_nnz); evaluator.eval_jacobian(x.data(), jacobian.data()); size_t global_hessian_nnz = 0; std::vector global_hessian_rows, global_hessian_cols; Hashmap, int> global_hessian_index_map; HessianSparsityType global_hessian_type = HessianSparsityType::Upper; evaluator.analyze_hessian_structure(global_hessian_nnz, global_hessian_rows, global_hessian_cols, global_hessian_index_map, global_hessian_type); std::vector hessian(global_hessian_nnz); std::vector lambda = {1.0}; evaluator.eval_lagrangian_hessian(lambda.data(), hessian.data()); } void test_cppad_pow_var_par() { std::vector> x(2); auto N_parameters = 2; std::vector> p(N_parameters); for (size_t i = 0; i < N_parameters; i++) { p[i] = CppAD::AD(i + 1); } if (N_parameters > 0) { CppAD::Independent(x, p); } else { CppAD::Independent(x); } auto N_outputs = 1; std::vector> y(N_outputs); // Trace the outputs for (size_t i = 0; i < N_outputs; i++) { y[i] = CppAD::pow(x[i], p[i]); } ADFunDouble f; f.Dependent(x, y); CppAD::cpp_graph f_graph; f.to_graph(f_graph); // Print the graph f_graph.print(std::cout); } auto main() -> int { test_cppad_pow_var_par(); return 0; } ================================================ FILE: lib/mosek_model.cpp ================================================ #include "pyoptinterface/mosek_model.hpp" #include "fmt/core.h" namespace mosek { #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } } // namespace mosek // specialize the template for MOSEK template <> template <> void QuadraticFunctionPtrForm::make( MOSEKModel *model, const ScalarQuadraticFunction &function) { auto f_numnz = function.size(); numnz = f_numnz; row_storage.resize(numnz); col_storage.resize(numnz); for (int i = 0; i < numnz; ++i) { auto v1 = model->_variable_index(function.variable_1s[i]); auto v2 = v1; if (function.variable_1s[i] != function.variable_2s[i]) { v2 = model->_variable_index(function.variable_2s[i]); // MOSEK only accepts the lower triangle (i >= j) if (v1 < v2) { std::swap(v1, v2); } } row_storage[i] = v1; col_storage[i] = v2; } row = row_storage.data(); col = col_storage.data(); // MOSEK has 1/2 * x^T @ Q @ x, so we need to multiply the coefficient by 2 for diagonal terms value_storage.resize(numnz); for (int i = 0; i < numnz; ++i) { if (function.variable_1s[i] == function.variable_2s[i]) { value_storage[i] = 2 * function.coefficients[i]; } else { value_storage[i] = function.coefficients[i]; } } value = value_storage.data(); } static void check_error(MSKrescodee error) { if (error != MSK_RES_OK) { char symname[MSK_MAX_STR_LEN]; char desc[MSK_MAX_STR_LEN]; mosek::MSK_getcodedesc(error, symname, desc); std::string errmsg = fmt::format("Error {} : {}", symname, desc); throw std::runtime_error(errmsg); } } static MSKboundkeye mosek_con_sense(ConstraintSense sense) { switch (sense) { case ConstraintSense::LessEqual: return MSK_BK_UP; case ConstraintSense::Equal: return MSK_BK_FX; case ConstraintSense::GreaterEqual: return MSK_BK_LO; default: throw std::runtime_error("Unknown constraint sense"); } } static MSKobjsensee mosek_obj_sense(ObjectiveSense sense) { switch (sense) { case ObjectiveSense::Minimize: return MSK_OBJECTIVE_SENSE_MINIMIZE; case ObjectiveSense::Maximize: return MSK_OBJECTIVE_SENSE_MAXIMIZE; default: throw std::runtime_error("Unknown objective sense"); } } static MSKvariabletypee mosek_vtype(VariableDomain domain) { switch (domain) { case VariableDomain::Continuous: return MSK_VAR_TYPE_CONT; case VariableDomain::Integer: case VariableDomain::Binary: return MSK_VAR_TYPE_INT; default: throw std::runtime_error("Unknown variable domain"); } } static VariableDomain mosek_vtype_to_domain(MSKvariabletypee vtype) { switch (vtype) { case MSK_VAR_TYPE_CONT: return VariableDomain::Continuous; case MSK_VAR_TYPE_INT: return VariableDomain::Integer; default: throw std::runtime_error("Unknown variable domain"); } } MOSEKModel::MOSEKModel(const MOSEKEnv &env) { init(env); } void MOSEKModel::init(const MOSEKEnv &env) { if (!mosek::is_library_loaded()) { throw std::runtime_error("Mosek library is not loaded"); } MSKtask_t model; auto error = mosek::MSK_makeemptytask(env.m_env, &model); check_error(error); m_model = std::unique_ptr(model); } void MOSEKModel::close() { m_model.reset(); } void MOSEKModel::write(const std::string &filename) { bool is_solution = false; if (filename.ends_with(".sol") || filename.ends_with(".bas") || filename.ends_with(".int") || filename.ends_with(".jsol")) { is_solution = true; } MSKrescodee error; if (is_solution) { if (m_soltype) { error = mosek::MSK_writesolution(m_model.get(), m_soltype.value(), filename.c_str()); } else { throw std::runtime_error( "Solution type is unavailable. Please optimize before writing solution."); } } else { error = mosek::MSK_writedata(m_model.get(), filename.c_str()); } check_error(error); } VariableIndex MOSEKModel::add_variable(VariableDomain domain, double lb, double ub, const char *name) { m_is_dirty = true; if (name != nullptr && name[0] == '\0') { name = nullptr; } IndexT index = m_variable_index.add_index(); VariableIndex variable(index); auto error = mosek::MSK_appendvars(m_model.get(), 1); check_error(error); MSKint32t column; error = mosek::MSK_getnumvar(m_model.get(), &column); check_error(error); // 0-based indexing column -= 1; auto vtype = mosek_vtype(domain); error = mosek::MSK_putvartype(m_model.get(), column, vtype); check_error(error); MSKboundkeye bk; if (domain == VariableDomain::Binary) { bk = MSK_BK_RA; lb = 0.0; ub = 1.0; binary_variables.insert(index); } else { bool lb_inf = lb < 1.0 - MSK_INFINITY; bool ub_inf = ub > MSK_INFINITY - 1.0; if (lb_inf && ub_inf) bk = MSK_BK_FR; else if (lb_inf) bk = MSK_BK_UP; else if (ub_inf) bk = MSK_BK_LO; else bk = MSK_BK_RA; } error = mosek::MSK_putvarbound(m_model.get(), column, bk, lb, ub); check_error(error); if (name) { error = mosek::MSK_putvarname(m_model.get(), column, name); check_error(error); } return variable; } // VariableIndex MOSEKModel::add_variables(int N, VariableDomain domain, double lb, double ub) //{ // IndexT index = m_variable_index.add_indices(N); // VariableIndex variable(index); // // auto error = MSK_appendvars(m_model.get(), N); // check_error(error); // // MSKint32t column; // error = MSK_getnumvar(m_model.get(), &column); // check_error(error); // // 0-based indexing // column -= N; // std::vector columns(N); // for (int i = 0; i < N; i++) // { // columns[i] = column + i; // } // // MSKvariabletypee vtype = mosek_vtype(domain); // std::vector vtypes(N, vtype); // error = MSK_putvartypelist(m_model.get(), N, columns.data(), vtypes.data()); // check_error(error); // // MSKboundkeye bk; // if (domain == VariableDomain::Binary) // { // bk = MSK_BK_RA; // lb = 0.0; // ub = 1.0; // for (int i = 0; i < N; i++) // { // binary_variables.insert(index + i); // } // } // else // { // bool lb_inf = lb < 1.0 - MSK_INFINITY; // bool ub_inf = ub > MSK_INFINITY - 1.0; // if (lb_inf && ub_inf) // bk = MSK_BK_FR; // else if (lb_inf) // bk = MSK_BK_UP; // else if (ub_inf) // bk = MSK_BK_LO; // else // bk = MSK_BK_RA; // } // std::vector bks(N, bk); // std::vector lbs(N, lb); // std::vector ubs(N, ub); // error = // MSK_putvarboundlist(m_model.get(), N, columns.data(), bks.data(), lbs.data(), ubs.data()); // check_error(error); // // return variable; // } void MOSEKModel::delete_variable(const VariableIndex &variable) { m_is_dirty = true; if (!is_variable_active(variable)) { throw std::runtime_error("Variable does not exist"); } int variable_column = _variable_index(variable); auto error = mosek::MSK_removevars(m_model.get(), 1, &variable_column); check_error(error); m_variable_index.delete_index(variable.index); binary_variables.erase(variable.index); } void MOSEKModel::delete_variables(const Vector &variables) { m_is_dirty = true; int n_variables = variables.size(); if (n_variables == 0) return; std::vector columns; columns.reserve(n_variables); for (int i = 0; i < n_variables; i++) { if (!is_variable_active(variables[i])) { continue; } auto column = _variable_index(variables[i]); columns.push_back(column); } auto error = mosek::MSK_removevars(m_model.get(), columns.size(), columns.data()); check_error(error); for (int i = 0; i < n_variables; i++) { m_variable_index.delete_index(variables[i].index); } } bool MOSEKModel::is_variable_active(const VariableIndex &variable) { return m_variable_index.has_index(variable.index); } double MOSEKModel::get_variable_value(const VariableIndex &variable) { auto column = _checked_variable_index(variable); MSKrealt retval; auto soltype = get_current_solution(); auto error = mosek::MSK_getxxslice(m_model.get(), soltype, column, column + 1, &retval); check_error(error); return retval; } std::string MOSEKModel::pprint_variable(const VariableIndex &variable) { return get_variable_name(variable); } void MOSEKModel::set_variable_bounds(const VariableIndex &variable, double lb, double ub) { m_is_dirty = true; auto column = _checked_variable_index(variable); MSKboundkeye bk; if (lb == ub) { bk = MSK_BK_FX; } else { bk = MSK_BK_RA; } auto error = mosek::MSK_putvarbound(m_model.get(), column, bk, lb, ub); check_error(error); } ConstraintIndex MOSEKModel::add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { m_is_dirty = true; IndexT index = m_linear_quadratic_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); auto error = mosek::MSK_appendcons(m_model.get(), 1); check_error(error); MSKint32t row; error = mosek::MSK_getnumcon(m_model.get(), &row); check_error(error); // 0-based indexing row -= 1; AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); MSKint32t numnz = ptr_form.numnz; MSKint32t *cind = ptr_form.index; MSKrealt *cval = ptr_form.value; MSKboundkeye g_sense = mosek_con_sense(sense); MSKrealt g_rhs = rhs - function.constant.value_or(0.0); error = mosek::MSK_putarow(m_model.get(), row, numnz, cind, cval); check_error(error); error = mosek::MSK_putconbound(m_model.get(), row, g_sense, g_rhs, g_rhs); check_error(error); if (name != nullptr && name[0] == '\0') { name = nullptr; } if (name) { error = mosek::MSK_putconname(m_model.get(), row, name); check_error(error); } return constraint_index; } ConstraintIndex MOSEKModel::add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name) { m_is_dirty = true; IndexT index = m_linear_quadratic_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); auto error = mosek::MSK_appendcons(m_model.get(), 1); check_error(error); MSKint32t row; error = mosek::MSK_getnumcon(m_model.get(), &row); check_error(error); // 0-based indexing row -= 1; AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); MSKint32t numnz = ptr_form.numnz; MSKint32t *cind = ptr_form.index; MSKrealt *cval = ptr_form.value; double lb = std::get<0>(interval); double ub = std::get<1>(interval); if (function.constant.has_value()) { lb -= function.constant.value(); ub -= function.constant.value(); } error = mosek::MSK_putarow(m_model.get(), row, numnz, cind, cval); check_error(error); error = mosek::MSK_putconbound(m_model.get(), row, MSK_BK_RA, lb, ub); check_error(error); if (name != nullptr && name[0] == '\0') { name = nullptr; } if (name) { error = mosek::MSK_putconname(m_model.get(), row, name); check_error(error); } return constraint_index; } ConstraintIndex MOSEKModel::add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { m_is_dirty = true; IndexT index = m_linear_quadratic_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Quadratic, index); auto error = mosek::MSK_appendcons(m_model.get(), 1); check_error(error); MSKint32t row; error = mosek::MSK_getnumcon(m_model.get(), &row); check_error(error); // 0-based indexing row -= 1; const auto &affine_part = function.affine_part; MSKint32t numlnz = 0; MSKint32t *lind = NULL; MSKrealt *lval = NULL; AffineFunctionPtrForm affine_ptr_form; if (affine_part.has_value()) { const auto &affine_function = affine_part.value(); affine_ptr_form.make(this, affine_function); numlnz = affine_ptr_form.numnz; lind = affine_ptr_form.index; lval = affine_ptr_form.value; } QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); MSKint32t numqnz = ptr_form.numnz; MSKint32t *qrow = ptr_form.row; MSKint32t *qcol = ptr_form.col; MSKrealt *qval = ptr_form.value; MSKboundkeye g_sense = mosek_con_sense(sense); MSKrealt g_rhs = rhs; if (affine_part) g_rhs -= affine_part->constant.value_or(0.0); error = mosek::MSK_putarow(m_model.get(), row, numlnz, lind, lval); check_error(error); error = mosek::MSK_putqconk(m_model.get(), row, numqnz, qrow, qcol, qval); check_error(error); error = mosek::MSK_putconbound(m_model.get(), row, g_sense, g_rhs, g_rhs); check_error(error); if (name != nullptr && name[0] == '\0') { name = nullptr; } if (name) { error = mosek::MSK_putconname(m_model.get(), row, name); check_error(error); } return constraint_index; } std::vector MOSEKModel::add_variables_as_afe(const Vector &variables) { m_is_dirty = true; auto N = variables.size(); // afe part MSKint64t numafe; auto error = mosek::MSK_getnumafe(m_model.get(), &numafe); check_error(error); std::vector afe_index(N); for (int i = 0; i < N; i++) { afe_index[i] = numafe + i; } std::vector var_index(N); for (int i = 0; i < N; i++) { var_index[i] = _checked_variable_index(variables[i]); } std::vector vals(N, 1.0); error = mosek::MSK_appendafes(m_model.get(), N); check_error(error); error = mosek::MSK_putafefentrylist(m_model.get(), N, afe_index.data(), var_index.data(), vals.data()); check_error(error); return afe_index; } ConstraintIndex MOSEKModel::add_variables_in_cone_constraint(const Vector &variables, MSKint64t domain_index, ConstraintType type, const char *name) { m_is_dirty = true; auto N = variables.size(); IndexT index = m_acc_index.size(); m_acc_index.push_back(true); ConstraintIndex constraint_index(type, index); // afe part std::vector afe_index = add_variables_as_afe(variables); // domain part // add acc auto error = mosek::MSK_appendacc(m_model.get(), domain_index, N, afe_index.data(), nullptr); check_error(error); // set name if (name != nullptr && name[0] == '\0') { name = nullptr; } if (name) { error = mosek::MSK_putaccname(m_model.get(), index, name); check_error(error); } return constraint_index; } ConstraintIndex MOSEKModel::add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated) { m_is_dirty = true; auto N = variables.size(); // domain part MSKint64t domain_index; MSKrescodee error; if (rotated) { error = mosek::MSK_appendrquadraticconedomain(m_model.get(), N, &domain_index); } else { error = mosek::MSK_appendquadraticconedomain(m_model.get(), N, &domain_index); } check_error(error); auto con = add_variables_in_cone_constraint(variables, domain_index, ConstraintType::SecondOrderCone, name); return con; } ConstraintIndex MOSEKModel::add_exp_cone_constraint(const Vector &variables, const char *name, bool dual) { m_is_dirty = true; auto N = variables.size(); if (N != 3) { throw std::runtime_error("Exponential cone constraint must have 3 variables"); } // domain part MSKint64t domain_index; MSKrescodee error; if (dual) { error = mosek::MSK_appenddualexpconedomain(m_model.get(), &domain_index); } else { error = mosek::MSK_appendprimalexpconedomain(m_model.get(), &domain_index); } check_error(error); auto con = add_variables_in_cone_constraint(variables, domain_index, ConstraintType::ExponentialCone, name); return con; } void MOSEKModel::delete_constraint(const ConstraintIndex &constraint) { m_is_dirty = true; MSKrescodee error; MSKint32t constraint_row = _constraint_index(constraint); if (constraint_row < 0) return; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: m_linear_quadratic_constraint_index.delete_index(constraint.index); error = mosek::MSK_removecons(m_model.get(), 1, &constraint_row); break; case ConstraintType::SecondOrderCone: case ConstraintType::ExponentialCone: { m_acc_index[constraint.index] = false; // ACC cannot be deleted, we just set its domain to R^n (no constraint) // get the dimension of this ACC MSKint64t N; error = mosek::MSK_getaccn(m_model.get(), constraint_row, &N); check_error(error); // get AFE list of ACC std::vector afeidxlist(N); error = mosek::MSK_getaccafeidxlist(m_model.get(), constraint_row, afeidxlist.data()); check_error(error); // add a N-dim Rn domain MSKint64t domain_index; error = mosek::MSK_appendrdomain(m_model.get(), N, &domain_index); check_error(error); // set the ACC to this domain error = mosek::MSK_putacc(m_model.get(), constraint_row, domain_index, N, afeidxlist.data(), nullptr); check_error(error); } break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); } bool MOSEKModel::is_constraint_active(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: return m_linear_quadratic_constraint_index.has_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } void MOSEKModel::_set_affine_objective(const ScalarAffineFunction &function, ObjectiveSense sense, bool clear_quadratic) { MSKrescodee error; if (clear_quadratic) { // First delete all quadratic terms error = mosek::MSK_putqobj(m_model.get(), 0, nullptr, nullptr, nullptr); check_error(error); } // Set Obj attribute of each variable MSKint32t n_variables; error = mosek::MSK_getnumvar(m_model.get(), &n_variables); check_error(error); std::vector obj_v(n_variables, 0.0); int numnz = function.size(); for (int i = 0; i < numnz; i++) { auto column = _variable_index(function.variables[i]); if (column < 0) { throw std::runtime_error("Variable does not exist"); } obj_v[column] = function.coefficients[i]; } error = mosek::MSK_putcslice(m_model.get(), 0, n_variables, obj_v.data()); check_error(error); error = mosek::MSK_putcfix(m_model.get(), function.constant.value_or(0.0)); check_error(error); MSKobjsensee obj_sense = mosek_obj_sense(sense); error = mosek::MSK_putobjsense(m_model.get(), obj_sense); check_error(error); } void MOSEKModel::set_objective(const ScalarAffineFunction &function, ObjectiveSense sense) { m_is_dirty = true; _set_affine_objective(function, sense, true); } void MOSEKModel::set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense) { m_is_dirty = true; MSKrescodee error; // Add quadratic term int numqnz = function.size(); if (numqnz > 0) { QuadraticFunctionPtrForm ptr_form; ptr_form.make(this, function); MSKint32t numqnz = ptr_form.numnz; MSKint32t *qrow = ptr_form.row; MSKint32t *qcol = ptr_form.col; MSKrealt *qval = ptr_form.value; error = mosek::MSK_putqobj(m_model.get(), numqnz, qrow, qcol, qval); check_error(error); } else { // delete all quadratic terms error = mosek::MSK_putqobj(m_model.get(), 0, nullptr, nullptr, nullptr); check_error(error); } // Affine part const auto &affine_part = function.affine_part; if (affine_part) { const auto &affine_function = affine_part.value(); _set_affine_objective(affine_function, sense, false); } else { ScalarAffineFunction zero; _set_affine_objective(zero, sense, false); } } void MOSEKModel::set_objective(const ExprBuilder &function, ObjectiveSense sense) { m_is_dirty = true; auto deg = function.degree(); if (deg <= 1) { ScalarAffineFunction f(function); set_objective(f, sense); } else if (deg == 2) { ScalarQuadraticFunction f(function); set_objective(f, sense); } else { throw std::runtime_error("Objective must be linear or quadratic"); } } int MOSEKModel::optimize() { m_is_dirty = false; auto error = mosek::MSK_optimize(m_model.get()); m_soltype = select_available_solution_after_optimization(); return error; } int MOSEKModel::raw_parameter_type(const char *name) { MSKparametertypee type; MSKint32t index; auto error = mosek::MSK_whichparam(m_model.get(), name, &type, &index); check_error(error); return type; } void MOSEKModel::set_raw_parameter_int(const char *param_name, int value) { m_is_dirty = true; auto error = mosek::MSK_putnaintparam(m_model.get(), param_name, value); check_error(error); } void MOSEKModel::set_raw_parameter_double(const char *param_name, double value) { m_is_dirty = true; auto error = mosek::MSK_putnadouparam(m_model.get(), param_name, value); check_error(error); } void MOSEKModel::set_raw_parameter_string(const char *param_name, const char *value) { m_is_dirty = true; auto error = mosek::MSK_putnastrparam(m_model.get(), param_name, value); check_error(error); } int MOSEKModel::get_raw_parameter_int(const char *param_name) { int retval; auto error = mosek::MSK_getnaintparam(m_model.get(), param_name, &retval); check_error(error); return retval; } double MOSEKModel::get_raw_parameter_double(const char *param_name) { double retval; auto error = mosek::MSK_getnadouparam(m_model.get(), param_name, &retval); check_error(error); return retval; } std::string MOSEKModel::get_raw_parameter_string(const char *param_name) { char retval[MSK_MAX_STR_LEN]; MSKint32t len; auto error = mosek::MSK_getnastrparam(m_model.get(), param_name, strlen(param_name), &len, retval); check_error(error); return std::string(retval, len); } int MOSEKModel::get_raw_information_int(const char *attr_name) { int retval; auto error = mosek::MSK_getnaintinf(m_model.get(), attr_name, &retval); check_error(error); return retval; } double MOSEKModel::get_raw_information_double(const char *attr_name) { double retval; auto error = mosek::MSK_getnadouinf(m_model.get(), attr_name, &retval); check_error(error); return retval; } MSKint32t MOSEKModel::getnumvar() { MSKint32t retval; auto error = mosek::MSK_getnumvar(m_model.get(), &retval); check_error(error); return retval; } MSKint32t MOSEKModel::getnumcon() { MSKint32t retval; auto error = mosek::MSK_getnumcon(m_model.get(), &retval); check_error(error); return retval; } int MOSEKModel::getprosta() { MSKprostae retval; auto error = mosek::MSK_getprosta(m_model.get(), get_current_solution(), &retval); check_error(error); return retval; } int MOSEKModel::getsolsta() { MSKsolstae retval; auto error = mosek::MSK_getsolsta(m_model.get(), get_current_solution(), &retval); check_error(error); return retval; } double MOSEKModel::getprimalobj() { MSKrealt retval; auto error = mosek::MSK_getprimalobj(m_model.get(), get_current_solution(), &retval); check_error(error); return retval; } double MOSEKModel::getdualobj() { MSKrealt retval; auto error = mosek::MSK_getdualobj(m_model.get(), get_current_solution(), &retval); check_error(error); return retval; } void MOSEKModel::disable_log() { auto error = mosek::MSK_linkfunctotaskstream(m_model.get(), MSK_STREAM_LOG, NULL, NULL); check_error(error); } std::string MOSEKModel::get_variable_name(const VariableIndex &variable) { auto column = _checked_variable_index(variable); char name[MSK_MAX_STR_LEN]; auto error = mosek::MSK_getvarname(m_model.get(), column, MSK_MAX_STR_LEN, name); check_error(error); return name; } void MOSEKModel::set_variable_name(const VariableIndex &variable, const char *name) { m_is_dirty = true; auto column = _checked_variable_index(variable); auto error = mosek::MSK_putvarname(m_model.get(), column, name); check_error(error); } VariableDomain MOSEKModel::get_variable_type(const VariableIndex &variable) { if (binary_variables.contains(variable.index)) { return VariableDomain::Binary; } auto column = _checked_variable_index(variable); MSKvariabletypee vtype; auto error = mosek::MSK_getvartype(m_model.get(), column, &vtype); check_error(error); return mosek_vtype_to_domain(vtype); } void MOSEKModel::set_variable_type(const VariableIndex &variable, VariableDomain domain) { m_is_dirty = true; MSKvariabletypee vtype = mosek_vtype(domain); auto column = _checked_variable_index(variable); auto error = mosek::MSK_putvartype(m_model.get(), column, vtype); check_error(error); if (domain == VariableDomain::Binary) { MSKrealt lb = 0.0; MSKrealt ub = 1.0; binary_variables.insert(variable.index); error = mosek::MSK_putvarbound(m_model.get(), column, MSK_BK_RA, lb, ub); check_error(error); } else { binary_variables.erase(variable.index); } } double MOSEKModel::get_variable_lower_bound(const VariableIndex &variable) { MSKboundkeye bound_type; MSKrealt lb, ub; auto column = _checked_variable_index(variable); auto error = mosek::MSK_getvarbound(m_model.get(), column, &bound_type, &lb, &ub); check_error(error); switch (bound_type) { case MSK_BK_FR: case MSK_BK_UP: lb = -MSK_INFINITY; break; case MSK_BK_LO: case MSK_BK_RA: case MSK_BK_FX: break; default: throw std::runtime_error("Unknown bound type"); } return lb; } double MOSEKModel::get_variable_upper_bound(const VariableIndex &variable) { MSKboundkeye bound_type; MSKrealt lb, ub; auto column = _checked_variable_index(variable); auto error = mosek::MSK_getvarbound(m_model.get(), column, &bound_type, &lb, &ub); check_error(error); switch (bound_type) { case MSK_BK_FR: case MSK_BK_LO: ub = MSK_INFINITY; break; case MSK_BK_UP: case MSK_BK_RA: case MSK_BK_FX: break; default: throw std::runtime_error("Unknown bound type"); } return ub; } void MOSEKModel::set_variable_lower_bound(const VariableIndex &variable, double lb) { m_is_dirty = true; MSKboundkeye bound_type_old, bound_key; MSKrealt lb_old, ub_old; auto column = _checked_variable_index(variable); auto error = mosek::MSK_getvarbound(m_model.get(), column, &bound_type_old, &lb_old, &ub_old); check_error(error); switch (bound_type_old) { case MSK_BK_FR: bound_key = MSK_BK_LO; break; case MSK_BK_LO: bound_key = MSK_BK_LO; break; case MSK_BK_UP: bound_key = MSK_BK_RA; break; case MSK_BK_RA: bound_key = MSK_BK_RA; break; case MSK_BK_FX: throw std::runtime_error("Cannot set lower bound for fixed variable"); default: throw std::runtime_error("Unknown bound type"); } error = mosek::MSK_putvarbound(m_model.get(), column, bound_key, lb, ub_old); check_error(error); } void MOSEKModel::set_variable_upper_bound(const VariableIndex &variable, double ub) { m_is_dirty = true; MSKboundkeye bound_type_old, bound_key; MSKrealt lb_old, ub_old; auto column = _checked_variable_index(variable); auto error = mosek::MSK_getvarbound(m_model.get(), column, &bound_type_old, &lb_old, &ub_old); check_error(error); switch (bound_type_old) { case MSK_BK_FR: bound_key = MSK_BK_UP; break; case MSK_BK_UP: bound_key = MSK_BK_UP; break; case MSK_BK_LO: bound_key = MSK_BK_RA; break; case MSK_BK_RA: bound_key = MSK_BK_RA; break; case MSK_BK_FX: throw std::runtime_error("Cannot set upper bound for fixed variable"); default: throw std::runtime_error("Unknown bound type"); } error = mosek::MSK_putvarbound(m_model.get(), column, bound_key, lb_old, ub); check_error(error); } void MOSEKModel::set_variable_primal(const VariableIndex &variable, double primal) { m_is_dirty = true; auto column = _checked_variable_index(variable); MSKrealt val = primal; auto error = mosek::MSK_putxxslice(m_model.get(), MSK_SOL_ITG, column, column + 1, &val); check_error(error); } double MOSEKModel::get_variable_dual(const VariableIndex &variable) { MSKrealt slx, sux; auto column = _checked_variable_index(variable); auto error = mosek::MSK_getslxslice(m_model.get(), get_current_solution(), column, column + 1, &slx); check_error(error); error = mosek::MSK_getsuxslice(m_model.get(), get_current_solution(), column, column + 1, &sux); check_error(error); double retval = slx - sux; return retval; } double MOSEKModel::get_constraint_primal(const ConstraintIndex &constraint) { int row = _checked_constraint_index(constraint); auto soltype = get_current_solution(); MSKrealt retval; int num = 1; MSKrescodee error; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: error = mosek::MSK_getxcslice(m_model.get(), soltype, constraint.index, constraint.index + num, &retval); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); return retval; } double MOSEKModel::get_constraint_dual(const ConstraintIndex &constraint) { int row = _checked_constraint_index(constraint); auto soltype = get_current_solution(); MSKrealt retval; int num = 1; MSKrescodee error; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: error = mosek::MSK_getyslice(m_model.get(), soltype, constraint.index, constraint.index + num, &retval); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); return retval; } std::string MOSEKModel::get_constraint_name(const ConstraintIndex &constraint) { auto row = _checked_constraint_index(constraint); MSKrescodee error; MSKint32t reqsize; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: error = mosek::MSK_getconnamelen(m_model.get(), row, &reqsize); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); std::string retval(reqsize - 1, '\0'); switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: error = mosek::MSK_getconname(m_model.get(), row, reqsize, retval.data()); break; } check_error(error); return retval; } void MOSEKModel::set_constraint_name(const ConstraintIndex &constraint, const char *name) { m_is_dirty = true; auto row = _checked_constraint_index(constraint); MSKrescodee error; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: error = mosek::MSK_putconname(m_model.get(), row, name); break; default: throw std::runtime_error("Unknown constraint type"); } check_error(error); } ObjectiveSense MOSEKModel::get_obj_sense() { MSKobjsensee obj_sense; auto error = mosek::MSK_getobjsense(m_model.get(), &obj_sense); check_error(error); auto sense = obj_sense == MSK_OBJECTIVE_SENSE_MINIMIZE ? ObjectiveSense::Minimize : ObjectiveSense::Maximize; return sense; } void MOSEKModel::set_obj_sense(ObjectiveSense sense) { m_is_dirty = true; auto obj_sense = mosek_obj_sense(sense); auto error = mosek::MSK_putobjsense(m_model.get(), obj_sense); check_error(error); } double MOSEKModel::get_normalized_rhs(const ConstraintIndex &constraint) { auto row = _checked_constraint_index(constraint); MSKrescodee error; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: { MSKboundkeye bk; MSKrealt lb, ub; error = mosek::MSK_getconbound(m_model.get(), row, &bk, &lb, &ub); check_error(error); double rhs; switch (bk) { case MSK_BK_UP: rhs = ub; break; case MSK_BK_LO: rhs = lb; break; case MSK_BK_FX: rhs = lb; break; case MSK_BK_FR: throw std::runtime_error("Constraint has no finite bound"); break; case MSK_BK_RA: throw std::runtime_error("Constraint has two finite bounds"); break; default: throw std::runtime_error("Unknown bound type"); } return rhs; } default: throw std::runtime_error("Unknown constraint type to get_normalized_rhs"); } } void MOSEKModel::set_normalized_rhs(const ConstraintIndex &constraint, double value) { m_is_dirty = true; auto row = _checked_constraint_index(constraint); MSKrescodee error; switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: { MSKboundkeye bk; MSKrealt lb, ub; error = mosek::MSK_getconbound(m_model.get(), row, &bk, &lb, &ub); check_error(error); switch (bk) { case MSK_BK_UP: ub = value; break; case MSK_BK_LO: lb = value; break; case MSK_BK_FX: ub = value; lb = value; break; case MSK_BK_FR: throw std::runtime_error("Constraint has no finite bound"); break; case MSK_BK_RA: throw std::runtime_error("Constraint has two finite bounds"); break; default: throw std::runtime_error("Unknown bound type"); } error = mosek::MSK_putconbound(m_model.get(), row, bk, lb, ub); check_error(error); } break; default: throw std::runtime_error("Unknown constraint type to set_normalized_rhs"); } } double MOSEKModel::get_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable) { if (constraint.type != ConstraintType::Linear && constraint.type != ConstraintType::Quadratic) { throw std::runtime_error( "Only linear and quadratic constraint supports get_normalized_coefficient"); } auto row = _checked_constraint_index(constraint); auto col = _checked_variable_index(variable); MSKrealt retval; auto error = mosek::MSK_getaij(m_model.get(), row, col, &retval); check_error(error); return retval; } void MOSEKModel::set_normalized_coefficient(const ConstraintIndex &constraint, const VariableIndex &variable, double value) { m_is_dirty = true; if (constraint.type != ConstraintType::Linear && constraint.type != ConstraintType::Quadratic) { throw std::runtime_error( "Only linear and quadratic constraint supports set_normalized_coefficient"); } auto row = _checked_constraint_index(constraint); auto col = _checked_variable_index(variable); auto error = mosek::MSK_putaij(m_model.get(), row, col, value); check_error(error); } double MOSEKModel::get_objective_coefficient(const VariableIndex &variable) { auto column = _checked_variable_index(variable); MSKrealt retval; auto error = mosek::MSK_getcj(m_model.get(), column, &retval); check_error(error); return retval; } void MOSEKModel::set_objective_coefficient(const VariableIndex &variable, double value) { m_is_dirty = true; auto column = _checked_variable_index(variable); auto error = mosek::MSK_putcj(m_model.get(), column, value); check_error(error); } MSKint32t MOSEKModel::_variable_index(const VariableIndex &variable) { return m_variable_index.get_index(variable.index); } MSKint32t MOSEKModel::_checked_variable_index(const VariableIndex &variable) { MSKint32t column = _variable_index(variable); if (column < 0) { throw std::runtime_error("Variable does not exist"); } return column; } MSKint32t MOSEKModel::_constraint_index(const ConstraintIndex &constraint) { switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: return m_linear_quadratic_constraint_index.get_index(constraint.index); break; case ConstraintType::SecondOrderCone: case ConstraintType::ExponentialCone: return m_acc_index[constraint.index] ? constraint.index : -1; break; default: throw std::runtime_error("Unknown constraint type"); } } MSKint32t MOSEKModel::_checked_constraint_index(const ConstraintIndex &constraint) { MSKint32t row = _constraint_index(constraint); if (row < 0) { throw std::runtime_error("Constraint does not exist"); } return row; } // Logging callback static void RealLoggingCallbackFunction(void *handle, const char *msg) { auto real_logdata = static_cast(handle); auto &callback = real_logdata->callback; callback(msg); } void MOSEKModel::set_logging(const MOSEKLoggingCallback &callback) { m_is_dirty = true; m_logging_callback_userdata.callback = callback; auto error = mosek::MSK_linkfunctotaskstream( m_model.get(), MSK_STREAM_LOG, &m_logging_callback_userdata, &RealLoggingCallbackFunction); check_error(error); } void *MOSEKModel::get_raw_model() { return m_model.get(); } std::string MOSEKModel::version_string() { MSKint32t major, minor, revision; auto error = mosek::MSK_getversion(&major, &minor, &revision); std::string version = fmt::format("v{}.{}.{}", major, minor, revision); return version; } MSKsoltypee MOSEKModel::get_current_solution() { if (m_soltype) { return m_soltype.value(); } throw std::runtime_error("No solution type is available"); } std::optional MOSEKModel::select_available_solution_after_optimization() { std::vector soltypes{ MSK_SOL_ITR, MSK_SOL_ITG, MSK_SOL_BAS, }; std::vector available_soltypes; std::vector optimal_soltypes; for (auto soltype : soltypes) { MSKbooleant available; auto error = mosek::MSK_solutiondef(m_model.get(), soltype, &available); check_error(error); if (available) { available_soltypes.push_back(soltype); MSKsolstae solsta; auto error = mosek::MSK_getsolsta(m_model.get(), soltype, &solsta); check_error(error); if (solsta == MSK_SOL_STA_OPTIMAL || solsta == MSK_SOL_STA_INTEGER_OPTIMAL) { optimal_soltypes.push_back(soltype); } } } if (!optimal_soltypes.empty()) { return optimal_soltypes[0]; } if (!available_soltypes.empty()) { return available_soltypes[0]; } return std::nullopt; } MOSEKEnv::MOSEKEnv() { if (!mosek::is_library_loaded()) { throw std::runtime_error("Mosek library is not loaded"); } auto error = mosek::MSK_makeenv(&m_env, NULL); check_error(error); } MOSEKEnv::~MOSEKEnv() { auto error = mosek::MSK_deleteenv(&m_env); check_error(error); } void MOSEKEnv::close() { if (m_env != nullptr) { auto error = mosek::MSK_deleteenv(&m_env); check_error(error); } m_env = nullptr; } void MOSEKEnv::putlicensecode(const std::vector &code) { auto error = mosek::MSK_putlicensecode(m_env, code.data()); check_error(error); } ================================================ FILE: lib/mosek_model_ext.cpp ================================================ #include #include #include #include #include #include "pyoptinterface/mosek_model.hpp" namespace nb = nanobind; extern void bind_mosek_constants(nb::module_ &m); NB_MODULE(mosek_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.def("is_library_loaded", &mosek::is_library_loaded); m.def("load_library", &mosek::load_library); bind_mosek_constants(m); nb::class_(m, "Env") .def(nb::init<>()) .def("close", &MOSEKEnv::close) .def("putlicensecode", &MOSEKEnv::putlicensecode); #define BIND_F(f) .def(#f, &MOSEKModel::f) nb::class_(m, "RawModel") .def(nb::init<>()) .def(nb::init()) // clang-format off BIND_F(init) BIND_F(write) BIND_F(close) // clang-format on .def("add_variable", &MOSEKModel::add_variable, nb::arg("domain") = VariableDomain::Continuous, nb::arg("lb") = -MSK_INFINITY, nb::arg("ub") = MSK_INFINITY, nb::arg("name") = "") // clang-format off BIND_F(delete_variable) BIND_F(delete_variables) BIND_F(is_variable_active) // clang-format on .def("set_variable_bounds", &MOSEKModel::set_variable_bounds, nb::arg("variable"), nb::arg("lb"), nb::arg("ub")) .def("get_value", nb::overload_cast(&MOSEKModel::get_variable_value)) .def("get_value", nb::overload_cast(&MOSEKModel::get_expression_value)) .def("get_value", nb::overload_cast(&MOSEKModel::get_expression_value)) .def("get_value", nb::overload_cast(&MOSEKModel::get_expression_value)) .def("pprint", &MOSEKModel::pprint_variable) .def("pprint", nb::overload_cast(&MOSEKModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def( "pprint", nb::overload_cast(&MOSEKModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("pprint", nb::overload_cast(&MOSEKModel::pprint_expression), nb::arg("expr"), nb::arg("precision") = 4) .def("_add_linear_constraint", nb::overload_cast( &MOSEKModel::add_linear_constraint), nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", nb::overload_cast &, const char *>(&MOSEKModel::add_linear_constraint), nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &MOSEKModel::add_linear_constraint_from_var, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &MOSEKModel::add_linear_interval_constraint_from_var, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_linear_constraint", &MOSEKModel::add_linear_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_linear_constraint", &MOSEKModel::add_linear_interval_constraint_from_expr, nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "") .def("_add_quadratic_constraint", &MOSEKModel::add_quadratic_constraint, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("_add_quadratic_constraint", &MOSEKModel::add_quadratic_constraint_from_expr, nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "") .def("add_second_order_cone_constraint", &MOSEKModel::add_second_order_cone_constraint, nb::arg("variables"), nb::arg("name") = "", nb::arg("rotated") = false) .def("add_exp_cone_constraint", &MOSEKModel::add_exp_cone_constraint, nb::arg("variables"), nb::arg("name") = "", nb::arg("dual") = false) // clang-format off BIND_F(delete_constraint) BIND_F(is_constraint_active) // clang-format on .def("set_objective", nb::overload_cast( &MOSEKModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &MOSEKModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&MOSEKModel::set_objective), nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", &MOSEKModel::set_objective_as_variable, nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("set_objective", &MOSEKModel::set_objective_as_constant, nb::arg("expr"), nb::arg("sense") = ObjectiveSense::Minimize) .def("optimize", &MOSEKModel::optimize, nb::call_guard()) // clang-format off BIND_F(version_string) BIND_F(get_raw_model) BIND_F(raw_parameter_type) BIND_F(set_raw_parameter_int) BIND_F(set_raw_parameter_double) BIND_F(set_raw_parameter_string) BIND_F(get_raw_parameter_int) BIND_F(get_raw_parameter_double) BIND_F(get_raw_parameter_string) BIND_F(get_raw_information_int) BIND_F(get_raw_information_double) BIND_F(getnumvar) BIND_F(getnumcon) BIND_F(getprosta) BIND_F(getsolsta) BIND_F(getprimalobj) BIND_F(getdualobj) BIND_F(set_logging) BIND_F(disable_log) BIND_F(set_variable_name) BIND_F(get_variable_name) BIND_F(set_variable_type) BIND_F(get_variable_type) BIND_F(set_variable_lower_bound) BIND_F(set_variable_upper_bound) BIND_F(get_variable_lower_bound) BIND_F(get_variable_upper_bound) BIND_F(set_variable_primal) BIND_F(get_variable_dual) BIND_F(get_constraint_primal) BIND_F(get_constraint_dual) BIND_F(get_constraint_name) BIND_F(set_constraint_name) BIND_F(set_obj_sense) BIND_F(get_obj_sense) BIND_F(get_normalized_rhs) BIND_F(set_normalized_rhs) BIND_F(get_normalized_coefficient) BIND_F(set_normalized_coefficient) BIND_F(get_objective_coefficient) BIND_F(set_objective_coefficient) // clang-format on .def_rw("m_is_dirty", &MOSEKModel::m_is_dirty); } ================================================ FILE: lib/mosek_model_ext_constants.cpp ================================================ #include #ifdef _MSC_VER #include "solvers/mosek/mosek_win.h" #else #include "solvers/mosek/mosek_linux.h" #endif namespace nb = nanobind; void bind_mosek_constants(nb::module_ &m) { nb::module_ Enum = m.def_submodule("Enum"); // must cast these enums to int, otherwise C++ compilation passes but we get bad cast when // import it in Python #define BIND_C(x) Enum.attr(#x) = static_cast(x) BIND_C(MSK_PRO_STA_UNKNOWN); BIND_C(MSK_PRO_STA_PRIM_AND_DUAL_FEAS); BIND_C(MSK_PRO_STA_PRIM_FEAS); BIND_C(MSK_PRO_STA_DUAL_FEAS); BIND_C(MSK_PRO_STA_PRIM_INFEAS); BIND_C(MSK_PRO_STA_DUAL_INFEAS); BIND_C(MSK_PRO_STA_PRIM_AND_DUAL_INFEAS); BIND_C(MSK_PRO_STA_ILL_POSED); BIND_C(MSK_PRO_STA_PRIM_INFEAS_OR_UNBOUNDED); BIND_C(MSK_SOL_STA_UNKNOWN); BIND_C(MSK_SOL_STA_OPTIMAL); BIND_C(MSK_SOL_STA_PRIM_FEAS); BIND_C(MSK_SOL_STA_DUAL_FEAS); BIND_C(MSK_SOL_STA_PRIM_AND_DUAL_FEAS); BIND_C(MSK_SOL_STA_PRIM_INFEAS_CER); BIND_C(MSK_SOL_STA_DUAL_INFEAS_CER); BIND_C(MSK_SOL_STA_PRIM_ILLPOSED_CER); BIND_C(MSK_SOL_STA_DUAL_ILLPOSED_CER); BIND_C(MSK_SOL_STA_INTEGER_OPTIMAL); BIND_C(MSK_RES_OK); BIND_C(MSK_RES_TRM_MAX_ITERATIONS); BIND_C(MSK_RES_TRM_MAX_TIME); BIND_C(MSK_RES_TRM_OBJECTIVE_RANGE); BIND_C(MSK_RES_TRM_STALL); BIND_C(MSK_RES_TRM_USER_CALLBACK); BIND_C(MSK_RES_TRM_MIO_NUM_RELAXS); BIND_C(MSK_RES_TRM_MIO_NUM_BRANCHES); BIND_C(MSK_RES_TRM_NUM_MAX_NUM_INT_SOLUTIONS); BIND_C(MSK_RES_TRM_MAX_NUM_SETBACKS); BIND_C(MSK_RES_TRM_NUMERICAL_PROBLEM); BIND_C(MSK_RES_TRM_LOST_RACE); BIND_C(MSK_RES_TRM_INTERNAL); BIND_C(MSK_RES_TRM_INTERNAL_STOP); } ================================================ FILE: lib/nleval.cpp ================================================ #include "pyoptinterface/nleval.hpp" #include #include ConstraintAutodiffEvaluator::ConstraintAutodiffEvaluator(bool has_parameter, uintptr_t fp, uintptr_t jp, uintptr_t hp) { if (has_parameter) { f_eval.p = (f_funcptr)fp; jacobian_eval.p = (jacobian_funcptr)jp; hessian_eval.p = (hessian_funcptr)hp; } else { f_eval.nop = (f_funcptr_noparam)fp; jacobian_eval.nop = (jacobian_funcptr_noparam)jp; hessian_eval.nop = (hessian_funcptr_noparam)hp; } } ObjectiveAutodiffEvaluator::ObjectiveAutodiffEvaluator(bool has_parameter, uintptr_t fp, uintptr_t ajp, uintptr_t hp) { if (has_parameter) { f_eval.p = (f_funcptr)fp; grad_eval.p = (additive_grad_funcptr)ajp; hessian_eval.p = (hessian_funcptr)hp; } else { f_eval.nop = (f_funcptr_noparam)fp; grad_eval.nop = (additive_grad_funcptr_noparam)ajp; hessian_eval.nop = (hessian_funcptr_noparam)hp; } } void LinearEvaluator::add_row(const ScalarAffineFunction &f) { coefs.insert(coefs.end(), f.coefficients.begin(), f.coefficients.end()); indices.insert(indices.end(), f.variables.begin(), f.variables.end()); constraint_intervals.push_back(coefs.size()); if (f.constant) { constant_values.push_back(f.constant.value()); constant_indices.push_back(n_constraints); } n_constraints += 1; } void LinearEvaluator::eval_function(const double *restrict x, double *restrict f) { for (size_t i = 0; i < n_constraints; i++) { auto start = constraint_intervals[i]; auto end = constraint_intervals[i + 1]; double sum = 0.0; for (size_t j = start; j < end; j++) { sum += coefs[j] * x[indices[j]]; } f[i] = sum; } for (size_t i = 0; i < constant_indices.size(); i++) { auto index = constant_indices[i]; auto value = constant_values[i]; f[index] += value; } } void LinearEvaluator::analyze_jacobian_structure(size_t &global_jacobian_nnz, std::vector &global_jacobian_rows, std::vector &global_jacobian_cols) const { global_jacobian_nnz += indices.size(); global_jacobian_rows.reserve(global_jacobian_nnz); for (size_t i = 0; i < n_constraints; i++) { auto start = constraint_intervals[i]; auto end = constraint_intervals[i + 1]; for (size_t j = start; j < end; j++) { global_jacobian_rows.push_back(i); } } global_jacobian_cols.insert(global_jacobian_cols.end(), indices.begin(), indices.end()); } void LinearEvaluator::eval_jacobian(const double *restrict x, double *restrict jacobian) const { std::copy(coefs.begin(), coefs.end(), jacobian); } void QuadraticEvaluator::add_row(const ScalarQuadraticFunction &f) { for (int i = 0; i < f.size(); i++) { auto coef = f.coefficients[i]; auto x1 = f.variable_1s[i]; auto x2 = f.variable_2s[i]; if (x1 == x2) { diag_coefs.push_back(coef); diag_indices.push_back(x1); } else { offdiag_coefs.push_back(coef); offdiag_rows.push_back(x1); offdiag_cols.push_back(x2); } } diag_intervals.push_back(diag_coefs.size()); offdiag_intervals.push_back(offdiag_coefs.size()); if (f.affine_part) { auto &affine = f.affine_part.value(); linear_coefs.insert(linear_coefs.end(), affine.coefficients.begin(), affine.coefficients.end()); linear_indices.insert(linear_indices.end(), affine.variables.begin(), affine.variables.end()); if (affine.constant) { linear_constant_values.push_back(affine.constant.value()); linear_constant_indices.push_back(n_constraints); } } linear_intervals.push_back(linear_coefs.size()); Hashmap variable_to_jacobian_nnz; for (int i = 0; i < f.size(); i++) { auto coef = f.coefficients[i]; auto x1 = f.variable_1s[i]; auto x2 = f.variable_2s[i]; if (x1 == x2) { auto [iter, inserted] = variable_to_jacobian_nnz.try_emplace(x1, jacobian_nnz); if (inserted) { jacobian_constant.push_back(0.0); jacobian_variable_indices.push_back(x1); jacobian_nnz += 1; } jacobian_diag_indices.push_back(iter->second); } else { { auto [iter, inserted] = variable_to_jacobian_nnz.try_emplace(x1, jacobian_nnz); if (inserted) { jacobian_constant.push_back(0.0); jacobian_variable_indices.push_back(x1); jacobian_nnz += 1; } jacobian_offdiag_row_indices.push_back(iter->second); } { auto [iter, inserted] = variable_to_jacobian_nnz.try_emplace(x2, jacobian_nnz); if (inserted) { jacobian_constant.push_back(0.0); jacobian_variable_indices.push_back(x2); jacobian_nnz += 1; } jacobian_offdiag_col_indices.push_back(iter->second); } } } if (f.affine_part) { auto &affine = f.affine_part.value(); for (int i = 0; i < affine.size(); i++) { auto coef = affine.coefficients[i]; auto x = affine.variables[i]; auto [iter, inserted] = variable_to_jacobian_nnz.try_emplace(x, jacobian_nnz); if (inserted) { jacobian_constant.push_back(coef); jacobian_variable_indices.push_back(x); jacobian_nnz += 1; } else { auto jacobian_index = iter->second; jacobian_constant[jacobian_index] += coef; } } } jacobian_constraint_intervals.push_back(jacobian_variable_indices.size()); n_constraints += 1; } void QuadraticEvaluator::eval_function(const double *restrict x, double *restrict f) const { for (size_t i = 0; i < n_constraints; i++) { auto start = diag_intervals[i]; auto end = diag_intervals[i + 1]; double sum = 0.0; for (size_t j = start; j < end; j++) { auto c = diag_coefs[j]; auto v = x[diag_indices[j]]; sum += c * v * v; } f[i] = sum; } for (size_t i = 0; i < n_constraints; i++) { auto start = offdiag_intervals[i]; auto end = offdiag_intervals[i + 1]; double sum = 0.0; for (size_t j = start; j < end; j++) { auto c = offdiag_coefs[j]; auto v1 = x[offdiag_rows[j]]; auto v2 = x[offdiag_cols[j]]; sum += c * v1 * v2; } f[i] += sum; } for (size_t i = 0; i < n_constraints; i++) { auto start = linear_intervals[i]; auto end = linear_intervals[i + 1]; double sum = 0.0; for (size_t j = start; j < end; j++) { sum += linear_coefs[j] * x[linear_indices[j]]; } f[i] += sum; } for (size_t i = 0; i < linear_constant_indices.size(); i++) { auto index = linear_constant_indices[i]; auto value = linear_constant_values[i]; f[index] += value; } } void QuadraticEvaluator::analyze_jacobian_structure(size_t row_base, size_t &global_jacobian_nnz, std::vector &global_jacobian_rows, std::vector &global_jacobian_cols) const { global_jacobian_nnz += jacobian_nnz; global_jacobian_rows.reserve(global_jacobian_nnz); for (size_t i = 0; i < n_constraints; i++) { auto start = jacobian_constraint_intervals[i]; auto end = jacobian_constraint_intervals[i + 1]; for (size_t j = start; j < end; j++) { global_jacobian_rows.push_back(row_base + i); } } global_jacobian_cols.insert(global_jacobian_cols.end(), jacobian_variable_indices.begin(), jacobian_variable_indices.end()); } void QuadraticEvaluator::eval_jacobian(const double *restrict x, double *restrict jacobian) const { std::copy(jacobian_constant.begin(), jacobian_constant.end(), jacobian); for (int i = 0; i < diag_coefs.size(); i++) { auto coef = diag_coefs[i]; auto x_index = diag_indices[i]; auto jacobian_index = jacobian_diag_indices[i]; jacobian[jacobian_index] += 2.0 * coef * x[x_index]; } for (int i = 0; i < offdiag_coefs.size(); i++) { auto coef = offdiag_coefs[i]; auto x1_index = offdiag_rows[i]; auto x2_index = offdiag_cols[i]; auto jacobian_index = jacobian_offdiag_row_indices[i]; jacobian[jacobian_index] += coef * x[x2_index]; jacobian_index = jacobian_offdiag_col_indices[i]; jacobian[jacobian_index] += coef * x[x1_index]; } } void QuadraticEvaluator::analyze_hessian_structure( size_t &global_hessian_nnz, std::vector &global_hessian_rows, std::vector &global_hessian_cols, Hashmap, int> &hessian_index_map, HessianSparsityType hessian_type) { hessian_diag_indices.resize(diag_coefs.size()); hessian_offdiag_indices.resize(offdiag_coefs.size()); for (int i = 0; i < diag_coefs.size(); i++) { auto x = diag_indices[i]; auto [iter, inserted] = hessian_index_map.try_emplace({x, x}, global_hessian_nnz); if (inserted) { global_hessian_rows.push_back(x); global_hessian_cols.push_back(x); global_hessian_nnz += 1; } auto hessian_index = iter->second; hessian_diag_indices[i] = hessian_index; } for (int i = 0; i < offdiag_coefs.size(); i++) { auto x1 = offdiag_rows[i]; auto x2 = offdiag_cols[i]; if (hessian_type == HessianSparsityType::Upper) { if (x1 > x2) std::swap(x1, x2); } else { if (x1 < x2) std::swap(x1, x2); } auto [iter, inserted] = hessian_index_map.try_emplace({x1, x2}, global_hessian_nnz); if (inserted) { global_hessian_rows.push_back(x1); global_hessian_cols.push_back(x2); global_hessian_nnz += 1; } auto hessian_index = iter->second; hessian_offdiag_indices[i] = hessian_index; } } void QuadraticEvaluator::eval_lagrangian_hessian(const double *restrict lambda, double *restrict hessian) const { for (size_t i = 0; i < n_constraints; i++) { auto start = diag_intervals[i]; auto end = diag_intervals[i + 1]; auto multiplier = lambda[i]; for (size_t j = start; j < end; j++) { auto coef = diag_coefs[j]; auto hessian_index = hessian_diag_indices[j]; hessian[hessian_index] += 2.0 * coef * multiplier; } } for (size_t i = 0; i < n_constraints; i++) { auto start = offdiag_intervals[i]; auto end = offdiag_intervals[i + 1]; auto multiplier = lambda[i]; for (size_t j = start; j < end; j++) { auto coef = offdiag_coefs[j]; auto hessian_index = hessian_offdiag_indices[j]; hessian[hessian_index] += coef * multiplier; } } } int NonlinearEvaluator::add_graph_instance() { auto current_graph_index = n_graph_instances; n_graph_instances += 1; graph_inputs.emplace_back(); return current_graph_index; } void NonlinearEvaluator::finalize_graph_instance(size_t graph_index, const ExpressionGraph &graph) { auto bodyhash = graph.main_structure_hash(); graph_inputs[graph_index].variables = graph.m_variables; graph_inputs[graph_index].constants = graph.m_constants; if (graph.has_constraint_output()) { auto hash = graph.constraint_structure_hash(bodyhash); constraint_graph_hashes.hashes.push_back( GraphHash{.hash = hash, .index = (int)graph_index}); } if (graph.has_objective_output()) { auto hash = graph.objective_structure_hash(bodyhash); objective_graph_hashes.hashes.push_back(GraphHash{.hash = hash, .index = (int)graph_index}); } } int NonlinearEvaluator::aggregate_constraint_groups() { auto &graph_hashes = constraint_graph_hashes; auto &group_memberships = constraint_group_memberships; // ensure we have enough space for every graph instance group_memberships.resize(n_graph_instances, GraphGroupMembership{.group = -1, .rank = -1}); // graph hashes that has not been aggregated std::span hashes_to_analyze(graph_hashes.hashes.begin() + graph_hashes.n_hashes_since_last_aggregation, graph_hashes.hashes.end()); for (const auto &graph_hash : hashes_to_analyze) { auto index = graph_hash.index; auto hash = graph_hash.hash; auto [iter, inserted] = hash_to_constraint_group.try_emplace(hash, constraint_groups.size()); auto group_index = iter->second; if (inserted) { constraint_groups.emplace_back(); } group_memberships[index].group = group_index; group_memberships[index].rank = (int)constraint_groups[group_index].instance_indices.size(); constraint_groups[group_index].instance_indices.push_back(index); } graph_hashes.n_hashes_since_last_aggregation = graph_hashes.hashes.size(); return constraint_groups.size(); } int NonlinearEvaluator::get_constraint_group_representative(int group_index) const { auto index = constraint_groups.at(group_index).instance_indices.at(0); return index; } int NonlinearEvaluator::aggregate_objective_groups() { auto &graph_hashes = objective_graph_hashes; auto &group_memberships = objective_group_memberships; // ensure we have enough space for every graph instance group_memberships.resize(n_graph_instances, GraphGroupMembership{.group = -1, .rank = -1}); // graph hashes that has not been aggregated std::span hashes_to_analyze(graph_hashes.hashes.begin() + graph_hashes.n_hashes_since_last_aggregation, graph_hashes.hashes.end()); for (const auto &graph_hash : hashes_to_analyze) { auto index = graph_hash.index; auto hash = graph_hash.hash; auto [iter, inserted] = hash_to_objective_group.try_emplace(hash, objective_groups.size()); auto group_index = iter->second; if (inserted) { objective_groups.emplace_back(); } group_memberships[index].group = group_index; group_memberships[index].rank = (int)objective_groups[group_index].instance_indices.size(); objective_groups[group_index].instance_indices.push_back(index); } graph_hashes.n_hashes_since_last_aggregation = graph_hashes.hashes.size(); return objective_groups.size(); } int NonlinearEvaluator::get_objective_group_representative(int group_index) const { auto index = objective_groups.at(group_index).instance_indices.at(0); return index; } void NonlinearEvaluator::assign_constraint_group_autodiff_structure( int group_index, const AutodiffSymbolicStructure &structure) { constraint_groups[group_index].autodiff_structure = structure; } void NonlinearEvaluator::assign_constraint_group_autodiff_evaluator( int group_index, const ConstraintAutodiffEvaluator &evaluator) { constraint_groups[group_index].autodiff_evaluator = evaluator; } void NonlinearEvaluator::assign_objective_group_autodiff_structure( int group_index, const AutodiffSymbolicStructure &structure) { objective_groups[group_index].autodiff_structure = structure; } void NonlinearEvaluator::assign_objective_group_autodiff_evaluator( int group_index, const ObjectiveAutodiffEvaluator &evaluator) { objective_groups[group_index].autodiff_evaluator = evaluator; } void NonlinearEvaluator::calculate_constraint_graph_instances_offset() { // now all graphs are aggregated we need to figure out which index each graph starts constraint_indices_offsets.resize(n_graph_instances, -1); int counter = 0; for (const auto &group : constraint_groups) { const auto &instance_indices = group.instance_indices; auto ny = group.autodiff_structure.ny; for (auto instance_index : instance_indices) { constraint_indices_offsets[instance_index] = counter; counter += ny; } } } void NonlinearEvaluator::eval_constraints(const double *restrict x, double *restrict f) const { auto &groups = constraint_groups; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; auto &evaluator = group.autodiff_evaluator; auto ny = structure.ny; if (!structure.has_parameter) { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; evaluator.f_eval.nop(x, f, variables.data()); f += ny; } } else { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; auto &constant = graph_inputs[instance_index].constants; evaluator.f_eval.p(x, constant.data(), f, variables.data()); f += ny; } } } } double NonlinearEvaluator::eval_objective(const double *restrict x) const { auto &groups = objective_groups; double obj_value = 0.0; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; auto &evaluator = group.autodiff_evaluator; if (!structure.has_parameter) { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; evaluator.f_eval.nop(x, &obj_value, variables.data()); } } else { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; auto &constant = graph_inputs[instance_index].constants; evaluator.f_eval.p(x, constant.data(), &obj_value, variables.data()); } } } return obj_value; } void NonlinearEvaluator::analyze_constraints_jacobian_structure( size_t row_base, size_t &global_jacobian_nnz, std::vector &global_jacobian_rows, std::vector &global_jacobian_cols) { auto &groups = constraint_groups; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; if (!structure.has_jacobian) { continue; } auto local_jacobian_nnz = structure.m_jacobian_nnz; auto &local_jacobian_rows = structure.m_jacobian_rows; auto &local_jacobian_cols = structure.m_jacobian_cols; for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; for (int k = 0; k < local_jacobian_nnz; k++) { auto row = local_jacobian_rows[k] + row_base; auto col = variables[local_jacobian_cols[k]]; global_jacobian_rows.push_back(row); global_jacobian_cols.push_back(col); } row_base += structure.ny; } global_jacobian_nnz += local_jacobian_nnz * n_instances; } } void NonlinearEvaluator::analyze_objective_gradient_structure( std::vector &global_gradient_cols, Hashmap &sparse_gradient_map) { auto &groups = objective_groups; for (auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; if (!structure.has_jacobian) { continue; } auto local_jacobian_nnz = structure.m_jacobian_nnz; auto &local_jacobian_cols = structure.m_jacobian_cols; std::vector jacobian_index_buffer(local_jacobian_nnz); group.gradient_indices.resize(n_instances * local_jacobian_nnz); for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; for (int k = 0; k < local_jacobian_nnz; k++) { auto col = variables[local_jacobian_cols[k]]; auto [iter, inserted] = sparse_gradient_map.try_emplace(col, global_gradient_cols.size()); if (inserted) { global_gradient_cols.push_back(col); } jacobian_index_buffer[k] = iter->second; } std::copy(jacobian_index_buffer.begin(), jacobian_index_buffer.end(), group.gradient_indices.begin() + j * local_jacobian_nnz); } } } void NonlinearEvaluator::eval_constraints_jacobian(const double *restrict x, double *restrict jacobian) const { auto &groups = constraint_groups; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; auto &evaluator = group.autodiff_evaluator; if (!structure.has_jacobian) { continue; } auto local_jacobian_nnz = structure.m_jacobian_nnz; if (!structure.has_parameter) { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; evaluator.jacobian_eval.nop(x, jacobian, variables.data()); jacobian += local_jacobian_nnz; } } else { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; auto &constant = graph_inputs[instance_index].constants; evaluator.jacobian_eval.p(x, constant.data(), jacobian, variables.data()); jacobian += local_jacobian_nnz; } } } } void NonlinearEvaluator::eval_objective_gradient(const double *restrict x, double *restrict grad_f) const { auto &groups = objective_groups; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; auto &evaluator = group.autodiff_evaluator; if (!structure.has_jacobian) { continue; } auto local_jacobian_nnz = structure.m_jacobian_nnz; const int *grad_index = group.gradient_indices.data(); if (!structure.has_parameter) { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; evaluator.grad_eval.nop(x, grad_f, variables.data(), grad_index); grad_index += local_jacobian_nnz; } } else { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; auto &constant = graph_inputs[instance_index].constants; evaluator.grad_eval.p(x, constant.data(), grad_f, variables.data(), grad_index); grad_index += local_jacobian_nnz; } } } } void NonlinearEvaluator::analyze_constraints_hessian_structure( size_t &global_hessian_nnz, std::vector &global_hessian_rows, std::vector &global_hessian_cols, Hashmap, int> &hessian_index_map, HessianSparsityType hessian_type) { auto &groups = constraint_groups; for (auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; if (!structure.has_hessian) { continue; } auto local_hessian_nnz = structure.m_hessian_nnz; auto &local_hessian_rows = structure.m_hessian_rows; auto &local_hessian_cols = structure.m_hessian_cols; std::vector hessian_index_buffer(local_hessian_nnz); group.hessian_indices.resize(n_instances * local_hessian_nnz); for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; for (int k = 0; k < local_hessian_nnz; k++) { auto row = variables[local_hessian_rows[k]]; auto col = variables[local_hessian_cols[k]]; if (hessian_type == HessianSparsityType::Upper) { if (row > col) std::swap(row, col); } else { if (row < col) std::swap(row, col); } auto [iter, inserted] = hessian_index_map.try_emplace({row, col}, global_hessian_nnz); if (inserted) { global_hessian_rows.push_back(row); global_hessian_cols.push_back(col); global_hessian_nnz += 1; } auto hessian_index = iter->second; hessian_index_buffer[k] = hessian_index; } std::copy(hessian_index_buffer.begin(), hessian_index_buffer.end(), group.hessian_indices.begin() + j * local_hessian_nnz); } } } void NonlinearEvaluator::analyze_objective_hessian_structure( size_t &global_hessian_nnz, std::vector &global_hessian_rows, std::vector &global_hessian_cols, Hashmap, int> &hessian_index_map, HessianSparsityType hessian_type) { auto &groups = objective_groups; for (auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; if (!structure.has_hessian) { continue; } auto local_hessian_nnz = structure.m_hessian_nnz; auto &local_hessian_rows = structure.m_hessian_rows; auto &local_hessian_cols = structure.m_hessian_cols; std::vector hessian_index_buffer(local_hessian_nnz); group.hessian_indices.resize(n_instances * local_hessian_nnz); for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; for (int k = 0; k < local_hessian_nnz; k++) { auto row = variables[local_hessian_rows[k]]; auto col = variables[local_hessian_cols[k]]; if (hessian_type == HessianSparsityType::Upper) { if (row > col) std::swap(row, col); } else { if (row < col) std::swap(row, col); } auto [iter, inserted] = hessian_index_map.try_emplace({row, col}, global_hessian_nnz); if (inserted) { global_hessian_rows.push_back(row); global_hessian_cols.push_back(col); global_hessian_nnz += 1; } auto hessian_index = iter->second; hessian_index_buffer[k] = hessian_index; } std::copy(hessian_index_buffer.begin(), hessian_index_buffer.end(), group.hessian_indices.begin() + j * local_hessian_nnz); } } } void NonlinearEvaluator::eval_lagrangian_hessian(const double *restrict x, const double *restrict lambda, const double obj_factor, double *restrict hessian) const { // lambda are the multipliers of constraints // obj_factor is the multiplier of objective function // objective { auto &groups = objective_groups; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; auto &evaluator = group.autodiff_evaluator; if (!structure.has_hessian) { continue; } auto local_hessian_nnz = structure.m_hessian_nnz; const int *hessian_index = group.hessian_indices.data(); if (!structure.has_parameter) { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; evaluator.hessian_eval.nop(x, &obj_factor, hessian, variables.data(), hessian_index); hessian_index += local_hessian_nnz; } } else { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; auto &constant = graph_inputs[instance_index].constants; evaluator.hessian_eval.p(x, constant.data(), &obj_factor, hessian, variables.data(), hessian_index); hessian_index += local_hessian_nnz; } } } } // constraints { auto &groups = constraint_groups; for (const auto &group : groups) { auto &instance_indices = group.instance_indices; auto n_instances = instance_indices.size(); auto &structure = group.autodiff_structure; auto &evaluator = group.autodiff_evaluator; if (!structure.has_hessian) { continue; } auto local_hessian_nnz = structure.m_hessian_nnz; auto ny = structure.ny; const int *hessian_index = group.hessian_indices.data(); if (!structure.has_parameter) { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; evaluator.hessian_eval.nop(x, lambda, hessian, variables.data(), hessian_index); hessian_index += local_hessian_nnz; lambda += ny; } } else { for (int j = 0; j < n_instances; j++) { auto instance_index = instance_indices[j]; auto &variables = graph_inputs[instance_index].variables; auto &constant = graph_inputs[instance_index].constants; evaluator.hessian_eval.p(x, constant.data(), lambda, hessian, variables.data(), hessian_index); hessian_index += local_hessian_nnz; lambda += ny; } } } } } ================================================ FILE: lib/nleval_ext.cpp ================================================ #include #include #include "pyoptinterface/nleval.hpp" namespace nb = nanobind; NB_MODULE(nleval_ext, m) { nb::class_(m, "AutodiffSymbolicStructure") .def(nb::init<>()) .def_ro("nx", &AutodiffSymbolicStructure::nx) .def_ro("np", &AutodiffSymbolicStructure::np) .def_ro("ny", &AutodiffSymbolicStructure::ny) .def_ro("m_jacobian_rows", &AutodiffSymbolicStructure::m_jacobian_rows) .def_ro("m_jacobian_cols", &AutodiffSymbolicStructure::m_jacobian_cols) .def_ro("m_jacobian_nnz", &AutodiffSymbolicStructure::m_jacobian_nnz) .def_ro("m_hessian_rows", &AutodiffSymbolicStructure::m_hessian_rows) .def_ro("m_hessian_cols", &AutodiffSymbolicStructure::m_hessian_cols) .def_ro("m_hessian_nnz", &AutodiffSymbolicStructure::m_hessian_nnz) .def_ro("has_parameter", &AutodiffSymbolicStructure::has_parameter) .def_ro("has_jacobian", &AutodiffSymbolicStructure::has_jacobian) .def_ro("has_hessian", &AutodiffSymbolicStructure::has_hessian); nb::class_(m, "ConstraintAutodiffEvaluator") .def(nb::init<>()) .def(nb::init()); nb::class_(m, "ObjectiveAutodiffEvaluator") .def(nb::init<>()) .def(nb::init()); } ================================================ FILE: lib/nlexpr.cpp ================================================ #include "pyoptinterface/nlexpr.hpp" #include #include #include "fmt/core.h" bool ExpressionHandle::operator==(const ExpressionHandle &x) const { return array == x.array && id == x.id; } std::string ExpressionHandle::to_string() const { switch (array) { case ArrayType::Constant: return fmt::format("c{}", id); case ArrayType::Variable: return fmt::format("v{}", id); case ArrayType::Parameter: return fmt::format("p{}", id); case ArrayType::Unary: return fmt::format("u{}", id); case ArrayType::Binary: return fmt::format("b{}", id); case ArrayType::Ternary: return fmt::format("t{}", id); case ArrayType::Nary: return fmt::format("n{}", id); default: return fmt::format("?{}", id); } } std::string ExpressionGraph::to_string() const { fmt::memory_buffer buf; fmt::format_to(fmt::appender(buf), "Variables: {}\n", m_variables.size()); for (size_t i = 0; i < m_variables.size(); i++) { fmt::format_to(fmt::appender(buf), "\tv{}: {}\n", i, m_variables[i]); } fmt::format_to(fmt::appender(buf), "Constants: {}\n", m_constants.size()); for (size_t i = 0; i < m_constants.size(); i++) { fmt::format_to(fmt::appender(buf), "\tc{}: {}\n", i, m_constants[i]); } fmt::format_to(fmt::appender(buf), "Parameters: {}\n", m_parameters.size()); for (size_t i = 0; i < m_parameters.size(); i++) { fmt::format_to(fmt::appender(buf), "\tp{}: {}\n", i, m_parameters[i]); } fmt::format_to(fmt::appender(buf), "Unary: {}\n", m_unaries.size()); for (size_t i = 0; i < m_unaries.size(); i++) { fmt::format_to(fmt::appender(buf), "\tu{}: {}({})\n", i, unary_operator_to_string(m_unaries[i].op), m_unaries[i].operand.to_string()); } fmt::format_to(fmt::appender(buf), "Binary: {}\n", m_binaries.size()); for (size_t i = 0; i < m_binaries.size(); i++) { fmt::format_to(fmt::appender(buf), "\tb{}: {}({},{})\n", i, binary_operator_to_string(m_binaries[i].op), m_binaries[i].left.to_string(), m_binaries[i].right.to_string()); } fmt::format_to(fmt::appender(buf), "Ternary: {}\n", m_ternaries.size()); for (size_t i = 0; i < m_ternaries.size(); i++) { fmt::format_to(fmt::appender(buf), "\tt{}: {}({},{},{})\n", i, ternary_operator_to_string(m_ternaries[i].op), m_ternaries[i].left.to_string(), m_ternaries[i].middle.to_string(), m_ternaries[i].right.to_string()); } fmt::format_to(fmt::appender(buf), "Nary: {}\n", m_naries.size()); for (size_t i = 0; i < m_naries.size(); i++) { fmt::format_to(fmt::appender(buf), "\tn{}: {}(", i, nary_operator_to_string(m_naries[i].op)); for (size_t j = 0; j < m_naries[i].operands.size(); j++) { fmt::format_to(fmt::appender(buf), "{}, ", m_naries[i].operands[j].to_string()); } fmt::format_to(fmt::appender(buf), ")\n"); } fmt::format_to(fmt::appender(buf), "Constraint outputs: {}\n", m_constraint_outputs.size()); for (size_t i = 0; i < m_constraint_outputs.size(); i++) { fmt::format_to(fmt::appender(buf), "\tcon{}: {}\n", i, m_constraint_outputs[i].to_string()); } fmt::format_to(fmt::appender(buf), "Objective outputs: {}\n", m_objective_outputs.size()); for (size_t i = 0; i < m_objective_outputs.size(); i++) { fmt::format_to(fmt::appender(buf), "\tobj{}: {}\n", i, m_objective_outputs[i].to_string()); } return fmt::to_string(buf); } size_t ExpressionGraph::n_variables() const { return m_variables.size(); } size_t ExpressionGraph::n_constants() const { return m_constants.size(); } size_t ExpressionGraph::n_parameters() const { return m_parameters.size(); } ExpressionHandle ExpressionGraph::add_variable(EntityId id) { auto iter = m_variable_index_map.find(id); if (iter != m_variable_index_map.end()) { return {ArrayType::Variable, static_cast(iter->second)}; } else { auto index = m_variables.size(); m_variables.emplace_back(id); m_variable_index_map.emplace(id, index); return {ArrayType::Variable, static_cast(index)}; } } ExpressionHandle ExpressionGraph::add_constant(double value) { m_constants.emplace_back(value); return {ArrayType::Constant, static_cast(m_constants.size() - 1)}; } ExpressionHandle ExpressionGraph::add_parameter(EntityId id) { m_parameters.emplace_back(id); return {ArrayType::Parameter, static_cast(m_parameters.size() - 1)}; } ExpressionHandle ExpressionGraph::add_unary(UnaryOperator op, ExpressionHandle operand) { // Constant folding: if the operand is a constant, compute the result directly if (operand.array == ArrayType::Constant) { double val = m_constants[operand.id]; double result; switch (op) { case UnaryOperator::Neg: result = -val; break; case UnaryOperator::Sin: result = std::sin(val); break; case UnaryOperator::Cos: result = std::cos(val); break; case UnaryOperator::Tan: result = std::tan(val); break; case UnaryOperator::Asin: result = std::asin(val); break; case UnaryOperator::Acos: result = std::acos(val); break; case UnaryOperator::Atan: result = std::atan(val); break; case UnaryOperator::Abs: result = std::abs(val); break; case UnaryOperator::Sqrt: result = std::sqrt(val); break; case UnaryOperator::Exp: result = std::exp(val); break; case UnaryOperator::Log: result = std::log(val); break; case UnaryOperator::Log10: result = std::log10(val); break; default: // Unknown operator, fall through to create the node goto create_node; } return add_constant(result); } create_node: m_unaries.emplace_back(op, operand); return {ArrayType::Unary, static_cast(m_unaries.size() - 1)}; } ExpressionHandle ExpressionGraph::add_binary(BinaryOperator op, ExpressionHandle left, ExpressionHandle right) { // Constant folding: if both operands are constants, compute the result directly // Note: comparison operators are not folded as they produce boolean results if (left.array == ArrayType::Constant && right.array == ArrayType::Constant && !is_binary_compare_op(op)) { double lval = m_constants[left.id]; double rval = m_constants[right.id]; double result; switch (op) { case BinaryOperator::Sub: result = lval - rval; break; case BinaryOperator::Div: result = lval / rval; break; case BinaryOperator::Pow: result = std::pow(lval, rval); break; case BinaryOperator::Mul2: result = lval * rval; break; default: // Comparison operators or unknown, fall through to create the node goto create_node; } return add_constant(result); } create_node: m_binaries.emplace_back(op, left, right); return {ArrayType::Binary, static_cast(m_binaries.size() - 1)}; } ExpressionHandle ExpressionGraph::add_ternary(TernaryOperator op, ExpressionHandle left, ExpressionHandle middle, ExpressionHandle right) { m_ternaries.emplace_back(op, left, middle, right); return {ArrayType::Ternary, static_cast(m_ternaries.size() - 1)}; } ExpressionHandle ExpressionGraph::add_nary(NaryOperator op, const std::vector &operands) { // Constant folding: if all operands are constants, compute the result directly bool all_constants = true; for (const auto &operand : operands) { if (operand.array != ArrayType::Constant) { all_constants = false; break; } } if (all_constants && !operands.empty()) { double result; switch (op) { case NaryOperator::Add: result = 0.0; for (const auto &operand : operands) { result += m_constants[operand.id]; } break; case NaryOperator::Mul: result = 1.0; for (const auto &operand : operands) { result *= m_constants[operand.id]; } break; default: goto create_node; } return add_constant(result); } create_node: m_naries.emplace_back(op, operands); return {ArrayType::Nary, static_cast(m_naries.size() - 1)}; } ExpressionHandle ExpressionGraph::add_repeat_nary(NaryOperator op, ExpressionHandle operand, int N) { std::vector operands(N, operand); return add_nary(op, operands); } void ExpressionGraph::append_nary(const ExpressionHandle &expression, const ExpressionHandle &operand) { assert(expression.array == ArrayType::Nary); m_naries[expression.id].operands.push_back(operand); } NaryOperator ExpressionGraph::get_nary_operator(const ExpressionHandle &expression) const { assert(expression.array == ArrayType::Nary); return m_naries[expression.id].op; } void ExpressionGraph::add_constraint_output(const ExpressionHandle &expression) { m_constraint_outputs.push_back(expression); } void ExpressionGraph::add_objective_output(const ExpressionHandle &expression) { m_objective_outputs.push_back(expression); } bool ExpressionGraph::has_constraint_output() const { return !m_constraint_outputs.empty(); } bool ExpressionGraph::has_objective_output() const { return !m_objective_outputs.empty(); } ExpressionHandle ExpressionGraph::merge_variableindex(const VariableIndex &v) { return add_variable(v.index); } ExpressionHandle ExpressionGraph::merge_scalaraffinefunction(const ScalarAffineFunction &f) { // Convert it to a n-ary sum of multiplication nodes auto N = f.size(); std::vector terms; terms.reserve(N); for (size_t i = 0; i < N; i++) { auto x = add_variable(f.variables[i]); auto coef = f.coefficients[i]; if (coef == 1.0) { terms.push_back(x); } else if (coef == -1.0) { auto neg = add_unary(UnaryOperator::Neg, x); terms.push_back(neg); } else { auto c = add_constant(coef); terms.push_back(add_nary(NaryOperator::Mul, {c, x})); } } if (f.constant) { terms.push_back(add_constant(f.constant.value())); } if (terms.size() == 1) { return terms[0]; } else { return add_nary(NaryOperator::Add, terms); } } ExpressionHandle ExpressionGraph::merge_scalarquadraticfunction(const ScalarQuadraticFunction &f) { auto N = f.size(); std::vector terms; terms.reserve(N + 1); for (size_t i = 0; i < N; i++) { auto x1 = f.variable_1s[i]; auto x2 = f.variable_2s[i]; auto coef = f.coefficients[i]; ExpressionHandle x1_var = add_variable(x1); ExpressionHandle x2_var; if (x1 == x2) { x2_var = x1_var; } else { x2_var = add_variable(x2); } if (coef == 1.0) { terms.push_back(add_nary(NaryOperator::Mul, {x1_var, x2_var})); } else if (coef == -1.0) { auto neg = add_unary(UnaryOperator::Neg, add_nary(NaryOperator::Mul, {x1_var, x2_var})); terms.push_back(neg); } else { auto c = add_constant(coef); terms.push_back(add_nary(NaryOperator::Mul, {c, x1_var, x2_var})); } } if (f.affine_part) { terms.push_back(merge_scalaraffinefunction(f.affine_part.value())); } if (terms.size() == 1) { return terms[0]; } else { return add_nary(NaryOperator::Add, terms); } } ExpressionHandle ExpressionGraph::merge_exprbuilder(const ExprBuilder &expr) { std::vector terms; terms.reserve(expr.quadratic_terms.size() + expr.affine_terms.size() + 1); for (const auto &[varpair, coef] : expr.quadratic_terms) { auto x1 = varpair.var_1; auto x2 = varpair.var_2; ExpressionHandle x1_var = add_variable(x1); ExpressionHandle x2_var; if (x1 == x2) { x2_var = x1_var; } else { x2_var = add_variable(x2); } if (coef == 1.0) { terms.push_back(add_nary(NaryOperator::Mul, {x1_var, x2_var})); } else if (coef == -1.0) { auto neg = add_unary(UnaryOperator::Neg, add_nary(NaryOperator::Mul, {x1_var, x2_var})); terms.push_back(neg); } else { auto c = add_constant(coef); terms.push_back(add_nary(NaryOperator::Mul, {c, x1_var, x2_var})); } } for (const auto &[var, coef] : expr.affine_terms) { auto x = add_variable(var); if (coef == 1.0) { terms.push_back(x); } else if (coef == -1.0) { auto neg = add_unary(UnaryOperator::Neg, x); terms.push_back(neg); } else { auto c = add_constant(coef); terms.push_back(add_nary(NaryOperator::Mul, {c, x})); } } if (expr.constant_term) { terms.push_back(add_constant(expr.constant_term.value())); } if (terms.size() == 1) { return terms[0]; } else { return add_nary(NaryOperator::Add, terms); } } bool is_binary_compare_op(BinaryOperator op) { return (op >= BinaryOperator::LessThan) && (op <= BinaryOperator::GreaterThan); } bool ExpressionGraph::is_compare_expression(const ExpressionHandle &expr) const { if (expr.array != ArrayType::Binary) { return false; } auto &binary = m_binaries[expr.id]; auto op = binary.op; return is_binary_compare_op(op); } std::string unary_operator_to_string(UnaryOperator op) { switch (op) { case UnaryOperator::Neg: return "Neg"; case UnaryOperator::Sin: return "Sin"; case UnaryOperator::Cos: return "Cos"; case UnaryOperator::Tan: return "Tan"; case UnaryOperator::Asin: return "Asin"; case UnaryOperator::Acos: return "Acos"; case UnaryOperator::Atan: return "Atan"; case UnaryOperator::Abs: return "Abs"; case UnaryOperator::Sqrt: return "Sqrt"; case UnaryOperator::Exp: return "Exp"; case UnaryOperator::Log: return "Log"; case UnaryOperator::Log10: return "Log10"; default: return "UnknownUnary"; } } std::string binary_operator_to_string(BinaryOperator op) { switch (op) { case BinaryOperator::Sub: return "Sub"; case BinaryOperator::Div: return "Div"; case BinaryOperator::Pow: return "Pow"; case BinaryOperator::LessThan: return "LessThan"; case BinaryOperator::LessEqual: return "LessEqual"; case BinaryOperator::Equal: return "Equal"; case BinaryOperator::NotEqual: return "NotEqual"; case BinaryOperator::GreaterEqual: return "GreaterEqual"; case BinaryOperator::GreaterThan: return "GreaterThan"; case BinaryOperator::Add2: return "Add2"; case BinaryOperator::Mul2: return "Mul2"; default: return "UnknownBinary"; } } std::string ternary_operator_to_string(TernaryOperator op) { switch (op) { case TernaryOperator::IfThenElse: return "IfThenElse"; default: return "UnknownTernary"; } } std::string nary_operator_to_string(NaryOperator op) { switch (op) { case NaryOperator::Add: return "Add"; case NaryOperator::Mul: return "Mul"; default: return "UnknownNary"; } } inline static void hash_combine(uint64_t &hash, const uint64_t subhash) { hash ^= subhash + 0x9e3779b9 + (hash << 6) + (hash >> 2); } inline static uint64_t to_i64(const ExpressionHandle &expr) { uint32_t array = static_cast(expr.array); uint32_t id = static_cast(expr.id); return (static_cast(array) << 32) | id; } uint64_t ExpressionGraph::main_structure_hash() const { uint64_t hash = 0; hash_combine(hash, m_variables.size()); hash_combine(hash, m_constants.size()); hash_combine(hash, m_parameters.size()); for (const auto &unary : m_unaries) { hash_combine(hash, (uint64_t)unary.op); hash_combine(hash, to_i64(unary.operand)); } for (const auto &binary : m_binaries) { hash_combine(hash, (uint64_t)binary.op); hash_combine(hash, to_i64(binary.left)); hash_combine(hash, to_i64(binary.right)); } for (const auto &ternary : m_ternaries) { hash_combine(hash, (uint64_t)ternary.op); hash_combine(hash, to_i64(ternary.left)); hash_combine(hash, to_i64(ternary.middle)); hash_combine(hash, to_i64(ternary.right)); } for (const auto &nary : m_naries) { hash_combine(hash, (uint64_t)nary.op); for (const auto &operand : nary.operands) { hash_combine(hash, to_i64(operand)); } } return hash; } uint64_t ExpressionGraph::constraint_structure_hash(uint64_t hash) const { for (const auto &output : m_constraint_outputs) { hash_combine(hash, to_i64(output)); } return hash; } uint64_t ExpressionGraph::objective_structure_hash(uint64_t hash) const { for (const auto &output : m_objective_outputs) { hash_combine(hash, to_i64(output)); } return hash; } void unpack_comparison_expression(ExpressionGraph &graph, const ExpressionHandle &expr, ExpressionHandle &real_expr, double &lb, double &ub) { // Only handles constraint in the form of f <= g or f >= g or f == g // We need to tell if f or g is constant // if not, it will be converted into f-g <= 0 or f-g >= 0 auto array_type = expr.array; auto index = expr.id; if (array_type != ArrayType::Binary) { throw std::runtime_error("Only binary operator is supported for comparison constraint"); } auto &binary = graph.m_binaries[index]; auto op = binary.op; if (op != BinaryOperator::LessEqual && op != BinaryOperator::GreaterEqual && op != BinaryOperator::Equal) { throw std::runtime_error("Only <= or >= or == is supported for comparison constraint"); } auto f = binary.left; auto g = binary.right; if (op == BinaryOperator::GreaterEqual) { // swap f and g auto temp = f; f = g; g = temp; op = BinaryOperator::LessEqual; } // Now we only handle f <= g or f == g // test if f or g is constant bool f_is_constant = f.array == ArrayType::Constant; bool g_is_constant = g.array == ArrayType::Constant; if (op == BinaryOperator::LessEqual) { if (f_is_constant) { lb = graph.m_constants[f.id]; real_expr = g; } else if (g_is_constant) { ub = graph.m_constants[g.id]; real_expr = f; } else { // f - g <= 0 real_expr = graph.add_binary(BinaryOperator::Sub, f, g); ub = 0.0; } } else { if (f_is_constant) { lb = ub = graph.m_constants[f.id]; real_expr = g; } else if (g_is_constant) { lb = ub = graph.m_constants[g.id]; real_expr = f; } else { // f - g == 0 real_expr = graph.add_binary(BinaryOperator::Sub, f, g); lb = ub = 0.0; } } } ================================================ FILE: lib/nlexpr_ext.cpp ================================================ #include #include #include #include #include "pyoptinterface/nlexpr.hpp" namespace nb = nanobind; NB_MODULE(nlexpr_ext, m) { m.import_("pyoptinterface._src.core_ext"); nb::enum_(m, "ArrayType") .value("Constant", ArrayType::Constant) .value("Variable", ArrayType::Variable) .value("Parameter", ArrayType::Parameter) .value("Unary", ArrayType::Unary) .value("Binary", ArrayType::Binary) .value("Ternary", ArrayType::Ternary) .value("Nary", ArrayType::Nary); nb::enum_(m, "UnaryOperator") .value("Neg", UnaryOperator::Neg) .value("Sin", UnaryOperator::Sin) .value("Cos", UnaryOperator::Cos) .value("Tan", UnaryOperator::Tan) .value("Asin", UnaryOperator::Asin) .value("Acos", UnaryOperator::Acos) .value("Atan", UnaryOperator::Atan) .value("Abs", UnaryOperator::Abs) .value("Sqrt", UnaryOperator::Sqrt) .value("Exp", UnaryOperator::Exp) .value("Log", UnaryOperator::Log) .value("Log10", UnaryOperator::Log10); nb::enum_(m, "BinaryOperator") .value("Sub", BinaryOperator::Sub) .value("Div", BinaryOperator::Div) .value("Pow", BinaryOperator::Pow) // compare ops .value("LessThan", BinaryOperator::LessThan) .value("LessEqual", BinaryOperator::LessEqual) .value("Equal", BinaryOperator::Equal) .value("NotEqual", BinaryOperator::NotEqual) .value("GreaterEqual", BinaryOperator::GreaterEqual) .value("GreaterThan", BinaryOperator::GreaterThan); nb::enum_(m, "TernaryOperator") .value("IfThenElse", TernaryOperator::IfThenElse); nb::enum_(m, "NaryOperator") .value("Add", NaryOperator::Add) .value("Mul", NaryOperator::Mul); nb::class_(m, "ExpressionHandle") .def(nb::init()) .def_ro("array", &ExpressionHandle::array) .def_ro("id", &ExpressionHandle::id); nb::class_(m, "UnaryNode") .def(nb::init()) .def_ro("op", &UnaryNode::op) .def_ro("operand", &UnaryNode::operand); nb::class_(m, "BinaryNode") .def(nb::init()) .def_ro("op", &BinaryNode::op) .def_ro("left", &BinaryNode::left) .def_ro("right", &BinaryNode::right); nb::class_(m, "NaryNode") .def(nb::init &>()) .def_ro("op", &NaryNode::op) .def_ro("operands", &NaryNode::operands); nb::class_(m, "ExpressionGraph") .def(nb::init<>()) .def("__str__", &ExpressionGraph::to_string) .def("n_variables", &ExpressionGraph::n_variables) .def("n_parameters", &ExpressionGraph::n_parameters) .def("add_variable", &ExpressionGraph::add_variable, nb::arg("id") = 0) .def("add_constant", &ExpressionGraph::add_constant, nb::arg("value")) .def("add_parameter", &ExpressionGraph::add_parameter, nb::arg("id") = 0) .def("add_unary", &ExpressionGraph::add_unary) .def("add_binary", &ExpressionGraph::add_binary) .def("add_ternary", &ExpressionGraph::add_ternary) .def("add_nary", &ExpressionGraph::add_nary) .def("add_repeat_nary", &ExpressionGraph::add_repeat_nary) .def("append_nary", &ExpressionGraph::append_nary) .def("get_nary_operator", &ExpressionGraph::get_nary_operator) .def("add_constraint_output", &ExpressionGraph::add_constraint_output) .def("add_objective_output", &ExpressionGraph::add_objective_output) .def("merge_variableindex", &ExpressionGraph::merge_variableindex) .def("merge_scalaraffinefunction", &ExpressionGraph::merge_scalaraffinefunction) .def("merge_scalarquadraticfunction", &ExpressionGraph::merge_scalarquadraticfunction) .def("merge_exprbuilder", &ExpressionGraph::merge_exprbuilder) .def("is_compare_expression", &ExpressionGraph::is_compare_expression); m.def("unpack_comparison_expression", [](ExpressionGraph &graph, const ExpressionHandle &expr, double INF) { ExpressionHandle real_expr; double lb = -INF, ub = INF; unpack_comparison_expression(graph, expr, real_expr, lb, ub); return std::make_tuple(real_expr, lb, ub); }); } ================================================ FILE: lib/tcc_interface.cpp ================================================ #include "pyoptinterface/tcc_interface.hpp" #include #include #include "fmt/core.h" namespace tcc { #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; return true; } else { return false; } } } // namespace tcc void TCCInstance::init() { TCCState *state = tcc::tcc_new(); if (state == nullptr) { throw std::runtime_error("Failed to create TCC state"); } m_state.reset(state); int ret = tcc::tcc_set_output_type(m_state.get(), TCC_OUTPUT_MEMORY); if (ret == -1) { throw std::runtime_error("Failed to set output type"); } } void TCCInstance::add_include_path(const std::string &path) { int ret = tcc::tcc_add_include_path(m_state.get(), path.c_str()); if (ret == -1) { throw std::runtime_error(fmt::format("Failed to add include path {}", path)); } } void TCCInstance::add_sysinclude_path(const std::string &path) { int ret = tcc::tcc_add_sysinclude_path(m_state.get(), path.c_str()); if (ret == -1) { throw std::runtime_error(fmt::format("Failed to add sysinclude path {}", path)); } } void TCCInstance::add_library_path(const std::string &path) { int ret = tcc::tcc_add_library_path(m_state.get(), path.c_str()); if (ret == -1) { throw std::runtime_error(fmt::format("Failed to add library path {}", path)); } } void TCCInstance::add_library(const std::string &name) { int ret = tcc::tcc_add_library(m_state.get(), name.c_str()); if (ret == -1) { throw std::runtime_error(fmt::format("Failed to add library {}", name)); } } void TCCInstance::import_math_symbols() { int ret; #define UNARY_MATH_SYMBOL(name) \ { \ auto ptr = static_cast(name); \ ret = tcc::tcc_add_symbol(m_state.get(), #name, reinterpret_cast(ptr)); \ if (ret == -1) \ { \ throw std::runtime_error(fmt::format("Failed to add symbol {}", #name)); \ } \ } UNARY_MATH_SYMBOL(sin); UNARY_MATH_SYMBOL(cos); UNARY_MATH_SYMBOL(tan); UNARY_MATH_SYMBOL(asin); UNARY_MATH_SYMBOL(acos); UNARY_MATH_SYMBOL(atan); UNARY_MATH_SYMBOL(fabs); UNARY_MATH_SYMBOL(sqrt); UNARY_MATH_SYMBOL(exp); UNARY_MATH_SYMBOL(log); UNARY_MATH_SYMBOL(log10); #define BINARY_MATH_SYMBOL(name) \ { \ auto ptr = static_cast(name); \ ret = tcc::tcc_add_symbol(m_state.get(), #name, reinterpret_cast(ptr)); \ if (ret == -1) \ { \ throw std::runtime_error(fmt::format("Failed to add symbol {}", #name)); \ } \ } BINARY_MATH_SYMBOL(pow); #undef UNARY_MATH_SYMBOL #undef BINARY_MATH_SYMBOL } void TCCInstance::compile_string(const std::string &code) { int ret = tcc::tcc_compile_string(m_state.get(), code.c_str()); if (ret == -1) { throw std::runtime_error("Failed to compile code"); } ret = tcc::tcc_relocate(m_state.get()); if (ret == -1) { throw std::runtime_error("Failed to relocate code"); } } uintptr_t TCCInstance::get_symbol(const std::string &name) { void *ptr = tcc::tcc_get_symbol(m_state.get(), name.c_str()); if (ptr == nullptr) { throw std::runtime_error(fmt::format("Failed to get symbol {}", name)); } return reinterpret_cast(ptr); } ================================================ FILE: lib/tcc_interface_ext.cpp ================================================ #include #include #include "pyoptinterface/tcc_interface.hpp" namespace nb = nanobind; NB_MODULE(tcc_interface_ext, m) { m.def("is_library_loaded", &tcc::is_library_loaded); m.def("load_library", &tcc::load_library); nb::class_(m, "TCCInstance") .def(nb::init<>()) .def("init", &TCCInstance::init) .def("add_include_path", &TCCInstance::add_include_path) .def("add_sysinclude_path", &TCCInstance::add_sysinclude_path) .def("add_library_path", &TCCInstance::add_library_path) .def("add_library", &TCCInstance::add_library) .def("import_math_symbols", &TCCInstance::import_math_symbols) .def("compile_string", &TCCInstance::compile_string) .def("get_symbol", &TCCInstance::get_symbol); } ================================================ FILE: lib/xpress_model.cpp ================================================ #include "pyoptinterface/xpress_model.hpp" #include "fmt/core.h" #include "pyoptinterface/core.hpp" #include #include #include #include #include #include #ifndef _WIN32 #include #endif namespace xpress { // Simple helper to have a Go-like defer functionality. // Mainly used to move resource release closer to its acquisition. template struct Defer : LmdT { Defer(LmdT l) : LmdT(l) {}; ~Defer() noexcept { (*this)(); } }; // Mimics what is done to load the other solvers. #define B DYLIB_DECLARE APILIST #undef B static DynamicLibrary lib; static bool is_loaded = false; bool is_library_loaded() { return is_loaded; } bool load_library(const std::string &path) { bool success = lib.try_load(path.c_str()); if (!success) { return false; } DYLIB_LOAD_INIT; #define B DYLIB_LOAD_FUNCTION APILIST #undef B if (IS_DYLIB_LOAD_SUCCESS) { #define B DYLIB_SAVE_FUNCTION APILIST #undef B is_loaded = true; int major = {}; int minor = {}; int build = {}; XPRSgetversionnumbers(&major, &minor, &build); // Use tuple comparison operator if (major < XPRS_VER_MAJOR) { fmt::print( stderr, "Warning: loaded Xpress version is older than the officially supported one.\n"); } } return is_loaded; } static void check_license(int error) { if (error == 0) { return; } char buffer[POI_XPRS_MAXMESSAGELENGTH]; if (XPRSgetlicerrmsg(buffer, sizeof buffer) != 0) { throw std::runtime_error("Error while getting the Xpress license error message"); } throw std::runtime_error( fmt::format("Error while initializing Xpress Environment: {}", buffer)); } Env::Env(const char *path) { if (!xpress::is_library_loaded()) { throw std::runtime_error("Xpress library is not loaded"); } auto lg = std::lock_guard(mtx); assert(init_count >= 0); if (init_count <= 0) { check_license(XPRSinit(path)); } ++init_count; initialized = true; } Env::~Env() { try { close(); } catch (std::exception e) { fmt::print(stderr, "{}\n", e.what()); fflush(stderr); } } void Env::close() { if (!initialized) { return; } initialized = false; auto lg = std::lock_guard(mtx); --init_count; assert(init_count >= 0); if (init_count <= 0 && XPRSfree() != 0) { throw std::runtime_error("Error while freeing Xpress environment"); } } std::pair license(int p_i, const char *p_c) { int i = p_i; std::string c(p_c); check_license(XPRSlicense(&i, c.data())); c.resize(strlen(c.data())); return std::make_pair(i, c); } bool beginlicensing() { int notyet = {}; check_license(XPRSbeginlicensing(¬yet)); return notyet != 0; } void endlicensing() { check_license(XPRSendlicensing()); } static char poi_to_xprs_cons_sense(ConstraintSense sense) { switch (sense) { case ConstraintSense::LessEqual: return 'L'; case ConstraintSense::Equal: return 'E'; case ConstraintSense::GreaterEqual: return 'G'; default: throw std::runtime_error("Unknown constraint sense"); } } static ConstraintSense xprs_to_poi_cons_sense(int ctype) { switch (ctype) { case 'L': return ConstraintSense::LessEqual; case 'E': return ConstraintSense::Equal; case 'G': return ConstraintSense::GreaterEqual; case 'R': // Range constraints case 'N': // Free constraints default: throw std::runtime_error("Unsupported constraint sense"); } } static int poi_to_xprs_obj_sense(ObjectiveSense sense) { switch (sense) { case ObjectiveSense::Minimize: return POI_XPRS_OBJ_MINIMIZE; case ObjectiveSense::Maximize: return POI_XPRS_OBJ_MAXIMIZE; default: throw std::runtime_error("Unknown objective function sense"); } } static char poi_to_xprs_var_type(VariableDomain domain) { switch (domain) { case VariableDomain::Continuous: return 'C'; case VariableDomain::Integer: return 'I'; case VariableDomain::Binary: return 'B'; case VariableDomain::SemiContinuous: return 'S'; default: throw std::runtime_error("Unknown variable domain"); } } static VariableDomain xprs_to_poi_var_type(char vtype) { switch (vtype) { case 'C': return VariableDomain::Continuous; case 'I': return VariableDomain::Integer; case 'B': return VariableDomain::Binary; case 'S': return VariableDomain::SemiContinuous; default: throw std::runtime_error("Unknown variable domain"); } } static char poi_to_xprs_sos_type(SOSType type) { switch (type) { case SOSType::SOS1: return '1'; case SOSType::SOS2: return '2'; default: throw std::runtime_error("Unknown SOS type"); } } // Check Xpress APIs return value for error and throws in case it is non-zero void Model::_check(int error) { // We allow users to define custom message callbacks which may throw exceptions. Since // Xpress APIs can trigger messages at any point, exceptions might be thrown even from // apparently "safe" operations. We always check for captured exceptions before returning. if (m_mode == XPRESS_MODEL_MODE::MAIN && !m_captured_exceptions.empty()) { Defer exceptions_clear = [&] { m_captured_exceptions.clear(); }; // Single exception - rethrow directly if (m_captured_exceptions.size() == 1) { std::rethrow_exception(m_captured_exceptions[0]); } // Multiple exceptions - aggregate into single message std::string new_what = "Multiple exceptions raised:\n"; for (int i = 0; const auto &exc : m_captured_exceptions) { try { std::rethrow_exception(exc); } catch (const std::exception &e) { fmt::format_to(std::back_inserter(new_what), "{}. {}\n", ++i, e.what()); } catch (...) { fmt::format_to(std::back_inserter(new_what), "{}. Unknown exception\n", ++i); } } throw std::runtime_error(new_what); } if (error == 0) { return; } char error_buffer[POI_XPRS_MAXMESSAGELENGTH]; if (XPRSgetlasterror(m_model.get(), error_buffer) != 0) { throw std::runtime_error("Error while getting Xpress message error"); } throw std::runtime_error(error_buffer); } // The default behavior of Xpress C APIs is to don't print anything unless a message CB is // registered. Thus, this is the default print callback that redirect to standard streams. static void default_print(XPRSprob prob, void *, char const *msg, int msgsize, int msgtype) { if (msgtype < 0) { // Negative values are used to signal output end, and can be use as flush trigger // But we flush at every message, so no problem need to flush again. return; } FILE *out = (msgtype == 1 ? stdout : stderr); fmt::print(out, "{}\n", msgsize > 0 ? msg : ""); fflush(out); } Model::Model(const Env &env) { init(env); // The default behavior expected by POI differ a bit from Xpress default behavior. Here we // adjust some controls: // Verbose by default, the user can silence if needed set_raw_control_int_by_id(POI_XPRS_OUTPUTLOG, 1); // Register a message callback (can be overridden) _check(XPRSaddcbmessage(m_model.get(), &default_print, nullptr, 0)); is_default_message_cb_set = true; // We do not support concurrent CBs invocation since each callback have to acquire Python GIL _check(XPRSsetintcontrol64(m_model.get(), POI_XPRS_MUTEXCALLBACKS, 1)); // Use global solver if the model contains non linear formulas set_raw_control_int_by_id(POI_XPRS_NLPSOLVER, POI_XPRS_NLPSOLVER_GLOBAL); } void Model::init(const Env &env) { if (!xpress::is_library_loaded()) { throw std::runtime_error("Xpress library is not loaded"); } if (auto lg = std::lock_guard(Env::mtx); Env::init_count <= 0) { throw std::runtime_error("Xpress environment is not initialized"); } XPRSprob prob = nullptr; _check(XPRScreateprob(&prob)); m_model.reset(prob); _clear_caches(); } XPRSprob Model::_toggle_model_mode(XPRSprob model) { if (m_mode == XPRESS_MODEL_MODE::MAIN) { m_mode = XPRESS_MODEL_MODE::CALLBACK_; } else { m_mode = XPRESS_MODEL_MODE::MAIN; } XPRSprob old = m_model.release(); m_model.reset(model); return old; } Model::~Model() try { close(); } catch (std::exception e) { fmt::print(stderr, "{}\n", e.what()); fflush(stderr); } void Model::close() { // In CALLBACK mode we cannot destroy the problem, we release the unique_ptr instead if (m_mode == XPRESS_MODEL_MODE::CALLBACK_) { [[maybe_unused]] auto _ = m_model.release(); } else { m_model.reset(); } } void Model::_clear_caches() { m_primal_ray.clear(); m_dual_ray.clear(); m_iis_cols.clear(); m_iss_rows.clear(); m_iis_bound_types.clear(); } double Model::get_infinity() { return POI_XPRS_PLUSINFINITY; } void Model::write(const std::string &filename) { // Detect if the file should be compressed by looking at the last file // extension. We exploit short-circuiting and fold expressions to avoid long // else if branches. auto find_compress_ext_len = [&](auto &...extensions) { size_t ext_len = 0; ((filename.ends_with(extensions) && (ext_len = sizeof extensions - 1)) || ...); return ext_len; }; size_t compress_ext_len = find_compress_ext_len(".gz", ".zip", ".tar", ".tgz", ".bz2", ".bzip", ".7z", ".xz", ".lz4", ".Z"); std::string_view fname = filename; fname.remove_suffix(compress_ext_len); // Based on the second last extension, we deduce what the user wants. if (fname.ends_with(".mps")) { _check(XPRSwriteprob(m_model.get(), filename.c_str(), "v")); } else if (fname.ends_with(".lp")) { _check(XPRSwriteprob(m_model.get(), filename.c_str(), "lv")); } else if (fname.ends_with(".bss")) { _check(XPRSwritebasis(m_model.get(), filename.c_str(), "v")); } else if (fname.ends_with(".hdr") || fname.ends_with(".asc")) { _check(XPRSwritesol(m_model.get(), filename.c_str(), "v")); } else if (fname.ends_with(".sol")) { _check(XPRSwritebinsol(m_model.get(), filename.c_str(), "v")); } else if (fname.ends_with(".prt")) { _check(XPRSwriteprtsol(m_model.get(), filename.c_str(), "v")); } else if (fname.ends_with(".slx")) { _check(XPRSwriteslxsol(m_model.get(), filename.c_str(), "v")); } else if (fname.ends_with(".svf")) { _check(XPRSsaveas(m_model.get(), filename.c_str())); } else { throw std::runtime_error("Unknow file extension"); } } std::string Model::get_problem_name() { int size = get_raw_attribute_int_by_id(POI_XPRS_MAXPROBNAMELENGTH) + 1; std::string probname; probname.resize(size); _check(XPRSgetprobname(m_model.get(), probname.data())); // Align string size with string length probname.resize(strlen(probname.c_str())); return probname; } void Model::set_problem_name(const std::string &probname) { _check(XPRSsetprobname(m_model.get(), probname.c_str())); } VariableIndex Model::add_variable(VariableDomain domain, double lb, double ub, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); IndexT index = m_variable_index.add_index(); VariableIndex variable(index); double zero[] = {0.0}; int colidx = get_raw_attribute_int_by_id(POI_XPRS_COLS); _check(XPRSaddcols64(m_model.get(), 1, 0, zero, nullptr, nullptr, nullptr, &lb, &ub)); _set_entity_name(POI_XPRS_NAMES_COLUMN, colidx, name); char vtype = poi_to_xprs_var_type(domain); if (domain != VariableDomain::Continuous) { char vtype = poi_to_xprs_var_type(domain); int icol = colidx; _check(XPRSchgcoltype(m_model.get(), 1, &icol, &vtype)); } return variable; } void Model::delete_variable(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); if (!is_variable_active(variable)) { throw std::runtime_error("Variable does not exist"); } int colidx = _variable_index(variable); _check(XPRSdelcols(m_model.get(), 1, &colidx)); m_variable_index.delete_index(variable.index); } void Model::delete_variables(const Vector &variables) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int n_variables = variables.size(); if (n_variables == 0) return; std::vector columns; columns.reserve(n_variables); for (int i = {}; i < n_variables; i++) { if (!is_variable_active(variables[i])) { continue; } auto column = _variable_index(variables[i]); columns.push_back(column); } _check(XPRSdelcols(m_model.get(), columns.size(), columns.data())); for (int i = {}; i < n_variables; i++) { m_variable_index.delete_index(variables[i].index); } } bool Model::is_variable_active(VariableIndex variable) { return m_variable_index.has_index(variable.index); } std::string Model::pprint_variable(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); return get_variable_name(variable); } std::string Model::_get_entity_name(int etype, int eidx) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int req_size = {}; _check(XPRSgetnamelist(m_model.get(), etype, nullptr, 0, &req_size, eidx, eidx)); std::string value = {}; value.resize(req_size); _check(XPRSgetnamelist(m_model.get(), etype, value.data(), req_size, &req_size, eidx, eidx)); while (value.back() == '\0') { value.pop_back(); } return value; } std::string Model::get_variable_name(VariableIndex variable) { int colidx = _checked_variable_index(variable); return _get_entity_name(POI_XPRS_NAMES_COLUMN, colidx); } std::string Model::get_constraint_name(ConstraintIndex constraint) { int rowidx = _checked_constraint_index(constraint); return _get_entity_name(POI_XPRS_NAMES_ROW, rowidx); } void Model::set_variable_bounds(VariableIndex variable, double lb, double ub) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int column = _checked_variable_index(variable); int columns[] = {column, column}; char btypes[] = "LU"; double bounds[] = {lb, ub}; _check(XPRSchgbounds(m_model.get(), 2, columns, btypes, bounds)); } void Model::set_variable_lowerbound(VariableIndex variable, double lb) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int colidx = _checked_variable_index(variable); _check(XPRSchgbounds(m_model.get(), 1, &colidx, "L", &lb)); } void Model::set_variable_upperbound(VariableIndex variable, double ub) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int colidx = _checked_variable_index(variable); _check(XPRSchgbounds(m_model.get(), 1, &colidx, "U", &ub)); } void Model::set_variable_type(VariableIndex variable, VariableDomain vdomain) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int colidx = _checked_variable_index(variable); char vtype = poi_to_xprs_var_type(vdomain); _check(XPRSchgcoltype(m_model.get(), 1, &colidx, &vtype)); } void Model::_set_entity_name(int etype, int index, const char *name) { if (name == nullptr || name[0] == '\0') { return; } _check(XPRSaddnames(m_model.get(), etype, name, index, index)); } void Model::add_mip_start(const std::vector &variables, const std::vector &values) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); if (variables.size() != values.size()) { throw std::runtime_error("Number of variables and values do not match"); } int numnz = variables.size(); if (numnz == 0) { return; } std::vector ind_v(numnz); for (int i = {}; i < numnz; i++) { ind_v[i] = _checked_variable_index(variables[i].index); } _check(XPRSaddmipsol(m_model.get(), numnz, values.data(), ind_v.data(), nullptr)); } void Model::set_variable_name(VariableIndex variable, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int column = _checked_variable_index(variable); _set_entity_name(POI_XPRS_NAMES_COLUMN, column, name); } void Model::set_constraint_name(ConstraintIndex constraint, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int row = _checked_constraint_index(constraint); _set_entity_name(POI_XPRS_NAMES_ROW, row, name); } ConstraintIndex Model::add_linear_constraint(const ScalarAffineFunction &function, const std::tuple &interval, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); auto [lb, ub] = interval; double constant = static_cast(function.constant.value_or(CoeffT{})); lb = std::clamp(lb - constant, POI_XPRS_MINUSINFINITY, POI_XPRS_PLUSINFINITY); ub = std::clamp(ub - constant, POI_XPRS_MINUSINFINITY, POI_XPRS_PLUSINFINITY); if (lb > ub - 1e-10) { throw std::runtime_error("LB > UB in the provieded interval."); } // Handle infinity bounds bool lb_inf = lb <= POI_XPRS_MINUSINFINITY; bool ub_inf = ub >= POI_XPRS_PLUSINFINITY; // Determine constraint type and parameters char g_sense = {}; double g_rhs = {}; double range_val = {}; const double *g_range = {}; if (lb_inf && ub_inf) { g_sense = 'N'; // Free row g_rhs = 0.0; } else if (lb_inf) { g_sense = 'L'; g_rhs = ub; } else if (ub_inf) { g_sense = 'G'; g_rhs = lb; } else if (std::abs(ub - lb) < 1e-10) { g_sense = 'E'; g_rhs = ub; } else { g_sense = 'R'; g_rhs = ub; range_val = ub - lb; g_range = &range_val; } IndexT index = m_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); int rowidx = get_raw_attribute_int_by_id(POI_XPRS_ROWS); AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; XPRSint64 beg[] = {0}; const int *cind = ptr_form.index; const double *cval = ptr_form.value; _check(XPRSaddrows64(m_model.get(), 1, numnz, &g_sense, &g_rhs, g_range, beg, cind, cval)); _set_entity_name(POI_XPRS_NAMES_ROW, rowidx, name); return constraint_index; } ConstraintIndex Model::add_linear_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); IndexT index = m_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::Linear, index); int rowidx = get_raw_attribute_int_by_id(POI_XPRS_ROWS); AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; double g_rhs = static_cast(rhs - function.constant.value_or(CoeffT{})); g_rhs = std::clamp(g_rhs, POI_XPRS_MINUSINFINITY, POI_XPRS_PLUSINFINITY); // Map expr >= -inf and expr <= +inf to free rows char g_sense = poi_to_xprs_cons_sense(sense); if ((g_sense == 'G' && g_rhs <= POI_XPRS_MINUSINFINITY) || (g_sense == 'L' && g_rhs >= POI_XPRS_PLUSINFINITY)) { g_sense = 'N'; // Free row g_rhs = 0.0; } XPRSint64 beg[] = {0}; const int *cind = ptr_form.index; const double *cval = ptr_form.value; _check(XPRSaddrows64(m_model.get(), 1, numnz, &g_sense, &g_rhs, nullptr, beg, cind, cval)); _set_entity_name(POI_XPRS_NAMES_ROW, rowidx, name); return constraint_index; } static QuadraticFunctionPtrForm poi_to_xprs_quad_formula( Model &model, const ScalarQuadraticFunction &function, bool is_objective) { QuadraticFunctionPtrForm ptr_form; ptr_form.make(&model, function); int numqnz = ptr_form.numnz; // Xpress uses different quadratic representations for objectives vs constraints: // // OBJECTIVES: Use 0.5*x'Qx form with automatic symmetry. // - Diagonal terms (i,i): Need 2× multiplication to compensate for the 0.5 factor // - Off-diagonal terms (i,j): Used as-is; Xpress automatically mirrors to (j,i) // // CONSTRAINTS: Use x'Qx form with upper-triangular specification. // - Diagonal terms (i,i): Used as-is // - Off-diagonal terms (i,j): Need 0.5× division; Xpress expects coefficients // for the upper triangular part only, which will be mirrored // // PyOptInterface provides coefficients in the standard x'Qx form through // QuadraticFunctionPtrForm, so we adjust based on whether this is for an objective function or // a constraint. // Copy coefficients (ptr_form.value may reference function.coefficients directly) if (ptr_form.value_storage.empty()) { ptr_form.value_storage.reserve(numqnz); for (CoeffT c : function.coefficients) { ptr_form.value_storage.push_back(static_cast(c)); } } // Apply Xpress-specific coefficient adjustments for (int i = 0; i < numqnz; ++i) { if (is_objective && (ptr_form.row[i] == ptr_form.col[i])) { // Objective diagonal terms: multiply by 2 for 0.5*x'Qx convention ptr_form.value_storage[i] *= 2.0; } if (!is_objective && (ptr_form.row[i] != ptr_form.col[i])) { // Constraint off-diagonal terms: divide by 2 for upper-triangular specification ptr_form.value_storage[i] /= 2.0; } } ptr_form.value = ptr_form.value_storage.data(); return ptr_form; } // Quadratic constraints are regular rows with a quadratic term. ConstraintIndex Model::add_quadratic_constraint(const ScalarQuadraticFunction &function, ConstraintSense sense, CoeffT rhs, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int rowidx = get_raw_attribute_int_by_id(POI_XPRS_ROWS); const auto &affine_part = function.affine_part.value_or(ScalarAffineFunction{}); ConstraintIndex constraint_index = add_linear_constraint(affine_part, sense, rhs, name); constraint_index.type = ConstraintType::Quadratic; // Fix constraint type // Add quadratic term QuadraticFunctionPtrForm ptr_form = poi_to_xprs_quad_formula(*this, function, false); int numqnz = ptr_form.numnz; const int *qrow = ptr_form.row; const int *qcol = ptr_form.col; const double *qval = ptr_form.value; _check(XPRSaddqmatrix64(m_model.get(), rowidx, numqnz, qrow, qcol, qval)); ++m_quad_nl_constr_num; return constraint_index; } ConstraintIndex Model::add_second_order_cone_constraint(const Vector &variables, const char *name, bool rotated) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int rot_offset = rotated ? 1 : 0; if (variables.size() <= rot_offset) { throw std::runtime_error("Not enough variables in SOC constraint."); } // SOC: 1 x_0 x_0 >= \sum_{i : [1,N)} x_i^2 // Rotated SOC: 2 x_0 x_1 >= \sum_{i : [2,N)} x_i^2 ScalarQuadraticFunction quadconstr; quadconstr.add_quadratic_term(variables[0], variables[rot_offset], 1.0 + rot_offset); for (int i = 1 + rot_offset; i < variables.size(); ++i) { quadconstr.add_quadratic_term(variables[i], variables[i], -1.0); } ConstraintIndex constraint_index = add_quadratic_constraint(quadconstr, ConstraintSense::GreaterEqual, 0.0, name); constraint_index.type = ConstraintType::SecondOrderCone; return constraint_index; } namespace { template struct NlpArrays { int types[N]; double values[N]; }; } // namespace ConstraintIndex Model::add_exp_cone_constraint(const Vector &variables, const char *name, bool dual) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); if (variables.size() != 3) { throw std::runtime_error("Exponential cone constraint must have 3 variables"); } // Add affine part auto constraint_index = add_linear_constraint_from_var(variables[0], ConstraintSense::GreaterEqual, 0.0); constraint_index.type = ConstraintType::ExponentialCone; // Fix constraint type int nnz = 0; const int *types = {}; const double *values = {}; double var1_idx = static_cast(_checked_variable_index(variables[1])); double var2_idx = static_cast(_checked_variable_index(variables[2])); int rowidx = _constraint_index(constraint_index); // Syntactic sugar to make hand written NLP formulas more readable auto make_type_value_arrays = [](auto... terms) { return NlpArrays{{terms.type...}, {terms.value...}}; }; if (dual) { // linear_term + x_2 * exp(x_1 / x_2 - 1) >= 0 auto [types, values] = make_type_value_arrays( // Tvp{POI_XPRS_TOK_COL, var2_idx}, // x_2 Tvp{POI_XPRS_TOK_RB, {}}, // ) Tvp{POI_XPRS_TOK_COL, var1_idx}, // x_1 Tvp{POI_XPRS_TOK_COL, var2_idx}, // x_2 Tvp{POI_XPRS_TOK_OP, POI_XPRS_OP_DIVIDE}, // / Tvp{POI_XPRS_TOK_CON, 1.0}, // 1.0 Tvp{POI_XPRS_TOK_OP, POI_XPRS_OP_MINUS}, // - Tvp{POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_EXP}, // exp( Tvp{POI_XPRS_TOK_OP, POI_XPRS_OP_MULTIPLY}, // * Tvp{POI_XPRS_TOK_EOF, {}}); // EOF int begs[] = {0, static_cast(std::ssize(types))}; _check(XPRSnlpaddformulas(m_model.get(), 1, &rowidx, begs, 1, types, values)); } else { // linear_term - x_1 * exp(x_2 / x_1) >= 0 auto [types, values] = make_type_value_arrays( // Tvp{POI_XPRS_TOK_COL, var1_idx}, // x_1 Tvp{POI_XPRS_TOK_RB, {}}, // ) Tvp{POI_XPRS_TOK_COL, var2_idx}, // x_2 Tvp{POI_XPRS_TOK_COL, var1_idx}, // x_1 Tvp{POI_XPRS_TOK_OP, POI_XPRS_OP_DIVIDE}, // / Tvp{POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_EXP}, // exp( Tvp{POI_XPRS_TOK_OP, POI_XPRS_OP_MULTIPLY}, // * Tvp{POI_XPRS_TOK_OP, POI_XPRS_OP_UMINUS}, // - Tvp{POI_XPRS_TOK_EOF, {}}); // EOF int begs[] = {0, static_cast(std::ssize(types))}; _check(XPRSnlpaddformulas(m_model.get(), 1, &rowidx, begs, 1, types, values)); } ++m_quad_nl_constr_num; return constraint_index; } ConstraintIndex Model::_add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights) { IndexT index = m_sos_constraint_index.add_index(); ConstraintIndex constraint_index(ConstraintType::SOS, index); const int nnz = variables.size(); const char type[] = {poi_to_xprs_sos_type(sos_type)}; const XPRSint64 beg[] = {0}; std::vector ind_v(nnz); for (int i = 0; i < nnz; i++) { ind_v[i] = _checked_variable_index(variables[i]); } _check(XPRSaddsets64(m_model.get(), 1, nnz, type, beg, ind_v.data(), weights.data())); return constraint_index; } ConstraintIndex Model::add_sos_constraint(const Vector &variables, SOSType sos_type) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); Vector weights(variables.size()); std::iota(weights.begin(), weights.end(), 1.0); return _add_sos_constraint(variables, sos_type, weights); } ConstraintIndex Model::add_sos_constraint(const Vector &variables, SOSType sos_type, const Vector &weights) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); // If CoeffT will ever be not a double, we need to convert weights if constexpr (std::is_same_v) { std::vector double_weights; double_weights.reserve(weights.size()); for (CoeffT w : weights) { double_weights.push_back(static_cast(w)); } return _add_sos_constraint(variables, sos_type, double_weights); } else { return _add_sos_constraint(variables, sos_type, weights); } } Tvp to_xprs_opcode(UnaryOperator opcode_enum) { switch (opcode_enum) { case UnaryOperator::Neg: return {POI_XPRS_TOK_OP, POI_XPRS_OP_UMINUS}; case UnaryOperator::Sin: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_SIN}; case UnaryOperator::Cos: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_COS}; case UnaryOperator::Tan: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_TAN}; case UnaryOperator::Asin: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_ARCSIN}; case UnaryOperator::Acos: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_ARCCOS}; case UnaryOperator::Atan: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_ARCTAN}; case UnaryOperator::Abs: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_ABS}; case UnaryOperator::Sqrt: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_SQRT}; case UnaryOperator::Exp: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_EXP}; case UnaryOperator::Log: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_LN}; case UnaryOperator::Log10: return {POI_XPRS_TOK_IFUN, POI_XPRS_IFUN_LOG10}; default: { auto opname = unary_operator_to_string(opcode_enum); auto msg = fmt::format("Unknown unary operator for Xpress: {}", opname); throw std::runtime_error(msg); } } } Tvp to_xprs_opcode(BinaryOperator opcode_enum) { switch (opcode_enum) { case BinaryOperator::Sub: return {POI_XPRS_TOK_OP, POI_XPRS_OP_MINUS}; case BinaryOperator::Div: return {POI_XPRS_TOK_OP, POI_XPRS_OP_DIVIDE}; case BinaryOperator::Pow: return {POI_XPRS_TOK_OP, POI_XPRS_OP_EXPONENT}; case BinaryOperator::Add2: return {POI_XPRS_TOK_OP, POI_XPRS_OP_PLUS}; case BinaryOperator::Mul2: return {POI_XPRS_TOK_OP, POI_XPRS_OP_MULTIPLY}; default: auto opname = binary_operator_to_string(opcode_enum); auto msg = fmt::format("Unknown unary operator for Xpress: {}", opname); throw std::runtime_error(msg); } } Tvp to_xprs_opcode(TernaryOperator opcode_enum) { auto opname = ternary_operator_to_string(opcode_enum); auto msg = fmt::format("Unknown unary operator for Xpress: {}", opname); throw std::runtime_error(msg); } Tvp to_xprs_opcode(NaryOperator opcode_enum) { switch (opcode_enum) { case NaryOperator::Add: return {POI_XPRS_TOK_OP, POI_XPRS_OP_PLUS}; case NaryOperator::Mul: return {POI_XPRS_TOK_OP, POI_XPRS_OP_MULTIPLY}; default: auto opname = nary_operator_to_string(opcode_enum); auto msg = fmt::format("Unknown nary operator for Xpress: {}", opname); throw std::runtime_error(msg); } } BinaryOperator nary_to_binary_op(NaryOperator opcode_enum) { switch (opcode_enum) { case NaryOperator::Add: return BinaryOperator::Add2; case NaryOperator::Mul: return BinaryOperator::Mul2; default: { auto opname = nary_operator_to_string(opcode_enum); auto msg = fmt::format("Unknown nary operator for Xpress: {}", opname); throw std::runtime_error(msg); } } } Tvp Model::_decode_expr(const ExpressionGraph &graph, const ExpressionHandle &expr) { auto array_type = expr.array; auto index = expr.id; switch (array_type) { case ArrayType::Constant: return {POI_XPRS_TOK_CON, static_cast(graph.m_constants[index])}; case ArrayType::Variable: return {POI_XPRS_TOK_COL, static_cast(_checked_variable_index(graph.m_variables[index]))}; case ArrayType::Parameter: break; case ArrayType::Unary: return to_xprs_opcode(graph.m_unaries[index].op); case ArrayType::Binary: return to_xprs_opcode(graph.m_binaries[index].op); case ArrayType::Ternary: return to_xprs_opcode(graph.m_ternaries[index].op); case ArrayType::Nary: return to_xprs_opcode(graph.m_naries[index].op); defaut: break; } throw std::runtime_error("Not supported expression."); } ExpressionHandle nary_to_binary(ExpressionGraph &graph, const ExpressionHandle &expr) { auto &nary = graph.m_naries[expr.id]; NaryOperator n_op = nary.op; BinaryOperator bin_opcode = nary_to_binary_op(n_op); int n_operands = nary.operands.size(); if (n_operands == 0 || (n_op != NaryOperator::Add && n_op != NaryOperator::Mul)) { return expr; } auto new_expr = nary.operands[0]; for (int i = 1; i < n_operands; ++i) { new_expr = graph.add_binary(bin_opcode, new_expr, nary.operands[i]); } return new_expr; } std::pair, std::vector> Model::_decode_graph_postfix_order( ExpressionGraph &graph, const ExpressionHandle &result) { std::vector types; std::vector values; // Xpress uses a reversed Polish notation (post fix). So we need to visit the expression tree // in post-order depth first. We keep a stack to go depth first and visit each element // twice. First time process its children, second time decode it. std::stack> expr_stack; expr_stack.emplace(result, true); while (!expr_stack.empty()) { auto &[expr, visit_children] = expr_stack.top(); auto [type, value] = _decode_expr(graph, expr); // If its children have already been processed and we can add it to the expression if (!visit_children) { types.push_back(type); values.push_back(value); expr_stack.pop(); continue; } // Xpress requires a parenthesis to start an internal or user function if (type == POI_XPRS_TOK_IFUN || type == POI_XPRS_TOK_FUN) { types.push_back(POI_XPRS_TOK_RB); values.push_back({}); } switch (expr.array) { case ArrayType::Constant: case ArrayType::Variable: break; case ArrayType::Unary: expr_stack.emplace(graph.m_unaries[expr.id].operand, true); break; case ArrayType::Nary: // Xpress does not have nary operators out of the box, we have to translate them into a // sequence of binary operators expr = nary_to_binary(graph, expr); [[fallthrough]]; case ArrayType::Binary: expr_stack.emplace(graph.m_binaries[expr.id].right, true); expr_stack.emplace(graph.m_binaries[expr.id].left, true); break; default: throw std::runtime_error("Unrecognized token."); } // Children has been processed and added to the stack, next time we'll add it to the // expression. visit_children = false; } types.push_back(POI_XPRS_TOK_EOF); values.push_back({}); return {std::move(types), std::move(values)}; } ConstraintIndex Model::add_single_nl_constraint(ExpressionGraph &graph, const ExpressionHandle &result, const std::tuple &interval, const char *name) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int rowidx = get_raw_attribute_int_by_id(POI_XPRS_ROWS); ConstraintIndex constraint = add_linear_constraint(ScalarAffineFunction{}, interval, name); constraint.type = ConstraintType::NL; auto [types, values] = _decode_graph_postfix_order(graph, result); int nnz = values.size(); int begs[] = {0, nnz}; _check(XPRSnlpaddformulas(m_model.get(), 1, &rowidx, begs, 1, types.data(), values.data())); ++m_quad_nl_constr_num; return constraint; } void Model::delete_constraint(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); if (!is_constraint_active(constraint)) { throw std::runtime_error("Constraint does not exist"); } int constraint_row = _checked_constraint_index(constraint); if (constraint_row >= 0) { switch (constraint.type) { case ConstraintType::Quadratic: case ConstraintType::SecondOrderCone: case ConstraintType::ExponentialCone: case ConstraintType::NL: --m_quad_nl_constr_num; [[fallthrough]]; case ConstraintType::Linear: m_constraint_index.delete_index(constraint.index); _check(XPRSdelrows(m_model.get(), 1, &constraint_row)); break; case ConstraintType::SOS: m_sos_constraint_index.delete_index(constraint.index); _check(XPRSdelsets(m_model.get(), 1, &constraint_row)); break; default: throw std::runtime_error("Unknown constraint type"); } } } bool Model::is_constraint_active(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: case ConstraintType::SecondOrderCone: case ConstraintType::ExponentialCone: case ConstraintType::NL: return m_constraint_index.has_index(constraint.index); case ConstraintType::SOS: return m_sos_constraint_index.has_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } return false; } void Model::set_objective(const ScalarAffineFunction &function, ObjectiveSense sense) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); // Delete linear and quadratic term of the objective function _check(XPRSdelobj(m_model.get(), 0)); _check(XPRSdelqmatrix(m_model.get(), -1)); has_quad_objective = false; if (has_nlp_objective) { delete_constraint(m_nlp_obj_constraint); delete_variable(m_nlp_obj_variable); has_nlp_objective = false; } if (function.size() > 0) { AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; const int *cind = ptr_form.index; const double *cval = ptr_form.value; _check(XPRSchgobj(m_model.get(), numnz, cind, cval)); } if (function.constant.has_value()) { int obj_constant_magic_index = -1; double obj_constant = -static_cast(function.constant.value()); _check(XPRSchgobj(m_model.get(), 1, &obj_constant_magic_index, &obj_constant)); } int stype = poi_to_xprs_obj_sense(sense); _check(XPRSchgobjsense(m_model.get(), stype)); } // Set quadratic objective function, replacing any previous objective. // Handles Xpress's symmetric matrix convention where off-diagonal terms are doubled. void Model::set_objective(const ScalarQuadraticFunction &function, ObjectiveSense sense) { // Set affine part (also clears any previous quadratic terms) const auto &affine_part = function.affine_part.value_or(ScalarAffineFunction{}); set_objective(affine_part, sense); // Add quadratic terms if present if (function.size() > 0) { QuadraticFunctionPtrForm ptr_form = poi_to_xprs_quad_formula(*this, function, true); int numqnz = ptr_form.numnz; const int *qrow = ptr_form.row; const int *qcol = ptr_form.col; const double *qval = ptr_form.value; _check(XPRSchgmqobj64(m_model.get(), numqnz, qrow, qcol, qval)); has_quad_objective = true; } } void Model::set_objective(const ExprBuilder &function, ObjectiveSense sense) { auto deg = function.degree(); if (deg <= 1) { ScalarAffineFunction f(function); set_objective(f, sense); } else if (deg == 2) { ScalarQuadraticFunction f(function); set_objective(f, sense); } else { throw std::runtime_error("Objective must be linear or quadratic"); } } void Model::add_single_nl_objective(ExpressionGraph &graph, const ExpressionHandle &result) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); if (!has_nlp_objective) { has_nlp_objective = true; m_nlp_obj_variable = add_variable(); m_nlp_obj_constraint = add_linear_constraint(ScalarAffineFunction(m_nlp_obj_variable, -1.0), ConstraintSense::Equal, 0.0, NULL); set_objective_coefficient(m_nlp_obj_variable, 1.0); } auto [types, values] = _decode_graph_postfix_order(graph, result); int nnz = values.size(); int begs[] = {0, nnz}; int rowidx = _constraint_index(m_nlp_obj_constraint); _check(XPRSnlpaddformulas(m_model.get(), 1, &rowidx, begs, 1, types.data(), values.data())); } double Model::get_normalized_rhs(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); double rhs = {}; int rowidx = _checked_constraint_index(constraint); _check(XPRSgetrhs(m_model.get(), &rhs, rowidx, rowidx)); return rhs; } void Model::set_normalized_rhs(ConstraintIndex constraint, double value) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int rowidx = _checked_constraint_index(constraint); _check(XPRSchgrhs(m_model.get(), 1, &rowidx, &value)); } double Model::get_normalized_coefficient(ConstraintIndex constraint, VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int rowidx = _checked_constraint_index(constraint); int colidx = _checked_variable_index(variable); double coeff = {}; _check(XPRSgetcoef(m_model.get(), rowidx, colidx, &coeff)); return coeff; } void Model::set_normalized_coefficient(ConstraintIndex constraint, VariableIndex variable, double value) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int rowidx = _checked_constraint_index(constraint); int colidx = _checked_variable_index(variable); _check(XPRSchgcoef(m_model.get(), rowidx, colidx, value)); } double Model::get_objective_coefficient(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int colidx = _checked_variable_index(variable); double coeff = {}; _check(XPRSgetobj(m_model.get(), &coeff, colidx, colidx)); return coeff; } void Model::set_objective_coefficient(VariableIndex variable, double value) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); _clear_caches(); int colidx = _checked_variable_index(variable); _check(XPRSchgobj(m_model.get(), 1, &colidx, &value)); } int Model::_constraint_index(ConstraintIndex constraint) { switch (constraint.type) { case ConstraintType::Linear: case ConstraintType::Quadratic: case ConstraintType::SecondOrderCone: case ConstraintType::ExponentialCone: case ConstraintType::NL: return m_constraint_index.get_index(constraint.index); case ConstraintType::SOS: return m_sos_constraint_index.get_index(constraint.index); default: throw std::runtime_error("Unknown constraint type"); } } int Model::_variable_index(VariableIndex variable) { return m_variable_index.get_index(variable.index); } int Model::_checked_constraint_index(ConstraintIndex constraint) { int rowidx = _constraint_index(constraint); if (rowidx < 0) { throw std::runtime_error("Constraint does not exists"); } return rowidx; } int Model::_checked_variable_index(VariableIndex variable) { int colidx = _variable_index(variable); if (colidx < 0) { throw std::runtime_error("Variable does not exists"); } return colidx; } bool Model::_is_mip() { return get_raw_attribute_int_by_id(POI_XPRS_MIPENTS) > 0 || get_raw_attribute_int_by_id(POI_XPRS_SETS) > 0; } void Model::optimize() { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _clear_caches(); int stop_status = 0; _check(XPRSoptimize(m_model.get(), "", &stop_status, nullptr)); m_need_postsolve = (stop_status == POI_XPRS_SOLVESTATUS_STOPPED); } double Model::get_variable_value(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); int colidx = _checked_variable_index(variable); int status = POI_XPRS_SOLAVAILABLE_NOTFOUND; double value = {}; _check(XPRSgetsolution(m_model.get(), &status, &value, colidx, colidx)); if (status == POI_XPRS_SOLAVAILABLE_NOTFOUND) { throw std::runtime_error("No solution found"); } return value; } double Model::get_variable_rc(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); int colidx = _checked_variable_index(variable); int status = POI_XPRS_SOLAVAILABLE_NOTFOUND; double value = {}; _check(XPRSgetredcosts(m_model.get(), &status, &value, colidx, colidx)); if (status == POI_XPRS_SOLAVAILABLE_NOTFOUND) { throw std::runtime_error("No solution found"); } return value; } double Model::get_variable_primal_ray(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); if (m_primal_ray.empty()) { int has_ray = 0; _check(XPRSgetprimalray(m_model.get(), nullptr, &has_ray)); if (has_ray == 0) { throw std::runtime_error("Primal ray not available"); } m_primal_ray.resize(get_raw_attribute_int_by_id(POI_XPRS_COLS)); _check(XPRSgetprimalray(m_model.get(), m_primal_ray.data(), &has_ray)); assert(has_ray != 0); } int colidx = _checked_variable_index(variable); return m_primal_ray[colidx]; } int Model::_get_basis_stat(int entity_idx, bool is_row) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int entity_stat = {}; if (is_row) { _check(XPRSgetbasisval(m_model.get(), entity_idx, 0, &entity_stat, nullptr)); } else { _check(XPRSgetbasisval(m_model.get(), 0, entity_idx, nullptr, &entity_stat)); } return entity_stat; } bool Model::is_variable_basic(VariableIndex variable) { return _get_basis_stat(_checked_variable_index(variable), false) == POI_XPRS_BASISSTATUS_BASIC; } bool Model::is_variable_nonbasic_lb(VariableIndex variable) { return _get_basis_stat(_checked_variable_index(variable), false) == POI_XPRS_BASISSTATUS_NONBASIC_LOWER; } bool Model::is_variable_nonbasic_ub(VariableIndex variable) { return _get_basis_stat(_checked_variable_index(variable), false) == POI_XPRS_BASISSTATUS_NONBASIC_UPPER; } bool Model::is_variable_superbasic(VariableIndex variable) { return _get_basis_stat(_checked_variable_index(variable), false) == POI_XPRS_BASISSTATUS_SUPERBASIC; } double Model::get_variable_lowerbound(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); double lb = {}; int colidx = _checked_variable_index(variable); _check(XPRSgetlb(m_model.get(), &lb, colidx, colidx)); return lb; } double Model::get_variable_upperbound(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); double ub = {}; int colidx = _checked_variable_index(variable); _check(XPRSgetub(m_model.get(), &ub, colidx, colidx)); return ub; } VariableDomain Model::get_variable_type(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int colidex = _checked_variable_index(variable); char vtype = {}; _check(XPRSgetcoltype(m_model.get(), &vtype, colidex, colidex)); return xprs_to_poi_var_type(vtype); } char Model::_get_variable_bound_IIS(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); if (m_iis_cols.empty() || m_iis_bound_types.empty()) { int m_nrows = {}; int m_ncols = {}; _check(XPRSgetiisdata(m_model.get(), 1, &m_nrows, &m_ncols, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); m_iis_cols.resize(m_ncols); m_iis_bound_types.resize(m_ncols); _check(XPRSgetiisdata(m_model.get(), 1, &m_nrows, &m_ncols, nullptr, m_iis_cols.data(), nullptr, m_iis_bound_types.data(), nullptr, nullptr, nullptr, nullptr)); } int colidx = _checked_variable_index(variable); for (int j = 0; j < m_iis_cols.size(); ++j) { if (m_iis_cols[j] == colidx) { return m_iis_bound_types[j]; } } return '\0'; } bool Model::is_variable_lowerbound_IIS(VariableIndex variable) { return _get_variable_bound_IIS(variable) == 'L'; } bool Model::is_variable_upperbound_IIS(VariableIndex variable) { return _get_variable_bound_IIS(variable) == 'U'; } void Model::set_constraint_sense(ConstraintIndex constraint, ConstraintSense sense) { const int rowidx = _checked_constraint_index(constraint); const char rowtype = poi_to_xprs_cons_sense(sense); _check(XPRSchgrowtype(m_model.get(), 1, &rowidx, &rowtype)); } ConstraintSense Model::get_constraint_sense(ConstraintIndex constraint) { const int rowidx = _checked_constraint_index(constraint); char rowtype = {}; _check(XPRSgetrowtype(m_model.get(), &rowtype, rowidx, rowidx)); return xprs_to_poi_cons_sense(rowtype); } void Model::set_constraint_rhs(ConstraintIndex constraint, CoeffT rhs) { const int rowidx = _checked_constraint_index(constraint); const double g_rhs[] = {static_cast(rhs)}; _check(XPRSchgrhs(m_model.get(), 1, &rowidx, g_rhs)); } CoeffT Model::get_constraint_rhs(ConstraintIndex constraint) { const int rowidx = _checked_constraint_index(constraint); double rhs = {}; _check(XPRSgetrhs(m_model.get(), &rhs, rowidx, rowidx)); return static_cast(rhs); } double Model::get_constraint_slack(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int rowidx = _checked_constraint_index(constraint); int status = POI_XPRS_SOLAVAILABLE_NOTFOUND; double value = {}; _check(XPRSgetslacks(m_model.get(), &status, &value, rowidx, rowidx)); if (status == POI_XPRS_SOLAVAILABLE_NOTFOUND) { throw std::runtime_error("No solution found"); } return value; } double Model::get_constraint_dual(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); int rowidx = _checked_constraint_index(constraint); int status = POI_XPRS_SOLAVAILABLE_NOTFOUND; double value = {}; _check(XPRSgetduals(m_model.get(), &status, &value, rowidx, rowidx)); if (status == POI_XPRS_SOLAVAILABLE_NOTFOUND) { throw std::runtime_error("No solution found"); } return value; } double Model::get_constraint_dual_ray(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); if (m_dual_ray.empty()) { int has_ray = 0; _check(XPRSgetdualray(m_model.get(), nullptr, &has_ray)); if (has_ray == 0) { throw std::runtime_error("Dual ray not available"); } m_dual_ray.resize(get_raw_attribute_int_by_id(POI_XPRS_ROWS)); _check(XPRSgetdualray(m_model.get(), m_dual_ray.data(), &has_ray)); assert(has_ray != 0); } int rowidx = _checked_constraint_index(constraint); return m_dual_ray[rowidx]; } bool Model::is_constraint_basic(ConstraintIndex constraint) { return _get_basis_stat(_checked_constraint_index(constraint), true) == POI_XPRS_BASISSTATUS_BASIC; } bool Model::is_constraint_nonbasic_lb(ConstraintIndex constraint) { return _get_basis_stat(_checked_constraint_index(constraint), true) == POI_XPRS_BASISSTATUS_NONBASIC_LOWER; } bool Model::is_constraint_nonbasic_ub(ConstraintIndex constraint) { return _get_basis_stat(_checked_constraint_index(constraint), true) == POI_XPRS_BASISSTATUS_NONBASIC_UPPER; } bool Model::is_constraint_superbasic(ConstraintIndex constraint) { return _get_basis_stat(_checked_constraint_index(constraint), true) == POI_XPRS_BASISSTATUS_SUPERBASIC; } bool Model::is_constraint_in_IIS(ConstraintIndex constraint) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); _ensure_postsolved(); if (m_iss_rows.empty()) { int nrow = {}; int ncol = {}; _check(XPRSgetiisdata(m_model.get(), 1, &nrow, &ncol, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); m_iss_rows.resize(nrow); _check(XPRSgetiisdata(m_model.get(), 1, &nrow, &ncol, m_iss_rows.data(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); } int rowidx = _constraint_index(constraint); for (int ridx : m_iss_rows) { if (ridx == rowidx) { return true; } } return false; } void *Model::get_raw_model() { return m_model.get(); } std::string Model::version_string() { char buffer[32]; XPRSgetversion(buffer); return buffer; } void Model::computeIIS() { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); int status = 0; // passing 1 emphasizes simplicity of the IIS // passing 2 emphasizes a quick result _check(XPRSiisfirst(m_model.get(), 2, &status)); switch (status) { case 0: return; case 1: throw std::runtime_error("IIS: problem is feasible"); case 2: throw std::runtime_error("IIS: generic error"); case 3: throw std::runtime_error("IIS: timeout or interruption"); default: throw std::runtime_error(fmt::format("IIS: unknown exit status {}", status)); } } //////////////////////////////////////////////////////////////////////////////// // ATTRIBUTES AND CONTROLS ACCESS // //////////////////////////////////////////////////////////////////////////////// static const char *xpress_type_to_string(CATypes type) { switch (type) { case CATypes::INT: return "int"; case CATypes::INT64: return "int64"; case CATypes::DOUBLE: return "double"; case CATypes::STRING: return "string"; default: return "unknown"; } } std::pair Model::_get_control_info(const char *control) { int control_id = {}; int control_type = {}; _check(XPRSgetcontrolinfo(m_model.get(), control, &control_id, &control_type)); return std::make_pair(control_id, static_cast(control_type)); } std::pair Model::_get_attribute_info(const char *attrib) { int attrib_id = {}; int attrib_type = {}; _check(XPRSgetattribinfo(m_model.get(), attrib, &attrib_id, &attrib_type)); return std::make_pair(attrib_id, static_cast(attrib_type)); } int Model::_get_checked_control_id(const char *control, CATypes expected, CATypes backup) { auto [id, type] = _get_control_info(control); if (type == CATypes::NOTDEFINED) { throw std::runtime_error("Error, unknown control type."); } if (type != expected && type != backup) { auto error_msg = fmt::format( "Error retriving control '{}'. Control type is '{}', but '{}' was expected.", control, xpress_type_to_string(type), xpress_type_to_string(expected)); throw std::runtime_error(error_msg); } return id; } int Model::_get_checked_attribute_id(const char *attrib, CATypes expected, CATypes backup) { auto [id, type] = _get_attribute_info(attrib); if (type == CATypes::NOTDEFINED) { throw std::runtime_error("Error, unknown attribute type."); } if (type != expected) { auto error_msg = fmt::format( "Error retriving attribute '{}'. Attribute type is '{}', but '{}' was expected.", attrib, xpress_type_to_string(type), xpress_type_to_string(expected)); throw std::runtime_error(error_msg); } return id; } // Generic access to attributes and controls Model::xprs_type_variant_t Model::get_raw_attribute(const char *attrib) { auto [id, type] = _get_attribute_info(attrib); switch (type) { case CATypes::INT: case CATypes::INT64: return get_raw_attribute_int_by_id(id); case CATypes::DOUBLE: return get_raw_attribute_dbl_by_id(id); case CATypes::STRING: return get_raw_attribute_str_by_id(id); default: throw std::runtime_error("Unknown attribute type"); } } Model::xprs_type_variant_t Model::get_raw_control(const char *control) { auto [id, type] = _get_control_info(control); switch (type) { case CATypes::INT: case CATypes::INT64: return get_raw_control_int_by_id(id); case CATypes::DOUBLE: return get_raw_control_dbl_by_id(id); case CATypes::STRING: return get_raw_control_str_by_id(id); default: throw std::runtime_error("Unknown attribute type"); } } // Helper struct to achieve a sort of pattern matching with the visitor pattern. // It basically exploit the overload resolution to get the equivalent of a series of // if constexpr(std::is_same_v) template struct OverloadSet : public Ts... { using Ts::operator()...; }; // Deduction guide for OverloadSet template OverloadSet(Ts...) -> OverloadSet; void Model::set_raw_control(const char *control, Model::xprs_type_variant_t &value) { std::visit(OverloadSet{[&](double d) { set_raw_control_dbl(control, d); }, [&](std::integral auto i) { set_raw_control_int(control, i); }, [&](const std::string &s) { set_raw_control_str(control, s.c_str()); }}, value); } void Model::set_raw_control_int(const char *control, XPRSint64 value) { int id = _get_checked_control_id(control, CATypes::INT64, CATypes::INT); return set_raw_control_int_by_id(id, value); } void Model::set_raw_control_dbl(const char *control, double value) { int id = _get_checked_control_id(control, CATypes::DOUBLE); return set_raw_control_dbl_by_id(id, value); } void Model::set_raw_control_str(const char *control, const char *value) { int id = _get_checked_control_id(control, CATypes::STRING); return set_raw_control_str_by_id(id, value); } XPRSint64 Model::get_raw_control_int(const char *control) { int id = _get_checked_control_id(control, CATypes::INT64, CATypes::INT); return get_raw_control_int_by_id(id); } double Model::get_raw_control_dbl(const char *control) { int id = _get_checked_control_id(control, CATypes::DOUBLE); return get_raw_control_dbl_by_id(id); } std::string Model::get_raw_control_str(const char *control) { int id = _get_checked_control_id(control, CATypes::STRING); return get_raw_control_str_by_id(id); } XPRSint64 Model::get_raw_attribute_int(const char *attrib) { int id = _get_checked_attribute_id(attrib, CATypes::INT64, CATypes::INT); return get_raw_attribute_int_by_id(id); } double Model::get_raw_attribute_dbl(const char *attrib) { int id = _get_checked_attribute_id(attrib, CATypes::DOUBLE); return get_raw_attribute_dbl_by_id(id); } std::string Model::get_raw_attribute_str(const char *attrib) { int id = _get_checked_attribute_id(attrib, CATypes::STRING); return get_raw_attribute_str_by_id(id); } void Model::set_raw_control_int_by_id(int control, XPRSint64 value) { // Disabling Xpress internal callback mutex is forbidden since this could easily create race // condition and deadlocks since it's used in conjunction with Python GIL. if (control == POI_XPRS_MUTEXCALLBACKS) { throw std::runtime_error( "Changing Xpress callback mutex setting is currently not supported."); } _check(XPRSsetintcontrol64(m_model.get(), control, value)); } void Model::set_raw_control_dbl_by_id(int control, double value) { _check(XPRSsetdblcontrol(m_model.get(), control, value)); } void Model::set_raw_control_str_by_id(int control, const char *value) { _check(XPRSsetstrcontrol(m_model.get(), control, value)); } XPRSint64 Model::get_raw_control_int_by_id(int control) { XPRSint64 value = {}; _check(XPRSgetintcontrol64(m_model.get(), control, &value)); return value; } double Model::get_raw_control_dbl_by_id(int control) { double value = {}; _check(XPRSgetdblcontrol(m_model.get(), control, &value)); return value; } std::string Model::get_raw_control_str_by_id(int control) { int req_size = {}; _check(XPRSgetstringcontrol(m_model.get(), control, nullptr, 0, &req_size)); std::string value = {}; value.resize(req_size); _check(XPRSgetstringcontrol(m_model.get(), control, value.data(), req_size, &req_size)); if (value.size() != req_size) { throw std::runtime_error("Error while getting control string"); } // Align string size with string length value.resize(strlen(value.c_str())); return value; } XPRSint64 Model::get_raw_attribute_int_by_id(int attrib) { XPRSint64 value = {}; _check(XPRSgetintattrib64(m_model.get(), attrib, &value)); return value; } double Model::get_raw_attribute_dbl_by_id(int attrib) { double value = {}; _check(XPRSgetdblattrib(m_model.get(), attrib, &value)); return value; } std::string Model::get_raw_attribute_str_by_id(int attrib) { int req_size = {}; _check(XPRSgetstringattrib(m_model.get(), attrib, nullptr, 0, &req_size)); std::string value = {}; value.resize(req_size); _check(XPRSgetstringattrib(m_model.get(), attrib, value.data(), req_size, &req_size)); if (value.size() != req_size) { throw std::runtime_error("Error while getting control string"); } return value; } LPSTATUS Model::get_lp_status() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_LPSTATUS)); } MIPSTATUS Model::get_mip_status() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_MIPSTATUS)); } NLPSTATUS Model::get_nlp_status() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_NLPSTATUS)); } SOLVESTATUS Model::get_solve_status() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_SOLVESTATUS)); } SOLSTATUS Model::get_sol_status() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_SOLSTATUS)); } IISSOLSTATUS Model::get_iis_sol_status() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_IISSOLSTATUS)); } OPTIMIZETYPE Model::get_optimize_type() { return static_cast(get_raw_attribute_int_by_id(POI_XPRS_OPTIMIZETYPEUSED)); } void Model::_ensure_postsolved() { if (m_need_postsolve) { _check(XPRSpostsolve(m_model.get())); // Non-convex quadratic constraint might be solved with the non linear solver, so we have to // make sure that the problem is nl-postsolved, even if it is not always strictly necessary // and could introduce minor overhead. if (m_quad_nl_constr_num >= 0 || has_quad_objective || has_nlp_objective) { _check(XPRSnlppostsolve(m_model.get())); } m_need_postsolve = false; } } void Model::_check_expected_mode(XPRESS_MODEL_MODE mode) { if (mode == XPRESS_MODEL_MODE::MAIN && m_mode == XPRESS_MODEL_MODE::CALLBACK_) { throw std::runtime_error("Cannot call this function from within a callback. " "This operation is only available on the main model."); } if (mode == XPRESS_MODEL_MODE::CALLBACK_ && m_mode == XPRESS_MODEL_MODE::MAIN) { throw std::runtime_error("This function can only be called from within a callback. " "It is not available on the main model."); } } xpress_cbs_data Model::cb_get_arguments() { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); return cb_args; } // NOTE: XPRSgetcallbacksolution return a context dependent solution double Model::_cb_get_context_solution(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); // Xpress already caches solutions internally int p_available = 0; int colidx = _checked_variable_index(variable); double value[] = {0.0}; _check(XPRSgetcallbacksolution(m_model.get(), &p_available, value, colidx, colidx)); if (p_available == 0) { throw std::runtime_error("No solution available"); } return value[0]; } // Get MIP solution value for a variable in callback context. // Returns the callback's candidate integer solution if available (intsol, preintsol contexts), // otherwise falls back to the current incumbent solution. double Model::cb_get_solution(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); if (cb_where == CB_CONTEXT::intsol || cb_where == CB_CONTEXT::preintsol) { // Context provides a candidate integer solution - return it directly return _cb_get_context_solution(variable); } // No integer solution in current context - return best known incumbent instead return cb_get_incumbent(variable); } // Get LP relaxation solution value for a variable in callback context. Returns the callback's LP // relaxation solution when available in contexts that solve LPs (bariteration, cutround, // chgbranchobject, nodelpsolved, optnode). It throws in other contexts. double Model::cb_get_relaxation(VariableIndex variable) { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); if (cb_where != CB_CONTEXT::bariteration && cb_where != CB_CONTEXT::cutround && cb_where != CB_CONTEXT::chgbranchobject && cb_where != CB_CONTEXT::nodelpsolved && cb_where != CB_CONTEXT::optnode) { throw std::runtime_error("LP relaxation solution not available."); } return _cb_get_context_solution(variable); } double Model::cb_get_incumbent(VariableIndex variable) { return get_variable_value(variable); } void Model::cb_set_solution(VariableIndex variable, double value) { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); cb_sol_cache.emplace_back(_checked_variable_index(variable), value); } void Model::cb_submit_solution() { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); auto &sol = cb_sol_cache; if (sol.empty()) { return; } // Merge together coefficient of duplicated indices std::ranges::sort(sol); std::vector indices; std::vector values; int curr_idx = std::numeric_limits::lowest(); for (auto [idx, val] : sol) { if (curr_idx != idx) { curr_idx = idx; indices.push_back(idx); values.emplace_back(); } values.back() += val; } int ncol = static_cast(indices.size()); _check(XPRSaddmipsol(m_model.get(), ncol, values.data(), indices.data(), nullptr)); } void Model::cb_exit() { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); _check(XPRSinterrupt(m_model.get(), POI_XPRS_STOP_USER)); } void Model::_cb_add_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { AffineFunctionPtrForm ptr_form; ptr_form.make(this, function); int numnz = ptr_form.numnz; char g_sense = poi_to_xprs_cons_sense(sense); double g_rhs = static_cast(rhs - function.constant.value_or(CoeffT{})); const int *cind = ptr_form.index; const double *cval = ptr_form.value; // Before adding the cut, we must translate it to the presolved model. If this translation fails // then we cannot continue. The translation can only fail if we have presolve operations enabled // that should be disabled in case of dynamically separated constraints. int ncols = get_raw_attribute_int_by_id(POI_XPRS_COLS); int ps_numnz = 0; std::vector ps_cind(ncols); std::vector ps_cval(ncols); double ps_rhs = 0.0; int ps_status = 0; _check(XPRSpresolverow(m_model.get(), g_sense, numnz, cind, cval, g_rhs, ncols, &ps_numnz, ps_cind.data(), ps_cval.data(), &ps_rhs, &ps_status)); if (ps_status != 0) { throw std::runtime_error("Failed to presolve new cut."); } XPRSint64 start[] = {0, ps_numnz}; int ctype = 1; if (cb_where == CB_CONTEXT::cutround) { // NOTE: we assume cuts to be global since other solvers only support those _check(XPRSaddmanagedcuts64(m_model.get(), 1, 1, &g_sense, &ps_rhs, start, ps_cind.data(), ps_cval.data())); } else { _check(XPRSaddcuts64(m_model.get(), 1, &ctype, &g_sense, &ps_rhs, start, ps_cind.data(), ps_cval.data())); } } // Tries to add a lazy constraints. If the context is right, but we are not in a node of the tree, // it fallbacks to simply rejecting the solution, since no cut can be added at that moment. void Model::cb_add_lazy_constraint(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { _check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); if (cb_where != CB_CONTEXT::nodelpsolved && cb_where != CB_CONTEXT::optnode && cb_where != CB_CONTEXT::preintsol && cb_where != CB_CONTEXT::prenode) { throw std::runtime_error("New constraints can be added only in NODELPSOLVED, OPTNODE, " "PREINTSOL, and PRENODE callbacks."); } if (cb_where != CB_CONTEXT::preintsol) { _cb_add_cut(function, sense, rhs); return; } auto *args = std::get(cb_args); if (args->soltype == 0) { _cb_add_cut(function, sense, rhs); return; } // If the solution didn't originated from a node of the tree, we can't reject it with a cut. // However, if the user cut makes the solution infeasible, we have to reject it. double pos_activity = 0.0; double neg_anctivity = 0.0; const int nnz = function.size(); for (int i = 0; i < nnz; ++i) { double col_val = _cb_get_context_solution(function.variables[i]); double term_val = col_val * function.coefficients[i]; (term_val > 0.0 ? pos_activity : neg_anctivity) += term_val; } const double activity = pos_activity + neg_anctivity; const double real_rhs = static_cast(rhs - function.constant.value_or(0.0)); double infeas = 0.0; // > 0 if solution violates constraint if (sense == ConstraintSense::Equal || sense == ConstraintSense::LessEqual) { infeas = std::max(infeas, activity - real_rhs); } if (sense == ConstraintSense::Equal || sense == ConstraintSense::GreaterEqual) { infeas = std::max(infeas, real_rhs - activity); } const double feastol = get_raw_control_dbl_by_id(POI_XPRS_FEASTOL); if (infeas > feastol) { // The user added a cut, but we are not in a context where it can be added. So, the only // thing we can reasonably do is to reject the solution iff it is made infeasible by the // user provided cut. *args->p_reject = 1; } } void Model::cb_add_lazy_constraint(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs) { ScalarAffineFunction f(function); cb_add_lazy_constraint(f, sense, rhs); } void Model::cb_add_user_cut(const ScalarAffineFunction &function, ConstraintSense sense, CoeffT rhs) { _cb_add_cut(function, sense, rhs); } void Model::cb_add_user_cut(const ExprBuilder &function, ConstraintSense sense, CoeffT rhs) { ScalarAffineFunction f(function); cb_add_user_cut(f, sense, rhs); } // Helper struct that defines a static function when instantiated. // The defined static function is the actual Xpress CB that will be registered to the context // selected by the Where argument. // It collects the due-diligence common to all CBs, to minimize code duplication. template struct Model::CbWrap { static RetT cb(XPRSprob cb_prob, void *user_data, ArgsT... args) noexcept { auto *model = reinterpret_cast(user_data); assert(model->m_mode == XPRESS_MODEL_MODE::MAIN); auto cb_args = CbStruct{{/*ret_code*/}, args...}; // Store additional arguments // Temporarily swap the XpressModel's problem pointer with the callback's thread-local // clone. This allows reusing the model's indexers and helper data without copying. // // Current design assumes serialized callback invocation (enforced by Python GIL). The swap // is safe because only one callback executes at a time. // // NOTE: Free-threading compatibility will require redesign. The approach would be to split // Model state into: // - Immutable shared state (indexers, problem structure) shared through a common pointer, // read-only when in callbacks // - Thread-local state (callback context, temporary buffers) // // Instead of swapping problem pointers, create lightweight callback problem objects that // reference the shared state. This is conceptually simple but requires refactoring all // Model methods to access shared state through indirection. XPRSprob main_prob = model->_toggle_model_mode(cb_prob); // Ensure restoration on all exit paths Defer main_prob_restore = [&] { model->_toggle_model_mode(main_prob); }; try { model->_check_expected_mode(XPRESS_MODEL_MODE::CALLBACK_); model->cb_sol_cache.clear(); model->cb_where = static_cast(Where); model->cb_args = &cb_args; model->m_callback(model, static_cast(Where)); model->cb_submit_solution(); } catch (...) { // We cannot let any exception slip through a callback, we have to catch it, // terminate Xpress gracefully and then we can throw it again. if (XPRSinterrupt(cb_prob, POI_XPRS_STOP_USER) != 0) { std::rethrow_exception(std::current_exception()); // We have to terminate somehow } model->m_captured_exceptions.push_back(std::current_exception()); } return cb_args.get_return_value(); } }; static constexpr unsigned long long as_flag(int ID) { assert("ID must be in the [0, 63] range" && ID >= 0 && ID < 63); return (1ULL << ID); } static constexpr bool test_ctx(CB_CONTEXT dest_ctx, unsigned long long curr_ctx) { auto ctx = static_cast(dest_ctx); return (curr_ctx & ctx) != 0; // The context matches the ID } void Model::set_callback(const Callback &cb, unsigned long long new_contexts) { _check_expected_mode(XPRESS_MODEL_MODE::MAIN); // Default message callback management - we always register a default message handler // unless the user explicitly registers their own. When the user callback is removed, // restore the default handler. if (is_default_message_cb_set && test_ctx(CB_CONTEXT::message, new_contexts)) { _check(XPRSremovecbmessage(m_model.get(), &default_print, nullptr)); is_default_message_cb_set = false; } if (!is_default_message_cb_set && !test_ctx(CB_CONTEXT::message, new_contexts)) { _check(XPRSaddcbmessage(m_model.get(), &default_print, nullptr, 0)); is_default_message_cb_set = true; } // Register/unregister Xpress callbacks based on context changes. For each callback type, // compare the old context set (m_curr_contexts) with the new one. If a context needs to be // added or removed, register/unregister the corresponding low-level wrapper function. // // Note: The wrapper functions are stateless - they just forward to the user callback pointer. // Updating the callback for an already-registered context only requires updating m_callback; // the wrapper stays registered. #define XPRSCB_SET_CTX(ID, NAME, RET, ...) \ { \ bool has_cb = test_ctx(CB_CONTEXT::NAME, m_curr_contexts); \ bool needs_cb = test_ctx(CB_CONTEXT::NAME, new_contexts); \ if (has_cb != needs_cb) \ { \ auto *cb = &CbWrap::cb; \ if (has_cb) \ { \ _check(XPRSremovecb##NAME(m_model.get(), cb, this)); \ } \ else /* needs_cb */ \ { \ _check(XPRSaddcb##NAME(m_model.get(), cb, this, 0)); \ } \ } \ } XPRSCB_LIST(XPRSCB_SET_CTX, XPRSCB_ARG_TYPE) #undef XPRSCB_SET_CTX m_curr_contexts = new_contexts; m_callback = cb; } } // namespace xpress ================================================ FILE: lib/xpress_model_ext.cpp ================================================ #include #include #include #include #include #include #include #include #include "pyoptinterface/core.hpp" #include "pyoptinterface/xpress_model.hpp" namespace nb = nanobind; using namespace nb::literals; using namespace xpress; extern void bind_xpress_constants(nb::module_ &m); NB_MODULE(xpress_model_ext, m) { m.import_("pyoptinterface._src.core_ext"); m.def("is_library_loaded", &xpress::is_library_loaded); m.def("load_library", &xpress::load_library); m.def("license", &xpress::license, "i"_a, "c"_a); m.def("beginlicensing", &xpress::beginlicensing); m.def("endlicensing", &xpress::endlicensing); bind_xpress_constants(m); nb::class_(m, "Env") .def(nb::init<>()) .def(nb::init(), "path"_a = nullptr) .def("close", &Env::close); nb::class_(m, "RawModel") .def(nb::init<>()) .def(nb::init(), nb::keep_alive<1, 2>()) // Model management .def("init", &Model::init, "env"_a) .def("close", &Model::close) .def("optimize", &Model::optimize, nb::call_guard()) .def("computeIIS", &Model::computeIIS, nb::call_guard()) .def("write", &Model::write, "filename"_a, nb::call_guard()) .def("_is_mip", &Model::_is_mip) .def_static("get_infinity", &Model::get_infinity) .def("get_problem_name", &Model::get_problem_name) .def("set_problem_name", &Model::set_problem_name, "probname"_a) .def("add_mip_start", &Model::add_mip_start, "variables"_a, "values"_a) .def("get_raw_model", &Model::get_raw_model) .def("version_string", &Model::version_string) // Index mappings .def("_constraint_index", &Model::_constraint_index, "constraint"_a) .def("_variable_index", &Model::_variable_index, "variable"_a) .def("_checked_constraint_index", &Model::_checked_constraint_index, "constraint"_a) .def("_checked_variable_index", &Model::_checked_variable_index, "variable"_a) // Variables .def("add_variable", &Model::add_variable, "domain"_a = VariableDomain::Continuous, "lb"_a = POI_XPRS_MINUSINFINITY, "ub"_a = POI_XPRS_PLUSINFINITY, "name"_a = "") .def("delete_variable", &Model::delete_variable, "variable"_a) .def("delete_variables", &Model::delete_variables, "variables"_a) .def("set_objective_coefficient", &Model::set_objective_coefficient, "variable"_a, "value"_a) .def("set_variable_bounds", &Model::set_variable_bounds, "variable"_a, "lb"_a, "ub"_a) .def("set_variable_lowerbound", &Model::set_variable_lowerbound, "variable"_a, "lb"_a) .def("set_variable_name", &Model::set_variable_name, "variable"_a, "name"_a) .def("set_variable_type", &Model::set_variable_type, "variable"_a, "vtype"_a) .def("set_variable_upperbound", &Model::set_variable_upperbound, "variable"_a, "ub"_a) .def("is_variable_active", &Model::is_variable_active, "variable"_a) .def("is_variable_basic", &Model::is_variable_basic, "variable"_a) .def("get_variable_lowerbound_IIS", &Model::is_variable_lowerbound_IIS, "variable"_a) .def("is_variable_nonbasic_lb", &Model::is_variable_nonbasic_lb, "variable"_a) .def("is_variable_nonbasic_ub", &Model::is_variable_nonbasic_ub, "variable"_a) .def("is_variable_superbasic", &Model::is_variable_superbasic, "variable"_a) .def("get_variable_upperbound_IIS", &Model::is_variable_upperbound_IIS, "variable"_a) .def("get_objective_coefficient", &Model::get_objective_coefficient, "variable"_a) .def("get_variable_lowerbound", &Model::get_variable_lowerbound, "variable"_a) .def("get_variable_primal_ray", &Model::get_variable_primal_ray, "variable"_a) .def("get_variable_rc", &Model::get_variable_rc, "variable"_a) .def("get_variable_upperbound", &Model::get_variable_upperbound, "variable"_a) .def("get_variable_value", &Model::get_variable_value, "variable"_a) .def("get_variable_name", &Model::get_variable_name, "variable"_a) .def("pprint", &Model::pprint_variable, "variable"_a) .def("get_variable_type", &Model::get_variable_type, "variable"_a) // Constraints .def("add_exp_cone_constraint", &Model::add_exp_cone_constraint, "variables"_a, "name"_a = "", "dual"_a = false) .def("_add_linear_constraint", nb::overload_cast &, const char *>(&Model::add_linear_constraint), "function"_a, "interval"_a, "name"_a = "") .def("_add_linear_constraint", nb::overload_cast( &Model::add_linear_constraint), "function"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("add_quadratic_constraint", &Model::add_quadratic_constraint, "function"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("_add_quadratic_constraint", &Model::add_quadratic_constraint, "function"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("add_second_order_cone_constraint", &Model::add_second_order_cone_constraint, "variables"_a, "name"_a = "", "rotated"_a = false) .def("_add_single_nl_constraint", &Model::add_single_nl_constraint, "graph"_a, "result"_a, "interval"_a, "name"_a = "") .def("add_sos_constraint", nb::overload_cast &, SOSType, const Vector &>( &Model::add_sos_constraint), "variables"_a, "sos_type"_a, "weights"_a) .def("add_sos_constraint", nb::overload_cast &, SOSType>(&Model::add_sos_constraint), "variables"_a, "sos_type"_a) .def("delete_constraint", &Model::delete_constraint, "constraint"_a) .def("set_constraint_name", &Model::set_constraint_name, "constraint"_a, "name"_a) .def("set_constraint_rhs", &Model::set_constraint_rhs, "constraint"_a, "rhs"_a) .def("set_constraint_sense", &Model::set_constraint_sense, "constraint"_a, "sense"_a) .def("set_normalized_coefficient", &Model::set_normalized_coefficient, "constraint"_a, "variable"_a, "value"_a) .def("set_normalized_rhs", &Model::set_normalized_rhs, "constraint"_a, "value"_a) .def("is_constraint_active", &Model::is_constraint_active, "constraint"_a) .def("is_constraint_basic", &Model::is_constraint_basic, "constraint"_a) .def("is_constraint_in_IIS", &Model::is_constraint_in_IIS, "constraint"_a) .def("is_constraint_nonbasic_lb", &Model::is_constraint_nonbasic_lb, "constraint"_a) .def("is_constraint_nonbasic_ub", &Model::is_constraint_nonbasic_ub, "constraint"_a) .def("is_constraint_superbasic", &Model::is_constraint_superbasic, "constraint"_a) .def("get_constraint_dual_ray", &Model::get_constraint_dual_ray, "constraint"_a) .def("get_constraint_dual", &Model::get_constraint_dual, "constraint"_a) .def("get_constraint_slack", &Model::get_constraint_slack, "constraint"_a) .def("get_normalized_coefficient", &Model::get_normalized_coefficient, "constraint"_a, "variable"_a) .def("get_normalized_rhs", &Model::get_normalized_rhs, "constraint"_a) .def("get_constraint_rhs", &Model::get_constraint_rhs, "constraint"_a) .def("get_constraint_name", &Model::get_constraint_name, "constraint"_a) .def("get_constraint_sense", &Model::get_constraint_sense, "constraint"_a) // Objective function .def("set_objective", nb::overload_cast(&Model::set_objective), "function"_a, "sense"_a = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast( &Model::set_objective), "function"_a, "sense"_a = ObjectiveSense::Minimize) .def("set_objective", nb::overload_cast(&Model::set_objective), "function"_a, "sense"_a = ObjectiveSense::Minimize) .def("_add_single_nl_objective", &Model::add_single_nl_objective, "graph"_a, "result"_a) // Status queries .def("get_lp_status", &Model::get_lp_status) .def("get_mip_status", &Model::get_mip_status) .def("get_nlp_status", &Model::get_nlp_status) .def("get_sol_status", &Model::get_sol_status) .def("get_solve_status", &Model::get_solve_status) .def("get_optimize_type", &Model::get_optimize_type) .def("get_iis_sol_status", &Model::get_iis_sol_status) // Raw control/attribute access by ID .def("set_raw_control_dbl_by_id", &Model::set_raw_control_dbl_by_id, "control"_a, "value"_a) .def("set_raw_control_int_by_id", &Model::set_raw_control_int_by_id, "control"_a, "value"_a) .def("set_raw_control_str_by_id", &Model::set_raw_control_str_by_id, "control"_a, "value"_a) .def("get_raw_control_int_by_id", &Model::get_raw_control_int_by_id, "control"_a) .def("get_raw_control_dbl_by_id", &Model::get_raw_control_dbl_by_id, "control"_a) .def("get_raw_control_str_by_id", &Model::get_raw_control_str_by_id, "control"_a) .def("get_raw_attribute_int_by_id", &Model::get_raw_attribute_int_by_id, "attrib"_a) .def("get_raw_attribute_dbl_by_id", &Model::get_raw_attribute_dbl_by_id, "attrib"_a) .def("get_raw_attribute_str_by_id", &Model::get_raw_attribute_str_by_id, "attrib"_a) // Raw control/attribute access by string .def("set_raw_control_int", &Model::set_raw_control_int, "control"_a, "value"_a) .def("set_raw_control_dbl", &Model::set_raw_control_dbl, "control"_a, "value"_a) .def("set_raw_control_str", &Model::set_raw_control_str, "control"_a, "value"_a) .def("get_raw_control_int", &Model::get_raw_control_int, "control"_a) .def("get_raw_control_dbl", &Model::get_raw_control_dbl, "control"_a) .def("get_raw_control_str", &Model::get_raw_control_str, "control"_a) .def("get_raw_attribute_int", &Model::get_raw_attribute_int, "attrib"_a) .def("get_raw_attribute_dbl", &Model::get_raw_attribute_dbl, "attrib"_a) .def("get_raw_attribute_str", &Model::get_raw_attribute_str, "attrib"_a) // Generic variant access .def("set_raw_control", &Model::set_raw_control, "control"_a, "value"_a) .def("get_raw_attribute", &Model::get_raw_attribute, "attrib"_a) .def("get_raw_control", &Model::get_raw_control, "control"_a) // Callback methods .def("set_callback", &Model::set_callback, "callback"_a, "cbctx"_a) .def("cb_get_arguments", &Model::cb_get_arguments, nb::rv_policy::reference) .def("cb_get_solution", &Model::cb_get_solution, "variable"_a) .def("cb_get_relaxation", &Model::cb_get_relaxation, "variable"_a) .def("cb_get_incumbent", &Model::cb_get_incumbent, "variable"_a) .def("cb_set_solution", &Model::cb_set_solution, "variable"_a, "value"_a) .def("cb_submit_solution", &Model::cb_submit_solution) .def("cb_exit", &Model::cb_exit) .def("cb_add_lazy_constraint", nb::overload_cast( &Model::cb_add_lazy_constraint), "function"_a, "sense"_a, "rhs"_a) .def("cb_add_lazy_constraint", nb::overload_cast( &Model::cb_add_lazy_constraint), "function"_a, "sense"_a, "rhs"_a) .def("cb_add_user_cut", nb::overload_cast( &Model::cb_add_user_cut), "function"_a, "sense"_a, "rhs"_a) .def("cb_add_user_cut", nb::overload_cast( &Model::cb_add_user_cut), "function"_a, "sense"_a, "rhs"_a) // Functions defined in CRTP Mixins .def("pprint", nb::overload_cast(&Model::pprint_expression), "expr"_a, "precision"_a = 4) .def("pprint", nb::overload_cast(&Model::pprint_expression), "expr"_a, "precision"_a = 4) .def("pprint", nb::overload_cast(&Model::pprint_expression), "expr"_a, "precision"_a = 4) .def("get_value", nb::overload_cast(&Model::get_variable_value)) .def("get_value", nb::overload_cast(&Model::get_expression_value)) .def("get_value", nb::overload_cast(&Model::get_expression_value)) .def("get_value", nb::overload_cast(&Model::get_expression_value)) .def("_add_linear_constraint", &Model::add_linear_constraint_from_var, "expr"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_interval_constraint_from_var, "expr"_a, "interval"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_constraint_from_expr, "expr"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_interval_constraint_from_expr, "expr"_a, "interval"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_constraint_from_var, "expr"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_interval_constraint_from_var, "expr"_a, "interval"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_constraint_from_expr, "expr"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("_add_linear_constraint", &Model::add_linear_interval_constraint_from_expr, "expr"_a, "interval"_a, "name"_a = "") .def("_add_single_nl_constraint", &Model::add_single_nl_constraint_sense_rhs, "graph"_a, "result"_a, "sense"_a, "rhs"_a, "name"_a = "") .def("_add_single_nl_constraint", &Model::add_single_nl_constraint_from_comparison, "graph"_a, "expr"_a, "name"_a = "") .def("set_objective", &Model::set_objective_as_variable, "expr"_a, "sense"_a = ObjectiveSense::Minimize) .def("set_objective", &Model::set_objective_as_constant, "expr"_a, "sense"_a = ObjectiveSense::Minimize); // Bind the return value only it the CB has one auto bind_ret_code = [](nb::class_ s) { // "if constexpr + templates" conditionally instantiates only the true branch. if constexpr (requires { &S::ret_code; }) s.def_rw("ret_code", &S::ret_code); }; // When callbacks provide pointer arguments, those are usually meant as output arguments. // An exception is with pointer to structs, which usually are just opaque objects passed around // between API calls. // We define pointer arguments as read-write, while all the other arguments stays read-only #define XPRSCB_ARG_NB_FIELD(TYPE, NAME) \ if constexpr (std::is_pointer_v) \ s.def_rw(#NAME, &struct_t::NAME); \ else \ s.def_ro(#NAME, &struct_t::NAME); // Define the binding for the argument struct of each CB. In this way, Nanobind will be able to // translate our std::variant of CB-struct pointers into the proper Python union object #define XPRSCB_NB_STRUCTS(ID, NAME, RET, ...) \ { \ using struct_t = NAME##_struct; \ auto s = nb::class_(m, #NAME "_struct"); \ __VA_ARGS__ \ bind_ret_code(s); \ } XPRSCB_LIST(XPRSCB_NB_STRUCTS, XPRSCB_ARG_NB_FIELD) #undef XPRSCB_NB_STRUCTS } ================================================ FILE: lib/xpress_model_ext_constants.cpp ================================================ #include #include "pyoptinterface/xpress_model.hpp" namespace nb = nanobind; using namespace xpress; void bind_xpress_constants(nb::module_ &m) { nb::module_ XPRS = m.def_submodule("XPRS"); /* useful constants */ XPRS.attr("PLUSINFINITY") = POI_XPRS_PLUSINFINITY; XPRS.attr("MINUSINFINITY") = POI_XPRS_MINUSINFINITY; XPRS.attr("MAXINT") = POI_XPRS_MAXINT; XPRS.attr("MAXBANNERLENGTH") = POI_XPRS_MAXBANNERLENGTH; XPRS.attr("VERSION") = POI_XPVERSION; XPRS.attr("VERSION_MAJOR") = POI_XPVERSION_FULL; XPRS.attr("VERSION_MINOR") = POI_XPVERSION_BUILD; XPRS.attr("VERSION_BUILD") = POI_XPVERSION_MINOR; XPRS.attr("VERSION_FULL") = POI_XPVERSION_MAJOR; XPRS.attr("MAXMESSAGELENGTH") = POI_XPRS_MAXMESSAGELENGTH; /* control parameters for XPRSprob */ /* String control parameters */ XPRS.attr("MPSRHSNAME") = POI_XPRS_MPSRHSNAME; XPRS.attr("MPSOBJNAME") = POI_XPRS_MPSOBJNAME; XPRS.attr("MPSRANGENAME") = POI_XPRS_MPSRANGENAME; XPRS.attr("MPSBOUNDNAME") = POI_XPRS_MPSBOUNDNAME; XPRS.attr("OUTPUTMASK") = POI_XPRS_OUTPUTMASK; XPRS.attr("TUNERMETHODFILE") = POI_XPRS_TUNERMETHODFILE; XPRS.attr("TUNEROUTPUTPATH") = POI_XPRS_TUNEROUTPUTPATH; XPRS.attr("TUNERSESSIONNAME") = POI_XPRS_TUNERSESSIONNAME; XPRS.attr("COMPUTEEXECSERVICE") = POI_XPRS_COMPUTEEXECSERVICE; /* Double control parameters */ XPRS.attr("MAXCUTTIME") = POI_XPRS_MAXCUTTIME; XPRS.attr("MAXSTALLTIME") = POI_XPRS_MAXSTALLTIME; XPRS.attr("TUNERMAXTIME") = POI_XPRS_TUNERMAXTIME; XPRS.attr("MATRIXTOL") = POI_XPRS_MATRIXTOL; XPRS.attr("PIVOTTOL") = POI_XPRS_PIVOTTOL; XPRS.attr("FEASTOL") = POI_XPRS_FEASTOL; XPRS.attr("OUTPUTTOL") = POI_XPRS_OUTPUTTOL; XPRS.attr("SOSREFTOL") = POI_XPRS_SOSREFTOL; XPRS.attr("OPTIMALITYTOL") = POI_XPRS_OPTIMALITYTOL; XPRS.attr("ETATOL") = POI_XPRS_ETATOL; XPRS.attr("RELPIVOTTOL") = POI_XPRS_RELPIVOTTOL; XPRS.attr("MIPTOL") = POI_XPRS_MIPTOL; XPRS.attr("MIPTOLTARGET") = POI_XPRS_MIPTOLTARGET; XPRS.attr("BARPERTURB") = POI_XPRS_BARPERTURB; XPRS.attr("MIPADDCUTOFF") = POI_XPRS_MIPADDCUTOFF; XPRS.attr("MIPABSCUTOFF") = POI_XPRS_MIPABSCUTOFF; XPRS.attr("MIPRELCUTOFF") = POI_XPRS_MIPRELCUTOFF; XPRS.attr("PSEUDOCOST") = POI_XPRS_PSEUDOCOST; XPRS.attr("PENALTY") = POI_XPRS_PENALTY; XPRS.attr("BIGM") = POI_XPRS_BIGM; XPRS.attr("MIPABSSTOP") = POI_XPRS_MIPABSSTOP; XPRS.attr("MIPRELSTOP") = POI_XPRS_MIPRELSTOP; XPRS.attr("CROSSOVERACCURACYTOL") = POI_XPRS_CROSSOVERACCURACYTOL; XPRS.attr("PRIMALPERTURB") = POI_XPRS_PRIMALPERTURB; XPRS.attr("DUALPERTURB") = POI_XPRS_DUALPERTURB; XPRS.attr("BAROBJSCALE") = POI_XPRS_BAROBJSCALE; XPRS.attr("BARRHSSCALE") = POI_XPRS_BARRHSSCALE; XPRS.attr("CHOLESKYTOL") = POI_XPRS_CHOLESKYTOL; XPRS.attr("BARGAPSTOP") = POI_XPRS_BARGAPSTOP; XPRS.attr("BARDUALSTOP") = POI_XPRS_BARDUALSTOP; XPRS.attr("BARPRIMALSTOP") = POI_XPRS_BARPRIMALSTOP; XPRS.attr("BARSTEPSTOP") = POI_XPRS_BARSTEPSTOP; XPRS.attr("ELIMTOL") = POI_XPRS_ELIMTOL; XPRS.attr("MARKOWITZTOL") = POI_XPRS_MARKOWITZTOL; XPRS.attr("MIPABSGAPNOTIFY") = POI_XPRS_MIPABSGAPNOTIFY; XPRS.attr("MIPRELGAPNOTIFY") = POI_XPRS_MIPRELGAPNOTIFY; XPRS.attr("BARLARGEBOUND") = POI_XPRS_BARLARGEBOUND; XPRS.attr("PPFACTOR") = POI_XPRS_PPFACTOR; XPRS.attr("REPAIRINDEFINITEQMAX") = POI_XPRS_REPAIRINDEFINITEQMAX; XPRS.attr("BARGAPTARGET") = POI_XPRS_BARGAPTARGET; XPRS.attr("DUMMYCONTROL") = POI_XPRS_DUMMYCONTROL; XPRS.attr("BARSTARTWEIGHT") = POI_XPRS_BARSTARTWEIGHT; XPRS.attr("BARFREESCALE") = POI_XPRS_BARFREESCALE; XPRS.attr("SBEFFORT") = POI_XPRS_SBEFFORT; XPRS.attr("HEURDIVERANDOMIZE") = POI_XPRS_HEURDIVERANDOMIZE; XPRS.attr("HEURSEARCHEFFORT") = POI_XPRS_HEURSEARCHEFFORT; XPRS.attr("CUTFACTOR") = POI_XPRS_CUTFACTOR; XPRS.attr("EIGENVALUETOL") = POI_XPRS_EIGENVALUETOL; XPRS.attr("INDLINBIGM") = POI_XPRS_INDLINBIGM; XPRS.attr("TREEMEMORYSAVINGTARGET") = POI_XPRS_TREEMEMORYSAVINGTARGET; XPRS.attr("INDPRELINBIGM") = POI_XPRS_INDPRELINBIGM; XPRS.attr("RELAXTREEMEMORYLIMIT") = POI_XPRS_RELAXTREEMEMORYLIMIT; XPRS.attr("MIPABSGAPNOTIFYOBJ") = POI_XPRS_MIPABSGAPNOTIFYOBJ; XPRS.attr("MIPABSGAPNOTIFYBOUND") = POI_XPRS_MIPABSGAPNOTIFYBOUND; XPRS.attr("PRESOLVEMAXGROW") = POI_XPRS_PRESOLVEMAXGROW; XPRS.attr("HEURSEARCHTARGETSIZE") = POI_XPRS_HEURSEARCHTARGETSIZE; XPRS.attr("CROSSOVERRELPIVOTTOL") = POI_XPRS_CROSSOVERRELPIVOTTOL; XPRS.attr("CROSSOVERRELPIVOTTOLSAFE") = POI_XPRS_CROSSOVERRELPIVOTTOLSAFE; XPRS.attr("DETLOGFREQ") = POI_XPRS_DETLOGFREQ; XPRS.attr("MAXIMPLIEDBOUND") = POI_XPRS_MAXIMPLIEDBOUND; XPRS.attr("FEASTOLTARGET") = POI_XPRS_FEASTOLTARGET; XPRS.attr("OPTIMALITYTOLTARGET") = POI_XPRS_OPTIMALITYTOLTARGET; XPRS.attr("PRECOMPONENTSEFFORT") = POI_XPRS_PRECOMPONENTSEFFORT; XPRS.attr("LPLOGDELAY") = POI_XPRS_LPLOGDELAY; XPRS.attr("HEURDIVEITERLIMIT") = POI_XPRS_HEURDIVEITERLIMIT; XPRS.attr("BARKERNEL") = POI_XPRS_BARKERNEL; XPRS.attr("FEASTOLPERTURB") = POI_XPRS_FEASTOLPERTURB; XPRS.attr("CROSSOVERFEASWEIGHT") = POI_XPRS_CROSSOVERFEASWEIGHT; XPRS.attr("LUPIVOTTOL") = POI_XPRS_LUPIVOTTOL; XPRS.attr("MIPRESTARTGAPTHRESHOLD") = POI_XPRS_MIPRESTARTGAPTHRESHOLD; XPRS.attr("NODEPROBINGEFFORT") = POI_XPRS_NODEPROBINGEFFORT; XPRS.attr("INPUTTOL") = POI_XPRS_INPUTTOL; XPRS.attr("MIPRESTARTFACTOR") = POI_XPRS_MIPRESTARTFACTOR; XPRS.attr("BAROBJPERTURB") = POI_XPRS_BAROBJPERTURB; XPRS.attr("CPIALPHA") = POI_XPRS_CPIALPHA; XPRS.attr("GLOBALSPATIALBRANCHPROPAGATIONEFFORT") = POI_XPRS_GLOBALSPATIALBRANCHPROPAGATIONEFFORT; XPRS.attr("GLOBALSPATIALBRANCHCUTTINGEFFORT") = POI_XPRS_GLOBALSPATIALBRANCHCUTTINGEFFORT; XPRS.attr("GLOBALBOUNDINGBOX") = POI_XPRS_GLOBALBOUNDINGBOX; XPRS.attr("TIMELIMIT") = POI_XPRS_TIMELIMIT; XPRS.attr("SOLTIMELIMIT") = POI_XPRS_SOLTIMELIMIT; XPRS.attr("REPAIRINFEASTIMELIMIT") = POI_XPRS_REPAIRINFEASTIMELIMIT; XPRS.attr("BARHGEXTRAPOLATE") = POI_XPRS_BARHGEXTRAPOLATE; XPRS.attr("WORKLIMIT") = POI_XPRS_WORKLIMIT; XPRS.attr("CALLBACKCHECKTIMEWORKDELAY") = POI_XPRS_CALLBACKCHECKTIMEWORKDELAY; XPRS.attr("PREROOTWORKLIMIT") = POI_XPRS_PREROOTWORKLIMIT; XPRS.attr("PREROOTEFFORT") = POI_XPRS_PREROOTEFFORT; /* Integer control parameters */ XPRS.attr("EXTRAROWS") = POI_XPRS_EXTRAROWS; XPRS.attr("EXTRACOLS") = POI_XPRS_EXTRACOLS; XPRS.attr("LPITERLIMIT") = POI_XPRS_LPITERLIMIT; XPRS.attr("LPLOG") = POI_XPRS_LPLOG; XPRS.attr("SCALING") = POI_XPRS_SCALING; XPRS.attr("PRESOLVE") = POI_XPRS_PRESOLVE; XPRS.attr("CRASH") = POI_XPRS_CRASH; XPRS.attr("PRICINGALG") = POI_XPRS_PRICINGALG; XPRS.attr("INVERTFREQ") = POI_XPRS_INVERTFREQ; XPRS.attr("INVERTMIN") = POI_XPRS_INVERTMIN; XPRS.attr("MAXNODE") = POI_XPRS_MAXNODE; XPRS.attr("MAXMIPSOL") = POI_XPRS_MAXMIPSOL; XPRS.attr("SIFTPASSES") = POI_XPRS_SIFTPASSES; XPRS.attr("DEFAULTALG") = POI_XPRS_DEFAULTALG; XPRS.attr("VARSELECTION") = POI_XPRS_VARSELECTION; XPRS.attr("NODESELECTION") = POI_XPRS_NODESELECTION; XPRS.attr("BACKTRACK") = POI_XPRS_BACKTRACK; XPRS.attr("MIPLOG") = POI_XPRS_MIPLOG; XPRS.attr("KEEPNROWS") = POI_XPRS_KEEPNROWS; XPRS.attr("MPSECHO") = POI_XPRS_MPSECHO; XPRS.attr("MAXPAGELINES") = POI_XPRS_MAXPAGELINES; XPRS.attr("OUTPUTLOG") = POI_XPRS_OUTPUTLOG; XPRS.attr("BARSOLUTION") = POI_XPRS_BARSOLUTION; XPRS.attr("CROSSOVER") = POI_XPRS_CROSSOVER; XPRS.attr("BARITERLIMIT") = POI_XPRS_BARITERLIMIT; XPRS.attr("CHOLESKYALG") = POI_XPRS_CHOLESKYALG; XPRS.attr("BAROUTPUT") = POI_XPRS_BAROUTPUT; XPRS.attr("EXTRAMIPENTS") = POI_XPRS_EXTRAMIPENTS; XPRS.attr("REFACTOR") = POI_XPRS_REFACTOR; XPRS.attr("BARTHREADS") = POI_XPRS_BARTHREADS; XPRS.attr("KEEPBASIS") = POI_XPRS_KEEPBASIS; XPRS.attr("CROSSOVEROPS") = POI_XPRS_CROSSOVEROPS; XPRS.attr("VERSION") = POI_XPRS_VERSION; XPRS.attr("CROSSOVERTHREADS") = POI_XPRS_CROSSOVERTHREADS; XPRS.attr("BIGMMETHOD") = POI_XPRS_BIGMMETHOD; XPRS.attr("MPSNAMELENGTH") = POI_XPRS_MPSNAMELENGTH; XPRS.attr("ELIMFILLIN") = POI_XPRS_ELIMFILLIN; XPRS.attr("PRESOLVEOPS") = POI_XPRS_PRESOLVEOPS; XPRS.attr("MIPPRESOLVE") = POI_XPRS_MIPPRESOLVE; XPRS.attr("MIPTHREADS") = POI_XPRS_MIPTHREADS; XPRS.attr("BARORDER") = POI_XPRS_BARORDER; XPRS.attr("BREADTHFIRST") = POI_XPRS_BREADTHFIRST; XPRS.attr("AUTOPERTURB") = POI_XPRS_AUTOPERTURB; XPRS.attr("DENSECOLLIMIT") = POI_XPRS_DENSECOLLIMIT; XPRS.attr("CALLBACKFROMMAINTHREAD") = POI_XPRS_CALLBACKFROMMAINTHREAD; XPRS.attr("MAXMCOEFFBUFFERELEMS") = POI_XPRS_MAXMCOEFFBUFFERELEMS; XPRS.attr("REFINEOPS") = POI_XPRS_REFINEOPS; XPRS.attr("LPREFINEITERLIMIT") = POI_XPRS_LPREFINEITERLIMIT; XPRS.attr("MIPREFINEITERLIMIT") = POI_XPRS_MIPREFINEITERLIMIT; XPRS.attr("DUALIZEOPS") = POI_XPRS_DUALIZEOPS; XPRS.attr("CROSSOVERITERLIMIT") = POI_XPRS_CROSSOVERITERLIMIT; XPRS.attr("PREBASISRED") = POI_XPRS_PREBASISRED; XPRS.attr("PRESORT") = POI_XPRS_PRESORT; XPRS.attr("PREPERMUTE") = POI_XPRS_PREPERMUTE; XPRS.attr("PREPERMUTESEED") = POI_XPRS_PREPERMUTESEED; XPRS.attr("MAXMEMORYSOFT") = POI_XPRS_MAXMEMORYSOFT; XPRS.attr("CUTFREQ") = POI_XPRS_CUTFREQ; XPRS.attr("SYMSELECT") = POI_XPRS_SYMSELECT; XPRS.attr("SYMMETRY") = POI_XPRS_SYMMETRY; XPRS.attr("MAXMEMORYHARD") = POI_XPRS_MAXMEMORYHARD; XPRS.attr("MIQCPALG") = POI_XPRS_MIQCPALG; XPRS.attr("QCCUTS") = POI_XPRS_QCCUTS; XPRS.attr("QCROOTALG") = POI_XPRS_QCROOTALG; XPRS.attr("PRECONVERTSEPARABLE") = POI_XPRS_PRECONVERTSEPARABLE; XPRS.attr("ALGAFTERNETWORK") = POI_XPRS_ALGAFTERNETWORK; XPRS.attr("TRACE") = POI_XPRS_TRACE; XPRS.attr("MAXIIS") = POI_XPRS_MAXIIS; XPRS.attr("CPUTIME") = POI_XPRS_CPUTIME; XPRS.attr("COVERCUTS") = POI_XPRS_COVERCUTS; XPRS.attr("GOMCUTS") = POI_XPRS_GOMCUTS; XPRS.attr("LPFOLDING") = POI_XPRS_LPFOLDING; XPRS.attr("MPSFORMAT") = POI_XPRS_MPSFORMAT; XPRS.attr("CUTSTRATEGY") = POI_XPRS_CUTSTRATEGY; XPRS.attr("CUTDEPTH") = POI_XPRS_CUTDEPTH; XPRS.attr("TREECOVERCUTS") = POI_XPRS_TREECOVERCUTS; XPRS.attr("TREEGOMCUTS") = POI_XPRS_TREEGOMCUTS; XPRS.attr("CUTSELECT") = POI_XPRS_CUTSELECT; XPRS.attr("TREECUTSELECT") = POI_XPRS_TREECUTSELECT; XPRS.attr("DUALIZE") = POI_XPRS_DUALIZE; XPRS.attr("DUALGRADIENT") = POI_XPRS_DUALGRADIENT; XPRS.attr("SBITERLIMIT") = POI_XPRS_SBITERLIMIT; XPRS.attr("SBBEST") = POI_XPRS_SBBEST; XPRS.attr("BARINDEFLIMIT") = POI_XPRS_BARINDEFLIMIT; XPRS.attr("HEURFREQ") = POI_XPRS_HEURFREQ; XPRS.attr("HEURDEPTH") = POI_XPRS_HEURDEPTH; XPRS.attr("HEURMAXSOL") = POI_XPRS_HEURMAXSOL; XPRS.attr("HEURNODES") = POI_XPRS_HEURNODES; XPRS.attr("LNPBEST") = POI_XPRS_LNPBEST; XPRS.attr("LNPITERLIMIT") = POI_XPRS_LNPITERLIMIT; XPRS.attr("BRANCHCHOICE") = POI_XPRS_BRANCHCHOICE; XPRS.attr("BARREGULARIZE") = POI_XPRS_BARREGULARIZE; XPRS.attr("SBSELECT") = POI_XPRS_SBSELECT; XPRS.attr("IISLOG") = POI_XPRS_IISLOG; XPRS.attr("LOCALCHOICE") = POI_XPRS_LOCALCHOICE; XPRS.attr("LOCALBACKTRACK") = POI_XPRS_LOCALBACKTRACK; XPRS.attr("DUALSTRATEGY") = POI_XPRS_DUALSTRATEGY; XPRS.attr("HEURDIVESTRATEGY") = POI_XPRS_HEURDIVESTRATEGY; XPRS.attr("HEURSELECT") = POI_XPRS_HEURSELECT; XPRS.attr("BARSTART") = POI_XPRS_BARSTART; XPRS.attr("PRESOLVEPASSES") = POI_XPRS_PRESOLVEPASSES; XPRS.attr("BARORDERTHREADS") = POI_XPRS_BARORDERTHREADS; XPRS.attr("EXTRASETS") = POI_XPRS_EXTRASETS; XPRS.attr("FEASIBILITYPUMP") = POI_XPRS_FEASIBILITYPUMP; XPRS.attr("PRECOEFELIM") = POI_XPRS_PRECOEFELIM; XPRS.attr("PREDOMCOL") = POI_XPRS_PREDOMCOL; XPRS.attr("HEURSEARCHFREQ") = POI_XPRS_HEURSEARCHFREQ; XPRS.attr("HEURDIVESPEEDUP") = POI_XPRS_HEURDIVESPEEDUP; XPRS.attr("SBESTIMATE") = POI_XPRS_SBESTIMATE; XPRS.attr("BARCORES") = POI_XPRS_BARCORES; XPRS.attr("MAXCHECKSONMAXTIME") = POI_XPRS_MAXCHECKSONMAXTIME; XPRS.attr("MAXCHECKSONMAXCUTTIME") = POI_XPRS_MAXCHECKSONMAXCUTTIME; XPRS.attr("HISTORYCOSTS") = POI_XPRS_HISTORYCOSTS; XPRS.attr("ALGAFTERCROSSOVER") = POI_XPRS_ALGAFTERCROSSOVER; XPRS.attr("MUTEXCALLBACKS") = POI_XPRS_MUTEXCALLBACKS; XPRS.attr("BARCRASH") = POI_XPRS_BARCRASH; XPRS.attr("HEURDIVESOFTROUNDING") = POI_XPRS_HEURDIVESOFTROUNDING; XPRS.attr("HEURSEARCHROOTSELECT") = POI_XPRS_HEURSEARCHROOTSELECT; XPRS.attr("HEURSEARCHTREESELECT") = POI_XPRS_HEURSEARCHTREESELECT; XPRS.attr("MPS18COMPATIBLE") = POI_XPRS_MPS18COMPATIBLE; XPRS.attr("ROOTPRESOLVE") = POI_XPRS_ROOTPRESOLVE; XPRS.attr("CROSSOVERDRP") = POI_XPRS_CROSSOVERDRP; XPRS.attr("FORCEOUTPUT") = POI_XPRS_FORCEOUTPUT; XPRS.attr("PRIMALOPS") = POI_XPRS_PRIMALOPS; XPRS.attr("DETERMINISTIC") = POI_XPRS_DETERMINISTIC; XPRS.attr("PREPROBING") = POI_XPRS_PREPROBING; XPRS.attr("TREEMEMORYLIMIT") = POI_XPRS_TREEMEMORYLIMIT; XPRS.attr("TREECOMPRESSION") = POI_XPRS_TREECOMPRESSION; XPRS.attr("TREEDIAGNOSTICS") = POI_XPRS_TREEDIAGNOSTICS; XPRS.attr("MAXTREEFILESIZE") = POI_XPRS_MAXTREEFILESIZE; XPRS.attr("PRECLIQUESTRATEGY") = POI_XPRS_PRECLIQUESTRATEGY; XPRS.attr("IFCHECKCONVEXITY") = POI_XPRS_IFCHECKCONVEXITY; XPRS.attr("PRIMALUNSHIFT") = POI_XPRS_PRIMALUNSHIFT; XPRS.attr("REPAIRINDEFINITEQ") = POI_XPRS_REPAIRINDEFINITEQ; XPRS.attr("MIPRAMPUP") = POI_XPRS_MIPRAMPUP; XPRS.attr("MAXLOCALBACKTRACK") = POI_XPRS_MAXLOCALBACKTRACK; XPRS.attr("USERSOLHEURISTIC") = POI_XPRS_USERSOLHEURISTIC; XPRS.attr("PRECONVERTOBJTOCONS") = POI_XPRS_PRECONVERTOBJTOCONS; XPRS.attr("FORCEPARALLELDUAL") = POI_XPRS_FORCEPARALLELDUAL; XPRS.attr("BACKTRACKTIE") = POI_XPRS_BACKTRACKTIE; XPRS.attr("BRANCHDISJ") = POI_XPRS_BRANCHDISJ; XPRS.attr("MIPFRACREDUCE") = POI_XPRS_MIPFRACREDUCE; XPRS.attr("CONCURRENTTHREADS") = POI_XPRS_CONCURRENTTHREADS; XPRS.attr("MAXSCALEFACTOR") = POI_XPRS_MAXSCALEFACTOR; XPRS.attr("HEURTHREADS") = POI_XPRS_HEURTHREADS; XPRS.attr("THREADS") = POI_XPRS_THREADS; XPRS.attr("HEURBEFORELP") = POI_XPRS_HEURBEFORELP; XPRS.attr("PREDOMROW") = POI_XPRS_PREDOMROW; XPRS.attr("BRANCHSTRUCTURAL") = POI_XPRS_BRANCHSTRUCTURAL; XPRS.attr("QUADRATICUNSHIFT") = POI_XPRS_QUADRATICUNSHIFT; XPRS.attr("BARPRESOLVEOPS") = POI_XPRS_BARPRESOLVEOPS; XPRS.attr("QSIMPLEXOPS") = POI_XPRS_QSIMPLEXOPS; XPRS.attr("MIPRESTART") = POI_XPRS_MIPRESTART; XPRS.attr("CONFLICTCUTS") = POI_XPRS_CONFLICTCUTS; XPRS.attr("PREPROTECTDUAL") = POI_XPRS_PREPROTECTDUAL; XPRS.attr("CORESPERCPU") = POI_XPRS_CORESPERCPU; XPRS.attr("RESOURCESTRATEGY") = POI_XPRS_RESOURCESTRATEGY; XPRS.attr("CLAMPING") = POI_XPRS_CLAMPING; XPRS.attr("PREDUPROW") = POI_XPRS_PREDUPROW; XPRS.attr("CPUPLATFORM") = POI_XPRS_CPUPLATFORM; XPRS.attr("BARALG") = POI_XPRS_BARALG; XPRS.attr("SIFTING") = POI_XPRS_SIFTING; XPRS.attr("BARKEEPLASTSOL") = POI_XPRS_BARKEEPLASTSOL; XPRS.attr("LPLOGSTYLE") = POI_XPRS_LPLOGSTYLE; XPRS.attr("RANDOMSEED") = POI_XPRS_RANDOMSEED; XPRS.attr("TREEQCCUTS") = POI_XPRS_TREEQCCUTS; XPRS.attr("PRELINDEP") = POI_XPRS_PRELINDEP; XPRS.attr("DUALTHREADS") = POI_XPRS_DUALTHREADS; XPRS.attr("PREOBJCUTDETECT") = POI_XPRS_PREOBJCUTDETECT; XPRS.attr("PREBNDREDQUAD") = POI_XPRS_PREBNDREDQUAD; XPRS.attr("PREBNDREDCONE") = POI_XPRS_PREBNDREDCONE; XPRS.attr("PRECOMPONENTS") = POI_XPRS_PRECOMPONENTS; XPRS.attr("MAXMIPTASKS") = POI_XPRS_MAXMIPTASKS; XPRS.attr("MIPTERMINATIONMETHOD") = POI_XPRS_MIPTERMINATIONMETHOD; XPRS.attr("PRECONEDECOMP") = POI_XPRS_PRECONEDECOMP; XPRS.attr("HEURFORCESPECIALOBJ") = POI_XPRS_HEURFORCESPECIALOBJ; XPRS.attr("HEURSEARCHROOTCUTFREQ") = POI_XPRS_HEURSEARCHROOTCUTFREQ; XPRS.attr("PREELIMQUAD") = POI_XPRS_PREELIMQUAD; XPRS.attr("PREIMPLICATIONS") = POI_XPRS_PREIMPLICATIONS; XPRS.attr("TUNERMODE") = POI_XPRS_TUNERMODE; XPRS.attr("TUNERMETHOD") = POI_XPRS_TUNERMETHOD; XPRS.attr("TUNERTARGET") = POI_XPRS_TUNERTARGET; XPRS.attr("TUNERTHREADS") = POI_XPRS_TUNERTHREADS; XPRS.attr("TUNERHISTORY") = POI_XPRS_TUNERHISTORY; XPRS.attr("TUNERPERMUTE") = POI_XPRS_TUNERPERMUTE; XPRS.attr("TUNERVERBOSE") = POI_XPRS_TUNERVERBOSE; XPRS.attr("TUNEROUTPUT") = POI_XPRS_TUNEROUTPUT; XPRS.attr("PREANALYTICCENTER") = POI_XPRS_PREANALYTICCENTER; XPRS.attr("LPFLAGS") = POI_XPRS_LPFLAGS; XPRS.attr("MIPKAPPAFREQ") = POI_XPRS_MIPKAPPAFREQ; XPRS.attr("OBJSCALEFACTOR") = POI_XPRS_OBJSCALEFACTOR; XPRS.attr("TREEFILELOGINTERVAL") = POI_XPRS_TREEFILELOGINTERVAL; XPRS.attr("IGNORECONTAINERCPULIMIT") = POI_XPRS_IGNORECONTAINERCPULIMIT; XPRS.attr("IGNORECONTAINERMEMORYLIMIT") = POI_XPRS_IGNORECONTAINERMEMORYLIMIT; XPRS.attr("MIPDUALREDUCTIONS") = POI_XPRS_MIPDUALREDUCTIONS; XPRS.attr("GENCONSDUALREDUCTIONS") = POI_XPRS_GENCONSDUALREDUCTIONS; XPRS.attr("PWLDUALREDUCTIONS") = POI_XPRS_PWLDUALREDUCTIONS; XPRS.attr("BARFAILITERLIMIT") = POI_XPRS_BARFAILITERLIMIT; XPRS.attr("AUTOSCALING") = POI_XPRS_AUTOSCALING; XPRS.attr("GENCONSABSTRANSFORMATION") = POI_XPRS_GENCONSABSTRANSFORMATION; XPRS.attr("COMPUTEJOBPRIORITY") = POI_XPRS_COMPUTEJOBPRIORITY; XPRS.attr("PREFOLDING") = POI_XPRS_PREFOLDING; XPRS.attr("COMPUTE") = POI_XPRS_COMPUTE; XPRS.attr("NETSTALLLIMIT") = POI_XPRS_NETSTALLLIMIT; XPRS.attr("SERIALIZEPREINTSOL") = POI_XPRS_SERIALIZEPREINTSOL; XPRS.attr("NUMERICALEMPHASIS") = POI_XPRS_NUMERICALEMPHASIS; XPRS.attr("PWLNONCONVEXTRANSFORMATION") = POI_XPRS_PWLNONCONVEXTRANSFORMATION; XPRS.attr("MIPCOMPONENTS") = POI_XPRS_MIPCOMPONENTS; XPRS.attr("MIPCONCURRENTNODES") = POI_XPRS_MIPCONCURRENTNODES; XPRS.attr("MIPCONCURRENTSOLVES") = POI_XPRS_MIPCONCURRENTSOLVES; XPRS.attr("OUTPUTCONTROLS") = POI_XPRS_OUTPUTCONTROLS; XPRS.attr("SIFTSWITCH") = POI_XPRS_SIFTSWITCH; XPRS.attr("HEUREMPHASIS") = POI_XPRS_HEUREMPHASIS; XPRS.attr("BARREFITER") = POI_XPRS_BARREFITER; XPRS.attr("COMPUTELOG") = POI_XPRS_COMPUTELOG; XPRS.attr("SIFTPRESOLVEOPS") = POI_XPRS_SIFTPRESOLVEOPS; XPRS.attr("CHECKINPUTDATA") = POI_XPRS_CHECKINPUTDATA; XPRS.attr("ESCAPENAMES") = POI_XPRS_ESCAPENAMES; XPRS.attr("IOTIMEOUT") = POI_XPRS_IOTIMEOUT; XPRS.attr("AUTOCUTTING") = POI_XPRS_AUTOCUTTING; XPRS.attr("GLOBALNUMINITNLPCUTS") = POI_XPRS_GLOBALNUMINITNLPCUTS; XPRS.attr("CALLBACKCHECKTIMEDELAY") = POI_XPRS_CALLBACKCHECKTIMEDELAY; XPRS.attr("MULTIOBJOPS") = POI_XPRS_MULTIOBJOPS; XPRS.attr("MULTIOBJLOG") = POI_XPRS_MULTIOBJLOG; XPRS.attr("BACKGROUNDMAXTHREADS") = POI_XPRS_BACKGROUNDMAXTHREADS; XPRS.attr("GLOBALLSHEURSTRATEGY") = POI_XPRS_GLOBALLSHEURSTRATEGY; XPRS.attr("GLOBALSPATIALBRANCHIFPREFERORIG") = POI_XPRS_GLOBALSPATIALBRANCHIFPREFERORIG; XPRS.attr("PRECONFIGURATION") = POI_XPRS_PRECONFIGURATION; XPRS.attr("FEASIBILITYJUMP") = POI_XPRS_FEASIBILITYJUMP; XPRS.attr("IISOPS") = POI_XPRS_IISOPS; XPRS.attr("RLTCUTS") = POI_XPRS_RLTCUTS; XPRS.attr("ALTERNATIVEREDCOSTS") = POI_XPRS_ALTERNATIVEREDCOSTS; XPRS.attr("HEURSHIFTPROP") = POI_XPRS_HEURSHIFTPROP; XPRS.attr("HEURSEARCHCOPYCONTROLS") = POI_XPRS_HEURSEARCHCOPYCONTROLS; XPRS.attr("GLOBALNLPCUTS") = POI_XPRS_GLOBALNLPCUTS; XPRS.attr("GLOBALTREENLPCUTS") = POI_XPRS_GLOBALTREENLPCUTS; XPRS.attr("BARHGOPS") = POI_XPRS_BARHGOPS; XPRS.attr("BARHGMAXRESTARTS") = POI_XPRS_BARHGMAXRESTARTS; XPRS.attr("MCFCUTSTRATEGY") = POI_XPRS_MCFCUTSTRATEGY; XPRS.attr("PREROOTTHREADS") = POI_XPRS_PREROOTTHREADS; XPRS.attr("BARITERATIVE") = POI_XPRS_BARITERATIVE; /* Integer control parameters that support 64-bit values */ XPRS.attr("EXTRAELEMS") = POI_XPRS_EXTRAELEMS; XPRS.attr("EXTRASETELEMS") = POI_XPRS_EXTRASETELEMS; XPRS.attr("BACKGROUNDSELECT") = POI_XPRS_BACKGROUNDSELECT; XPRS.attr("HEURSEARCHBACKGROUNDSELECT") = POI_XPRS_HEURSEARCHBACKGROUNDSELECT; /* attributes for XPRSprob */ /* String attributes */ XPRS.attr("MATRIXNAME") = POI_XPRS_MATRIXNAME; XPRS.attr("BOUNDNAME") = POI_XPRS_BOUNDNAME; XPRS.attr("RHSNAME") = POI_XPRS_RHSNAME; XPRS.attr("RANGENAME") = POI_XPRS_RANGENAME; XPRS.attr("XPRESSVERSION") = POI_XPRS_XPRESSVERSION; XPRS.attr("UUID") = POI_XPRS_UUID; /* Double attributes */ XPRS.attr("MIPSOLTIME") = POI_XPRS_MIPSOLTIME; XPRS.attr("TIME") = POI_XPRS_TIME; XPRS.attr("LPOBJVAL") = POI_XPRS_LPOBJVAL; XPRS.attr("SUMPRIMALINF") = POI_XPRS_SUMPRIMALINF; XPRS.attr("MIPOBJVAL") = POI_XPRS_MIPOBJVAL; XPRS.attr("BESTBOUND") = POI_XPRS_BESTBOUND; XPRS.attr("OBJRHS") = POI_XPRS_OBJRHS; XPRS.attr("MIPBESTOBJVAL") = POI_XPRS_MIPBESTOBJVAL; XPRS.attr("OBJSENSE") = POI_XPRS_OBJSENSE; XPRS.attr("BRANCHVALUE") = POI_XPRS_BRANCHVALUE; XPRS.attr("PENALTYVALUE") = POI_XPRS_PENALTYVALUE; XPRS.attr("CURRMIPCUTOFF") = POI_XPRS_CURRMIPCUTOFF; XPRS.attr("BARCONDA") = POI_XPRS_BARCONDA; XPRS.attr("BARCONDD") = POI_XPRS_BARCONDD; XPRS.attr("MAXABSPRIMALINFEAS") = POI_XPRS_MAXABSPRIMALINFEAS; XPRS.attr("MAXRELPRIMALINFEAS") = POI_XPRS_MAXRELPRIMALINFEAS; XPRS.attr("MAXABSDUALINFEAS") = POI_XPRS_MAXABSDUALINFEAS; XPRS.attr("MAXRELDUALINFEAS") = POI_XPRS_MAXRELDUALINFEAS; XPRS.attr("PRIMALDUALINTEGRAL") = POI_XPRS_PRIMALDUALINTEGRAL; XPRS.attr("MAXMIPINFEAS") = POI_XPRS_MAXMIPINFEAS; XPRS.attr("ATTENTIONLEVEL") = POI_XPRS_ATTENTIONLEVEL; XPRS.attr("MAXKAPPA") = POI_XPRS_MAXKAPPA; XPRS.attr("TREECOMPLETION") = POI_XPRS_TREECOMPLETION; XPRS.attr("PREDICTEDATTLEVEL") = POI_XPRS_PREDICTEDATTLEVEL; XPRS.attr("OBSERVEDPRIMALINTEGRAL") = POI_XPRS_OBSERVEDPRIMALINTEGRAL; XPRS.attr("CPISCALEFACTOR") = POI_XPRS_CPISCALEFACTOR; XPRS.attr("OBJVAL") = POI_XPRS_OBJVAL; XPRS.attr("WORK") = POI_XPRS_WORK; XPRS.attr("BARPRIMALOBJ") = POI_XPRS_BARPRIMALOBJ; XPRS.attr("BARDUALOBJ") = POI_XPRS_BARDUALOBJ; XPRS.attr("BARPRIMALINF") = POI_XPRS_BARPRIMALINF; XPRS.attr("BARDUALINF") = POI_XPRS_BARDUALINF; XPRS.attr("BARCGAP") = POI_XPRS_BARCGAP; /* Integer attributes */ XPRS.attr("ROWS") = POI_XPRS_ROWS; XPRS.attr("SETS") = POI_XPRS_SETS; XPRS.attr("PRIMALINFEAS") = POI_XPRS_PRIMALINFEAS; XPRS.attr("DUALINFEAS") = POI_XPRS_DUALINFEAS; XPRS.attr("SIMPLEXITER") = POI_XPRS_SIMPLEXITER; XPRS.attr("LPSTATUS") = POI_XPRS_LPSTATUS; XPRS.attr("MIPSTATUS") = POI_XPRS_MIPSTATUS; XPRS.attr("CUTS") = POI_XPRS_CUTS; XPRS.attr("NODES") = POI_XPRS_NODES; XPRS.attr("NODEDEPTH") = POI_XPRS_NODEDEPTH; XPRS.attr("ACTIVENODES") = POI_XPRS_ACTIVENODES; XPRS.attr("MIPSOLNODE") = POI_XPRS_MIPSOLNODE; XPRS.attr("MIPSOLS") = POI_XPRS_MIPSOLS; XPRS.attr("COLS") = POI_XPRS_COLS; XPRS.attr("SPAREROWS") = POI_XPRS_SPAREROWS; XPRS.attr("SPARECOLS") = POI_XPRS_SPARECOLS; XPRS.attr("SPAREMIPENTS") = POI_XPRS_SPAREMIPENTS; XPRS.attr("ERRORCODE") = POI_XPRS_ERRORCODE; XPRS.attr("MIPINFEAS") = POI_XPRS_MIPINFEAS; XPRS.attr("PRESOLVESTATE") = POI_XPRS_PRESOLVESTATE; XPRS.attr("PARENTNODE") = POI_XPRS_PARENTNODE; XPRS.attr("NAMELENGTH") = POI_XPRS_NAMELENGTH; XPRS.attr("QELEMS") = POI_XPRS_QELEMS; XPRS.attr("NUMIIS") = POI_XPRS_NUMIIS; XPRS.attr("MIPENTS") = POI_XPRS_MIPENTS; XPRS.attr("BRANCHVAR") = POI_XPRS_BRANCHVAR; XPRS.attr("MIPTHREADID") = POI_XPRS_MIPTHREADID; XPRS.attr("ALGORITHM") = POI_XPRS_ALGORITHM; XPRS.attr("CROSSOVERITER") = POI_XPRS_CROSSOVERITER; XPRS.attr("SOLSTATUS") = POI_XPRS_SOLSTATUS; XPRS.attr("CUTROUNDS") = POI_XPRS_CUTROUNDS; XPRS.attr("ORIGINALROWS") = POI_XPRS_ORIGINALROWS; XPRS.attr("CALLBACKCOUNT_OPTNODE") = POI_XPRS_CALLBACKCOUNT_OPTNODE; XPRS.attr("ORIGINALQELEMS") = POI_XPRS_ORIGINALQELEMS; XPRS.attr("MAXPROBNAMELENGTH") = POI_XPRS_MAXPROBNAMELENGTH; XPRS.attr("STOPSTATUS") = POI_XPRS_STOPSTATUS; XPRS.attr("ORIGINALMIPENTS") = POI_XPRS_ORIGINALMIPENTS; XPRS.attr("ORIGINALSETS") = POI_XPRS_ORIGINALSETS; XPRS.attr("SPARESETS") = POI_XPRS_SPARESETS; XPRS.attr("CHECKSONMAXTIME") = POI_XPRS_CHECKSONMAXTIME; XPRS.attr("CHECKSONMAXCUTTIME") = POI_XPRS_CHECKSONMAXCUTTIME; XPRS.attr("ORIGINALCOLS") = POI_XPRS_ORIGINALCOLS; XPRS.attr("QCELEMS") = POI_XPRS_QCELEMS; XPRS.attr("QCONSTRAINTS") = POI_XPRS_QCONSTRAINTS; XPRS.attr("ORIGINALQCELEMS") = POI_XPRS_ORIGINALQCELEMS; XPRS.attr("ORIGINALQCONSTRAINTS") = POI_XPRS_ORIGINALQCONSTRAINTS; XPRS.attr("PEAKTOTALTREEMEMORYUSAGE") = POI_XPRS_PEAKTOTALTREEMEMORYUSAGE; XPRS.attr("CURRENTNODE") = POI_XPRS_CURRENTNODE; XPRS.attr("TREEMEMORYUSAGE") = POI_XPRS_TREEMEMORYUSAGE; XPRS.attr("TREEFILESIZE") = POI_XPRS_TREEFILESIZE; XPRS.attr("TREEFILEUSAGE") = POI_XPRS_TREEFILEUSAGE; XPRS.attr("INDICATORS") = POI_XPRS_INDICATORS; XPRS.attr("ORIGINALINDICATORS") = POI_XPRS_ORIGINALINDICATORS; XPRS.attr("CORESPERCPUDETECTED") = POI_XPRS_CORESPERCPUDETECTED; XPRS.attr("CPUSDETECTED") = POI_XPRS_CPUSDETECTED; XPRS.attr("CORESDETECTED") = POI_XPRS_CORESDETECTED; XPRS.attr("PHYSICALCORESDETECTED") = POI_XPRS_PHYSICALCORESDETECTED; XPRS.attr("PHYSICALCORESPERCPUDETECTED") = POI_XPRS_PHYSICALCORESPERCPUDETECTED; XPRS.attr("OPTIMIZETYPEUSED") = POI_XPRS_OPTIMIZETYPEUSED; XPRS.attr("BARSING") = POI_XPRS_BARSING; XPRS.attr("BARSINGR") = POI_XPRS_BARSINGR; XPRS.attr("PRESOLVEINDEX") = POI_XPRS_PRESOLVEINDEX; XPRS.attr("CONES") = POI_XPRS_CONES; XPRS.attr("CONEELEMS") = POI_XPRS_CONEELEMS; XPRS.attr("PWLCONS") = POI_XPRS_PWLCONS; XPRS.attr("GENCONS") = POI_XPRS_GENCONS; XPRS.attr("TREERESTARTS") = POI_XPRS_TREERESTARTS; XPRS.attr("ORIGINALPWLS") = POI_XPRS_ORIGINALPWLS; XPRS.attr("ORIGINALGENCONS") = POI_XPRS_ORIGINALGENCONS; XPRS.attr("COMPUTEEXECUTIONS") = POI_XPRS_COMPUTEEXECUTIONS; XPRS.attr("RESTARTS") = POI_XPRS_RESTARTS; XPRS.attr("SOLVESTATUS") = POI_XPRS_SOLVESTATUS; XPRS.attr("GLOBALBOUNDINGBOXAPPLIED") = POI_XPRS_GLOBALBOUNDINGBOXAPPLIED; XPRS.attr("OBJECTIVES") = POI_XPRS_OBJECTIVES; XPRS.attr("SOLVEDOBJS") = POI_XPRS_SOLVEDOBJS; XPRS.attr("OBJSTOSOLVE") = POI_XPRS_OBJSTOSOLVE; XPRS.attr("GLOBALNLPINFEAS") = POI_XPRS_GLOBALNLPINFEAS; XPRS.attr("IISSOLSTATUS") = POI_XPRS_IISSOLSTATUS; XPRS.attr("INPUTROWS") = POI_XPRS_INPUTROWS; XPRS.attr("INPUTCOLS") = POI_XPRS_INPUTCOLS; XPRS.attr("BARITER") = POI_XPRS_BARITER; XPRS.attr("BARDENSECOL") = POI_XPRS_BARDENSECOL; XPRS.attr("BARCROSSOVER") = POI_XPRS_BARCROSSOVER; /* Integer attributes that support 64-bit values */ XPRS.attr("SETMEMBERS") = POI_XPRS_SETMEMBERS; XPRS.attr("ELEMS") = POI_XPRS_ELEMS; XPRS.attr("SPAREELEMS") = POI_XPRS_SPAREELEMS; XPRS.attr("SYSTEMMEMORY") = POI_XPRS_SYSTEMMEMORY; XPRS.attr("ORIGINALSETMEMBERS") = POI_XPRS_ORIGINALSETMEMBERS; XPRS.attr("SPARESETELEMS") = POI_XPRS_SPARESETELEMS; XPRS.attr("CURRENTMEMORY") = POI_XPRS_CURRENTMEMORY; XPRS.attr("PEAKMEMORY") = POI_XPRS_PEAKMEMORY; XPRS.attr("TOTALMEMORY") = POI_XPRS_TOTALMEMORY; XPRS.attr("AVAILABLEMEMORY") = POI_XPRS_AVAILABLEMEMORY; XPRS.attr("PWLPOINTS") = POI_XPRS_PWLPOINTS; XPRS.attr("GENCONCOLS") = POI_XPRS_GENCONCOLS; XPRS.attr("GENCONVALS") = POI_XPRS_GENCONVALS; XPRS.attr("ORIGINALPWLPOINTS") = POI_XPRS_ORIGINALPWLPOINTS; XPRS.attr("ORIGINALGENCONCOLS") = POI_XPRS_ORIGINALGENCONCOLS; XPRS.attr("ORIGINALGENCONVALS") = POI_XPRS_ORIGINALGENCONVALS; XPRS.attr("MEMORYLIMITDETECTED") = POI_XPRS_MEMORYLIMITDETECTED; XPRS.attr("BARAASIZE") = POI_XPRS_BARAASIZE; XPRS.attr("BARLSIZE") = POI_XPRS_BARLSIZE; // Nonlinear solver related controls and attributes XPRS.attr("NLPFUNCEVAL") = POI_XPRS_NLPFUNCEVAL; XPRS.attr("NLPLOG") = POI_XPRS_NLPLOG; XPRS.attr("NLPKEEPEQUALSCOLUMN") = POI_XPRS_NLPKEEPEQUALSCOLUMN; XPRS.attr("NLPEVALUATE") = POI_XPRS_NLPEVALUATE; XPRS.attr("NLPPRESOLVE") = POI_XPRS_NLPPRESOLVE; XPRS.attr("SLPLOG") = POI_XPRS_SLPLOG; XPRS.attr("LOCALSOLVER") = POI_XPRS_LOCALSOLVER; XPRS.attr("NLPSTOPOUTOFRANGE") = POI_XPRS_NLPSTOPOUTOFRANGE; XPRS.attr("NLPTHREADSAFEUSERFUNC") = POI_XPRS_NLPTHREADSAFEUSERFUNC; XPRS.attr("NLPJACOBIAN") = POI_XPRS_NLPJACOBIAN; XPRS.attr("NLPHESSIAN") = POI_XPRS_NLPHESSIAN; XPRS.attr("MULTISTART") = POI_XPRS_MULTISTART; XPRS.attr("MULTISTART_THREADS") = POI_XPRS_MULTISTART_THREADS; XPRS.attr("MULTISTART_MAXSOLVES") = POI_XPRS_MULTISTART_MAXSOLVES; XPRS.attr("MULTISTART_MAXTIME") = POI_XPRS_MULTISTART_MAXTIME; XPRS.attr("NLPMAXTIME") = POI_XPRS_NLPMAXTIME; XPRS.attr("NLPDERIVATIVES") = POI_XPRS_NLPDERIVATIVES; XPRS.attr("NLPREFORMULATE") = POI_XPRS_NLPREFORMULATE; XPRS.attr("NLPPRESOLVEOPS") = POI_XPRS_NLPPRESOLVEOPS; XPRS.attr("MULTISTART_LOG") = POI_XPRS_MULTISTART_LOG; XPRS.attr("MULTISTART_SEED") = POI_XPRS_MULTISTART_SEED; XPRS.attr("MULTISTART_POOLSIZE") = POI_XPRS_MULTISTART_POOLSIZE; XPRS.attr("NLPPOSTSOLVE") = POI_XPRS_NLPPOSTSOLVE; XPRS.attr("NLPDETERMINISTIC") = POI_XPRS_NLPDETERMINISTIC; XPRS.attr("NLPPRESOLVELEVEL") = POI_XPRS_NLPPRESOLVELEVEL; XPRS.attr("NLPPROBING") = POI_XPRS_NLPPROBING; XPRS.attr("NLPCALCTHREADS") = POI_XPRS_NLPCALCTHREADS; XPRS.attr("NLPTHREADS") = POI_XPRS_NLPTHREADS; XPRS.attr("NLPFINDIV") = POI_XPRS_NLPFINDIV; XPRS.attr("NLPLINQUADBR") = POI_XPRS_NLPLINQUADBR; XPRS.attr("NLPSOLVER") = POI_XPRS_NLPSOLVER; // SLP related integer controls XPRS.attr("SLPALGORITHM") = POI_XPRS_SLPALGORITHM; XPRS.attr("SLPAUGMENTATION") = POI_XPRS_SLPAUGMENTATION; XPRS.attr("SLPBARLIMIT") = POI_XPRS_SLPBARLIMIT; XPRS.attr("SLPCASCADE") = POI_XPRS_SLPCASCADE; XPRS.attr("SLPCASCADENLIMIT") = POI_XPRS_SLPCASCADENLIMIT; XPRS.attr("SLPDAMPSTART") = POI_XPRS_SLPDAMPSTART; XPRS.attr("SLPCUTSTRATEGY") = POI_XPRS_SLPCUTSTRATEGY; XPRS.attr("SLPDELTAZLIMIT") = POI_XPRS_SLPDELTAZLIMIT; XPRS.attr("SLPINFEASLIMIT") = POI_XPRS_SLPINFEASLIMIT; XPRS.attr("SLPITERLIMIT") = POI_XPRS_SLPITERLIMIT; XPRS.attr("SLPSAMECOUNT") = POI_XPRS_SLPSAMECOUNT; XPRS.attr("SLPSAMEDAMP") = POI_XPRS_SLPSAMEDAMP; XPRS.attr("SLPSBSTART") = POI_XPRS_SLPSBSTART; XPRS.attr("SLPXCOUNT") = POI_XPRS_SLPXCOUNT; XPRS.attr("SLPXLIMIT") = POI_XPRS_SLPXLIMIT; XPRS.attr("SLPDELAYUPDATEROWS") = POI_XPRS_SLPDELAYUPDATEROWS; XPRS.attr("SLPAUTOSAVE") = POI_XPRS_SLPAUTOSAVE; XPRS.attr("SLPANALYZE") = POI_XPRS_SLPANALYZE; XPRS.attr("SLPOCOUNT") = POI_XPRS_SLPOCOUNT; XPRS.attr("SLPMIPALGORITHM") = POI_XPRS_SLPMIPALGORITHM; XPRS.attr("SLPMIPRELAXSTEPBOUNDS") = POI_XPRS_SLPMIPRELAXSTEPBOUNDS; XPRS.attr("SLPMIPFIXSTEPBOUNDS") = POI_XPRS_SLPMIPFIXSTEPBOUNDS; XPRS.attr("SLPMIPITERLIMIT") = POI_XPRS_SLPMIPITERLIMIT; XPRS.attr("SLPMIPCUTOFFLIMIT") = POI_XPRS_SLPMIPCUTOFFLIMIT; XPRS.attr("SLPMIPOCOUNT") = POI_XPRS_SLPMIPOCOUNT; XPRS.attr("SLPMIPDEFAULTALGORITHM") = POI_XPRS_SLPMIPDEFAULTALGORITHM; XPRS.attr("SLPMIPLOG") = POI_XPRS_SLPMIPLOG; XPRS.attr("SLPDELTAOFFSET") = POI_XPRS_SLPDELTAOFFSET; XPRS.attr("SLPUPDATEOFFSET") = POI_XPRS_SLPUPDATEOFFSET; XPRS.attr("SLPERROROFFSET") = POI_XPRS_SLPERROROFFSET; XPRS.attr("SLPSBROWOFFSET") = POI_XPRS_SLPSBROWOFFSET; XPRS.attr("SLPVCOUNT") = POI_XPRS_SLPVCOUNT; XPRS.attr("SLPVLIMIT") = POI_XPRS_SLPVLIMIT; XPRS.attr("SLPSCALE") = POI_XPRS_SLPSCALE; XPRS.attr("SLPSCALECOUNT") = POI_XPRS_SLPSCALECOUNT; XPRS.attr("SLPECFCHECK") = POI_XPRS_SLPECFCHECK; XPRS.attr("SLPMIPCUTOFFCOUNT") = POI_XPRS_SLPMIPCUTOFFCOUNT; XPRS.attr("SLPWCOUNT") = POI_XPRS_SLPWCOUNT; XPRS.attr("SLPUNFINISHEDLIMIT") = POI_XPRS_SLPUNFINISHEDLIMIT; XPRS.attr("SLPCONVERGENCEOPS") = POI_XPRS_SLPCONVERGENCEOPS; XPRS.attr("SLPZEROCRITERION") = POI_XPRS_SLPZEROCRITERION; XPRS.attr("SLPZEROCRITERIONSTART") = POI_XPRS_SLPZEROCRITERIONSTART; XPRS.attr("SLPZEROCRITERIONCOUNT") = POI_XPRS_SLPZEROCRITERIONCOUNT; XPRS.attr("SLPLSPATTERNLIMIT") = POI_XPRS_SLPLSPATTERNLIMIT; XPRS.attr("SLPLSITERLIMIT") = POI_XPRS_SLPLSITERLIMIT; XPRS.attr("SLPLSSTART") = POI_XPRS_SLPLSSTART; XPRS.attr("SLPPENALTYINFOSTART") = POI_XPRS_SLPPENALTYINFOSTART; XPRS.attr("SLPFILTER") = POI_XPRS_SLPFILTER; XPRS.attr("SLPTRACEMASKOPS") = POI_XPRS_SLPTRACEMASKOPS; XPRS.attr("SLPLSZEROLIMIT") = POI_XPRS_SLPLSZEROLIMIT; XPRS.attr("SLPHEURSTRATEGY") = POI_XPRS_SLPHEURSTRATEGY; XPRS.attr("SLPBARCROSSOVERSTART") = POI_XPRS_SLPBARCROSSOVERSTART; XPRS.attr("SLPBARSTALLINGLIMIT") = POI_XPRS_SLPBARSTALLINGLIMIT; XPRS.attr("SLPBARSTALLINGOBJLIMIT") = POI_XPRS_SLPBARSTALLINGOBJLIMIT; XPRS.attr("SLPBARSTARTOPS") = POI_XPRS_SLPBARSTARTOPS; XPRS.attr("SLPGRIDHEURSELECT") = POI_XPRS_SLPGRIDHEURSELECT; // Nonlinear related double controls XPRS.attr("NLPINFINITY") = POI_XPRS_NLPINFINITY; XPRS.attr("NLPZERO") = POI_XPRS_NLPZERO; XPRS.attr("NLPDEFAULTIV") = POI_XPRS_NLPDEFAULTIV; XPRS.attr("NLPOPTTIME") = POI_XPRS_NLPOPTTIME; XPRS.attr("NLPVALIDATIONTOL_A") = POI_XPRS_NLPVALIDATIONTOL_A; XPRS.attr("NLPVALIDATIONTOL_R") = POI_XPRS_NLPVALIDATIONTOL_R; XPRS.attr("NLPVALIDATIONINDEX_A") = POI_XPRS_NLPVALIDATIONINDEX_A; XPRS.attr("NLPVALIDATIONINDEX_R") = POI_XPRS_NLPVALIDATIONINDEX_R; XPRS.attr("NLPPRIMALINTEGRALREF") = POI_XPRS_NLPPRIMALINTEGRALREF; XPRS.attr("NLPPRIMALINTEGRALALPHA") = POI_XPRS_NLPPRIMALINTEGRALALPHA; XPRS.attr("NLPOBJVAL") = POI_XPRS_NLPOBJVAL; XPRS.attr("NLPPRESOLVEZERO") = POI_XPRS_NLPPRESOLVEZERO; XPRS.attr("NLPMERITLAMBDA") = POI_XPRS_NLPMERITLAMBDA; XPRS.attr("MSMAXBOUNDRANGE") = POI_XPRS_MSMAXBOUNDRANGE; XPRS.attr("NLPVALIDATIONTOL_K") = POI_XPRS_NLPVALIDATIONTOL_K; XPRS.attr("NLPPRESOLVE_ELIMTOL") = POI_XPRS_NLPPRESOLVE_ELIMTOL; XPRS.attr("NLPVALIDATIONTARGET_R") = POI_XPRS_NLPVALIDATIONTARGET_R; XPRS.attr("NLPVALIDATIONTARGET_K") = POI_XPRS_NLPVALIDATIONTARGET_K; XPRS.attr("NLPVALIDATIONFACTOR") = POI_XPRS_NLPVALIDATIONFACTOR; XPRS.attr("NLPRELTOLBOUNDTHRESHOLD") = POI_XPRS_NLPRELTOLBOUNDTHRESHOLD; // SLP related double controls XPRS.attr("SLPDAMP") = POI_XPRS_SLPDAMP; XPRS.attr("SLPDAMPEXPAND") = POI_XPRS_SLPDAMPEXPAND; XPRS.attr("SLPDAMPSHRINK") = POI_XPRS_SLPDAMPSHRINK; XPRS.attr("SLPDELTA_A") = POI_XPRS_SLPDELTA_A; XPRS.attr("SLPDELTA_R") = POI_XPRS_SLPDELTA_R; XPRS.attr("SLPDELTA_Z") = POI_XPRS_SLPDELTA_Z; XPRS.attr("SLPDELTACOST") = POI_XPRS_SLPDELTACOST; XPRS.attr("SLPDELTAMAXCOST") = POI_XPRS_SLPDELTAMAXCOST; XPRS.attr("SLPDJTOL") = POI_XPRS_SLPDJTOL; XPRS.attr("SLPERRORCOST") = POI_XPRS_SLPERRORCOST; XPRS.attr("SLPERRORMAXCOST") = POI_XPRS_SLPERRORMAXCOST; XPRS.attr("SLPERRORTOL_A") = POI_XPRS_SLPERRORTOL_A; XPRS.attr("SLPEXPAND") = POI_XPRS_SLPEXPAND; XPRS.attr("SLPMAXWEIGHT") = POI_XPRS_SLPMAXWEIGHT; XPRS.attr("SLPMINWEIGHT") = POI_XPRS_SLPMINWEIGHT; XPRS.attr("SLPSHRINK") = POI_XPRS_SLPSHRINK; XPRS.attr("SLPCTOL") = POI_XPRS_SLPCTOL; XPRS.attr("SLPATOL_A") = POI_XPRS_SLPATOL_A; XPRS.attr("SLPATOL_R") = POI_XPRS_SLPATOL_R; XPRS.attr("SLPMTOL_A") = POI_XPRS_SLPMTOL_A; XPRS.attr("SLPMTOL_R") = POI_XPRS_SLPMTOL_R; XPRS.attr("SLPITOL_A") = POI_XPRS_SLPITOL_A; XPRS.attr("SLPITOL_R") = POI_XPRS_SLPITOL_R; XPRS.attr("SLPSTOL_A") = POI_XPRS_SLPSTOL_A; XPRS.attr("SLPSTOL_R") = POI_XPRS_SLPSTOL_R; XPRS.attr("SLPMVTOL") = POI_XPRS_SLPMVTOL; XPRS.attr("SLPXTOL_A") = POI_XPRS_SLPXTOL_A; XPRS.attr("SLPXTOL_R") = POI_XPRS_SLPXTOL_R; XPRS.attr("SLPDEFAULTSTEPBOUND") = POI_XPRS_SLPDEFAULTSTEPBOUND; XPRS.attr("SLPDAMPMAX") = POI_XPRS_SLPDAMPMAX; XPRS.attr("SLPDAMPMIN") = POI_XPRS_SLPDAMPMIN; XPRS.attr("SLPDELTACOSTFACTOR") = POI_XPRS_SLPDELTACOSTFACTOR; XPRS.attr("SLPERRORCOSTFACTOR") = POI_XPRS_SLPERRORCOSTFACTOR; XPRS.attr("SLPERRORTOL_P") = POI_XPRS_SLPERRORTOL_P; XPRS.attr("SLPCASCADETOL_PA") = POI_XPRS_SLPCASCADETOL_PA; XPRS.attr("SLPCASCADETOL_PR") = POI_XPRS_SLPCASCADETOL_PR; XPRS.attr("SLPCASCADETOL_Z") = POI_XPRS_SLPCASCADETOL_Z; XPRS.attr("SLPOTOL_A") = POI_XPRS_SLPOTOL_A; XPRS.attr("SLPOTOL_R") = POI_XPRS_SLPOTOL_R; XPRS.attr("SLPDELTA_X") = POI_XPRS_SLPDELTA_X; XPRS.attr("SLPERRORCOSTS") = POI_XPRS_SLPERRORCOSTS; XPRS.attr("SLPGRANULARITY") = POI_XPRS_SLPGRANULARITY; XPRS.attr("SLPMIPCUTOFF_A") = POI_XPRS_SLPMIPCUTOFF_A; XPRS.attr("SLPMIPCUTOFF_R") = POI_XPRS_SLPMIPCUTOFF_R; XPRS.attr("SLPMIPOTOL_A") = POI_XPRS_SLPMIPOTOL_A; XPRS.attr("SLPMIPOTOL_R") = POI_XPRS_SLPMIPOTOL_R; XPRS.attr("SLPESCALATION") = POI_XPRS_SLPESCALATION; XPRS.attr("SLPOBJTOPENALTYCOST") = POI_XPRS_SLPOBJTOPENALTYCOST; XPRS.attr("SLPSHRINKBIAS") = POI_XPRS_SLPSHRINKBIAS; XPRS.attr("SLPFEASTOLTARGET") = POI_XPRS_SLPFEASTOLTARGET; XPRS.attr("SLPOPTIMALITYTOLTARGET") = POI_XPRS_SLPOPTIMALITYTOLTARGET; XPRS.attr("SLPDELTA_INFINITY") = POI_XPRS_SLPDELTA_INFINITY; XPRS.attr("SLPVTOL_A") = POI_XPRS_SLPVTOL_A; XPRS.attr("SLPVTOL_R") = POI_XPRS_SLPVTOL_R; XPRS.attr("SLPETOL_A") = POI_XPRS_SLPETOL_A; XPRS.attr("SLPETOL_R") = POI_XPRS_SLPETOL_R; XPRS.attr("SLPEVTOL_A") = POI_XPRS_SLPEVTOL_A; XPRS.attr("SLPEVTOL_R") = POI_XPRS_SLPEVTOL_R; XPRS.attr("SLPDELTA_ZERO") = POI_XPRS_SLPDELTA_ZERO; XPRS.attr("SLPMINSBFACTOR") = POI_XPRS_SLPMINSBFACTOR; XPRS.attr("SLPCLAMPVALIDATIONTOL_A") = POI_XPRS_SLPCLAMPVALIDATIONTOL_A; XPRS.attr("SLPCLAMPVALIDATIONTOL_R") = POI_XPRS_SLPCLAMPVALIDATIONTOL_R; XPRS.attr("SLPCLAMPSHRINK") = POI_XPRS_SLPCLAMPSHRINK; XPRS.attr("SLPECFTOL_A") = POI_XPRS_SLPECFTOL_A; XPRS.attr("SLPECFTOL_R") = POI_XPRS_SLPECFTOL_R; XPRS.attr("SLPWTOL_A") = POI_XPRS_SLPWTOL_A; XPRS.attr("SLPWTOL_R") = POI_XPRS_SLPWTOL_R; XPRS.attr("SLPMATRIXTOL") = POI_XPRS_SLPMATRIXTOL; XPRS.attr("SLPDRFIXRANGE") = POI_XPRS_SLPDRFIXRANGE; XPRS.attr("SLPDRCOLTOL") = POI_XPRS_SLPDRCOLTOL; XPRS.attr("SLPMIPERRORTOL_A") = POI_XPRS_SLPMIPERRORTOL_A; XPRS.attr("SLPMIPERRORTOL_R") = POI_XPRS_SLPMIPERRORTOL_R; XPRS.attr("SLPCDTOL_A") = POI_XPRS_SLPCDTOL_A; XPRS.attr("SLPCDTOL_R") = POI_XPRS_SLPCDTOL_R; XPRS.attr("SLPENFORCEMAXCOST") = POI_XPRS_SLPENFORCEMAXCOST; XPRS.attr("SLPENFORCECOSTSHRINK") = POI_XPRS_SLPENFORCECOSTSHRINK; XPRS.attr("SLPDRCOLDJTOL") = POI_XPRS_SLPDRCOLDJTOL; XPRS.attr("SLPBARSTALLINGTOL") = POI_XPRS_SLPBARSTALLINGTOL; XPRS.attr("SLPOBJTHRESHOLD") = POI_XPRS_SLPOBJTHRESHOLD; XPRS.attr("SLPBOUNDTHRESHOLD") = POI_XPRS_SLPBOUNDTHRESHOLD; // Nonlinear related string controls XPRS.attr("NLPIVNAME") = POI_XPRS_NLPIVNAME; // SLP related string controls XPRS.attr("SLPDELTAFORMAT") = POI_XPRS_SLPDELTAFORMAT; XPRS.attr("SLPMINUSDELTAFORMAT") = POI_XPRS_SLPMINUSDELTAFORMAT; XPRS.attr("SLPMINUSERRORFORMAT") = POI_XPRS_SLPMINUSERRORFORMAT; XPRS.attr("SLPPLUSDELTAFORMAT") = POI_XPRS_SLPPLUSDELTAFORMAT; XPRS.attr("SLPPLUSERRORFORMAT") = POI_XPRS_SLPPLUSERRORFORMAT; XPRS.attr("SLPSBNAME") = POI_XPRS_SLPSBNAME; XPRS.attr("SLPTOLNAME") = POI_XPRS_SLPTOLNAME; XPRS.attr("SLPUPDATEFORMAT") = POI_XPRS_SLPUPDATEFORMAT; XPRS.attr("SLPPENALTYROWFORMAT") = POI_XPRS_SLPPENALTYROWFORMAT; XPRS.attr("SLPPENALTYCOLFORMAT") = POI_XPRS_SLPPENALTYCOLFORMAT; XPRS.attr("SLPSBLOROWFORMAT") = POI_XPRS_SLPSBLOROWFORMAT; XPRS.attr("SLPSBUPROWFORMAT") = POI_XPRS_SLPSBUPROWFORMAT; XPRS.attr("SLPTRACEMASK") = POI_XPRS_SLPTRACEMASK; XPRS.attr("SLPITERFALLBACKOPS") = POI_XPRS_SLPITERFALLBACKOPS; // Nonlinear related integer attributes XPRS.attr("NLPVALIDATIONSTATUS") = POI_XPRS_NLPVALIDATIONSTATUS; XPRS.attr("NLPSOLSTATUS") = POI_XPRS_NLPSOLSTATUS; XPRS.attr("NLPORIGINALROWS") = POI_XPRS_NLPORIGINALROWS; XPRS.attr("NLPORIGINALCOLS") = POI_XPRS_NLPORIGINALCOLS; XPRS.attr("NLPUFS") = POI_XPRS_NLPUFS; XPRS.attr("NLPIFS") = POI_XPRS_NLPIFS; XPRS.attr("NLPEQUALSCOLUMN") = POI_XPRS_NLPEQUALSCOLUMN; XPRS.attr("NLPVARIABLES") = POI_XPRS_NLPVARIABLES; XPRS.attr("NLPIMPLICITVARIABLES") = POI_XPRS_NLPIMPLICITVARIABLES; XPRS.attr("NONLINEARCONSTRAINTS") = POI_XPRS_NONLINEARCONSTRAINTS; XPRS.attr("NLPUSERFUNCCALLS") = POI_XPRS_NLPUSERFUNCCALLS; XPRS.attr("NLPUSEDERIVATIVES") = POI_XPRS_NLPUSEDERIVATIVES; XPRS.attr("NLPKEEPBESTITER") = POI_XPRS_NLPKEEPBESTITER; XPRS.attr("NLPSTATUS") = POI_XPRS_NLPSTATUS; XPRS.attr("LOCALSOLVERSELECTED") = POI_XPRS_LOCALSOLVERSELECTED; XPRS.attr("NLPMODELROWS") = POI_XPRS_NLPMODELROWS; XPRS.attr("NLPMODELCOLS") = POI_XPRS_NLPMODELCOLS; XPRS.attr("NLPJOBID") = POI_XPRS_NLPJOBID; XPRS.attr("MSJOBS") = POI_XPRS_MSJOBS; XPRS.attr("NLPSTOPSTATUS") = POI_XPRS_NLPSTOPSTATUS; XPRS.attr("NLPPRESOLVEELIMINATIONS") = POI_XPRS_NLPPRESOLVEELIMINATIONS; XPRS.attr("NLPTOTALEVALUATIONERRORS") = POI_XPRS_NLPTOTALEVALUATIONERRORS; // SLP related integer attributes XPRS.attr("SLPEXPLOREDELTAS") = POI_XPRS_SLPEXPLOREDELTAS; XPRS.attr("SLPSEMICONTDELTAS") = POI_XPRS_SLPSEMICONTDELTAS; XPRS.attr("SLPINTEGERDELTAS") = POI_XPRS_SLPINTEGERDELTAS; XPRS.attr("SLPITER") = POI_XPRS_SLPITER; XPRS.attr("SLPSTATUS") = POI_XPRS_SLPSTATUS; XPRS.attr("SLPUNCONVERGED") = POI_XPRS_SLPUNCONVERGED; XPRS.attr("SLPSBXCONVERGED") = POI_XPRS_SLPSBXCONVERGED; XPRS.attr("SLPPENALTYDELTAROW") = POI_XPRS_SLPPENALTYDELTAROW; XPRS.attr("SLPPENALTYDELTACOLUMN") = POI_XPRS_SLPPENALTYDELTACOLUMN; XPRS.attr("SLPPENALTYERRORROW") = POI_XPRS_SLPPENALTYERRORROW; XPRS.attr("SLPPENALTYERRORCOLUMN") = POI_XPRS_SLPPENALTYERRORCOLUMN; XPRS.attr("SLPCOEFFICIENTS") = POI_XPRS_SLPCOEFFICIENTS; XPRS.attr("SLPPENALTYDELTAS") = POI_XPRS_SLPPENALTYDELTAS; XPRS.attr("SLPPENALTYERRORS") = POI_XPRS_SLPPENALTYERRORS; XPRS.attr("SLPPLUSPENALTYERRORS") = POI_XPRS_SLPPLUSPENALTYERRORS; XPRS.attr("SLPMINUSPENALTYERRORS") = POI_XPRS_SLPMINUSPENALTYERRORS; XPRS.attr("SLPUCCONSTRAINEDCOUNT") = POI_XPRS_SLPUCCONSTRAINEDCOUNT; XPRS.attr("SLPMIPNODES") = POI_XPRS_SLPMIPNODES; XPRS.attr("SLPMIPITER") = POI_XPRS_SLPMIPITER; XPRS.attr("SLPTOLSETS") = POI_XPRS_SLPTOLSETS; XPRS.attr("SLPECFCOUNT") = POI_XPRS_SLPECFCOUNT; XPRS.attr("SLPDELTAS") = POI_XPRS_SLPDELTAS; XPRS.attr("SLPZEROESRESET") = POI_XPRS_SLPZEROESRESET; XPRS.attr("SLPZEROESTOTAL") = POI_XPRS_SLPZEROESTOTAL; XPRS.attr("SLPZEROESRETAINED") = POI_XPRS_SLPZEROESRETAINED; XPRS.attr("SLPNONCONSTANTCOEFFS") = POI_XPRS_SLPNONCONSTANTCOEFFS; XPRS.attr("SLPMIPSOLS") = POI_XPRS_SLPMIPSOLS; // Nonlinear related double attributes XPRS.attr("NLPVALIDATIONINDEX_K") = POI_XPRS_NLPVALIDATIONINDEX_K; XPRS.attr("NLPVALIDATIONNETOBJ") = POI_XPRS_NLPVALIDATIONNETOBJ; XPRS.attr("NLPPRIMALINTEGRAL") = POI_XPRS_NLPPRIMALINTEGRAL; // SLP related double attributes XPRS.attr("SLPCURRENTDELTACOST") = POI_XPRS_SLPCURRENTDELTACOST; XPRS.attr("SLPCURRENTERRORCOST") = POI_XPRS_SLPCURRENTERRORCOST; XPRS.attr("SLPPENALTYERRORTOTAL") = POI_XPRS_SLPPENALTYERRORTOTAL; XPRS.attr("SLPPENALTYERRORVALUE") = POI_XPRS_SLPPENALTYERRORVALUE; XPRS.attr("SLPPENALTYDELTATOTAL") = POI_XPRS_SLPPENALTYDELTATOTAL; XPRS.attr("SLPPENALTYDELTAVALUE") = POI_XPRS_SLPPENALTYDELTAVALUE; // Nonlinear related string attributes // SLP related string attributes // Knitro's parameters XPRS.attr("KNITRO_PARAM_NEWPOINT") = POI_XPRS_KNITRO_PARAM_NEWPOINT; XPRS.attr("KNITRO_PARAM_HONORBNDS") = POI_XPRS_KNITRO_PARAM_HONORBNDS; XPRS.attr("KNITRO_PARAM_ALGORITHM") = POI_XPRS_KNITRO_PARAM_ALGORITHM; XPRS.attr("KNITRO_PARAM_BAR_MURULE") = POI_XPRS_KNITRO_PARAM_BAR_MURULE; XPRS.attr("KNITRO_PARAM_BAR_FEASIBLE") = POI_XPRS_KNITRO_PARAM_BAR_FEASIBLE; XPRS.attr("KNITRO_PARAM_GRADOPT") = POI_XPRS_KNITRO_PARAM_GRADOPT; XPRS.attr("KNITRO_PARAM_HESSOPT") = POI_XPRS_KNITRO_PARAM_HESSOPT; XPRS.attr("KNITRO_PARAM_BAR_INITPT") = POI_XPRS_KNITRO_PARAM_BAR_INITPT; XPRS.attr("KNITRO_PARAM_MAXCGIT") = POI_XPRS_KNITRO_PARAM_MAXCGIT; XPRS.attr("KNITRO_PARAM_MAXIT") = POI_XPRS_KNITRO_PARAM_MAXIT; XPRS.attr("KNITRO_PARAM_OUTLEV") = POI_XPRS_KNITRO_PARAM_OUTLEV; XPRS.attr("KNITRO_PARAM_SCALE") = POI_XPRS_KNITRO_PARAM_SCALE; XPRS.attr("KNITRO_PARAM_SOC") = POI_XPRS_KNITRO_PARAM_SOC; XPRS.attr("KNITRO_PARAM_DELTA") = POI_XPRS_KNITRO_PARAM_DELTA; XPRS.attr("KNITRO_PARAM_BAR_FEASMODETOL") = POI_XPRS_KNITRO_PARAM_BAR_FEASMODETOL; XPRS.attr("KNITRO_PARAM_FEASTOL") = POI_XPRS_KNITRO_PARAM_FEASTOL; XPRS.attr("KNITRO_PARAM_FEASTOLABS") = POI_XPRS_KNITRO_PARAM_FEASTOLABS; XPRS.attr("KNITRO_PARAM_BAR_INITMU") = POI_XPRS_KNITRO_PARAM_BAR_INITMU; XPRS.attr("KNITRO_PARAM_OBJRANGE") = POI_XPRS_KNITRO_PARAM_OBJRANGE; XPRS.attr("KNITRO_PARAM_OPTTOL") = POI_XPRS_KNITRO_PARAM_OPTTOL; XPRS.attr("KNITRO_PARAM_OPTTOLABS") = POI_XPRS_KNITRO_PARAM_OPTTOLABS; XPRS.attr("KNITRO_PARAM_PIVOT") = POI_XPRS_KNITRO_PARAM_PIVOT; XPRS.attr("KNITRO_PARAM_XTOL") = POI_XPRS_KNITRO_PARAM_XTOL; XPRS.attr("KNITRO_PARAM_DEBUG") = POI_XPRS_KNITRO_PARAM_DEBUG; XPRS.attr("KNITRO_PARAM_MULTISTART") = POI_XPRS_KNITRO_PARAM_MULTISTART; XPRS.attr("KNITRO_PARAM_MSMAXSOLVES") = POI_XPRS_KNITRO_PARAM_MSMAXSOLVES; XPRS.attr("KNITRO_PARAM_MSMAXBNDRANGE") = POI_XPRS_KNITRO_PARAM_MSMAXBNDRANGE; XPRS.attr("KNITRO_PARAM_LMSIZE") = POI_XPRS_KNITRO_PARAM_LMSIZE; XPRS.attr("KNITRO_PARAM_BAR_MAXCROSSIT") = POI_XPRS_KNITRO_PARAM_BAR_MAXCROSSIT; XPRS.attr("KNITRO_PARAM_BLASOPTION") = POI_XPRS_KNITRO_PARAM_BLASOPTION; XPRS.attr("KNITRO_PARAM_BAR_MAXREFACTOR") = POI_XPRS_KNITRO_PARAM_BAR_MAXREFACTOR; XPRS.attr("KNITRO_PARAM_BAR_MAXBACKTRACK") = POI_XPRS_KNITRO_PARAM_BAR_MAXBACKTRACK; XPRS.attr("KNITRO_PARAM_BAR_PENRULE") = POI_XPRS_KNITRO_PARAM_BAR_PENRULE; XPRS.attr("KNITRO_PARAM_BAR_PENCONS") = POI_XPRS_KNITRO_PARAM_BAR_PENCONS; XPRS.attr("KNITRO_PARAM_MSNUMTOSAVE") = POI_XPRS_KNITRO_PARAM_MSNUMTOSAVE; XPRS.attr("KNITRO_PARAM_MSSAVETOL") = POI_XPRS_KNITRO_PARAM_MSSAVETOL; XPRS.attr("KNITRO_PARAM_MSTERMINATE") = POI_XPRS_KNITRO_PARAM_MSTERMINATE; XPRS.attr("KNITRO_PARAM_MSSTARTPTRANGE") = POI_XPRS_KNITRO_PARAM_MSSTARTPTRANGE; XPRS.attr("KNITRO_PARAM_INFEASTOL") = POI_XPRS_KNITRO_PARAM_INFEASTOL; XPRS.attr("KNITRO_PARAM_LINSOLVER") = POI_XPRS_KNITRO_PARAM_LINSOLVER; XPRS.attr("KNITRO_PARAM_BAR_DIRECTINTERVAL") = POI_XPRS_KNITRO_PARAM_BAR_DIRECTINTERVAL; XPRS.attr("KNITRO_PARAM_PRESOLVE") = POI_XPRS_KNITRO_PARAM_PRESOLVE; XPRS.attr("KNITRO_PARAM_PRESOLVE_TOL") = POI_XPRS_KNITRO_PARAM_PRESOLVE_TOL; XPRS.attr("KNITRO_PARAM_BAR_SWITCHRULE") = POI_XPRS_KNITRO_PARAM_BAR_SWITCHRULE; XPRS.attr("KNITRO_PARAM_MA_TERMINATE") = POI_XPRS_KNITRO_PARAM_MA_TERMINATE; XPRS.attr("KNITRO_PARAM_MSSEED") = POI_XPRS_KNITRO_PARAM_MSSEED; XPRS.attr("KNITRO_PARAM_BAR_RELAXCONS") = POI_XPRS_KNITRO_PARAM_BAR_RELAXCONS; XPRS.attr("KNITRO_PARAM_SOLTYPE") = POI_XPRS_KNITRO_PARAM_SOLTYPE; XPRS.attr("KNITRO_PARAM_MIP_METHOD") = POI_XPRS_KNITRO_PARAM_MIP_METHOD; XPRS.attr("KNITRO_PARAM_MIP_BRANCHRULE") = POI_XPRS_KNITRO_PARAM_MIP_BRANCHRULE; XPRS.attr("KNITRO_PARAM_MIP_SELECTRULE") = POI_XPRS_KNITRO_PARAM_MIP_SELECTRULE; XPRS.attr("KNITRO_PARAM_MIP_INTGAPABS") = POI_XPRS_KNITRO_PARAM_MIP_INTGAPABS; XPRS.attr("KNITRO_PARAM_MIP_INTGAPREL") = POI_XPRS_KNITRO_PARAM_MIP_INTGAPREL; XPRS.attr("KNITRO_PARAM_MIP_OUTLEVEL") = POI_XPRS_KNITRO_PARAM_MIP_OUTLEVEL; XPRS.attr("KNITRO_PARAM_MIP_OUTINTERVAL") = POI_XPRS_KNITRO_PARAM_MIP_OUTINTERVAL; XPRS.attr("KNITRO_PARAM_MIP_DEBUG") = POI_XPRS_KNITRO_PARAM_MIP_DEBUG; XPRS.attr("KNITRO_PARAM_MIP_IMPLICATNS") = POI_XPRS_KNITRO_PARAM_MIP_IMPLICATNS; XPRS.attr("KNITRO_PARAM_MIP_GUB_BRANCH") = POI_XPRS_KNITRO_PARAM_MIP_GUB_BRANCH; XPRS.attr("KNITRO_PARAM_MIP_KNAPSACK") = POI_XPRS_KNITRO_PARAM_MIP_KNAPSACK; XPRS.attr("KNITRO_PARAM_MIP_ROUNDING") = POI_XPRS_KNITRO_PARAM_MIP_ROUNDING; XPRS.attr("KNITRO_PARAM_MIP_ROOTALG") = POI_XPRS_KNITRO_PARAM_MIP_ROOTALG; XPRS.attr("KNITRO_PARAM_MIP_LPALG") = POI_XPRS_KNITRO_PARAM_MIP_LPALG; XPRS.attr("KNITRO_PARAM_MIP_MAXNODES") = POI_XPRS_KNITRO_PARAM_MIP_MAXNODES; XPRS.attr("KNITRO_PARAM_MIP_HEURISTIC") = POI_XPRS_KNITRO_PARAM_MIP_HEURISTIC; XPRS.attr("KNITRO_PARAM_MIP_HEUR_MAXIT") = POI_XPRS_KNITRO_PARAM_MIP_HEUR_MAXIT; XPRS.attr("KNITRO_PARAM_MIP_PSEUDOINIT") = POI_XPRS_KNITRO_PARAM_MIP_PSEUDOINIT; XPRS.attr("KNITRO_PARAM_MIP_STRONG_MAXIT") = POI_XPRS_KNITRO_PARAM_MIP_STRONG_MAXIT; XPRS.attr("KNITRO_PARAM_MIP_STRONG_CANDLIM") = POI_XPRS_KNITRO_PARAM_MIP_STRONG_CANDLIM; XPRS.attr("KNITRO_PARAM_MIP_STRONG_LEVEL") = POI_XPRS_KNITRO_PARAM_MIP_STRONG_LEVEL; XPRS.attr("KNITRO_PARAM_PAR_NUMTHREADS") = POI_XPRS_KNITRO_PARAM_PAR_NUMTHREADS; nb::enum_(XPRS, "SOLSTATUS") .value("NOTFOUND", SOLSTATUS::NOTFOUND) .value("OPTIMAL", SOLSTATUS::OPTIMAL) .value("FEASIBLE", SOLSTATUS::FEASIBLE) .value("INFEASIBLE", SOLSTATUS::INFEASIBLE) .value("UNBOUNDED", SOLSTATUS::UNBOUNDED); nb::enum_(XPRS, "SOLVESTATUS") .value("UNSTARTED", SOLVESTATUS::UNSTARTED) .value("STOPPED", SOLVESTATUS::STOPPED) .value("FAILED", SOLVESTATUS::FAILED) .value("COMPLETED", SOLVESTATUS::COMPLETED); nb::enum_(XPRS, "LPSTATUS") .value("UNSTARTED", LPSTATUS::UNSTARTED) .value("OPTIMAL", LPSTATUS::OPTIMAL) .value("INFEAS", LPSTATUS::INFEAS) .value("CUTOFF", LPSTATUS::CUTOFF) .value("UNFINISHED", LPSTATUS::UNFINISHED) .value("UNBOUNDED", LPSTATUS::UNBOUNDED) .value("CUTOFF_IN_DUAL", LPSTATUS::CUTOFF_IN_DUAL) .value("UNSOLVED", LPSTATUS::UNSOLVED) .value("NONCONVEX", LPSTATUS::NONCONVEX); nb::enum_(XPRS, "MIPSTATUS") .value("NOT_LOADED", MIPSTATUS::NOT_LOADED) .value("LP_NOT_OPTIMAL", MIPSTATUS::LP_NOT_OPTIMAL) .value("LP_OPTIMAL", MIPSTATUS::LP_OPTIMAL) .value("NO_SOL_FOUND", MIPSTATUS::NO_SOL_FOUND) .value("SOLUTION", MIPSTATUS::SOLUTION) .value("INFEAS", MIPSTATUS::INFEAS) .value("OPTIMAL", MIPSTATUS::OPTIMAL) .value("UNBOUNDED", MIPSTATUS::UNBOUNDED); nb::enum_(XPRS, "NLPSTATUS") .value("UNSTARTED", NLPSTATUS::UNSTARTED) .value("SOLUTION", NLPSTATUS::SOLUTION) .value("LOCALLY_OPTIMAL", NLPSTATUS::LOCALLY_OPTIMAL) .value("OPTIMAL", NLPSTATUS::OPTIMAL) .value("NOSOLUTION", NLPSTATUS::NOSOLUTION) .value("LOCALLY_INFEASIBLE", NLPSTATUS::LOCALLY_INFEASIBLE) .value("INFEASIBLE", NLPSTATUS::INFEASIBLE) .value("UNBOUNDED", NLPSTATUS::UNBOUNDED) .value("UNFINISHED", NLPSTATUS::UNFINISHED) .value("UNSOLVED", NLPSTATUS::UNSOLVED); nb::enum_(XPRS, "IISSOLSTATUS") .value("UNSTARTED", IISSOLSTATUS::UNSTARTED) .value("FEASIBLE", IISSOLSTATUS::FEASIBLE) .value("COMPLETED", IISSOLSTATUS::COMPLETED) .value("UNFINISHED", IISSOLSTATUS::UNFINISHED); nb::enum_(XPRS, "SOLAVAILABLE") .value("NOTFOUND", SOLAVAILABLE::NOTFOUND) .value("OPTIMAL", SOLAVAILABLE::OPTIMAL) .value("FEASIBLE", SOLAVAILABLE::FEASIBLE); nb::enum_(XPRS, "OPTIMIZETYPE") .value("NONE", OPTIMIZETYPE::NONE) .value("LP", OPTIMIZETYPE::LP) .value("MIP", OPTIMIZETYPE::MIP) .value("LOCAL", OPTIMIZETYPE::LOCAL) .value("GLOBAL", OPTIMIZETYPE::GLOBAL); // Define Callbacks context enum auto to_upper = [](char const *name) { std::string res(name); for (char &c : res) c = std::toupper(c); return res; }; nb::enum_(XPRS, "CB_CONTEXT", nb::is_arithmetic()) // Use the callback list to define a value for each callback #define XPRSCB_NB_ENUM(ID, NAME, ...) .value(to_upper(#NAME).c_str(), CB_CONTEXT::NAME) XPRSCB_LIST(XPRSCB_NB_ENUM, XPRSCB_ARG_IGNORE); #undef XPRSCB_NB_ENUM } ================================================ FILE: optimizer_version.toml ================================================ Gurobi = "13.0.0" COPT = "8.0.2" MOSEK = "10.2.0" HiGHS = "1.12.0" IPOPT = "3.13.2" Xpress = "9.8" KNITRO = "15.1.0" ================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["scikit-build-core", "nanobind", "typing_extensions"] build-backend = "scikit_build_core.build" [project] name = "pyoptinterface" version = "0.6.1" description = "Python interface to multiple optimization solvers" readme = "README.md" requires-python = ">=3.9" authors = [{ name = "Yue Yang", email = "metab0t@outlook.com" }] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering', 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', ] [project.urls] Homepage = "https://github.com/metab0t/pyoptinterface" [project.optional-dependencies] matrix = ["numpy", "scipy"] highs = ["highsbox"] nlp = ["llvmlite", "tccbox"] test = ["pytest", "numpy", "scipy>=1.11.0", "highsbox", "llvmlite", "tccbox"] [tool.scikit-build] editable.rebuild = true build-dir = "build/{wheel_tag}" cmake.build-type = "Release" # Build stable ABI wheels for CPython 3.12+ wheel.py-api = "cp312" [tool.scikit-build.cmake.define] PYTHON_VERSION = { env = "PYTHON_VERSION", default = "3.9" } CMAKE_FIND_DEBUG_MODE = "OFF" ENABLE_TEST_MAIN = "OFF" ================================================ FILE: scripts/generate_attribute_table.py ================================================ from typing import IO from pathlib import Path import pyoptinterface as poi from pyoptinterface import gurobi, copt, mosek, highs, ipopt def attribute_support_table(f, attribute_enum): results = [] for attribute in attribute_enum: support_get = f(attribute) support_set = f(attribute, settable=True) results.append((attribute, support_get, support_set)) return results emoji_map = {True: "✅", False: "❌"} def print_table(io: IO[str], results): io.write(""":::{list-table} :header-rows: 1 * - Attribute - Get - Set """) for attribute, support_get, support_set in results: io.write(f"* - {attribute.name}\n") io.write(f" - {emoji_map[support_get]}\n") io.write(f" - {emoji_map[support_set]}\n") io.write(":::\n\n") io_pairs = [ (gurobi.Model, "gurobi"), (copt.Model, "copt"), (mosek.Model, "mosek"), (highs.Model, "highs"), (ipopt.Model, "ipopt"), ] rootdir = Path(__file__).parent.parent docsource_dir = rootdir / "docs" / "source" / "attribute" for model, name in io_pairs: path = docsource_dir / f"{name}.md" with open(path, "w", encoding="utf-8") as io: io.write(f"### Supported [model attribute](#pyoptinterface.ModelAttribute)\n\n") print_table( io, attribute_support_table(model.supports_model_attribute, poi.ModelAttribute), ) io.write( f"### Supported [variable attribute](#pyoptinterface.VariableAttribute)\n\n" ) print_table( io, attribute_support_table( model.supports_variable_attribute, poi.VariableAttribute ), ) io.write( f"### Supported [constraint attribute](#pyoptinterface.ConstraintAttribute)\n\n" ) print_table( io, attribute_support_table( model.supports_constraint_attribute, poi.ConstraintAttribute ), ) ================================================ FILE: scripts/generate_solver_constants.py ================================================ from typing import IO from pathlib import Path import gurobipy as gp GRB = gp.GRB import coptpy as cp COPT = cp.COPT def value_to_str(value): if isinstance(value, str): return f'"{value}"' return str(value) def extract_gurobi_constants(): # we want to extract all variables with all UPPERCASE names in dir(GRB) GRB_constants = [] for name in dir(GRB): if name.isupper(): GRB_constants.append(name) # extract all variables with first letter in UPPERCASE in dir(GRB.Attr) and dir(GRB.Param) and dir(GRB.Callback) GRB_Attr_constants = [] for name in dir(GRB.Attr): if name[0].isupper(): GRB_Attr_constants.append(name) GRB_Param_constants = [] for name in dir(GRB.Param): if name[0].isupper(): GRB_Param_constants.append(name) GRB_Callback_constants = [] for name in dir(GRB.Callback): if name[0].isupper(): GRB_Callback_constants.append(name) return { "GRB": GRB_constants, "GRB.Attr": GRB_Attr_constants, "GRB.Param": GRB_Param_constants, "GRB.Callback": GRB_Callback_constants, } def export_gurobi_constants(io: IO[str], gurobi_constants): io.write('nb::module_ GRB = m.def_submodule("GRB");\n') for name in gurobi_constants["GRB"]: value = getattr(GRB, name) value = value_to_str(value) io.write(f'GRB.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ Attr = GRB.def_submodule("Attr");\n') Attr = GRB.Attr for name in gurobi_constants["GRB.Attr"]: value = getattr(Attr, name) value = value_to_str(value) io.write(f'Attr.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ Param = GRB.def_submodule("Param");\n') Param = GRB.Param for name in gurobi_constants["GRB.Param"]: value = getattr(Param, name) value = value_to_str(value) io.write(f'Param.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ Callback = GRB.def_submodule("Callback");\n') Callback = GRB.Callback for name in gurobi_constants["GRB.Callback"]: value = getattr(Callback, name) value = value_to_str(value) io.write(f'Callback.attr("{name}") = {value};\n') def extract_copt_constants(): # dir(COPT) COPT_constants = [] for name in dir(COPT): if name.isupper(): COPT_constants.append(name) # dir(COPT.Attr) COPT_Attr_constants = [] for name in dir(COPT.Attr): if name[0].isupper(): COPT_Attr_constants.append(name) # dir(COPT.Param) COPT_Param_constants = [] for name in dir(COPT.Param): if name[0].isupper(): COPT_Param_constants.append(name) # dir(COPT.Info) COPT_Info_constants = [] for name in dir(COPT.Info): if name[0].isupper(): COPT_Info_constants.append(name) # dir(COPT.CbInfo) COPT_CbInfo_constants = [] for name in dir(COPT.CbInfo): if name[0].isupper(): COPT_CbInfo_constants.append(name) return { "COPT": COPT_constants, "COPT.Attr": COPT_Attr_constants, "COPT.Param": COPT_Param_constants, "COPT.Info": COPT_Info_constants, "COPT.CbInfo": COPT_CbInfo_constants, } def export_copt_constants(io: IO[str], copt_constants): io.write('nb::module_ COPT = m.def_submodule("COPT");\n') for name in copt_constants["COPT"]: value = getattr(COPT, name) value = value_to_str(value) io.write(f'COPT.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ Attr = COPT.def_submodule("Attr");\n') Attr = COPT.Attr for name in copt_constants["COPT.Attr"]: value = getattr(Attr, name) value = value_to_str(value) io.write(f'Attr.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ Param = COPT.def_submodule("Param");\n') Param = COPT.Param for name in copt_constants["COPT.Param"]: value = getattr(Param, name) value = value_to_str(value) io.write(f'Param.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ Info = COPT.def_submodule("Info");\n') Info = COPT.Info for name in copt_constants["COPT.Info"]: value = getattr(Info, name) value = value_to_str(value) io.write(f'Info.attr("{name}") = {value};\n') io.write("\n") io.write('nb::module_ CbInfo = COPT.def_submodule("CbInfo");\n') CbInfo = COPT.CbInfo for name in copt_constants["COPT.CbInfo"]: value = getattr(CbInfo, name) value = value_to_str(value) io.write(f'CbInfo.attr("{name}") = {value};\n') if __name__ == "__main__": current_dir = Path(__file__).parent.resolve() with open(current_dir / "gurobi_constants.txt", "w") as io: gurobi_constants = extract_gurobi_constants() export_gurobi_constants(io, gurobi_constants) print("Done!") with open(current_dir / "copt_constants.txt", "w") as io: copt_constants = extract_copt_constants() export_copt_constants(io, copt_constants) print("Done!") ================================================ FILE: skills/pyoptinterface-expert/SKILL.md ================================================ --- name: pyoptinterface-expert description: Specialized guidance for modeling mathematical optimization problems using the pyoptinterface_native library. Use when building optimization models (LP, QP, MIP, NLP), interfacing with solvers (Gurobi, HiGHS, Ipopt, etc.), and performing matrix-based or nonlinear modeling in Python. --- # PyOptInterface Expert Skill This skill provides expert guidance for using `pyoptinterface` (POI), a high-performance Python modeling interface for mathematical optimization solvers. ## Core Concepts POI follows a common modeling pattern: 1. Create a solver-specific model (e.g., `poi.highs.Model()`). 2. Add variables using `model.add_variable()` or `model.add_m_variables()`. 3. Set the objective using `model.set_objective()`. 4. Add constraints using `model.add_linear_constraint()`, `model.add_quadratic_constraint()`, or `model.add_nl_constraint()`. 5. Call `model.optimize()` and retrieve results. ## Key Workflows ### 1. Basic Modeling ```python import pyoptinterface as poi from pyoptinterface import highs model = highs.Model() x = model.add_variable(lb=0, name="x") y = model.add_variable(lb=0, name="y") model.set_objective(x + 2 * y, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x + y >= 10, name="c1") model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) if status == poi.TerminationStatusCode.OPTIMAL: print(f"x: {model.get_variable_attribute(x, poi.VariableAttribute.Value)}") ``` ### 2. Matrix-based Modeling Use `add_m_variables` and `add_m_linear_constraints` for performance with NumPy/SciPy. ```python import numpy as np x = model.add_m_variables((10,), lb=0) A = np.random.rand(5, 10) b = np.ones(5) model.add_m_linear_constraints(A, x, poi.Leq, b) ``` ### 3. Nonlinear Modeling Nonlinear expressions must be wrapped in `nl.graph()` context. ```python from pyoptinterface import nl with nl.graph(): model.add_nl_objective(x * x + nl.sin(y)) model.add_nl_constraint(x * y >= 5) ``` ## Reference Documentation - API Reference: [references/api.md](references/api.md) - Modeling Examples: [references/examples.md](references/examples.md) ## Best Practices - Use `poi.quicksum()` for large summations. - Check `TerminationStatus` before accessing variable values. - For large-scale models, prefer the Matrix API (`add_m_...`). ================================================ FILE: skills/pyoptinterface-expert/references/api.md ================================================ # API Reference ## Model Classes Imported from solver submodules: - `pyoptinterface.highs.Model` - `pyoptinterface.gurobi.Model` - `pyoptinterface.copt.Model` - `pyoptinterface.ipopt.Model` - `pyoptinterface.knitro.Model` - `pyoptinterface.mosek.Model` - `pyoptinterface.xpress.Model` ## Core Methods ### Model - `add_variable(lb, ub, domain, name, start)`: Add a single variable. - `add_m_variables(shape, lb, ub, domain, name, start)`: Add a NumPy array of variables. - `add_linear_constraint(expr, sense, rhs, name)`: Add a linear constraint. - `add_quadratic_constraint(expr, sense, rhs, name)`: Add a quadratic constraint. - `add_m_linear_constraints(A, x, sense, rhs)`: Add constraints in matrix form $Ax \sim b$. - `add_nl_constraint(expr)`: Add a nonlinear constraint (within `nl.graph()`). - `set_objective(expr, sense)`: Set objective function. - `set_nl_objective(expr, sense)`: Set nonlinear objective. - `optimize()`: Solve the model. - `get_model_attribute(attr)`: Get model information (Status, ObjVal, etc.). - `get_variable_attribute(v, attr)`: Get variable information (Value, ReducedCost, etc.). - `set_variable_attribute(v, attr, value)`: Set variable bounds, starts, etc. - `get_value(expr)`: Evaluate expression at the current solution. ## Enums ### `poi.VariableDomain` - `Continuous` - `Integer` - `Binary` ### `poi.ConstraintSense` - `Equal` (or `poi.Eq`) - `LessEqual` (or `poi.Leq`) - `GreaterEqual` (or `poi.Geq`) ### `poi.ObjectiveSense` - `Minimize` - `Maximize` - `Feasibility` ### `poi.TerminationStatusCode` - `OPTIMAL` - `INFEASIBLE` - `UNBOUNDED` - `TIME_LIMIT` - `ITERATION_LIMIT` ### `poi.ModelAttribute` - `TerminationStatus` - `ObjectiveValue` - `SolveTimeSec` - `RelativeGap` - `Silent` ### `poi.VariableAttribute` - `Value` - `Name` - `LowerBound` - `UpperBound` - `Domain` - `PrimalStart` ================================================ FILE: skills/pyoptinterface-expert/references/examples.md ================================================ # Modeling Examples ## 1. Knapsack Problem (MIP) Maximize total value given a weight limit. ```python import pyoptinterface as poi from pyoptinterface import highs values = [10, 15, 20, 8] weights = [2, 3, 5, 2] limit = 8 model = highs.Model() x = model.add_m_variables(len(values), domain=poi.VariableDomain.Binary) model.set_objective(poi.quicksum(values[i] * x[i] for i in range(len(values))), poi.ObjectiveSense.Maximize) model.add_linear_constraint(poi.quicksum(weights[i] * x[i] for i in range(len(weights))) <= limit) model.optimize() if model.get_model_attribute(poi.ModelAttribute.TerminationStatus) == poi.TerminationStatusCode.OPTIMAL: print("Selected items:", [i for i in range(len(values)) if model.get_variable_attribute(x[i], poi.VariableAttribute.Value) > 0.5]) ``` ## 2. Sparse Matrix Model (LP) Solve $Ax \le b$ using the matrix API. ```python import numpy as np from scipy import sparse import pyoptinterface as poi from pyoptinterface import highs m, n = 100, 500 A = sparse.random(m, n, density=0.1) b = np.random.rand(m) c = np.random.rand(n) model = highs.Model() x = model.add_m_variables(n, lb=0) model.set_objective(poi.quicksum(c[i] * x[i] for i in range(n))) model.add_m_linear_constraints(A, x, poi.Leq, b) model.optimize() ``` ## 3. Nonlinear Rosenbrock Function (NLP) Minimize $f(x, y) = (1-x)^2 + 100(y-x^2)^2$. ```python import pyoptinterface as poi from pyoptinterface import ipopt, nl model = ipopt.Model() x = model.add_variable(lb=-5, ub=5) y = model.add_variable(lb=-5, ub=5) with nl.graph(): model.add_nl_objective((1 - x)**2 + 100 * (y - x**2)**2, poi.ObjectiveSense.Minimize) model.optimize() print(f"Optimal x: {model.get_value(x)}, y: {model.get_value(y)}") ``` ================================================ FILE: src/pyoptinterface/__init__.py ================================================ from pyoptinterface._src.core_ext import ( VariableIndex, ConstraintIndex, ExprBuilder, VariableDomain, ConstraintSense, ConstraintType, SOSType, ObjectiveSense, ScalarAffineFunction, ScalarQuadraticFunction, ) from pyoptinterface._src.attributes import ( VariableAttribute, ModelAttribute, TerminationStatusCode, ResultStatusCode, ConstraintAttribute, ) from pyoptinterface._src.tupledict import ( tupledict, make_tupledict, ) from pyoptinterface._src.aml import quicksum, quicksum_ # Alias of ConstraintSense Eq = ConstraintSense.Equal """Alias of `ConstraintSense.Equal` for equality constraints. """ Leq = ConstraintSense.LessEqual """Alias of `ConstraintSense.LessEqual` for less-than-or-equal-to constraints. """ Geq = ConstraintSense.GreaterEqual """Alias of `ConstraintSense.GreaterEqual` for greater-than-or-equal-to constraints. """ from pyoptinterface._src.monkeypatch import _monkeypatch_all _monkeypatch_all() __all__ = [ "VariableIndex", "ConstraintIndex", "ExprBuilder", "VariableDomain", "ConstraintSense", "ConstraintType", "SOSType", "ObjectiveSense", "ScalarAffineFunction", "ScalarQuadraticFunction", "VariableAttribute", "ModelAttribute", "TerminationStatusCode", "ResultStatusCode", "ConstraintAttribute", "tupledict", "make_tupledict", "quicksum", "quicksum_", "Eq", "Leq", "Geq", ] ================================================ FILE: src/pyoptinterface/_src/__init__.py ================================================ ================================================ FILE: src/pyoptinterface/_src/aml.py ================================================ from .core_ext import ExprBuilder, VariableDomain from .tupledict import make_tupledict from collections.abc import Collection from typing import Tuple, Union, Optional def make_variable_ndarray( model, shape: Union[Tuple[int, ...], int], domain: Optional[VariableDomain] = None, lb: Optional[float] = None, ub: Optional[float] = None, name: Optional[str] = None, start: Optional[float] = None, ): import numpy as np if isinstance(shape, int): shape = (shape,) variables = np.empty(shape, dtype=object) kw_args = dict() if domain is not None: kw_args["domain"] = domain if lb is not None: kw_args["lb"] = lb if ub is not None: kw_args["ub"] = ub if start is not None: kw_args["start"] = start for index in np.ndindex(shape): if name is not None: suffix = str(index) kw_args["name"] = f"{name}{suffix}" variables[index] = model.add_variable(**kw_args) return variables def make_variable_tupledict( model, *coords: Collection, domain: Optional[VariableDomain] = None, lb: Optional[float] = None, ub: Optional[float] = None, name: Optional[str] = None, start: Optional[float] = None, ): kw_args = dict() if domain is not None: kw_args["domain"] = domain if lb is not None: kw_args["lb"] = lb if ub is not None: kw_args["ub"] = ub if start is not None: kw_args["start"] = start def f(*args): if name is not None: suffix = str(args) kw_args["name"] = f"{name}{suffix}" return model.add_variable(**kw_args) td = make_tupledict(*coords, rule=f) return td # def make_nd_variable_batch( # model, # *coords: Collection, # domain=None, # lb=None, # ub=None, # name=None, # ): # assert model.supports_batch_add_variables() # # kw_args = dict() # if domain is not None: # kw_args["domain"] = domain # if lb is not None: # kw_args["lb"] = lb # if ub is not None: # kw_args["ub"] = ub # # N = math.prod(len(c) for c in coords) # # start_vi = model.add_variables(N, **kw_args) # start_index = start_vi.index # # kvs = [] # assert len(coords) > 0 # for i, coord in enumerate(product(*coords)): # coord = tuple(flatten_tuple(coord)) # value = VariableIndex(start_index + i) # if len(coord) == 1: # coord = coord[0] # if value is not None: # kvs.append((coord, value)) # # suffix = str(coord) # if name is not None: # model.set_variable_name(value, f"{name}{suffix}") # return tupledict(kvs) def quicksum_(expr: ExprBuilder, terms, f=None): import numpy as np if isinstance(terms, dict): iter = terms.values() elif isinstance(terms, np.ndarray): iter = terms.flat else: iter = terms if f: iter = map(f, iter) for v in iter: expr += v def quicksum(terms, f=None): expr = ExprBuilder() quicksum_(expr, terms, f) return expr ================================================ FILE: src/pyoptinterface/_src/attributes.py ================================================ from enum import Enum, auto from .core_ext import VariableDomain, ObjectiveSense class VariableAttribute(Enum): Value = auto() LowerBound = auto() UpperBound = auto() Domain = auto() PrimalStart = auto() Name = auto() IISLowerBound = auto() IISUpperBound = auto() ReducedCost = auto() var_attr_type_map = { VariableAttribute.Value: float, VariableAttribute.LowerBound: float, VariableAttribute.UpperBound: float, VariableAttribute.PrimalStart: float, VariableAttribute.Domain: VariableDomain, VariableAttribute.Name: str, VariableAttribute.IISLowerBound: bool, VariableAttribute.IISUpperBound: bool, VariableAttribute.ReducedCost: float, } class ModelAttribute(Enum): # ModelLike API # NumberOfConstraints = auto() # NumberOfVariables = auto() Name = auto() ObjectiveSense = auto() # AbstractOptimizer API DualStatus = auto() PrimalStatus = auto() RawStatusString = auto() TerminationStatus = auto() BarrierIterations = auto() DualObjectiveValue = auto() NodeCount = auto() NumberOfThreads = auto() ObjectiveBound = auto() ObjectiveValue = auto() RelativeGap = auto() Silent = auto() SimplexIterations = auto() SolverName = auto() SolverVersion = auto() SolveTimeSec = auto() TimeLimitSec = auto() # ObjectiveLimit = auto() class ResultStatusCode(Enum): NO_SOLUTION = auto() FEASIBLE_POINT = auto() NEARLY_FEASIBLE_POINT = auto() INFEASIBLE_POINT = auto() INFEASIBILITY_CERTIFICATE = auto() NEARLY_INFEASIBILITY_CERTIFICATE = auto() REDUCTION_CERTIFICATE = auto() NEARLY_REDUCTION_CERTIFICATE = auto() UNKNOWN_RESULT_STATUS = auto() OTHER_RESULT_STATUS = auto() class TerminationStatusCode(Enum): OPTIMIZE_NOT_CALLED = auto() OPTIMAL = auto() INFEASIBLE = auto() DUAL_INFEASIBLE = auto() LOCALLY_SOLVED = auto() LOCALLY_INFEASIBLE = auto() INFEASIBLE_OR_UNBOUNDED = auto() ALMOST_OPTIMAL = auto() ALMOST_INFEASIBLE = auto() ALMOST_DUAL_INFEASIBLE = auto() ALMOST_LOCALLY_SOLVED = auto() ITERATION_LIMIT = auto() TIME_LIMIT = auto() NODE_LIMIT = auto() SOLUTION_LIMIT = auto() MEMORY_LIMIT = auto() OBJECTIVE_LIMIT = auto() NORM_LIMIT = auto() OTHER_LIMIT = auto() SLOW_PROGRESS = auto() NUMERICAL_ERROR = auto() INVALID_MODEL = auto() INVALID_OPTION = auto() INTERRUPTED = auto() OTHER_ERROR = auto() model_attr_type_map = { ModelAttribute.Name: str, ModelAttribute.ObjectiveSense: ObjectiveSense, ModelAttribute.DualStatus: ResultStatusCode, ModelAttribute.PrimalStatus: ResultStatusCode, ModelAttribute.RawStatusString: str, ModelAttribute.TerminationStatus: TerminationStatusCode, ModelAttribute.BarrierIterations: int, ModelAttribute.DualObjectiveValue: float, ModelAttribute.NodeCount: int, ModelAttribute.NumberOfThreads: int, ModelAttribute.ObjectiveBound: float, ModelAttribute.ObjectiveValue: float, ModelAttribute.RelativeGap: float, ModelAttribute.Silent: bool, ModelAttribute.SimplexIterations: int, ModelAttribute.SolverName: str, ModelAttribute.SolverVersion: str, ModelAttribute.SolveTimeSec: float, ModelAttribute.TimeLimitSec: float, } class ConstraintAttribute(Enum): Name = auto() # PrimalStart = auto() # DualStart = auto() Primal = auto() Dual = auto() # BasisStatus = auto() IIS = auto() constraint_attr_type_map = { ConstraintAttribute.Name: str, ConstraintAttribute.Primal: float, ConstraintAttribute.Dual: float, ConstraintAttribute.IIS: bool, } ================================================ FILE: src/pyoptinterface/_src/codegen_c.py ================================================ from .cppad_interface_ext import ( graph_op, ) from .cpp_graph_iter import cpp_graph_iterator from typing import IO op2name = { graph_op.abs: "fabs", graph_op.acos: "acos", graph_op.asin: "asin", graph_op.atan: "atan", graph_op.cos: "cos", graph_op.exp: "exp", graph_op.log: "log", graph_op.pow: "pow", graph_op.sign: "sign", graph_op.sin: "sin", graph_op.sqrt: "sqrt", graph_op.tan: "tan", graph_op.add: "+", graph_op.sub: "-", graph_op.mul: "*", graph_op.div: "/", graph_op.azmul: "*", graph_op.neg: "-", } compare_ops = set([graph_op.cexp_eq, graph_op.cexp_le, graph_op.cexp_lt]) compare_ops_string = { graph_op.cexp_eq: "==", graph_op.cexp_le: "<=", graph_op.cexp_lt: "<", } def generate_csrc_prelude(io: IO[str]): io.write("""// includes #include // typedefs typedef double float_point_t; // declare mathematical functions #define UNARY(f) extern float_point_t f(float_point_t x) #define BINARY(f) extern float_point_t f(float_point_t x, float_point_t y) // unary functions UNARY(fabs); UNARY(acos); UNARY(asin); UNARY(atan); UNARY(cos); UNARY(exp); UNARY(log); UNARY(sin); UNARY(sqrt); UNARY(tan); // binary functions BINARY(pow); // externals // azmul float_point_t azmul(float_point_t x, float_point_t y) { if( x == 0.0 ) return 0.0; return x * y; } // sign float_point_t sign(float_point_t x) { if( x > 0.0 ) return 1.0; if( x == 0.0 ) return 0.0; return -1.0; } """) def generate_csrc_from_graph( io: IO[str], graph_obj, name: str, np: int = 0, hessian_lagrange: bool = False, nw: int = 0, indirect_x: bool = False, indirect_p: bool = False, indirect_w: bool = False, indirect_y: bool = False, add_y: bool = False, ): n_dynamic_ind = graph_obj.n_dynamic_ind n_variable_ind = graph_obj.n_variable_ind n_constant = graph_obj.n_constant n_dependent = graph_obj.n_dependent # Simple case # 0 -> dummy # [1, 1 + np) -> *p # [1 + np, 1 + n_dynamic_ind + n_variable_ind) -> *x # [1 + n_dynamic_ind + n_variable_ind, 1 + n_dynamic_ind + n_variable_ind + n_constant) -> c[...] # [1 + n_dynamic_ind + n_variable_ind + n_constant, ...) -> v[...] # Hessian lagragian case # 0 -> dummy # [1, 1 + np) -> *p # [1 + np, 1 + np + nw) -> *w # [1 + np + nw, 1 + n_dynamic_ind + n_variable_ind) -> *x # [1 + n_dynamic_ind + n_variable_ind, 1 + n_dynamic_ind + n_variable_ind + n_constant) -> c[...] # [1 + n_dynamic_ind + n_variable_ind + n_constant, ...) -> v[...] n_node = 0 for graph_iter in cpp_graph_iterator(graph_obj): n_node += graph_iter.n_result has_parameter = np > 0 function_args_signature = ["const float_point_t* x"] if has_parameter: function_args_signature.append("const float_point_t* p") if hessian_lagrange: function_args_signature.append("const float_point_t* w") function_args_signature.append("float_point_t* y") if indirect_x: function_args_signature.append("const int* xi") if has_parameter and indirect_p: function_args_signature.append("const int* pi") if hessian_lagrange and indirect_w: function_args_signature.append("const int* wi") if indirect_y: function_args_signature.append("const int* yi") function_args = ", ".join(function_args_signature) function_prototype = f""" void {name}( {function_args} ) """ io.write(function_prototype) if not hessian_lagrange: nx = n_dynamic_ind + n_variable_ind - np else: nx = n_dynamic_ind + n_variable_ind - np - nw ny = n_dependent io.write(f"""{{ // begin function body // size checks // const size_t nx = {nx}; // const size_t np = {np}; // const size_t ny = {ny}; """) if hessian_lagrange: io.write(f" // const size_t nw = {nw};\n") io.write(f""" // declare variables float_point_t v[{n_node}]; """) nc = n_constant if nc > 0: cs = (graph_obj.constant_vec_get(i) for i in range(nc)) cs_str = ", ".join(f"{c}" for c in cs) io.write(f""" // constants // set c[i] for i = 0, ..., nc-1 // nc = {nc} static const float_point_t c[{nc}] = {{ {cs_str} }}; """) n_result_node = n_node io.write(f""" // result nodes // set v[i] for i = 0, ..., n_result_node-1 // n_result_node = {n_result_node} """) def get_node_name(node: int) -> str: if node < 1: raise ValueError(f"Invalid node: {node}") if node < 1 + np: index = node - 1 if indirect_p: return f"p[pi[{index}]]" else: return f"p[{index}]" elif node < 1 + n_dynamic_ind + n_variable_ind: if hessian_lagrange: if node < 1 + np + nw: index = node - 1 - np if indirect_w: return f"w[wi[{index}]]" else: return f"w[{index}]" else: index = node - 1 - np - nw if indirect_x: return f"x[xi[{index}]]" else: return f"x[{index}]" else: index = node - 1 - np if indirect_x: return f"x[xi[{index}]]" else: return f"x[{index}]" elif node < 1 + n_dynamic_ind + n_variable_ind + n_constant: index = node - 1 - n_dynamic_ind - n_variable_ind return f"c[{index}]" else: node = node - 1 - n_dynamic_ind - n_variable_ind - n_constant assert node < n_node return f"v[{node}]" result_node = 0 infix_operators = set(["+", "-", "*", "/"]) for iter in cpp_graph_iterator(graph_obj): op = iter.op n_result = iter.n_result args = iter.args assert n_result == 1 n_arg = len(args) op_name = op2name.get(op, None) if op_name is not None: if n_arg == 1: arg1 = get_node_name(args[0]) io.write(f" v[{result_node}] = {op_name}({arg1});\n") elif n_arg == 2: arg1 = get_node_name(args[0]) arg2 = get_node_name(args[1]) if op_name in infix_operators: io.write(f" v[{result_node}] = {arg1} {op_name} {arg2};\n") else: io.write(f" v[{result_node}] = {op_name}({arg1}, {arg2});\n") else: message = f"Unknown n_arg: {n_arg} for op_enum: {op}\n" raise ValueError(message) elif op in compare_ops: cmp_op = compare_ops_string[op] assert n_arg == 4 predicate = get_node_name(args[0]) target = get_node_name(args[1]) then_value = get_node_name(args[2]) else_value = get_node_name(args[3]) io.write( f" v[{result_node}] = {predicate} {cmp_op} {target} ? {then_value} : {else_value};\n" ) else: message = f"Unknown name for op_enum: {op}\n" debug_context = f"name: {name}\nfull graph:\n{str(graph_obj)}" raise ValueError(message + debug_context) result_node += n_result io.write(""" // dependent variables // set y[i] for i = 0, ny-1 """) op = "=" if add_y: op = "+=" for i in range(ny): node = graph_obj.dependent_vec_get(i) node_name = get_node_name(node) if indirect_y: assignment = f" y[yi[{i}]] {op} {node_name};\n" else: assignment = f" y[{i}] {op} {node_name};\n" io.write(assignment) io.write(""" // end function body } """) ================================================ FILE: src/pyoptinterface/_src/codegen_llvm.py ================================================ from .cppad_interface_ext import ( graph_op, ) from .cpp_graph_iter import cpp_graph_iterator from llvmlite import ir D = ir.DoubleType() D_PTR = ir.PointerType(D) I = ir.IntType(32) I_PTR = ir.PointerType(I) def create_azmul(module: ir.Module): # Define and create the azmul function azmul_func_type = ir.FunctionType(D, [D, D]) azmul_func = ir.Function(module, azmul_func_type, name="azmul") azmul_func.attributes.add("alwaysinline") x, y = azmul_func.args x.name = "x" y.name = "y" block = azmul_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the azmul logic with builder.if_else(builder.fcmp_unordered("==", x, D(0.0))) as ( then, otherwise, ): with then: builder.ret(D(0.0)) with otherwise: result = builder.fmul(x, y) builder.ret(result) builder.unreachable() def create_sign(module: ir.Module): # Define and create the sign function sign_func_type = ir.FunctionType(D, [D]) sign_func = ir.Function(module, sign_func_type, name="sign") sign_func.attributes.add("alwaysinline") x = sign_func.args[0] x.name = "x" block = sign_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the sign logic with builder.if_else(builder.fcmp_ordered(">", x, D(0.0))) as ( then_positive, otherwise, ): with then_positive: builder.ret(D(1.0)) with otherwise: with builder.if_else(builder.fcmp_ordered("==", x, D(0.0))) as ( then_zero, otherwise_negative, ): with then_zero: builder.ret(D(0.0)) with otherwise_negative: builder.ret(D(-1.0)) builder.unreachable() def create_direct_load_store(module: ir.Module): # Define and create the load_direct function # D load_directly(D_PTR, I) # returns ptr[idx_ptr[idx]] load_direct_func_type = ir.FunctionType(D, [D_PTR, I]) load_direct_func = ir.Function(module, load_direct_func_type, name="load_direct") load_direct_func.attributes.add("alwaysinline") ptr, idx = load_direct_func.args ptr.name = "ptr" idx.name = "idx" block = load_direct_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the load_direct logic ptr = builder.gep(ptr, [idx], name="gep_ptr") val = builder.load(ptr, name="val") builder.ret(val) # Define and create the store_direct function # void store_directly(D_PTR, I, D) # ptr[idx] = val store_direct_func_type = ir.FunctionType(ir.VoidType(), [D_PTR, I, D]) store_direct_func = ir.Function(module, store_direct_func_type, name="store_direct") store_direct_func.attributes.add("alwaysinline") ptr, idx, val = store_direct_func.args ptr.name = "ptr" idx.name = "idx" val.name = "val" block = store_direct_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the store_direct logic ptr = builder.gep(ptr, [idx], name="gep_ptr") builder.store(val, ptr) builder.ret_void() # Define and create the add_store_direct function # void add_store_directly(D_PTR, I, D) # ptr[idx] += val add_store_direct_func_type = ir.FunctionType(ir.VoidType(), [D_PTR, I, D]) add_store_direct_func = ir.Function( module, add_store_direct_func_type, name="add_store_direct" ) add_store_direct_func.attributes.add("alwaysinline") ptr, idx, val = add_store_direct_func.args ptr.name = "ptr" idx.name = "idx" val.name = "val" block = add_store_direct_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the add_store_direct logic ptr = builder.gep(ptr, [idx], name="gep_ptr") old_val = builder.load(ptr, name="old_val") new_val = builder.fadd(old_val, val, name="new_val") builder.store(new_val, ptr) builder.ret_void() def create_indirect_load_store(module: ir.Module): # Define and create the load_indirect function # D load_indirectly(D_PTR, I_PTR, I) # returns ptr[idx_ptr[idx]] load_indirect_func_type = ir.FunctionType(D, [D_PTR, I_PTR, I]) load_indirect_func = ir.Function( module, load_indirect_func_type, name="load_indirect" ) load_indirect_func.attributes.add("alwaysinline") ptr, idx_ptr, idx = load_indirect_func.args ptr.name = "ptr" idx_ptr.name = "idx_ptr" idx.name = "idx" block = load_indirect_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the load_indirect logic idx = builder.gep(idx_ptr, [idx], name="gep_idx") idx = builder.load(idx, name="real_idx") ptr = builder.gep(ptr, [idx], name="gep_ptr") val = builder.load(ptr, name="val") builder.ret(val) # Define and create the store_indirect function # void store_indirectly(D_PTR, I_PTR, I, D) # ptr[idx_ptr[idx]] = val store_indirect_func_type = ir.FunctionType(ir.VoidType(), [D_PTR, I_PTR, I, D]) store_indirect_func = ir.Function( module, store_indirect_func_type, name="store_indirect" ) store_indirect_func.attributes.add("alwaysinline") ptr, idx_ptr, idx, val = store_indirect_func.args ptr.name = "ptr" idx_ptr.name = "idx_ptr" idx.name = "idx" val.name = "val" block = store_indirect_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the store_indirect logic idx = builder.gep(idx_ptr, [idx], name="gep_idx") idx = builder.load(idx, name="real_idx") ptr = builder.gep(ptr, [idx], name="gep_ptr") builder.store(val, ptr) builder.ret_void() # Define and create the add_store_indirect function # void add_store_indirectly(D_PTR, I_PTR, I, D) # ptr[idx_ptr[idx]] += val add_store_indirect_func_type = ir.FunctionType(ir.VoidType(), [D_PTR, I_PTR, I, D]) add_store_indirect_func = ir.Function( module, add_store_indirect_func_type, name="add_store_indirect" ) add_store_indirect_func.attributes.add("alwaysinline") ptr, idx_ptr, idx, val = add_store_indirect_func.args ptr.name = "ptr" idx_ptr.name = "idx_ptr" idx.name = "idx" val.name = "val" block = add_store_indirect_func.append_basic_block(name="entry") builder = ir.IRBuilder(block) # Implement the add_store_indirect logic idx = builder.gep(idx_ptr, [idx], name="gep_idx") idx = builder.load(idx, name="real_idx") ptr = builder.gep(ptr, [idx], name="gep_ptr") old_val = builder.load(ptr, name="old_val") new_val = builder.fadd(old_val, val, name="new_val") builder.store(new_val, ptr) builder.ret_void() op2name = { graph_op.abs: "fabs", graph_op.acos: "acos", graph_op.asin: "asin", graph_op.atan: "atan", graph_op.cos: "cos", graph_op.exp: "exp", graph_op.log: "log", graph_op.pow: "pow", graph_op.sin: "sin", graph_op.sqrt: "sqrt", graph_op.tan: "tan", } math_ops = set(op2name.keys()) binary_ops = set([graph_op.pow]) compare_ops = set([graph_op.cexp_eq, graph_op.cexp_le, graph_op.cexp_lt]) compare_ops_string = { graph_op.cexp_eq: "==", graph_op.cexp_le: "<=", graph_op.cexp_lt: "<", } def create_llvmir_basic_functions(module: ir.Module): create_azmul(module) create_sign(module) create_direct_load_store(module) create_indirect_load_store(module) for op, op_name in op2name.items(): if op in binary_ops: func_type = ir.FunctionType(D, [D, D]) else: func_type = ir.FunctionType(D, [D]) ir.Function(module, func_type, name=op_name) # sin = module.declare_intrinsic('llvm.sin', [D]) # cos = module.declare_intrinsic('llvm.cos', [D]) # Define graph to LLVM IR translation function def generate_llvmir_from_graph( module: ir.Module, graph_obj, name: str, np: int = 0, hessian_lagrange: bool = False, nw: int = 0, indirect_x: bool = False, indirect_p: bool = False, indirect_w: bool = False, indirect_y: bool = False, add_y: bool = False, ): n_dynamic_ind = graph_obj.n_dynamic_ind n_variable_ind = graph_obj.n_variable_ind n_constant = graph_obj.n_constant n_dependent = graph_obj.n_dependent n_node = 0 for graph_iter in cpp_graph_iterator(graph_obj): n_node += graph_iter.n_result has_parameter = np > 0 # Define function signature func_args = [D_PTR] arg_names = ["x"] if has_parameter: func_args.append(D_PTR) arg_names.append("p") if hessian_lagrange: func_args.append(D_PTR) arg_names.append("w") func_args.append(D_PTR) arg_names.append("y") if indirect_x: func_args.append(I_PTR) arg_names.append("xi") if has_parameter and indirect_p: func_args.append(I_PTR) arg_names.append("pi") if hessian_lagrange and indirect_w: func_args.append(I_PTR) arg_names.append("wi") if indirect_y: func_args.append(I_PTR) arg_names.append("yi") func_type = ir.FunctionType(ir.VoidType(), func_args) func = ir.Function(module, func_type, name=name) # set argument names args_dict = {} args = func.args for i, arg in enumerate(args): arg.name = arg_names[i] args_dict[arg.name] = arg # Destructure the args x = args_dict.get("x", None) p = args_dict.get("p", None) w = args_dict.get("w", None) y = args_dict.get("y", None) xi = args_dict.get("xi", None) pi = args_dict.get("pi", None) wi = args_dict.get("wi", None) yi = args_dict.get("yi", None) # add noalias attribute for arg in [x, p, w, y, xi, pi, wi, yi]: if arg is not None: arg.add_attribute("noalias") # Define entry block block = func.append_basic_block(name="entry") builder = ir.IRBuilder(block) comment = "" if not hessian_lagrange: nx = n_dynamic_ind + n_variable_ind - np comment += f"nx = {nx}, np = {np}, " else: nx = n_dynamic_ind + n_variable_ind - np - nw comment += f"nx = {nx}, np = {np}, nw = {nw}, " ny = n_dependent comment += f"ny = {ny}" builder.comment(comment) # Constants and initial setup (if any) nc = n_constant constants = [graph_obj.constant_vec_get(i) for i in range(nc)] c_array = ir.Constant(ir.ArrayType(D, len(constants)), constants) c_global = ir.GlobalVariable(module, c_array.type, name=f"{name}_constants") c_global.linkage = "internal" c_global.initializer = c_array c_global_ptr = builder.bitcast(c_global, D_PTR) # sin = module.get_global("llvm.sin.f64") # cos = module.get_global("llvm.cos.f64") math_functions = dict() for op_name in op2name.values(): op_function = module.get_global(op_name) if op_function is None: raise ValueError(f"Math function {op_name} not found in module") math_functions[op_name] = op_function azmul = module.get_global("azmul") sign = module.get_global("sign") load_direct = module.get_global("load_direct") store_direct = module.get_global("store_direct") add_store_direct = module.get_global("add_store_direct") load_indirect = module.get_global("load_indirect") store_indirect = module.get_global("store_indirect") add_store_indirect = module.get_global("add_store_indirect") result_node = 0 p_dict = {} w_dict = {} x_dict = {} c_dict = {} v_dict = {} def get_node_value(node: int): if node < 1: raise ValueError(f"Invalid node: {node}") if node < 1 + np: p_index = node - 1 val = p_dict.get(p_index, None) if val is None: if indirect_p: val = builder.call(load_indirect, [p, pi, I(p_index)]) else: val = builder.call(load_direct, [p, I(p_index)]) val.name = f"p[{p_index}]" p_dict[p_index] = val elif node < 1 + n_dynamic_ind + n_variable_ind: if hessian_lagrange: if node < 1 + np + nw: w_index = node - 1 - np val = w_dict.get(w_index, None) if val is None: if indirect_w: val = builder.call(load_indirect, [w, wi, I(w_index)]) else: val = builder.call(load_direct, [w, I(w_index)]) val.name = f"w[{w_index}]" w_dict[w_index] = val else: x_index = node - 1 - np - nw val = x_dict.get(x_index, None) if val is None: if indirect_x: val = builder.call(load_indirect, [x, xi, I(x_index)]) else: val = builder.call(load_direct, [x, I(x_index)]) val.name = f"x[{x_index}]" x_dict[x_index] = val else: x_index = node - 1 - np val = x_dict.get(x_index, None) if val is None: if indirect_x: val = builder.call(load_indirect, [x, xi, I(x_index)]) else: val = builder.call(load_direct, [x, I(x_index)]) val.name = f"x[{x_index}]" x_dict[x_index] = val elif node < 1 + n_dynamic_ind + n_variable_ind + n_constant: c_index = node - 1 - n_dynamic_ind - n_variable_ind val = c_dict.get(c_index, None) if val is None: val = builder.call(load_direct, [c_global_ptr, I(c_index)]) val.name = f"c[{c_index}]" c_dict[c_index] = val else: v_index = node - 1 - n_dynamic_ind - n_variable_ind - n_constant assert v_index < n_node v_val = v_dict.get(v_index, None) if v_val is None: raise ValueError(f"Node value not found: {v_index}") val = v_val return val arithmetic_flags = ("fast",) for iter in cpp_graph_iterator(graph_obj): op = iter.op n_result = iter.n_result args = iter.args assert n_result == 1 arg1 = get_node_value(args[0]) if len(args) >= 2: arg2 = get_node_value(args[1]) if op == graph_op.add: ret_val = builder.fadd(arg1, arg2, flags=arithmetic_flags) elif op == graph_op.sub: ret_val = builder.fsub(arg1, arg2, flags=arithmetic_flags) elif op == graph_op.mul: ret_val = builder.fmul(arg1, arg2, flags=arithmetic_flags) elif op == graph_op.div: ret_val = builder.fdiv(arg1, arg2, flags=arithmetic_flags) elif op == graph_op.azmul: ret_val = builder.fmul(arg1, arg2, flags=arithmetic_flags) # ret_val = builder.call(azmul, [arg1, arg2]) elif op == graph_op.neg: ret_val = builder.fneg(arg1, flags=arithmetic_flags) elif op == graph_op.sign: ret_val = builder.call(sign, [arg1]) elif op in math_ops: op_name = op2name[op] op_function = math_functions[op_name] if op in binary_ops: ret_val = builder.call(op_function, [arg1, arg2]) else: ret_val = builder.call(op_function, [arg1]) elif op in compare_ops: assert len(args) == 4 predicate = arg1 target = arg2 then_value = get_node_value(args[2]) else_value = get_node_value(args[3]) cmp_string = compare_ops_string[op] cond = builder.fcmp_ordered(cmp_string, predicate, target) ret_val = builder.select(cond, then_value, else_value) else: raise ValueError(f"Unknown CppAD graph op_enum: {op}") ret_val.name = f"v[{result_node}]" v_dict[result_node] = ret_val result_node += n_result store_f_dict = { (True, True): add_store_indirect, (True, False): store_indirect, (False, True): add_store_direct, (False, False): store_direct, } store_f = store_f_dict[(indirect_y, add_y)] for i in range(ny): node = graph_obj.dependent_vec_get(i) val = get_node_value(node) builder.comment(f"write y[{i}]") if indirect_y: builder.call(store_f, [y, yi, I(i), val]) else: builder.call(store_f, [y, I(i), val]) # Return from the function builder.ret_void() ================================================ FILE: src/pyoptinterface/_src/comparison_constraint.py ================================================ from dataclasses import dataclass from typing import Any from .core_ext import ConstraintSense @dataclass class ComparisonConstraint: sense: ConstraintSense lhs: Any rhs: Any ================================================ FILE: src/pyoptinterface/_src/constraint_bridge.py ================================================ from .core_ext import ScalarQuadraticFunction, ConstraintSense def bridge_soc_quadratic_constraint(model, cone_variables, name="", rotated=False): """ Convert a second order cone constraint to a quadratic constraint. x[0] >= sqrt(x[1]^2 + ... + x[n]^2) to x[0]^2 - x[1]^2 - ... - x[n]^2 >= 0 or convert a rotated second order cone constraint to a quadratic constraint. 2 * x[0] * x[1] >= x[2]^2 + ... + x[n]^2 to 2 * x[0] * x[1] - x[2]^2 - ... - x[n]^2 >= 0 """ N = len(cone_variables) if N < 2: raise ValueError( "Second order cone constraint must have at least two variables" ) expr = ScalarQuadraticFunction() expr.reserve_quadratic(N) if not rotated: x0 = cone_variables[0] expr.add_quadratic_term(x0, x0, 1.0) for i in range(1, N): xi = cone_variables[i] expr.add_quadratic_term(xi, xi, -1.0) con = model.add_quadratic_constraint( expr, ConstraintSense.GreaterEqual, 0.0, name ) else: x0 = cone_variables[0] x1 = cone_variables[1] expr.add_quadratic_term(x0, x1, 2.0) for i in range(2, N): xi = cone_variables[i] expr.add_quadratic_term(xi, xi, -1.0) con = model.add_quadratic_constraint( expr, ConstraintSense.GreaterEqual, 0.0, name ) return con ================================================ FILE: src/pyoptinterface/_src/copt.py ================================================ import os import platform from pathlib import Path import logging from typing import Dict, Tuple, Union, overload from .copt_model_ext import RawModel, Env, COPT, load_library from .attributes import ( VariableAttribute, ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, ConstraintType, ConstraintSense, ObjectiveSense, ) from .nlexpr_ext import ExpressionHandle from .nlfunc import ExpressionGraphContext, convert_to_expressionhandle from .comparison_constraint import ComparisonConstraint from .solver_common import ( _direct_get_model_attribute, _direct_set_model_attribute, _direct_get_entity_attribute, _direct_set_entity_attribute, ) from .aml import make_variable_tupledict, make_variable_ndarray from .matrix import add_matrix_constraints def detected_libraries(): libs = [] subdir = { "Linux": "lib", "Darwin": "lib", "Windows": "bin", }[platform.system()] libname = { "Linux": "libcopt.so", "Darwin": "libcopt.dylib", "Windows": "copt.dll", }[platform.system()] # Environment home = os.environ.get("COPT_HOME", None) if home and os.path.exists(home): lib = Path(home) / subdir / libname if lib.exists(): libs.append(str(lib)) # default names default_libname = libname libs.append(default_libname) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded COPT library: {lib}") return True return False autoload_library() DEFAULT_ENV = None def init_default_env(): global DEFAULT_ENV if DEFAULT_ENV is None: DEFAULT_ENV = Env() variable_attribute_get_func_map = { VariableAttribute.Value: lambda model, v: model.get_variable_info(v, "Value"), VariableAttribute.LowerBound: lambda model, v: model.get_variable_info(v, "LB"), VariableAttribute.UpperBound: lambda model, v: model.get_variable_info(v, "UB"), VariableAttribute.PrimalStart: lambda model, v: model.variable_start_values.get( v, None ), VariableAttribute.Domain: lambda model, v: model.get_variable_type(v), VariableAttribute.Name: lambda model, v: model.get_variable_name(v), VariableAttribute.IISLowerBound: lambda model, v: model._get_variable_lowerbound_IIS( v ) > 0, VariableAttribute.IISUpperBound: lambda model, v: model._get_variable_upperbound_IIS( v ) > 0, VariableAttribute.ReducedCost: lambda model, v: model.get_variable_info( v, "RedCost" ), } def set_variable_start(model, v, val): model.variable_start_values[v] = val variable_attribute_set_func_map = { VariableAttribute.LowerBound: lambda model, v, val: model.set_variable_lower_bound( v, val ), VariableAttribute.UpperBound: lambda model, v, val: model.set_variable_upper_bound( v, val ), VariableAttribute.PrimalStart: set_variable_start, VariableAttribute.Domain: lambda model, v, val: model.set_variable_type(v, val), VariableAttribute.Name: lambda model, v, val: model.set_variable_name(v, val), } constraint_type_attribute_name_map = { ConstraintType.Linear: "Rows", ConstraintType.Quadratic: "QConstrs", } copt_parameter_raw_type_map = { 0: float, 1: int, } copt_attribute_raw_type_map = { 2: float, 3: int, } def get_objsense(model): raw_sense = model.get_raw_attribute_int("ObjSense") if raw_sense == COPT.MINIMIZE: return ObjectiveSense.Minimize elif raw_sense == COPT.MAXIMIZE: return ObjectiveSense.Maximize else: raise ValueError(f"Unknown objective sense: {raw_sense}") def get_objval(model): if model._is_mip(): attr_name = "BestObj" else: attr_name = "LpObjval" obj = model.get_raw_attribute_double(attr_name) return obj def get_primalstatus(model): if model._is_mip(): attr_name = "HasMipSol" else: attr_name = "HasLpSol" has_sol = model.get_raw_attribute_int(attr_name) if has_sol != 0: return ResultStatusCode.FEASIBLE_POINT else: return ResultStatusCode.NO_SOLUTION def get_dualstatus(model): if not model._is_mip(): has_sol = model.get_raw_attribute_int("HasLpSol") if has_sol != 0: return ResultStatusCode.FEASIBLE_POINT return ResultStatusCode.NO_SOLUTION # LP status codes. Descriptions taken from COPT user guide. # Code : (TerminationStatus, RawStatusString) _RAW_LPSTATUS_STRINGS = { COPT.UNSTARTED: ( TerminationStatusCode.OPTIMIZE_NOT_CALLED, "The LP optimization is not started yet.", ), COPT.OPTIMAL: ( TerminationStatusCode.OPTIMAL, "The LP problem is solved to optimality.", ), COPT.INFEASIBLE: ( TerminationStatusCode.INFEASIBLE, "The LP problem is infeasible.", ), COPT.UNBOUNDED: ( TerminationStatusCode.DUAL_INFEASIBLE, "The LP problem is unbounded.", ), COPT.NUMERICAL: ( TerminationStatusCode.NUMERICAL_ERROR, "Numerical trouble encountered.", ), COPT.IMPRECISE: ( TerminationStatusCode.ALMOST_OPTIMAL, "The LP problem is solved to optimality with relaxed tolerances.", ), COPT.TIMEOUT: ( TerminationStatusCode.TIME_LIMIT, "The LP optimization is stopped because of time limit.", ), COPT.UNFINISHED: ( TerminationStatusCode.NUMERICAL_ERROR, "The LP optimization is stopped but the solver cannot provide a solution because of numerical difficulties.", ), COPT.INTERRUPTED: ( TerminationStatusCode.INTERRUPTED, "The LP optimization is stopped by user interrupt.", ), } # MIP status codes. Descriptions taken from COPT user guide. # Code : (TerminationStatus, RawStatusString) _RAW_MIPSTATUS_STRINGS = { COPT.UNSTARTED: ( TerminationStatusCode.OPTIMIZE_NOT_CALLED, "The MIP optimization is not started yet.", ), COPT.OPTIMAL: ( TerminationStatusCode.OPTIMAL, "The MIP problem is solved to optimality.", ), COPT.INFEASIBLE: ( TerminationStatusCode.INFEASIBLE, "The MIP problem is infeasible.", ), COPT.UNBOUNDED: ( TerminationStatusCode.DUAL_INFEASIBLE, "The MIP problem is unbounded.", ), COPT.INF_OR_UNB: ( TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED, "The MIP problem is infeasible or unbounded.", ), COPT.NODELIMIT: ( TerminationStatusCode.NODE_LIMIT, "The MIP optimization is stopped because of node limit.", ), COPT.TIMEOUT: ( TerminationStatusCode.TIME_LIMIT, "The MIP optimization is stopped because of time limit.", ), COPT.UNFINISHED: ( TerminationStatusCode.NUMERICAL_ERROR, "The MIP optimization is stopped but the solver cannot provide a solution because of numerical difficulties.", ), COPT.INTERRUPTED: ( TerminationStatusCode.INTERRUPTED, "The MIP optimization is stopped by user interrupt.", ), } def get_terminationstatus(model): if model._is_mip(): raw_status = model.get_raw_attribute_int("MipStatus") status_string_pair = _RAW_MIPSTATUS_STRINGS.get(raw_status, None) else: raw_status = model.get_raw_attribute_int("LpStatus") status_string_pair = _RAW_LPSTATUS_STRINGS.get(raw_status, None) if not status_string_pair: raise ValueError(f"Unknown termination status: {raw_status}") return status_string_pair[0] def get_rawstatusstring(model): if model._is_mip(): raw_status = model.get_raw_attribute_int("MipStatus") status_string_pair = _RAW_MIPSTATUS_STRINGS.get(raw_status, None) else: raw_status = model.get_raw_attribute_int("LpStatus") status_string_pair = _RAW_LPSTATUS_STRINGS.get(raw_status, None) if not status_string_pair: raise ValueError(f"Unknown termination status: {raw_status}") return status_string_pair[1] def get_silent(model): return model.get_raw_parameter_int("LogToConsole") == 0 model_attribute_get_func_map = { ModelAttribute.ObjectiveSense: get_objsense, ModelAttribute.BarrierIterations: lambda model: model.get_model_raw_attribute_int( "BarrierIter" ), ModelAttribute.DualObjectiveValue: get_objval, ModelAttribute.NodeCount: lambda model: model.get_model_raw_attribute_int( "NodeCnt" ), ModelAttribute.ObjectiveBound: get_objval, ModelAttribute.ObjectiveValue: get_objval, ModelAttribute.SimplexIterations: lambda model: model.get_model_raw_attribute_int( "SimplexIter" ), ModelAttribute.SolveTimeSec: lambda model: model.get_raw_attribute_double( "SolvingTime" ), ModelAttribute.NumberOfThreads: lambda model: model.get_raw_parameter_int( "Threads" ), ModelAttribute.RelativeGap: lambda model: model.get_raw_parameter_double("RelGap"), ModelAttribute.TimeLimitSec: lambda model: model.get_raw_parameter_double( "TimeLimit" ), ModelAttribute.DualStatus: get_dualstatus, ModelAttribute.PrimalStatus: get_primalstatus, ModelAttribute.RawStatusString: get_rawstatusstring, ModelAttribute.TerminationStatus: get_terminationstatus, ModelAttribute.Silent: get_silent, ModelAttribute.SolverName: lambda _: "COPT", ModelAttribute.SolverVersion: lambda model: model.version_string(), } def set_silent(model, value: bool): if value: model.set_raw_parameter_int("LogToConsole", 0) else: model.set_raw_parameter_int("LogToConsole", 1) model_attribute_set_func_map = { ModelAttribute.ObjectiveSense: lambda model, v: model.set_obj_sense(v), ModelAttribute.NumberOfThreads: lambda model, v: model.set_raw_parameter_int( "Threads", v ), ModelAttribute.RelativeGap: lambda model, v: model.set_raw_parameter_double( "RelGap", v ), ModelAttribute.TimeLimitSec: lambda model, v: model.set_raw_parameter_double( "TimeLimit", v ), ModelAttribute.Silent: set_silent, } constraint_attribute_get_func_map = { ConstraintAttribute.Name: lambda model, constraint: model.get_constraint_name( constraint ), ConstraintAttribute.Primal: lambda model, constraint: model.get_constraint_info( constraint, "Slack" ), ConstraintAttribute.Dual: lambda model, constraint: model.get_constraint_info( constraint, "Dual" ), ConstraintAttribute.IIS: lambda model, constraint: model._get_constraint_IIS( constraint ) > 0, } constraint_attribute_set_func_map = { ConstraintAttribute.Name: lambda model, constraint, value: model.set_constraint_name( constraint, value ), } callback_info_typemap = { "BestObj": float, "BestBnd": float, "HasIncumbent": int, "MipCandObj": float, "RelaxSolObj": float, "NodeStatus": int, } class Model(RawModel): def __init__(self, env=None): if env is None: init_default_env() env = DEFAULT_ENV super().__init__(env) # We must keep a reference to the environment to prevent it from being garbage collected self._env = env self.variable_start_values: Dict[VariableIndex, float] = dict() self.nl_start_values: Dict[VariableIndex, float] = dict() # override logging self.set_raw_parameter("LogToConsole", 0) self.set_logging(print) def add_variables(self, *args, **kwargs): return make_variable_tupledict(self, *args, **kwargs) @staticmethod def supports_variable_attribute(attribute: VariableAttribute, settable=False): if settable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_model_attribute(attribute: ModelAttribute, settable=False): if settable: return attribute in model_attribute_set_func_map else: return attribute in model_attribute_get_func_map @staticmethod def supports_constraint_attribute(attribute: ConstraintAttribute, settable=False): if settable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map def get_variable_attribute(self, variable, attribute: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") value = _direct_get_entity_attribute( self, variable, attribute, variable_attribute_get_func_map, e, ) return value def set_variable_attribute(self, variable, attribute: VariableAttribute, value): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _direct_set_entity_attribute( self, variable, attribute, value, variable_attribute_set_func_map, e, ) def number_of_constraints(self, type: ConstraintType): attr_name = constraint_type_attribute_name_map.get(type, None) if not attr_name: raise ValueError(f"Unknown constraint type: {type}") return self.get_raw_attribute_int(attr_name) def number_of_variables(self): return self.get_raw_attribute_int("Cols") def _is_mip(self): ismip = self.get_raw_attribute_int("IsMIP") return ismip > 0 def _has_nl(self): nlconstrs = self.get_raw_attribute_int("NLConstrs") hasnlobj = self.get_raw_attribute_int("HasNLObj") return nlconstrs > 0 or hasnlobj > 0 def get_model_attribute(self, attribute: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") value = _direct_get_model_attribute( self, attribute, model_attribute_get_func_map, e, ) return value def set_model_attribute(self, attribute: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _direct_set_model_attribute( self, attribute, value, model_attribute_set_func_map, e, ) def get_constraint_attribute(self, constraint, attribute: ConstraintAttribute): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") value = _direct_get_entity_attribute( self, constraint, attribute, constraint_attribute_get_func_map, e, ) return value def set_constraint_attribute( self, constraint, attribute: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _direct_set_entity_attribute( self, constraint, attribute, value, constraint_attribute_set_func_map, e, ) def get_raw_parameter(self, param_name: str): param_type = copt_parameter_raw_type_map[ self.raw_parameter_attribute_type(param_name) ] get_function_map = { int: self.get_raw_parameter_int, float: self.get_raw_parameter_double, } get_function = get_function_map[param_type] return get_function(param_name) def set_raw_parameter(self, param_name: str, value): param_type = copt_parameter_raw_type_map[ self.raw_parameter_attribute_type(param_name) ] set_function_map = { int: self.set_raw_parameter_int, float: self.set_raw_parameter_double, } set_function = set_function_map[param_type] set_function(param_name, value) def get_raw_attribute(self, param_name: str): param_type = copt_attribute_raw_type_map[ self.raw_parameter_attribute_type(param_name) ] get_function_map = { int: self.get_raw_attribute_int, float: self.get_raw_attribute_double, } get_function = get_function_map[param_type] return get_function(param_name) def optimize(self): is_mip = self._is_mip() is_nl = self._has_nl() if is_mip or is_nl: variable_start_values = self.variable_start_values if len(variable_start_values) != 0: variables = list(variable_start_values.keys()) values = list(variable_start_values.values()) variable_start_values.clear() if is_mip: self.add_mip_start(variables, values) if is_nl: self.add_nl_start(variables, values) super().optimize() def cb_get_info(self, what): cb_info_type = callback_info_typemap.get(what, None) if cb_info_type is None: raise ValueError(f"Unknown callback info type: {what}") if cb_info_type is int: return self.cb_get_info_int(what) elif cb_info_type is float: return self.cb_get_info_double(what) else: raise ValueError(f"Unknown callback info type: {what}") @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], interval: Tuple[float, float], name: str = "", ): ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_linear_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_quadratic_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_quadratic_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_quadratic_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_quadratic_constraint(arg, *args, **kwargs) @overload def add_nl_constraint( self, expr, sense: ConstraintSense, rhs: float, /, name: str = "", ): ... @overload def add_nl_constraint( self, expr, interval: Tuple[float, float], /, name: str = "", ): ... @overload def add_nl_constraint( self, con, /, name: str = "", ): ... def add_nl_constraint(self, expr, *args, **kwargs): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError( "Expression should be able to be converted to ExpressionHandle" ) con = self._add_single_nl_constraint(graph, expr, *args, **kwargs) return con def add_nl_objective(self, expr): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError( "Expression should be able to be converted to ExpressionHandle" ) self._add_single_nl_objective(graph, expr) add_variables = make_variable_tupledict add_m_variables = make_variable_ndarray add_m_linear_constraints = add_matrix_constraints ================================================ FILE: src/pyoptinterface/_src/cpp_graph_iter.py ================================================ from collections import namedtuple from .cppad_interface_ext import cpp_graph_cursor cpp_graph_instruction = namedtuple("cpp_graph_instruction", ["op", "args", "n_result"]) class cpp_graph_iterator: def __init__(self, graph): self.graph = graph self.init = False self.cursor = None self.N = graph.n_operator def __iter__(self): return self def __next__(self): if self.N == 0: raise StopIteration graph = self.graph if not self.init: self.init = True self.cursor = cpp_graph_cursor() else: if self.cursor.op_index >= self.N: raise StopIteration cursor = self.cursor op = graph.get_cursor_op(cursor) args = graph.get_cursor_args(cursor) n_result = 1 instruction = cpp_graph_instruction(op, args, n_result) graph.next_cursor(self.cursor) return instruction ================================================ FILE: src/pyoptinterface/_src/dylib.py ================================================ import platform def dylib_suffix(): system = platform.system() if system == "Linux": return "so" elif system == "Darwin": return "dylib" elif system == "Windows": return "dll" else: raise RuntimeError("Unknown platform: %s" % system) ================================================ FILE: src/pyoptinterface/_src/gurobi.py ================================================ import os import platform from pathlib import Path import re import sys import logging from typing import Tuple, Union, overload from .gurobi_model_ext import RawModel, RawEnv, GRB, load_library from .attributes import ( VariableAttribute, ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, VariableDomain, ConstraintType, ConstraintSense, ObjectiveSense, ) from .nlexpr_ext import ExpressionHandle from .nlfunc import ExpressionGraphContext, convert_to_expressionhandle from .comparison_constraint import ComparisonConstraint from .solver_common import ( _get_model_attribute, _set_model_attribute, _get_entity_attribute, _direct_get_entity_attribute, _set_entity_attribute, _direct_set_entity_attribute, ) from .constraint_bridge import bridge_soc_quadratic_constraint from .aml import make_variable_tupledict, make_variable_ndarray from .matrix import add_matrix_constraints def detected_libraries(): libs = [] subdir = { "Linux": "lib", "Darwin": "lib", "Windows": "bin", }[platform.system()] libname_pattern = { "Linux": r"libgurobi(\d+)\.so", "Darwin": r"libgurobi(\d+)\.dylib", "Windows": r"gurobi(\d+)\.dll", }[platform.system()] suffix_pattern = { "Linux": "*.so", "Darwin": "*.dylib", "Windows": "*.dll", }[platform.system()] # Environment home = os.environ.get("GUROBI_HOME", None) if home and os.path.exists(home): dir = Path(home) / subdir for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) # gurobipy installation try: import gurobipy dir = Path(gurobipy.__path__[0]) if platform.system() != "Windows": dir = dir / ".libs" for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) except Exception: pass # conda/pixi environment prefix = Path(sys.prefix) dir = prefix / subdir for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) # default names gurobi_names = [ "gurobi130", "gurobi120", "gurobi110", ] default_libname = { "Linux": ["lib" + name + ".so" for name in gurobi_names], "Darwin": ["lib" + name + ".dylib" for name in gurobi_names], "Windows": [name + ".dll" for name in gurobi_names], }[platform.system()] libs.extend(default_libname) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded Gurobi library: {lib}") return True return False autoload_library() DEFAULT_ENV = None def init_default_env(): global DEFAULT_ENV if DEFAULT_ENV is None: DEFAULT_ENV = RawEnv() # Variable Attribute variable_attribute_get_func_map = { VariableAttribute.Value: lambda model, v: model.get_variable_raw_attribute_double( v, "X" ), VariableAttribute.LowerBound: lambda model, v: model.get_variable_raw_attribute_double( v, "LB" ), VariableAttribute.UpperBound: lambda model, v: model.get_variable_raw_attribute_double( v, "UB" ), VariableAttribute.PrimalStart: lambda model, v: model.get_variable_raw_attribute_double( v, "Start" ), VariableAttribute.Domain: lambda model, v: model.get_variable_raw_attribute_char( v, "VType" ), VariableAttribute.Name: lambda model, v: model.get_variable_raw_attribute_string( v, "VarName" ), VariableAttribute.IISLowerBound: lambda model, v: model.get_variable_raw_attribute_int( v, "IISLB" ) > 0, VariableAttribute.IISUpperBound: lambda model, v: model.get_variable_raw_attribute_int( v, "IISUB" ) > 0, VariableAttribute.ReducedCost: lambda model, v: model.get_variable_raw_attribute_double( v, "RC" ), } variable_attribute_get_translate_func_map = { VariableAttribute.Domain: lambda v: { GRB.BINARY: VariableDomain.Binary, GRB.INTEGER: VariableDomain.Integer, GRB.CONTINUOUS: VariableDomain.Continuous, GRB.SEMICONT: VariableDomain.SemiContinuous, }[v], } variable_attribute_set_translate_func_map = { VariableAttribute.Domain: lambda v: { VariableDomain.Binary: GRB.BINARY, VariableDomain.Integer: GRB.INTEGER, VariableDomain.Continuous: GRB.CONTINUOUS, VariableDomain.SemiContinuous: GRB.SEMICONT, }[v], } variable_attribute_set_func_map = { VariableAttribute.LowerBound: lambda model, v, x: model.set_variable_raw_attribute_double( v, "LB", x ), VariableAttribute.UpperBound: lambda model, v, x: model.set_variable_raw_attribute_double( v, "UB", x ), VariableAttribute.PrimalStart: lambda model, v, x: model.set_variable_raw_attribute_double( v, "Start", x ), VariableAttribute.Domain: lambda model, v, x: model.set_variable_raw_attribute_char( v, "VType", x ), VariableAttribute.Name: lambda model, v, x: model.set_variable_raw_attribute_string( v, "VarName", x ), } # Model Attribute constraint_type_attribute_name_map = { ConstraintType.Linear: "NumConstrs", ConstraintType.Quadratic: "NumQConstrs", } _RAW_STATUS_STRINGS = [ # TerminationStatus, RawStatusString ( TerminationStatusCode.OPTIMIZE_NOT_CALLED, "Model is loaded, but no solution information is available.", ), ( TerminationStatusCode.OPTIMAL, "Model was solved to optimality (subject to tolerances), and an optimal solution is available.", ), (TerminationStatusCode.INFEASIBLE, "Model was proven to be infeasible."), ( TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED, "Model was proven to be either infeasible or unbounded. To obtain a more definitive conclusion, set the DualReductions parameter to 0 and reoptimize.", ), ( TerminationStatusCode.DUAL_INFEASIBLE, "Model was proven to be unbounded. Important note: an unbounded status indicates the presence of an unbounded ray that allows the objective to improve without limit. It says nothing about whether the model has a feasible solution. If you require information on feasibility, you should set the objective to zero and reoptimize.", ), ( TerminationStatusCode.OBJECTIVE_LIMIT, "Optimal objective for model was proven to be worse than the value specified in the Cutoff parameter. No solution information is available.", ), ( TerminationStatusCode.ITERATION_LIMIT, "Optimization terminated because the total number of simplex iterations performed exceeded the value specified in the IterationLimit parameter, or because the total number of barrier iterations exceeded the value specified in the BarIterLimit parameter.", ), ( TerminationStatusCode.NODE_LIMIT, "Optimization terminated because the total number of branch-and-cut nodes explored exceeded the value specified in the NodeLimit parameter.", ), ( TerminationStatusCode.TIME_LIMIT, "Optimization terminated because the time expended exceeded the value specified in the TimeLimit parameter.", ), ( TerminationStatusCode.SOLUTION_LIMIT, "Optimization terminated because the number of solutions found reached the value specified in the SolutionLimit parameter.", ), (TerminationStatusCode.INTERRUPTED, "Optimization was terminated by the user."), ( TerminationStatusCode.NUMERICAL_ERROR, "Optimization was terminated due to unrecoverable numerical difficulties.", ), ( TerminationStatusCode.LOCALLY_SOLVED, "Unable to satisfy optimality tolerances; a sub-optimal solution is available.", ), ( TerminationStatusCode.OTHER_ERROR, "An asynchronous optimization call was made, but the associated optimization run is not yet complete.", ), ( TerminationStatusCode.OBJECTIVE_LIMIT, "User specified an objective limit (a bound on either the best objective or the best bound), and that limit has been reached.", ), ( TerminationStatusCode.OTHER_LIMIT, "Optimization terminated because the work expended exceeded the value specified in the WorkLimit parameter.", ), ( TerminationStatusCode.MEMORY_LIMIT, "Optimization terminated because the total amount of allocated memory exceeded the value specified in the SoftMemLimit parameter.", ), ( TerminationStatusCode.LOCALLY_SOLVED, "Model solved to local optimality. A solution satisfying the first-order optimality conditions (subject to tolerances) was found and is available.", ), ( TerminationStatusCode.LOCALLY_INFEASIBLE, "Model appears to be locally infeasible. A first-order minimizer of a measure for the constraint violations was found.", ), ] gurobi_raw_type_map = { 0: "char", 1: int, 2: float, 3: str, } def get_terminationstatus(model): status = model.get_model_raw_attribute_int("Status") assert status >= 1 and status <= 19, f"Unknown status code: {status}" return _RAW_STATUS_STRINGS[status - 1][0] def get_primalstatus(model): termination_status = get_terminationstatus(model) if termination_status in ( TerminationStatusCode.DUAL_INFEASIBLE, TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED, ): try: model.get_model_raw_attribute_list_double("UnbdRay", [0]) return ResultStatusCode.INFEASIBILITY_CERTIFICATE except: return ResultStatusCode.NO_SOLUTION solcount = model.get_model_raw_attribute_int("SolCount") if solcount == 0: return ResultStatusCode.NO_SOLUTION else: return ResultStatusCode.FEASIBLE_POINT def get_dualstatus(model): if model.get_model_raw_attribute_int("IsMIP") != 0: return ResultStatusCode.NO_SOLUTION elif ( model.get_model_raw_attribute_int("IsQCP") != 0 and model.get_model_raw_attribute("QCPDual") != 1 ): return ResultStatusCode.NO_SOLUTION termination_status = get_terminationstatus(model) if termination_status in ( TerminationStatusCode.DUAL_INFEASIBLE, TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED, ): try: model.get_model_raw_attribute_list_double("FarkasDual", [0]) return ResultStatusCode.INFEASIBILITY_CERTIFICATE except: return ResultStatusCode.NO_SOLUTION solcount = model.get_model_raw_attribute_int("SolCount") if solcount == 0: return ResultStatusCode.NO_SOLUTION try: model.get_model_raw_attribute_list_double("RC", [0]) return ResultStatusCode.FEASIBLE_POINT except: return ResultStatusCode.NO_SOLUTION def get_rawstatusstring(model): status = model.get_model_raw_attribute_int("Status") assert status >= 1 and status <= 15, f"Unknown status code: {status}" return _RAW_STATUS_STRINGS[status - 1][1] def get_silent(model): return model.get_model_raw_parameter_int("OutputFlag") == 0 model_attribute_get_func_map = { ModelAttribute.Name: lambda model: model.get_model_raw_attribute_string( "ModelName" ), ModelAttribute.ObjectiveSense: lambda model: model.get_model_raw_attribute_int( "ModelSense" ), ModelAttribute.BarrierIterations: lambda model: model.get_model_raw_attribute_int( "BarIterCount" ), ModelAttribute.DualObjectiveValue: lambda model: model.get_model_raw_attribute_double( "ObjBound" ), ModelAttribute.NodeCount: lambda model: model.get_model_raw_attribute_int( "NodeCount" ), ModelAttribute.ObjectiveBound: lambda model: model.get_model_raw_attribute_double( "ObjBound" ), ModelAttribute.ObjectiveValue: lambda model: model.get_model_raw_attribute_double( "ObjVal" ), ModelAttribute.SimplexIterations: lambda model: model.get_model_raw_attribute_int( "IterCount" ), ModelAttribute.SolveTimeSec: lambda model: model.get_model_raw_attribute_double( "RunTime" ), ModelAttribute.NumberOfThreads: lambda model: model.get_raw_parameter_int( "Threads" ), ModelAttribute.RelativeGap: lambda model: model.get_raw_parameter_double("MIPGap"), ModelAttribute.TimeLimitSec: lambda model: model.get_raw_parameter_double( "TimeLimit" ), ModelAttribute.DualStatus: get_dualstatus, ModelAttribute.PrimalStatus: get_primalstatus, ModelAttribute.RawStatusString: get_rawstatusstring, ModelAttribute.TerminationStatus: get_terminationstatus, ModelAttribute.Silent: get_silent, ModelAttribute.SolverName: lambda _: "Gurobi", ModelAttribute.SolverVersion: lambda model: model.version_string(), } model_attribute_get_translate_func_map = { ModelAttribute.ObjectiveSense: lambda v: { GRB.MINIMIZE: ObjectiveSense.Minimize, GRB.MAXIMIZE: ObjectiveSense.Maximize, }[v], } model_attribute_set_translate_func_map = { ModelAttribute.ObjectiveSense: lambda v: { ObjectiveSense.Minimize: GRB.MINIMIZE, ObjectiveSense.Maximize: GRB.MAXIMIZE, }[v], } def set_silent(model, value: bool): if value: model.set_raw_parameter_int("OutputFlag", 0) else: model.set_raw_parameter_int("OutputFlag", 1) model_attribute_set_func_map = { ModelAttribute.Name: lambda model, v: model.set_model_raw_attribute_string( "ModelName", v ), ModelAttribute.ObjectiveSense: lambda model, v: model.set_model_raw_attribute_int( "ModelSense", v ), ModelAttribute.NumberOfThreads: lambda model, v: model.set_raw_parameter_int( "Threads", v ), ModelAttribute.RelativeGap: lambda model, v: model.set_raw_parameter_double( "MIPGap", v ), ModelAttribute.TimeLimitSec: lambda model, v: model.set_raw_parameter_double( "TimeLimit", v ), ModelAttribute.Silent: set_silent, } # Constraint Attribute def get_constraint_name(model, constraint): type = constraint.type attr_name_dict = { ConstraintType.Linear: "ConstrName", ConstraintType.Quadratic: "QConstrName", } attr_name = attr_name_dict.get(type, None) if not attr_name: raise ValueError(f"Unknown constraint type: {type}") return model.get_constraint_raw_attribute_string(constraint, attr_name) def get_constraint_primal(model, constraint): # Linear : RHS - Slack # Quadratic : QCRHS - QCSlack type = constraint.type attr_name_dict = { ConstraintType.Linear: ("RHS", "Slack"), ConstraintType.Quadratic: ("QCRHS", "QCSlack"), } attr_name = attr_name_dict.get(type, None) if not attr_name: raise ValueError(f"Unknown constraint type: {type}") rhs = model.get_constraint_raw_attribute_double(constraint, attr_name[0]) slack = model.get_constraint_raw_attribute_double(constraint, attr_name[1]) return rhs - slack def get_constraint_dual(model, constraint): type = constraint.type attr_name_dict = { ConstraintType.Linear: "Pi", ConstraintType.Quadratic: "QCPi", } attr_name = attr_name_dict.get(type, None) if not attr_name: raise ValueError(f"Unknown constraint type: {type}") return model.get_constraint_raw_attribute_double(constraint, attr_name) def get_constraint_IIS(model, constraint): type = constraint.type attr_name_dict = { ConstraintType.Linear: "IISConstr", ConstraintType.SOS: "IISSOS", ConstraintType.Quadratic: "IISQConstr", } attr_name = attr_name_dict.get(type, None) if not attr_name: raise ValueError(f"Unknown constraint type: {type}") return model.get_constraint_raw_attribute_int(constraint, attr_name) > 0 constraint_attribute_get_func_map = { ConstraintAttribute.Name: get_constraint_name, ConstraintAttribute.Primal: get_constraint_primal, ConstraintAttribute.Dual: get_constraint_dual, ConstraintAttribute.IIS: get_constraint_IIS, } constraint_attribute_set_func_map = { ConstraintAttribute.Name: lambda model, constraint, value: model.set_constraint_name( constraint, value ), } callback_info_typemap = { GRB.Callback.RUNTIME: float, GRB.Callback.WORK: float, GRB.Callback.PRE_COLDEL: int, GRB.Callback.PRE_ROWDEL: int, GRB.Callback.PRE_SENCHG: int, GRB.Callback.PRE_BNDCHG: int, GRB.Callback.PRE_COECHG: int, GRB.Callback.SPX_ITRCNT: float, GRB.Callback.SPX_OBJVAL: float, GRB.Callback.SPX_PRIMINF: float, GRB.Callback.SPX_DUALINF: float, GRB.Callback.SPX_ISPERT: int, GRB.Callback.MIP_OBJBST: float, GRB.Callback.MIP_OBJBND: float, GRB.Callback.MIP_NODCNT: float, GRB.Callback.MIP_SOLCNT: int, GRB.Callback.MIP_CUTCNT: int, GRB.Callback.MIP_NODLFT: float, GRB.Callback.MIP_ITRCNT: float, GRB.Callback.MIP_OPENSCENARIOS: int, GRB.Callback.MIP_PHASE: int, GRB.Callback.MIPSOL_OBJ: float, GRB.Callback.MIPSOL_OBJBST: float, GRB.Callback.MIPSOL_OBJBND: float, GRB.Callback.MIPSOL_NODCNT: float, GRB.Callback.MIPSOL_SOLCNT: int, GRB.Callback.MIPSOL_OPENSCENARIOS: int, GRB.Callback.MIPSOL_PHASE: int, GRB.Callback.MIPNODE_STATUS: int, GRB.Callback.MIPNODE_OBJBST: float, GRB.Callback.MIPNODE_OBJBND: float, GRB.Callback.MIPNODE_NODCNT: float, GRB.Callback.MIPNODE_SOLCNT: int, GRB.Callback.MIPNODE_OPENSCENARIOS: int, GRB.Callback.MIPNODE_PHASE: int, GRB.Callback.MIPNODE_REL: float, GRB.Callback.BARRIER_ITRCNT: int, GRB.Callback.BARRIER_PRIMOBJ: float, GRB.Callback.BARRIER_DUALOBJ: float, GRB.Callback.BARRIER_PRIMINF: float, GRB.Callback.BARRIER_DUALINF: float, GRB.Callback.BARRIER_COMPL: float, } class Env(RawEnv): def set_raw_parameter(self, param_name: str, value): param_type = gurobi_raw_type_map[self.raw_parameter_type(param_name)] set_function_map = { int: self.set_raw_parameter_int, float: self.set_raw_parameter_double, str: self.set_raw_parameter_string, } set_function = set_function_map[param_type] set_function(param_name, value) class Model(RawModel): def __init__(self, env=None): if env is None: init_default_env() env = DEFAULT_ENV super().__init__(env) # We must keep a reference to the environment to prevent it from being garbage collected self._env = env # Replace default logging behavior to use Python print # otherwise it prints directly to stdout/stderr bypassing Python and causing issues in Jupyter notebooks self.set_raw_parameter("LogToConsole", 0) self.set_logging(lambda msg: print(msg, end="")) @staticmethod def supports_variable_attribute(attribute: VariableAttribute, settable=False): if settable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_model_attribute(attribute: ModelAttribute, settable=False): if settable: return attribute in model_attribute_set_func_map else: return attribute in model_attribute_get_func_map @staticmethod def supports_constraint_attribute(attribute: ConstraintAttribute, settable=False): if settable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map def get_variable_attribute(self, variable, attribute: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") value = _get_entity_attribute( self, variable, attribute, variable_attribute_get_func_map, variable_attribute_get_translate_func_map, e, ) return value def set_variable_attribute(self, variable, attribute: VariableAttribute, value): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _set_entity_attribute( self, variable, attribute, value, variable_attribute_set_func_map, variable_attribute_set_translate_func_map, e, ) def number_of_constraints(self, type: ConstraintType): attr_name = constraint_type_attribute_name_map.get(type, None) if not attr_name: raise ValueError(f"Unknown constraint type: {type}") return self.get_model_raw_attribute_int(attr_name) def number_of_variables(self): return self.get_model_raw_attribute_int("NumVars") def get_model_attribute(self, attribute: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") value = _get_model_attribute( self, attribute, model_attribute_get_func_map, model_attribute_get_translate_func_map, e, ) return value def set_model_attribute(self, attribute: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _set_model_attribute( self, attribute, value, model_attribute_set_func_map, model_attribute_set_translate_func_map, e, ) def get_constraint_attribute(self, constraint, attribute: ConstraintAttribute): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") value = _direct_get_entity_attribute( self, constraint, attribute, constraint_attribute_get_func_map, e, ) return value def set_constraint_attribute( self, constraint, attribute: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _direct_set_entity_attribute( self, constraint, attribute, value, constraint_attribute_set_func_map, e, ) def get_raw_parameter(self, param_name: str): param_type = gurobi_raw_type_map[self.raw_parameter_type(param_name)] get_function_map = { int: self.get_raw_parameter_int, float: self.get_raw_parameter_double, str: self.get_raw_parameter_string, } get_function = get_function_map[param_type] return get_function(param_name) def set_raw_parameter(self, param_name: str, value): param_type = gurobi_raw_type_map[self.raw_parameter_type(param_name)] set_function_map = { int: self.set_raw_parameter_int, float: self.set_raw_parameter_double, str: self.set_raw_parameter_string, } set_function = set_function_map[param_type] set_function(param_name, value) def get_model_raw_attribute(self, name: str): param_type = gurobi_raw_type_map[self.raw_attribute_type(name)] get_function_map = { int: self.get_model_raw_attribute_int, float: self.get_model_raw_attribute_double, str: self.get_model_raw_attribute_string, } get_function = get_function_map[param_type] return get_function(name) def set_model_raw_attribute(self, name: str, value): param_type = gurobi_raw_type_map[self.raw_attribute_type(name)] set_function_map = { int: self.set_model_raw_attribute_int, float: self.set_model_raw_attribute_double, str: self.set_model_raw_attribute_string, } set_function = set_function_map[param_type] set_function(name, value) def get_variable_raw_attribute(self, variable, name: str): param_type = gurobi_raw_type_map[self.raw_attribute_type(name)] get_function_map = { "char": self.get_variable_raw_attribute_char, int: self.get_variable_raw_attribute_int, float: self.get_variable_raw_attribute_double, str: self.get_variable_raw_attribute_string, } get_function = get_function_map[param_type] return get_function(variable, name) def set_variable_raw_attribute(self, variable, name: str, value): param_type = gurobi_raw_type_map[self.raw_attribute_type(name)] set_function_map = { "char": self.set_variable_raw_attribute_char, int: self.set_variable_raw_attribute_int, float: self.set_variable_raw_attribute_double, str: self.set_variable_raw_attribute_string, } set_function = set_function_map[param_type] set_function(variable, name, value) def get_constraint_raw_attribute(self, constraint, name: str): param_type = gurobi_raw_type_map[self.raw_attribute_type(name)] get_function_map = { "char": self.get_constraint_raw_attribute_char, int: self.get_constraint_raw_attribute_int, float: self.get_constraint_raw_attribute_double, str: self.get_constraint_raw_attribute_string, } get_function = get_function_map[param_type] return get_function(constraint, name) def set_constraint_raw_attribute(self, constraint, name: str, value): param_type = gurobi_raw_type_map[self.raw_attribute_type(name)] set_function_map = { "char": self.set_constraint_raw_attribute_char, int: self.set_constraint_raw_attribute_int, float: self.set_constraint_raw_attribute_double, str: self.set_constraint_raw_attribute_string, } set_function = set_function_map[param_type] set_function(constraint, name, value) def cb_get_info(self, what): cb_info_type = callback_info_typemap.get(what, None) if cb_info_type is None: raise ValueError(f"Unknown callback info type: {what}") if cb_info_type is int: return self.cb_get_info_int(what) elif cb_info_type is float: return self.cb_get_info_double(what) else: raise ValueError(f"Unknown callback info type: {what}") @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_linear_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_quadratic_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_quadratic_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_quadratic_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_quadratic_constraint(arg, *args, **kwargs) @overload def add_nl_constraint( self, expr, sense: ConstraintSense, rhs: float, /, name: str = "", ): ... @overload def add_nl_constraint( self, expr, interval: Tuple[float, float], /, name: str = "", ): ... @overload def add_nl_constraint( self, con, /, name: str = "", ): ... def add_nl_constraint(self, expr, *args, **kwargs): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError( "Expression should be able to be converted to ExpressionHandle" ) con = self._add_single_nl_constraint(graph, expr, *args, **kwargs) return con def add_nl_objective(self, expr): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError( "Expression should be able to be converted to ExpressionHandle" ) self._add_single_nl_objective(graph, expr) add_variables = make_variable_tupledict add_m_variables = make_variable_ndarray add_m_linear_constraints = add_matrix_constraints add_second_order_cone_constraint = bridge_soc_quadratic_constraint ================================================ FILE: src/pyoptinterface/_src/highs.py ================================================ import logging import os import platform from pathlib import Path from typing import Dict, Union, overload from .highs_model_ext import ( RawModel, HighsSolutionStatus, Enum, load_library, ) from .attributes import ( VariableAttribute, ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ExprBuilder, ConstraintType, ConstraintSense, ) from .comparison_constraint import ComparisonConstraint from .solver_common import ( _direct_get_model_attribute, _direct_set_model_attribute, _direct_get_entity_attribute, _direct_set_entity_attribute, ) from .aml import make_variable_tupledict, make_variable_ndarray from .matrix import add_matrix_constraints def detected_libraries(): libs = [] subdir = { "Linux": "lib", "Darwin": "lib", "Windows": "bin", }[platform.system()] libname = { "Linux": "libhighs.so", "Darwin": "libhighs.dylib", "Windows": "highs.dll", }[platform.system()] # Environment home = os.environ.get("HiGHS_HOME", None) if home and os.path.exists(home): lib = Path(home) / subdir / libname if lib.exists(): libs.append(str(lib)) # HiGHS pypi installation try: import highsbox home = highsbox.highs_dist_dir() if os.path.exists(home): lib = Path(home) / subdir / libname if lib.exists(): libs.append(str(lib)) except Exception: pass # default names default_libname = libname libs.append(default_libname) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded HiGHS library: {lib}") return True return False autoload_library() variable_attribute_get_func_map = { VariableAttribute.Value: lambda model, v: model.get_value(v), VariableAttribute.LowerBound: lambda model, v: model.get_variable_lower_bound(v), VariableAttribute.UpperBound: lambda model, v: model.get_variable_upper_bound(v), VariableAttribute.PrimalStart: lambda model, v: model.mip_start_values.get(v, None), VariableAttribute.Domain: lambda model, v: model.get_variable_type(v), VariableAttribute.Name: lambda model, v: model.get_variable_name(v), VariableAttribute.ReducedCost: lambda model, v: model.get_variable_dual(v), } variable_attribute_set_func_map = { VariableAttribute.LowerBound: lambda model, v, val: model.set_variable_lower_bound( v, val ), VariableAttribute.UpperBound: lambda model, v, val: model.set_variable_upper_bound( v, val ), VariableAttribute.PrimalStart: lambda model, v, val: model.mip_start_values.__setitem__( v, val ), VariableAttribute.Domain: lambda model, v, val: model.set_variable_type(v, val), VariableAttribute.Name: lambda model, v, val: model.set_variable_name(v, val), } def get_dualstatus(model): sol = model.solution if sol.dual_solution_status == Enum.kHighsSolutionStatusFeasible: return ResultStatusCode.FEASIBLE_POINT elif sol.dual_solution_status == Enum.kHighsSolutionStatusInfeasible: return ResultStatusCode.INFEASIBLE_POINT elif model.solution.has_dual_ray: return ResultStatusCode.INFEASIBILITY_CERTIFICATE else: return ResultStatusCode.NO_SOLUTION def get_primalstatus(model): sol = model.solution if sol.primal_solution_status == Enum.kHighsSolutionStatusFeasible: return ResultStatusCode.FEASIBLE_POINT elif sol.primal_solution_status == Enum.kHighsSolutionStatusInfeasible: return ResultStatusCode.INFEASIBLE_POINT elif sol.has_primal_ray: return ResultStatusCode.INFEASIBILITY_CERTIFICATE return ResultStatusCode.NO_SOLUTION def get_rawstatusstring(model): status = model.solution.status model_status = model.solution.model_status if status == HighsSolutionStatus.OPTIMIZE_NOT_CALLED: return "OPTIMIZE_NOT_CALLED" elif status == HighsSolutionStatus.OPTIMIZE_ERROR: return "There was an error calling optimize!" elif model_status == Enum.kHighsModelStatusNotset: return "kHighsModelStatusNotset" elif model_status == Enum.kHighsModelStatusLoadError: return "kHighsModelStatusLoadError" elif model_status == Enum.kHighsModelStatusModelError: return "kHighsModelStatusModelError" elif model_status == Enum.kHighsModelStatusPresolveError: return "kHighsModelStatusPresolveError" elif model_status == Enum.kHighsModelStatusSolveError: return "kHighsModelStatusSolveError" elif model_status == Enum.kHighsModelStatusPostsolveError: return "kHighsModelStatusPostsolveError" elif model_status == Enum.kHighsModelStatusModelEmpty: return "kHighsModelStatusModelEmpty" elif model_status == Enum.kHighsModelStatusOptimal: return "kHighsModelStatusOptimal" elif model_status == Enum.kHighsModelStatusInfeasible: return "kHighsModelStatusInfeasible" elif model_status == Enum.kHighsModelStatusUnboundedOrInfeasible: return "kHighsModelStatusUnboundedOrInfeasible" elif model_status == Enum.kHighsModelStatusUnbounded: return "kHighsModelStatusUnbounded" elif model_status == Enum.kHighsModelStatusObjectiveBound: return "kHighsModelStatusObjectiveBound" elif model_status == Enum.kHighsModelStatusObjectiveTarget: return "kHighsModelStatusObjectiveTarget" elif model_status == Enum.kHighsModelStatusTimeLimit: return "kHighsModelStatusTimeLimit" elif model_status == Enum.kHighsModelStatusIterationLimit: return "kHighsModelStatusIterationLimit" else: assert model_status == Enum.kHighsModelStatusUnknown return "kHighsModelStatusUnknown" def get_terminationstatus(model): status = model.solution.status model_status = model.solution.model_status if status == HighsSolutionStatus.OPTIMIZE_NOT_CALLED: return TerminationStatusCode.OPTIMIZE_NOT_CALLED elif status == HighsSolutionStatus.OPTIMIZE_ERROR: return TerminationStatusCode.OTHER_ERROR elif model_status == Enum.kHighsModelStatusNotset: return TerminationStatusCode.OTHER_ERROR elif model_status == Enum.kHighsModelStatusLoadError: return TerminationStatusCode.OTHER_ERROR elif model_status == Enum.kHighsModelStatusModelError: return TerminationStatusCode.INVALID_MODEL elif model_status == Enum.kHighsModelStatusPresolveError: return TerminationStatusCode.OTHER_ERROR elif model_status == Enum.kHighsModelStatusSolveError: return TerminationStatusCode.OTHER_ERROR elif model_status == Enum.kHighsModelStatusPostsolveError: return TerminationStatusCode.OTHER_ERROR elif model_status == Enum.kHighsModelStatusModelEmpty: return TerminationStatusCode.INVALID_MODEL elif model_status == Enum.kHighsModelStatusOptimal: return TerminationStatusCode.OPTIMAL elif model_status == Enum.kHighsModelStatusInfeasible: return TerminationStatusCode.INFEASIBLE elif model_status == Enum.kHighsModelStatusUnboundedOrInfeasible: return TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED elif model_status == Enum.kHighsModelStatusUnbounded: return TerminationStatusCode.DUAL_INFEASIBLE elif model_status == Enum.kHighsModelStatusObjectiveBound: return TerminationStatusCode.OBJECTIVE_LIMIT elif model_status == Enum.kHighsModelStatusObjectiveTarget: return TerminationStatusCode.OBJECTIVE_LIMIT elif model_status == Enum.kHighsModelStatusTimeLimit: return TerminationStatusCode.TIME_LIMIT elif model_status == Enum.kHighsModelStatusIterationLimit: return TerminationStatusCode.ITERATION_LIMIT elif model_status == Enum.kHighsModelStatusUnknown: return TerminationStatusCode.OTHER_ERROR else: assert model_status == Enum.kHighsModelStatusSolutionLimit return TerminationStatusCode.SOLUTION_LIMIT model_attribute_get_func_map = { ModelAttribute.ObjectiveSense: lambda model: model.get_obj_sense(), # ModelAttribute.DualObjectiveValue: lambda model: model.getdualobj(), ModelAttribute.ObjectiveValue: lambda model: model.get_obj_value(), ModelAttribute.SolveTimeSec: lambda model: model.getruntime(), ModelAttribute.NumberOfThreads: lambda model: model.get_raw_option_int("threads"), ModelAttribute.RelativeGap: lambda model: model.get_raw_option_double( "mip_rel_gap" ), ModelAttribute.TimeLimitSec: lambda model: model.get_raw_option_double( "time_limit" ), ModelAttribute.DualStatus: get_dualstatus, ModelAttribute.PrimalStatus: get_primalstatus, ModelAttribute.RawStatusString: get_rawstatusstring, ModelAttribute.TerminationStatus: get_terminationstatus, ModelAttribute.Silent: lambda model: not model.get_raw_option_bool("output_flag"), ModelAttribute.SolverName: lambda _: "HiGHS", ModelAttribute.SolverVersion: lambda model: model.version_string(), } model_attribute_set_func_map = { ModelAttribute.ObjectiveSense: lambda model, v: model.set_obj_sense(v), ModelAttribute.NumberOfThreads: lambda model, v: model.set_raw_option_int( "threads", v ), ModelAttribute.RelativeGap: lambda model, v: model.set_raw_option_double( "mip_rel_gap", v ), ModelAttribute.TimeLimitSec: lambda model, v: model.set_raw_option_double( "time_limit", v ), ModelAttribute.Silent: lambda model, v: model.set_raw_option_bool( "output_flag", not v ), } constraint_attribute_get_func_map = { ConstraintAttribute.Name: lambda model, constraint: model.get_constraint_name( constraint ), ConstraintAttribute.Primal: lambda model, constraint: model.get_constraint_primal( constraint ), ConstraintAttribute.Dual: lambda model, constraint: model.get_constraint_dual( constraint ), } constraint_attribute_set_func_map = { ConstraintAttribute.Name: lambda model, constraint, value: model.set_constraint_name( constraint, value ), } highs_option_raw_type_map = { Enum.kHighsOptionTypeBool: bool, Enum.kHighsOptionTypeInt: int, Enum.kHighsOptionTypeDouble: float, Enum.kHighsOptionTypeString: str, } highs_info_raw_type_map = { Enum.kHighsInfoTypeInt: int, Enum.kHighsInfoTypeInt64: "int64", Enum.kHighsInfoTypeDouble: float, } class Model(RawModel): def __init__(self): super().__init__() self.mip_start_values: Dict[VariableIndex, float] = dict() @staticmethod def supports_variable_attribute(attribute: VariableAttribute, settable=False): if settable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_model_attribute(attribute: ModelAttribute, settable=False): if settable: return attribute in model_attribute_set_func_map else: return attribute in model_attribute_get_func_map @staticmethod def supports_constraint_attribute(attribute: ConstraintAttribute, settable=False): if settable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map def get_variable_attribute(self, variable, attribute: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") value = _direct_get_entity_attribute( self, variable, attribute, variable_attribute_get_func_map, e, ) return value def set_variable_attribute(self, variable, attribute: VariableAttribute, value): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _direct_set_entity_attribute( self, variable, attribute, value, variable_attribute_set_func_map, e, ) def number_of_constraints(self, type: ConstraintType): if type not in {ConstraintType.Linear}: raise ValueError(f"Unknown constraint type: {type}") return self.m_n_constraints def number_of_variables(self): return self.m_n_variables def get_model_attribute(self, attribute: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") value = _direct_get_model_attribute( self, attribute, model_attribute_get_func_map, e, ) return value def set_model_attribute(self, attribute: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _direct_set_model_attribute( self, attribute, value, model_attribute_set_func_map, e, ) def get_constraint_attribute(self, constraint, attribute: ConstraintAttribute): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") value = _direct_get_entity_attribute( self, constraint, attribute, constraint_attribute_get_func_map, e, ) return value def set_constraint_attribute( self, constraint, attribute: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _direct_set_entity_attribute( self, constraint, attribute, value, constraint_attribute_set_func_map, e, ) def get_raw_parameter(self, param_name: str): param_type = highs_option_raw_type_map[self.raw_option_type(param_name)] get_function_map = { bool: self.get_raw_option_bool, int: self.get_raw_option_int, float: self.get_raw_option_double, str: self.get_raw_option_string, } get_function = get_function_map[param_type] return get_function(param_name) def set_raw_parameter(self, param_name: str, value): param_type = highs_option_raw_type_map[self.raw_option_type(param_name)] set_function_map = { bool: self.set_raw_option_bool, int: self.set_raw_option_int, float: self.set_raw_option_double, str: self.set_raw_option_string, } set_function = set_function_map[param_type] set_function(param_name, value) def get_raw_information(self, param_name: str): param_type = highs_info_raw_type_map[self.raw_info_type(param_name)] get_function_map = { int: self.get_raw_attribute_int, "int64": self.get_raw_attribute_int64, float: self.get_raw_attribute_double, } get_function = get_function_map[param_type] return get_function(param_name) def optimize(self): mip_start = self.mip_start_values if len(mip_start) != 0: variables = list(mip_start.keys()) values = list(mip_start.values()) self.set_primal_start(variables, values) mip_start.clear() super().optimize() @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_linear_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) add_variables = make_variable_tupledict add_m_variables = make_variable_ndarray add_m_linear_constraints = add_matrix_constraints ================================================ FILE: src/pyoptinterface/_src/ipopt.py ================================================ from io import StringIO import logging import platform from typing import Optional, List, Dict, Set, Union, Tuple, overload from llvmlite import ir from .ipopt_model_ext import RawModel, ApplicationReturnStatus, load_library from .codegen_c import generate_csrc_prelude, generate_csrc_from_graph from .jit_c import TCCJITCompiler from .codegen_llvm import create_llvmir_basic_functions, generate_llvmir_from_graph from .jit_llvm import LLJITCompiler from .nlexpr_ext import ExpressionHandle, ExpressionGraph, unpack_comparison_expression from .nlfunc import ( ExpressionGraphContext, convert_to_expressionhandle, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, ConstraintSense, ) from .comparison_constraint import ComparisonConstraint from .nleval_ext import ( AutodiffSymbolicStructure, ConstraintAutodiffEvaluator, ObjectiveAutodiffEvaluator, ) from .cppad_interface_ext import ( CppADAutodiffGraph, cppad_trace_graph_constraints, cppad_trace_graph_objective, cppad_autodiff, ) from .attributes import ( VariableAttribute, ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, ) from .solver_common import ( _direct_get_model_attribute, _direct_set_model_attribute, _direct_get_entity_attribute, _direct_set_entity_attribute, ) from .constraint_bridge import bridge_soc_quadratic_constraint from .aml import make_variable_tupledict, make_variable_ndarray from .matrix import add_matrix_constraints def detected_libraries(): libs = [] # default names default_libnames = { "Linux": ["libipopt.so"], "Darwin": ["libipopt.dylib"], "Windows": ["ipopt-3.dll", "ipopt.dll", "libipopt-3.dll", "libipopt.dll"], }[platform.system()] libs.extend(default_libnames) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded IPOPT library: {lib}") return True return False autoload_library() variable_attribute_get_func_map = { VariableAttribute.Value: lambda model, v: model.get_value(v), VariableAttribute.LowerBound: lambda model, v: model.get_variable_lb(v), VariableAttribute.UpperBound: lambda model, v: model.get_variable_ub(v), VariableAttribute.PrimalStart: lambda model, v: model.get_variable_start(v), VariableAttribute.Name: lambda model, v: model.get_variable_name(v), } variable_attribute_set_func_map = { VariableAttribute.LowerBound: lambda model, v, val: model.set_variable_lb(v, val), VariableAttribute.UpperBound: lambda model, v, val: model.set_variable_ub(v, val), VariableAttribute.PrimalStart: lambda model, v, val: model.set_variable_start( v, val ), VariableAttribute.Name: lambda model, v, val: model.set_variable_name(v, val), } def get_dualstatus(model): status = model.m_status if status == ApplicationReturnStatus.Solve_Succeeded: return ResultStatusCode.FEASIBLE_POINT elif status == ApplicationReturnStatus.Feasible_Point_Found: return ResultStatusCode.FEASIBLE_POINT elif status == ApplicationReturnStatus.Solved_To_Acceptable_Level: return ResultStatusCode.NEARLY_FEASIBLE_POINT else: return ResultStatusCode.UNKNOWN_RESULT_STATUS def get_primalstatus(model): status = model.m_status if status == ApplicationReturnStatus.Solve_Succeeded: return ResultStatusCode.FEASIBLE_POINT elif status == ApplicationReturnStatus.Feasible_Point_Found: return ResultStatusCode.FEASIBLE_POINT elif status == ApplicationReturnStatus.Solved_To_Acceptable_Level: return ResultStatusCode.NEARLY_FEASIBLE_POINT elif status == ApplicationReturnStatus.Infeasible_Problem_Detected: return ResultStatusCode.INFEASIBLE_POINT else: return ResultStatusCode.UNKNOWN_RESULT_STATUS def get_rawstatusstring(model): status = model.m_status return status.name def get_terminationstatus(model): is_dirty = model.m_is_dirty if is_dirty: return TerminationStatusCode.OPTIMIZE_NOT_CALLED status = model.m_status if ( status == ApplicationReturnStatus.Solve_Succeeded or status == ApplicationReturnStatus.Feasible_Point_Found ): return TerminationStatusCode.LOCALLY_SOLVED elif status == ApplicationReturnStatus.Infeasible_Problem_Detected: return TerminationStatusCode.LOCALLY_INFEASIBLE elif status == ApplicationReturnStatus.Solved_To_Acceptable_Level: return TerminationStatusCode.ALMOST_LOCALLY_SOLVED elif status == ApplicationReturnStatus.Search_Direction_Becomes_Too_Small: return TerminationStatusCode.NUMERICAL_ERROR elif status == ApplicationReturnStatus.Diverging_Iterates: return TerminationStatusCode.NORM_LIMIT elif status == ApplicationReturnStatus.User_Requested_Stop: return TerminationStatusCode.INTERRUPTED elif status == ApplicationReturnStatus.Maximum_Iterations_Exceeded: return TerminationStatusCode.ITERATION_LIMIT elif status == ApplicationReturnStatus.Maximum_CpuTime_Exceeded: return TerminationStatusCode.TIME_LIMIT elif status == ApplicationReturnStatus.Maximum_WallTime_Exceeded: return TerminationStatusCode.TIME_LIMIT elif status == ApplicationReturnStatus.Restoration_Failed: return TerminationStatusCode.NUMERICAL_ERROR elif status == ApplicationReturnStatus.Error_In_Step_Computation: return TerminationStatusCode.NUMERICAL_ERROR elif status == ApplicationReturnStatus.Invalid_Option: return TerminationStatusCode.INVALID_OPTION elif status == ApplicationReturnStatus.Not_Enough_Degrees_Of_Freedom: return TerminationStatusCode.INVALID_MODEL elif status == ApplicationReturnStatus.Invalid_Problem_Definition: return TerminationStatusCode.INVALID_MODEL elif status == ApplicationReturnStatus.Invalid_Number_Detected: return TerminationStatusCode.INVALID_MODEL elif status == ApplicationReturnStatus.Unrecoverable_Exception: return TerminationStatusCode.OTHER_ERROR elif status == ApplicationReturnStatus.NonIpopt_Exception_Thrown: return TerminationStatusCode.OTHER_ERROR else: assert status == ApplicationReturnStatus.Insufficient_Memory return TerminationStatusCode.MEMORY_LIMIT model_attribute_get_func_map = { ModelAttribute.ObjectiveValue: lambda model: model.get_obj_value(), ModelAttribute.DualStatus: get_dualstatus, ModelAttribute.PrimalStatus: get_primalstatus, ModelAttribute.RawStatusString: get_rawstatusstring, ModelAttribute.TerminationStatus: get_terminationstatus, ModelAttribute.SolverName: lambda _: "IPOPT", } model_attribute_set_func_map = { ModelAttribute.TimeLimitSec: lambda model, v: model.set_raw_parameter( "max_wall_time", v ), ModelAttribute.Silent: lambda model, v: model.set_raw_parameter( "print_level", 0 if v else 5 ), } def get_constraint_primal(model, constraint): return model.get_constraint_primal(constraint) def get_constraint_dual(model, constraint): return model.get_constraint_dual(constraint) constraint_attribute_get_func_map = { ConstraintAttribute.Primal: get_constraint_primal, ConstraintAttribute.Dual: get_constraint_dual, } constraint_attribute_set_func_map = {} class Model(RawModel): def __init__(self, jit: str = "LLVM"): super().__init__() if jit == "C": self.jit_compiler = TCCJITCompiler() elif jit == "LLVM": self.jit_compiler = LLJITCompiler() else: raise ValueError(f"JIT engine can only be 'C' or 'LLVM', got {jit}") self.jit = jit # store graph_instance to graph_index self.graph_instance_to_index: Dict[ExpressionGraph, int] = {} self.graph_instances: List[ExpressionGraph] = [] self.nl_constraint_group_num = 0 self.nl_constraint_group_representatives: List[int] = [] self.nl_constraint_cppad_autodiff_graphs: List[CppADAutodiffGraph] = [] self.nl_constraint_autodiff_structures: List[AutodiffSymbolicStructure] = [] self.nl_constraint_evaluators: List[ConstraintAutodiffEvaluator] = [] self.nl_objective_group_num = 0 self.nl_objective_group_representatives: List[int] = [] self.nl_objective_cppad_autodiff_graphs: List[CppADAutodiffGraph] = [] self.nl_objective_autodiff_structures: List[AutodiffSymbolicStructure] = [] self.nl_objective_evaluators: List[ObjectiveAutodiffEvaluator] = [] # record the analyzed part of the problem self.n_graph_instances_since_last_optimize = 0 self.nl_constraint_group_num_since_last_optimize = 0 self.nl_objective_group_num_since_last_optimize = 0 @staticmethod def supports_variable_attribute(attribute: VariableAttribute, settable=False): if settable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_model_attribute(attribute: ModelAttribute, settable=False): if settable: return attribute in model_attribute_set_func_map else: return attribute in model_attribute_get_func_map @staticmethod def supports_constraint_attribute(attribute: ConstraintAttribute, settable=False): if settable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map def get_variable_attribute(self, variable, attribute: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") value = _direct_get_entity_attribute( self, variable, attribute, variable_attribute_get_func_map, e, ) return value def set_variable_attribute(self, variable, attribute: VariableAttribute, value): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _direct_set_entity_attribute( self, variable, attribute, value, variable_attribute_set_func_map, e, ) def get_model_attribute(self, attribute: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") value = _direct_get_model_attribute( self, attribute, model_attribute_get_func_map, e, ) return value def set_model_attribute(self, attribute: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _direct_set_model_attribute( self, attribute, value, model_attribute_set_func_map, e, ) def get_constraint_attribute(self, constraint, attribute: ConstraintAttribute): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") value = _direct_get_entity_attribute( self, constraint, attribute, constraint_attribute_get_func_map, e, ) return value def set_constraint_attribute( self, constraint, attribute: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _direct_set_entity_attribute( self, constraint, attribute, value, constraint_attribute_set_func_map, e, ) def set_raw_parameter(self, param_name: str, value): ty = type(value) if ty is int: self.set_raw_option_int(param_name, value) elif ty is float: self.set_raw_option_double(param_name, value) elif ty is str: self.set_raw_option_string(param_name, value) else: raise ValueError(f"Unsupported parameter type: {ty}") @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], interval: Tuple[float, float], name: str = "", ): ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_linear_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], interval: Tuple[float, float], name: str = "", ): ... @overload def add_quadratic_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_quadratic_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_quadratic_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_quadratic_constraint(arg, *args, **kwargs) @overload def add_nl_constraint( self, expr, sense: ConstraintSense, rhs: float, /, name: str = "", ): ... @overload def add_nl_constraint( self, expr, interval: Tuple[float, float], /, name: str = "", ): ... @overload def add_nl_constraint( self, con, /, name: str = "", ): ... def add_nl_constraint(self, expr, *args, **kwargs): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError( "Expression should be able to be converted to ExpressionHandle" ) n_args = len(args) if n_args == 0: is_comparison = graph.is_compare_expression(expr) if is_comparison: expr, lb, ub = unpack_comparison_expression(graph, expr, float("inf")) else: raise ValueError("Must specify either equality or inequality bounds") elif n_args == 1: arg = args[0] if isinstance(arg, tuple): lb, ub = arg else: raise ValueError("Must specify either equality or inequality bounds") elif n_args == 2: sense = args[0] rhs = args[1] if isinstance(sense, ConstraintSense) and isinstance(rhs, float): if sense == ConstraintSense.Equal: lb = ub = rhs elif sense == ConstraintSense.LessEqual: lb = -float("inf") ub = rhs elif sense == ConstraintSense.GreaterEqual: lb = rhs ub = float("inf") else: raise ValueError(f"Unknown constraint sense: {sense}") else: raise ValueError("Must specify either equality or inequality bounds") else: raise ValueError("Must specify either equality or inequality bounds") graph.add_constraint_output(expr) graph_index = self.graph_instance_to_index.get(graph, None) if graph_index is None: graph_index = self._add_graph_index() self.graph_instance_to_index[graph] = graph_index self.graph_instances.append(graph) con = self._add_single_nl_constraint(graph_index, graph, lb, ub) return con def add_nl_objective(self, expr): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError( "Expression should be able to be converted to ExpressionHandle" ) graph.add_objective_output(expr) graph_index = self.graph_instance_to_index.get(graph, None) if graph_index is None: graph_index = self._add_graph_index() self.graph_instance_to_index[graph] = graph_index self.graph_instances.append(graph) self.m_is_dirty = True def optimize(self): self._find_similar_graphs() self._compile_evaluators() # print("Compiling evaluators successfully") # print(self.jit_compiler.source_codes[0]) self.n_graph_instances_since_last_optimize = len(self.graph_instances) self.nl_constraint_group_num_since_last_optimize = self.nl_constraint_group_num self.nl_objective_group_num_since_last_optimize = self.nl_objective_group_num super()._optimize() def _find_similar_graphs(self): for i in range( self.n_graph_instances_since_last_optimize, len(self.graph_instances) ): graph = self.graph_instances[i] self._finalize_graph_instance(i, graph) # constraint part n_groups = self._aggregate_nl_constraint_groups() # print(f"Found {n_groups} nonlinear constraint groups of similar graphs") self.nl_constraint_group_num = n_groups rep_instances = self.nl_constraint_group_representatives for i in range(self.nl_constraint_group_num_since_last_optimize, n_groups): graph_index = self._get_nl_constraint_group_representative(i) rep_instances.append(graph_index) # objective part n_groups = self._aggregate_nl_objective_groups() # print(f"Found {n_groups} nonlinear objective groups of similar graphs") self.nl_objective_group_num = n_groups rep_instances = self.nl_objective_group_representatives for i in range(self.nl_objective_group_num_since_last_optimize, n_groups): graph_index = self._get_nl_objective_group_representative(i) rep_instances.append(graph_index) def _compile_evaluators(self): # for each group of nonlinear constraint and objective, we construct a cppad_autodiff graph # and then compile them to get the function pointers # constraint # self.nl_constraint_cppad_autodiff_graphs.clear() # self.nl_constraint_autodiff_structures.clear() for i in range( self.nl_constraint_group_num_since_last_optimize, self.nl_constraint_group_num, ): graph_index = self.nl_constraint_group_representatives[i] graph = self.graph_instances[graph_index] # print(graph) cppad_function = cppad_trace_graph_constraints(graph) nx = cppad_function.nx var_values = [(i + 1) / (nx + 1) for i in range(nx)] np = cppad_function.np param_values = [(i + 1) / (np + 1) for i in range(np)] # print(f"nx = {nx}, np = {np}") # print(f"var_values = {var_values}") # print(f"param_values = {param_values}") autodiff_structure = AutodiffSymbolicStructure() cppad_graph = CppADAutodiffGraph() cppad_autodiff( cppad_function, autodiff_structure, cppad_graph, var_values, param_values, ) # print(cppad_graph.f) self._assign_nl_constraint_group_autodiff_structure(i, autodiff_structure) self.nl_constraint_cppad_autodiff_graphs.append(cppad_graph) self.nl_constraint_autodiff_structures.append(autodiff_structure) # objective # self.nl_objective_cppad_autodiff_graphs.clear() # self.nl_objective_autodiff_structures.clear() for i in range( self.nl_objective_group_num_since_last_optimize, self.nl_objective_group_num ): graph_index = self.nl_objective_group_representatives[i] graph = self.graph_instances[graph_index] cppad_function = cppad_trace_graph_objective(graph) nx = cppad_function.nx var_values = [(i + 1) / (nx + 1) for i in range(nx)] np = cppad_function.np param_values = [(i + 1) / (np + 1) for i in range(np)] autodiff_structure = AutodiffSymbolicStructure() cppad_graph = CppADAutodiffGraph() cppad_autodiff( cppad_function, autodiff_structure, cppad_graph, var_values, param_values, ) self._assign_nl_objective_group_autodiff_structure(i, autodiff_structure) self.nl_objective_cppad_autodiff_graphs.append(cppad_graph) self.nl_objective_autodiff_structures.append(autodiff_structure) # compile the evaluators jit_compiler = self.jit_compiler if isinstance(jit_compiler, TCCJITCompiler): self._codegen_c() elif isinstance(jit_compiler, LLJITCompiler): self._codegen_llvm() def _codegen_c(self): jit_compiler: TCCJITCompiler = self.jit_compiler io = StringIO() generate_csrc_prelude(io) for group_index in range( self.nl_constraint_group_num_since_last_optimize, self.nl_constraint_group_num, ): cppad_autodiff_graph = self.nl_constraint_cppad_autodiff_graphs[group_index] autodiff_structure = self.nl_constraint_autodiff_structures[group_index] np = autodiff_structure.np ny = autodiff_structure.ny name = f"nlconstraint_{group_index}" f_name = name generate_csrc_from_graph( io, cppad_autodiff_graph.f, f_name, np=np, indirect_x=True, ) if autodiff_structure.has_jacobian: jacobian_name = name + "_jacobian" generate_csrc_from_graph( io, cppad_autodiff_graph.jacobian, jacobian_name, np=np, indirect_x=True, ) if autodiff_structure.has_hessian: hessian_name = name + "_hessian" generate_csrc_from_graph( io, cppad_autodiff_graph.hessian, hessian_name, np=np, hessian_lagrange=True, nw=ny, indirect_x=True, indirect_y=True, add_y=True, ) for group_index in range( self.nl_objective_group_num_since_last_optimize, self.nl_objective_group_num ): cppad_autodiff_graph = self.nl_objective_cppad_autodiff_graphs[group_index] autodiff_structure = self.nl_objective_autodiff_structures[group_index] np = autodiff_structure.np ny = autodiff_structure.ny name = f"nlobjective_{group_index}" f_name = name generate_csrc_from_graph( io, cppad_autodiff_graph.f, f_name, np=np, indirect_x=True, add_y=True ) if autodiff_structure.has_jacobian: jacobian_name = name + "_jacobian" generate_csrc_from_graph( io, cppad_autodiff_graph.jacobian, jacobian_name, np=np, indirect_x=True, indirect_y=True, add_y=True, ) if autodiff_structure.has_hessian: hessian_name = name + "_hessian" generate_csrc_from_graph( io, cppad_autodiff_graph.hessian, hessian_name, np=np, hessian_lagrange=True, nw=ny, indirect_x=True, indirect_y=True, add_y=True, ) csrc = io.getvalue() inst = jit_compiler.create_instance() jit_compiler.compile_string(inst, csrc) for group_index in range( self.nl_constraint_group_num_since_last_optimize, self.nl_constraint_group_num, ): name = f"nlconstraint_{group_index}" autodiff_structure = self.nl_constraint_autodiff_structures[group_index] has_parameter = autodiff_structure.has_parameter f_name = name jacobian_name = name + "_jacobian" hessian_name = name + "_hessian" f_ptr = inst.get_symbol(f_name) jacobian_ptr = hessian_ptr = 0 if autodiff_structure.has_jacobian: jacobian_ptr = inst.get_symbol(jacobian_name) if autodiff_structure.has_hessian: hessian_ptr = inst.get_symbol(hessian_name) evaluator = ConstraintAutodiffEvaluator( has_parameter, f_ptr, jacobian_ptr, hessian_ptr ) self._assign_nl_constraint_group_autodiff_evaluator(group_index, evaluator) for group_index in range( self.nl_objective_group_num_since_last_optimize, self.nl_objective_group_num ): name = f"nlobjective_{group_index}" autodiff_structure = self.nl_objective_autodiff_structures[group_index] has_parameter = autodiff_structure.has_parameter f_name = name jacobian_name = name + "_jacobian" hessian_name = name + "_hessian" f_ptr = inst.get_symbol(f_name) jacobian_ptr = hessian_ptr = 0 if autodiff_structure.has_jacobian: jacobian_ptr = inst.get_symbol(jacobian_name) if autodiff_structure.has_hessian: hessian_ptr = inst.get_symbol(hessian_name) evaluator = ObjectiveAutodiffEvaluator( has_parameter, f_ptr, jacobian_ptr, hessian_ptr ) self._assign_nl_objective_group_autodiff_evaluator(group_index, evaluator) def _codegen_llvm(self): jit_compiler: LLJITCompiler = self.jit_compiler module = ir.Module(name="my_module") create_llvmir_basic_functions(module) export_functions = [] for group_index in range( self.nl_constraint_group_num_since_last_optimize, self.nl_constraint_group_num, ): cppad_autodiff_graph = self.nl_constraint_cppad_autodiff_graphs[group_index] autodiff_structure = self.nl_constraint_autodiff_structures[group_index] np = autodiff_structure.np ny = autodiff_structure.ny name = f"nlconstraint_{group_index}" f_name = name generate_llvmir_from_graph( module, cppad_autodiff_graph.f, f_name, np=np, indirect_x=True, ) export_functions.append(f_name) if autodiff_structure.has_jacobian: jacobian_name = name + "_jacobian" generate_llvmir_from_graph( module, cppad_autodiff_graph.jacobian, jacobian_name, np=np, indirect_x=True, ) export_functions.append(jacobian_name) if autodiff_structure.has_hessian: hessian_name = name + "_hessian" generate_llvmir_from_graph( module, cppad_autodiff_graph.hessian, hessian_name, np=np, hessian_lagrange=True, nw=ny, indirect_x=True, indirect_y=True, add_y=True, ) export_functions.append(hessian_name) for group_index in range( self.nl_objective_group_num_since_last_optimize, self.nl_objective_group_num ): cppad_autodiff_graph = self.nl_objective_cppad_autodiff_graphs[group_index] autodiff_structure = self.nl_objective_autodiff_structures[group_index] np = autodiff_structure.np ny = autodiff_structure.ny name = f"nlobjective_{group_index}" f_name = name generate_llvmir_from_graph( module, cppad_autodiff_graph.f, f_name, np=np, indirect_x=True, add_y=True, ) export_functions.append(f_name) if autodiff_structure.has_jacobian: jacobian_name = name + "_jacobian" generate_llvmir_from_graph( module, cppad_autodiff_graph.jacobian, jacobian_name, np=np, indirect_x=True, indirect_y=True, add_y=True, ) export_functions.append(jacobian_name) if autodiff_structure.has_hessian: hessian_name = name + "_hessian" generate_llvmir_from_graph( module, cppad_autodiff_graph.hessian, hessian_name, np=np, hessian_lagrange=True, nw=ny, indirect_x=True, indirect_y=True, add_y=True, ) export_functions.append(hessian_name) rt = jit_compiler.compile_module(module, export_functions) for group_index in range( self.nl_constraint_group_num_since_last_optimize, self.nl_constraint_group_num, ): name = f"nlconstraint_{group_index}" autodiff_structure = self.nl_constraint_autodiff_structures[group_index] has_parameter = autodiff_structure.has_parameter f_name = name jacobian_name = name + "_jacobian" hessian_name = name + "_hessian" f_ptr = rt[f_name] jacobian_ptr = hessian_ptr = 0 if autodiff_structure.has_jacobian: jacobian_ptr = rt[jacobian_name] if autodiff_structure.has_hessian: hessian_ptr = rt[hessian_name] evaluator = ConstraintAutodiffEvaluator( has_parameter, f_ptr, jacobian_ptr, hessian_ptr ) self._assign_nl_constraint_group_autodiff_evaluator(group_index, evaluator) for group_index in range( self.nl_objective_group_num_since_last_optimize, self.nl_objective_group_num ): name = f"nlobjective_{group_index}" autodiff_structure = self.nl_objective_autodiff_structures[group_index] has_parameter = autodiff_structure.has_parameter f_name = name jacobian_name = name + "_jacobian" hessian_name = name + "_hessian" f_ptr = rt[f_name] jacobian_ptr = hessian_ptr = 0 if autodiff_structure.has_jacobian: jacobian_ptr = rt[jacobian_name] if autodiff_structure.has_hessian: hessian_ptr = rt[hessian_name] evaluator = ObjectiveAutodiffEvaluator( has_parameter, f_ptr, jacobian_ptr, hessian_ptr ) self._assign_nl_objective_group_autodiff_evaluator(group_index, evaluator) add_variables = make_variable_tupledict add_m_variables = make_variable_ndarray add_m_linear_constraints = add_matrix_constraints add_second_order_cone_constraint = bridge_soc_quadratic_constraint ================================================ FILE: src/pyoptinterface/_src/jit_c.py ================================================ import platform import os import tccbox from .tcc_interface_ext import load_library, TCCInstance system = platform.system() libtcc_dir = tccbox.tcc_lib_dir() # windows: libtcc.dll # linux: libtcc.so # macos: libtcc.dylib sharedlib_suffix = { "Windows": "dll", "Linux": "so", "Darwin": "dylib", }[system] libtcc_path = os.path.join(libtcc_dir, f"libtcc.{sharedlib_suffix}") ret = load_library(libtcc_path) if not ret: raise Exception("Failed to load libtcc from tccbox") # On Linux/Mac, tcc has lib/tcc/include/ and lib/tcc/libtcc1.a which must be included in compilation libtcc_extra_include_paths = [] libtcc_extra_lib_paths = [] libtcc_extra_lib_names = [] if system in ["Linux", "Darwin"]: libtcc_extra_include_paths.append(os.path.join(libtcc_dir, "tcc", "include")) libtcc_extra_lib_paths.append(os.path.join(libtcc_dir, "tcc")) libtcc_extra_lib_names.append("m") if system == "Linux": libtcc_extra_include_paths.extend( [ "/usr/include", "/usr/local/include", "/usr/include/x86_64-linux-gnu", # arm "/usr/include/aarch64-linux-gnu", ] ) libtcc_extra_lib_paths.extend( [ "/usr/lib", "/usr/local/lib", "/usr/lib/x86_64-linux-gnu", # arm "/usr/lib/aarch64-linux-gnu", ] ) class TCCJITCompiler: def __init__(self): self.instances = [] self.source_codes = [] def create_instance(self): inst = TCCInstance() inst.init() # Add extra include path and library path for path in libtcc_extra_include_paths: inst.add_include_path(path) inst.add_sysinclude_path(path) for path in libtcc_extra_lib_paths: inst.add_library_path(path) for name in libtcc_extra_lib_names: inst.add_library(name) self.instances.append(inst) return inst def compile_string(self, inst, c_code: str): inst.compile_string(c_code) self.source_codes.append(c_code) ================================================ FILE: src/pyoptinterface/_src/jit_llvm.py ================================================ from llvmlite import ir, binding from typing import List # Initialize LLVM try: # llvmlite older than 0.45.0 still requires this binding.initialize() except Exception: pass binding.initialize_native_target() binding.initialize_native_asmprinter() class LLJITCompiler: def __init__(self): target = binding.Target.from_default_triple() target_machine = target.create_target_machine(jit=True, opt=3) self.lljit = binding.create_lljit_compiler(target_machine) self.rts = [] self.source_codes = [] def compile_module(self, module: ir.Module, export_functions: List[str] = []): ir_str = str(module) self.source_codes.append(ir_str) builder = binding.JITLibraryBuilder().add_ir(ir_str).add_current_process() for f in export_functions: builder.export_symbol(f) n = len(self.rts) libname = f"lib{n}" rt = builder.link(self.lljit, libname) self.rts.append(rt) return rt ================================================ FILE: src/pyoptinterface/_src/knitro.py ================================================ import logging import os import platform import re from pathlib import Path from typing import Tuple, Union, overload from .aml import make_variable_ndarray, make_variable_tupledict from .attributes import ( ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, VariableAttribute, ) from .comparison_constraint import ComparisonConstraint from .core_ext import ( ConstraintIndex, ConstraintSense, ExprBuilder, ScalarAffineFunction, ScalarQuadraticFunction, VariableIndex, ) from .knitro_model_ext import KN, RawEnv, RawModel, load_library from .matrix import add_matrix_constraints from .nlexpr_ext import ExpressionGraph, ExpressionHandle from .nlfunc import ExpressionGraphContext, convert_to_expressionhandle from .solver_common import ( _get_entity_attribute, _get_model_attribute, _set_entity_attribute, _set_model_attribute, ) def detected_libraries(): libs = [] subdir = { "Linux": "lib", "Darwin": "lib", "Windows": "bin", }[platform.system()] libname_pattern = { "Linux": r"libknitro\.so.*", "Darwin": r"libknitro\.dylib.*", "Windows": r"knitro\d*\.dll", }[platform.system()] suffix_pattern = { "Linux": "*.so*", "Darwin": "*.dylib*", "Windows": "*.dll", }[platform.system()] # Environment variable knitro_dir = os.environ.get("KNITRODIR", None) if knitro_dir and os.path.exists(knitro_dir): dir = Path(knitro_dir) / subdir if dir.exists(): for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) try: import knitro dir = Path(knitro.__path__[0]) dir = dir / "lib" for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) except ImportError: pass # Default library names default_libname = { "Linux": ["libknitro.so"], "Darwin": ["libknitro.dylib"], "Windows": ["knitro.dll"], }[platform.system()] libs.extend(default_libname) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded KNITRO library: {lib}") return True return False autoload_library() # Variable Attribute variable_attribute_get_func_map = { VariableAttribute.Value: lambda model, v: model.get_value(v), VariableAttribute.LowerBound: lambda model, v: model.get_variable_lb(v), VariableAttribute.UpperBound: lambda model, v: model.get_variable_ub(v), VariableAttribute.Name: lambda model, v: model.get_variable_name(v), VariableAttribute.Domain: lambda model, v: model.get_variable_domain(v), VariableAttribute.ReducedCost: lambda model, v: model.get_variable_rc(v), } variable_attribute_set_func_map = { VariableAttribute.LowerBound: lambda model, v, x: model.set_variable_lb(v, x), VariableAttribute.UpperBound: lambda model, v, x: model.set_variable_ub(v, x), VariableAttribute.PrimalStart: lambda model, v, x: model.set_variable_start(v, x), VariableAttribute.Name: lambda model, v, x: model.set_variable_name(v, x), VariableAttribute.Domain: lambda model, v, x: model.set_variable_domain(v, x), } # Constraint Attribute constraint_attribute_get_func_map = { ConstraintAttribute.Primal: lambda model, c: model.get_constraint_primal(c), ConstraintAttribute.Dual: lambda model, c: model.get_constraint_dual(c), ConstraintAttribute.Name: lambda model, c: model.get_constraint_name(c), } constraint_attribute_set_func_map = { ConstraintAttribute.Name: lambda model, c, x: model.set_constraint_name(c, x), } _RAW_STATUS_STRINGS = [ (TerminationStatusCode.OPTIMAL, KN.RC_OPTIMAL), (TerminationStatusCode.OPTIMAL, KN.RC_OPTIMAL_OR_SATISFACTORY), (TerminationStatusCode.ALMOST_OPTIMAL, KN.RC_NEAR_OPT), (TerminationStatusCode.ALMOST_OPTIMAL, KN.RC_FEAS_XTOL), (TerminationStatusCode.ALMOST_OPTIMAL, KN.RC_FEAS_NO_IMPROVE), (TerminationStatusCode.ALMOST_OPTIMAL, KN.RC_FEAS_FTOL), (TerminationStatusCode.LOCALLY_SOLVED, KN.RC_FEAS_BEST), (TerminationStatusCode.LOCALLY_SOLVED, KN.RC_FEAS_MULTISTART), (TerminationStatusCode.INFEASIBLE, KN.RC_INFEASIBLE), (TerminationStatusCode.LOCALLY_INFEASIBLE, KN.RC_INFEAS_XTOL), (TerminationStatusCode.LOCALLY_INFEASIBLE, KN.RC_INFEAS_NO_IMPROVE), (TerminationStatusCode.LOCALLY_INFEASIBLE, KN.RC_INFEAS_MULTISTART), (TerminationStatusCode.INFEASIBLE, KN.RC_INFEAS_CON_BOUNDS), (TerminationStatusCode.INFEASIBLE, KN.RC_INFEAS_VAR_BOUNDS), (TerminationStatusCode.DUAL_INFEASIBLE, KN.RC_UNBOUNDED), (TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED, KN.RC_UNBOUNDED_OR_INFEAS), (TerminationStatusCode.ITERATION_LIMIT, KN.RC_ITER_LIMIT_FEAS), (TerminationStatusCode.ITERATION_LIMIT, KN.RC_ITER_LIMIT_INFEAS), (TerminationStatusCode.TIME_LIMIT, KN.RC_TIME_LIMIT_FEAS), (TerminationStatusCode.TIME_LIMIT, KN.RC_TIME_LIMIT_INFEAS), (TerminationStatusCode.OTHER_LIMIT, KN.RC_FEVAL_LIMIT_FEAS), (TerminationStatusCode.OTHER_LIMIT, KN.RC_FEVAL_LIMIT_INFEAS), (TerminationStatusCode.OTHER_LIMIT, KN.RC_MIP_EXH_FEAS), (TerminationStatusCode.OTHER_LIMIT, KN.RC_MIP_EXH_INFEAS), (TerminationStatusCode.NODE_LIMIT, KN.RC_MIP_NODE_LIMIT_FEAS), (TerminationStatusCode.NODE_LIMIT, KN.RC_MIP_NODE_LIMIT_INFEAS), (TerminationStatusCode.INTERRUPTED, KN.RC_USER_TERMINATION), (TerminationStatusCode.NUMERICAL_ERROR, KN.RC_EVAL_ERR), (TerminationStatusCode.MEMORY_LIMIT, KN.RC_OUT_OF_MEMORY), (TerminationStatusCode.OTHER_ERROR, KN.RC_CALLBACK_ERR), (TerminationStatusCode.OTHER_ERROR, KN.RC_LP_SOLVER_ERR), (TerminationStatusCode.OTHER_ERROR, KN.RC_LINEAR_SOLVER_ERR), (TerminationStatusCode.OTHER_ERROR, KN.RC_INTERNAL_ERROR), ] def _termination_status_knitro(model: "Model"): if model.is_dirty: return TerminationStatusCode.OPTIMIZE_NOT_CALLED code = model.solve_status for ts, rs in _RAW_STATUS_STRINGS: if code == rs: return ts return TerminationStatusCode.OTHER_ERROR def _result_status_knitro(model: "Model"): if model.is_dirty: return ResultStatusCode.NO_SOLUTION code = model.solve_status feasible = { KN.RC_OPTIMAL, KN.RC_OPTIMAL_OR_SATISFACTORY, KN.RC_NEAR_OPT, KN.RC_FEAS_XTOL, KN.RC_FEAS_NO_IMPROVE, KN.RC_FEAS_FTOL, KN.RC_FEAS_BEST, KN.RC_FEAS_MULTISTART, KN.RC_ITER_LIMIT_FEAS, KN.RC_TIME_LIMIT_FEAS, KN.RC_FEVAL_LIMIT_FEAS, KN.RC_MIP_EXH_FEAS, KN.RC_MIP_TERM_FEAS, KN.RC_MIP_SOLVE_LIMIT_FEAS, KN.RC_MIP_NODE_LIMIT_FEAS, } infeasible = { KN.RC_INFEASIBLE, KN.RC_INFEAS_XTOL, KN.RC_INFEAS_NO_IMPROVE, KN.RC_INFEAS_MULTISTART, KN.RC_INFEAS_CON_BOUNDS, KN.RC_INFEAS_VAR_BOUNDS, KN.RC_ITER_LIMIT_INFEAS, KN.RC_TIME_LIMIT_INFEAS, KN.RC_FEVAL_LIMIT_INFEAS, KN.RC_MIP_EXH_INFEAS, KN.RC_MIP_SOLVE_LIMIT_INFEAS, KN.RC_MIP_NODE_LIMIT_INFEAS, } if code in feasible: return ResultStatusCode.FEASIBLE_POINT if code in infeasible: return ResultStatusCode.INFEASIBLE_POINT return ResultStatusCode.NO_SOLUTION # Model Attribute model_attribute_get_func_map = { ModelAttribute.ObjectiveValue: lambda model: model.get_obj_value(), ModelAttribute.ObjectiveSense: lambda model: model.get_obj_sense(), ModelAttribute.TerminationStatus: _termination_status_knitro, ModelAttribute.RawStatusString: lambda model: ( f"KNITRO status code: {model.solve_status}" ), ModelAttribute.PrimalStatus: _result_status_knitro, ModelAttribute.NumberOfThreads: lambda model: model.get_raw_parameter( KN.PARAM_NUMTHREADS ), ModelAttribute.TimeLimitSec: lambda model: model.get_raw_parameter( KN.PARAM_MAXTIME ), ModelAttribute.BarrierIterations: lambda model: model.get_number_iterations(), ModelAttribute.NodeCount: lambda model: model.get_mip_node_count(), ModelAttribute.ObjectiveBound: lambda model: model.get_obj_bound(), ModelAttribute.RelativeGap: lambda model: model.get_mip_relative_gap(), ModelAttribute.SolverName: lambda model: model.get_solver_name(), ModelAttribute.SolverVersion: lambda model: model.get_release(), ModelAttribute.SolveTimeSec: lambda model: model.get_solve_time(), } model_attribute_set_func_map = { ModelAttribute.ObjectiveSense: lambda model, x: model.set_obj_sense(x), ModelAttribute.NumberOfThreads: lambda model, x: model.set_raw_parameter( KN.PARAM_NUMTHREADS, x ), ModelAttribute.Silent: lambda model, x: model.set_raw_parameter( KN.PARAM_OUTLEV, KN.OUTLEV_NONE if x else KN.OUTLEV_ITER_10 ), ModelAttribute.TimeLimitSec: lambda model, x: model.set_raw_parameter( KN.PARAM_MAXTIME, x ), } class Env(RawEnv): """ KNITRO license manager environment. """ @property def is_empty(self): return self.empty() class Model(RawModel): """ KNITRO model class for PyOptInterface. """ def __init__(self, env: Env = None) -> None: if env is not None: super().__init__(env) else: super().__init__() self.graph_map: dict[ExpressionGraph, int] = {} def _reset_graph_map(self) -> None: self.graph_map.clear() def _add_graph_expr( self, expr: ExpressionHandle ) -> tuple[ExpressionGraph, ExpressionHandle]: graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError("Expression should be convertible to ExpressionHandle") if graph not in self.graph_map: self.graph_map[graph] = len(self.graph_map) return graph, expr def init(self, env: Env = None) -> None: if env is not None: super().init(env) else: super().init() self._reset_graph_map() def close(self) -> None: super().close() self._reset_graph_map() @staticmethod def supports_variable_attribute( attribute: VariableAttribute, setable: bool = False ) -> bool: if setable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_constraint_attribute( attribute: ConstraintAttribute, setable: bool = False ) -> bool: if setable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map @staticmethod def supports_model_attribute( attribute: ModelAttribute, setable: bool = False ) -> bool: if setable: return attribute in model_attribute_set_func_map else: return attribute in model_attribute_get_func_map @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], interval: Tuple[float, float], name: str = "", ) -> ConstraintIndex: ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ) -> ConstraintIndex: ... def add_linear_constraint(self, arg, *args, **kwargs) -> ConstraintIndex: if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ) -> ConstraintIndex: ... @overload def add_quadratic_constraint( self, con: ComparisonConstraint, name: str = "", ) -> ConstraintIndex: ... def add_quadratic_constraint(self, arg, *args, **kwargs) -> ConstraintIndex: if isinstance(arg, ComparisonConstraint): return self._add_quadratic_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_quadratic_constraint(arg, *args, **kwargs) @overload def add_nl_constraint( self, expr, sense: ConstraintSense, rhs: float, /, name: str = "", ) -> ConstraintIndex: ... @overload def add_nl_constraint( self, expr, interval: Tuple[float, float], /, name: str = "", ) -> ConstraintIndex: ... @overload def add_nl_constraint( self, con, /, name: str = "", ) -> ConstraintIndex: ... def add_nl_constraint(self, expr, *args, **kwargs) -> ConstraintIndex: graph, expr = self._add_graph_expr(expr) return self._add_single_nl_constraint(graph, expr, *args, **kwargs) def add_nl_objective(self, expr) -> None: graph, expr = self._add_graph_expr(expr) self._add_single_nl_objective(graph, expr) def get_model_attribute(self, attr: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") return _get_model_attribute(self, attr, model_attribute_get_func_map, {}, e) def set_model_attribute(self, attr: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _set_model_attribute(self, attr, value, model_attribute_set_func_map, {}, e) def get_variable_attribute(self, variable: VariableIndex, attr: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") return _get_entity_attribute( self, variable, attr, variable_attribute_get_func_map, {}, e, ) def set_variable_attribute( self, variable: VariableIndex, attr: VariableAttribute, value ): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _set_entity_attribute( self, variable, attr, value, variable_attribute_set_func_map, {}, e, ) def get_constraint_attribute( self, constraint: ConstraintIndex, attr: ConstraintAttribute ): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") return _get_entity_attribute( self, constraint, attr, constraint_attribute_get_func_map, {}, e, ) def set_constraint_attribute( self, constraint: ConstraintIndex, attr: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _set_entity_attribute( self, constraint, attr, value, constraint_attribute_set_func_map, {}, e, ) @property def is_dirty(self) -> bool: return self.dirty() @property def is_empty(self) -> bool: return self.empty() and not self.graph_map @property def solve_status(self) -> int: return self.get_solve_status() Model.add_variables = make_variable_tupledict Model.add_m_variables = make_variable_ndarray Model.add_m_linear_constraints = add_matrix_constraints ================================================ FILE: src/pyoptinterface/_src/matrix.py ================================================ from .tupledict import tupledict from .core_ext import ScalarAffineFunction def iterate_sparse_matrix_rows(A): """ Iterate over rows of a sparse matrix and get non-zero elements for each row. A is a 2-dimensional scipy sparse matrix isinstance(A, scipy.sparse.sparray) = True and A.ndim = 2 """ from scipy.sparse import csr_array if not isinstance(A, csr_array): A = csr_array(A) # Convert to CSR format if not already for i in range(A.shape[0]): row_start = A.indptr[i] row_end = A.indptr[i + 1] row_indices = A.indices[row_start:row_end] row_data = A.data[row_start:row_end] yield row_indices, row_data def add_matrix_constraints(model, A, x, sense, b): """ add constraints Ax <= / = / >= b A is a 2-dimensional numpy array or scipy sparse matrix x is an iterable of variables sense is one of (poi.Leq, poi.Eq, poi.Geq) b is an iterable of values or a single scalar """ import numpy as np from scipy.sparse import sparray is_ndarray = isinstance(A, np.ndarray) is_sparse = isinstance(A, sparray) if not is_ndarray and not is_sparse: raise ValueError("A must be a numpy array or scipy.sparse array") ndim = A.ndim if ndim != 2: raise ValueError("A must be a 2-dimensional array") M, N = A.shape # turn x into a list if x is an iterable if isinstance(x, np.ndarray): xdim = x.ndim if xdim != 1: raise ValueError("x must be a 1-dimensional array") elif isinstance(x, tupledict): x = list(x.values()) else: x = list(x) if len(x) != N: raise ValueError("x must have length equal to the number of columns of A") # check b if np.isscalar(b): b = np.full(M, b) elif len(b) != M: raise ValueError("b must have length equal to the number of rows of A") constraints = np.empty(M, dtype=object) if is_ndarray: for i in range(M): expr = ScalarAffineFunction() row = A[i] for coef, var in zip(row, x): expr.add_term(var, coef) con = model.add_linear_constraint(expr, sense, b[i]) constraints[i] = con elif is_sparse: for i, (row_indices, row_data), rhs in zip( range(M), iterate_sparse_matrix_rows(A), b ): expr = ScalarAffineFunction() for j, coef in zip(row_indices, row_data): expr.add_term(x[j], coef) con = model.add_linear_constraint(expr, sense, rhs) constraints[i] = con return constraints ================================================ FILE: src/pyoptinterface/_src/monkeypatch.py ================================================ from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, ConstraintSense, ) from .comparison_constraint import ComparisonConstraint from .nlexpr_ext import ( ExpressionHandle, BinaryOperator, NaryOperator, UnaryOperator, ArrayType, ) from .nlfunc import ExpressionGraphContext, convert_to_expressionhandle def patch_core_compararison_operator(cls): def _compare(self, other, op: ConstraintSense): if isinstance( other, (VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder), ): lhs = self - other rhs = 0.0 elif isinstance(other, (int, float)): lhs = self rhs = other else: return NotImplemented constraint = ComparisonConstraint(op, lhs, rhs) return constraint def __eq__(self, other): return _compare(self, other, ConstraintSense.Equal) def __le__(self, other): return _compare(self, other, ConstraintSense.LessEqual) def __ge__(self, other): return _compare(self, other, ConstraintSense.GreaterEqual) cls.__eq__ = __eq__ cls.__le__ = __le__ cls.__ge__ = __ge__ def patch_more_compararison_operator(cls): def _compare(self, other, op: BinaryOperator): graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented converted_other = convert_to_expressionhandle(graph, other) if isinstance(converted_other, ExpressionHandle): converted_self = convert_to_expressionhandle(graph, self) fallback_result = graph.add_binary(op, converted_self, converted_other) return fallback_result else: return NotImplemented def __lt__(self, other): return _compare(self, other, BinaryOperator.LessThan) def __gt__(self, other): return _compare(self, other, BinaryOperator.GreaterThan) def __ne__(self, other): return _compare(self, other, BinaryOperator.NotEqual) cls.__lt__ = __lt__ cls.__gt__ = __gt__ cls.__ne__ = __ne__ def patch_quadratic_mul(cls): old_mul = getattr(cls, "__mul__", None) old_rmul = getattr(cls, "__rmul__", None) assert old_mul is not None, f"{cls} does not have __mul__ method" assert old_rmul is not None, f"{cls} does not have __rmul__ method" def __mul__(self, other): original_result = NotImplemented try: original_result = old_mul(self, other) except TypeError: original_result = NotImplemented if original_result is not NotImplemented: return original_result graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented converted_other = convert_to_expressionhandle(graph, other) if isinstance(converted_other, ExpressionHandle): fallback_result = converted_other * self return fallback_result else: return NotImplemented def __rmul__(self, other): original_result = NotImplemented try: original_result = old_rmul(self, other) except TypeError: original_result = NotImplemented if original_result is not NotImplemented: return original_result graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented converted_other = convert_to_expressionhandle(graph, other) if isinstance(converted_other, ExpressionHandle): fallback_result = converted_other * self return fallback_result else: return NotImplemented cls.__mul__ = __mul__ cls.__rmul__ = __rmul__ def patch_div(cls): old_truediv = getattr(cls, "__truediv__", None) assert old_truediv is not None, f"{cls} does not have __truediv__ method" def __truediv__(self, other): original_result = NotImplemented try: original_result = old_truediv(self, other) except TypeError: original_result = NotImplemented if original_result is not NotImplemented: return original_result graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented converted_other = convert_to_expressionhandle(graph, other) if isinstance(converted_other, ExpressionHandle): converted_self = convert_to_expressionhandle(graph, self) fallback_result = graph.add_binary( BinaryOperator.Div, converted_self, converted_other ) return fallback_result else: return NotImplemented def __rtruediv__(self, other): graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented converted_other = convert_to_expressionhandle(graph, other) if isinstance(converted_other, ExpressionHandle): converted_self = convert_to_expressionhandle(graph, self) fallback_result = graph.add_binary( BinaryOperator.Div, converted_other, converted_self ) return fallback_result else: return NotImplemented cls.__truediv__ = __truediv__ cls.__rtruediv__ = __rtruediv__ def pow_int(graph, expr, N): if N == 0: return 1 if N < 0: return graph.add_binary(BinaryOperator.Div, 1, pow_int(graph, expr, -N)) if N == 1: return expr M, r = divmod(N, 2) pow_2 = graph.add_nary(NaryOperator.Mul, [expr, expr]) pow_2M = pow_int(graph, pow_2, M) if r == 0: return pow_2M else: return graph.add_nary(NaryOperator.Mul, [pow_2M, expr]) def patch_expressionhandle(cls): def _binary_operator(self, other, op: BinaryOperator, swap=False): graph = ExpressionGraphContext.current_graph() other = convert_to_expressionhandle(graph, other) if not isinstance(other, ExpressionHandle): return NotImplemented if swap: new_expression = graph.add_binary(op, other, self) else: new_expression = graph.add_binary(op, self, other) return new_expression def _nary_operator(self, other, op: NaryOperator): graph = ExpressionGraphContext.current_graph() other = convert_to_expressionhandle(graph, other) if not isinstance(other, ExpressionHandle): return NotImplemented if self.array == ArrayType.Nary and graph.get_nary_operator(self) == op: graph.append_nary(self, other) return self else: new_expression = graph.add_nary(op, [self, other]) return new_expression def __add__(self, other): return self._nary_operator(other, NaryOperator.Add) def __radd__(self, other): return self._nary_operator(other, NaryOperator.Add) def __sub__(self, other): return self._binary_operator(other, BinaryOperator.Sub) def __rsub__(self, other): return self._binary_operator(other, BinaryOperator.Sub, swap=True) def __mul__(self, other): return self._nary_operator(other, NaryOperator.Mul) def __rmul__(self, other): return self._nary_operator(other, NaryOperator.Mul) def __truediv__(self, other): return self._binary_operator(other, BinaryOperator.Div) def __rtruediv__(self, other): return self._binary_operator(other, BinaryOperator.Div, swap=True) def __neg__(self): graph = ExpressionGraphContext.current_graph() new_expression = graph.add_unary(UnaryOperator.Neg, self) return new_expression def __pow__(self, other): # special case for integer powers, which can be implemented as a repeated multiplication if isinstance(other, int): graph = ExpressionGraphContext.current_graph() new_expression = pow_int(graph, self, other) return new_expression else: return self._binary_operator(other, BinaryOperator.Pow) def __rpow__(self, other): return self._binary_operator(other, BinaryOperator.Pow, swap=True) # compare operators def __eq__(self, other): return self._binary_operator(other, BinaryOperator.Equal) def __ne__(self, other): return self._binary_operator(other, BinaryOperator.NotEqual) def __lt__(self, other): return self._binary_operator(other, BinaryOperator.LessThan) def __le__(self, other): return self._binary_operator(other, BinaryOperator.LessEqual) def __gt__(self, other): return self._binary_operator(other, BinaryOperator.GreaterThan) def __ge__(self, other): return self._binary_operator(other, BinaryOperator.GreaterEqual) cls._binary_operator = _binary_operator cls._nary_operator = _nary_operator cls.__add__ = __add__ cls.__radd__ = __radd__ cls.__sub__ = __sub__ cls.__rsub__ = __rsub__ cls.__mul__ = __mul__ cls.__rmul__ = __rmul__ cls.__truediv__ = __truediv__ cls.__rtruediv__ = __rtruediv__ cls.__neg__ = __neg__ cls.__pow__ = __pow__ cls.__rpow__ = __rpow__ cls.__eq__ = __eq__ cls.__ne__ = __ne__ cls.__lt__ = __lt__ cls.__le__ = __le__ cls.__gt__ = __gt__ cls.__ge__ = __ge__ def patch_pow(cls): def __pow__(self, other): if other == 0: return 1 elif other == 1: return self elif other == 2: return self * self graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented self = convert_to_expressionhandle(graph, self) if isinstance(other, int): new_expression = pow_int(graph, self, other) return new_expression other = convert_to_expressionhandle(graph, other) if isinstance(other, ExpressionHandle): result = graph.add_binary(BinaryOperator.Pow, self, other) return result else: return NotImplemented def __rpow__(self, other): if other == 0: return 0 elif other == 1: return 1 graph = ExpressionGraphContext.current_graph_no_exception() if graph is None: return NotImplemented self = convert_to_expressionhandle(graph, self) other = convert_to_expressionhandle(graph, other) if isinstance(other, ExpressionHandle): result = graph.add_binary(BinaryOperator.Pow, other, self) return result else: return NotImplemented cls.__pow__ = __pow__ cls.__rpow__ = __rpow__ def _monkeypatch_all(): patch_core_compararison_operator(VariableIndex) patch_core_compararison_operator(ScalarAffineFunction) patch_core_compararison_operator(ScalarQuadraticFunction) patch_core_compararison_operator(ExprBuilder) patch_more_compararison_operator(VariableIndex) patch_more_compararison_operator(ScalarAffineFunction) patch_more_compararison_operator(ScalarQuadraticFunction) patch_more_compararison_operator(ExprBuilder) patch_quadratic_mul(ScalarQuadraticFunction) patch_quadratic_mul(ExprBuilder) patch_div(VariableIndex) patch_div(ScalarAffineFunction) patch_div(ScalarQuadraticFunction) patch_div(ExprBuilder) patch_pow(VariableIndex) patch_pow(ScalarAffineFunction) patch_pow(ScalarQuadraticFunction) patch_pow(ExprBuilder) patch_expressionhandle(ExpressionHandle) ================================================ FILE: src/pyoptinterface/_src/mosek.py ================================================ import os import platform from pathlib import Path import re import logging from typing import Optional, Union, overload from .mosek_model_ext import RawModel, Env, Enum, load_library from .attributes import ( VariableAttribute, ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, ConstraintType, ConstraintSense, ) from .comparison_constraint import ComparisonConstraint from .solver_common import ( _direct_get_model_attribute, _direct_set_model_attribute, _direct_get_entity_attribute, _direct_set_entity_attribute, ) from .aml import make_variable_tupledict, make_variable_ndarray from .matrix import add_matrix_constraints def detected_libraries(): libs = [] libname_pattern = { "Linux": r"libmosek64\.so", "Darwin": r"libmosek64\.dylib", "Windows": r"mosek64_(\d+)_(\d+)\.dll", }[platform.system()] suffix_pattern = { "Linux": "*.so", "Darwin": "*.dylib", "Windows": "*.dll", }[platform.system()] # Environment possible_envs = [ "MOSEK_11_1_BINDIR", "MOSEK_11_0_BINDIR", "MOSEK_10_2_BINDIR", "MOSEK_10_1_BINDIR", ] for env in possible_envs: home = os.environ.get(env, None) if home and os.path.exists(home): dir = Path(home) for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) break # mosekpy installation try: import mosek dir = Path(mosek.__path__[0]) libname_pattern = { "Linux": r"libmosek64\.so\.*", "Darwin": r"libmosek64\.*\.dylib", "Windows": r"mosek64_(\d+)_(\d+)\.dll", }[platform.system()] suffix_pattern = { "Linux": "*.so.*", "Darwin": "*.dylib", "Windows": "*.dll", }[platform.system()] for path in dir.glob(suffix_pattern): match = re.match(libname_pattern, path.name) if match: libs.append(str(path)) except Exception: pass # default names default_libname = { "Linux": ["libmosek64.so"], "Darwin": ["libmosek64.dylib"], "Windows": [ "mosek64_11_1.dll", "mosek64_11_0.dll", "mosek64_10_2.dll", "mosek64_10_1.dll", ], }[platform.system()] libs.extend(default_libname) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded Mosek library: {lib}") return True return False autoload_library() DEFAULT_ENV = None def init_default_env(): global DEFAULT_ENV if DEFAULT_ENV is None: DEFAULT_ENV = Env() variable_attribute_get_func_map = { VariableAttribute.Value: lambda model, v: model.get_value(v), VariableAttribute.LowerBound: lambda model, v: model.get_variable_lower_bound(v), VariableAttribute.UpperBound: lambda model, v: model.get_variable_upper_bound(v), # VariableAttribute.PrimalStart: lambda model, v: model.mip_start_values.get(v, None), VariableAttribute.Domain: lambda model, v: model.get_variable_type(v), VariableAttribute.Name: lambda model, v: model.get_variable_name(v), VariableAttribute.ReducedCost: lambda model, v: model.get_variable_dual(v), } variable_attribute_set_func_map = { VariableAttribute.LowerBound: lambda model, v, val: model.set_variable_lower_bound( v, val ), VariableAttribute.UpperBound: lambda model, v, val: model.set_variable_upper_bound( v, val ), VariableAttribute.PrimalStart: lambda model, v, val: model.set_variable_primal( v, val ), VariableAttribute.Domain: lambda model, v, val: model.set_variable_type(v, val), VariableAttribute.Name: lambda model, v, val: model.set_variable_name(v, val), } def get_primalstatus(model): if model.m_is_dirty: return ResultStatusCode.NO_SOLUTION solsta = model.getsolsta() if solsta == Enum.MSK_SOL_STA_UNKNOWN: return ResultStatusCode.UNKNOWN_RESULT_STATUS elif solsta == Enum.MSK_SOL_STA_OPTIMAL: return ResultStatusCode.FEASIBLE_POINT elif solsta == Enum.MSK_SOL_STA_PRIM_FEAS: return ResultStatusCode.FEASIBLE_POINT elif solsta == Enum.MSK_SOL_STA_DUAL_FEAS: return ResultStatusCode.UNKNOWN_RESULT_STATUS elif solsta == Enum.MSK_SOL_STA_PRIM_AND_DUAL_FEAS: return ResultStatusCode.FEASIBLE_POINT elif solsta == Enum.MSK_SOL_STA_PRIM_INFEAS_CER: return ResultStatusCode.NO_SOLUTION elif solsta == Enum.MSK_SOL_STA_DUAL_INFEAS_CER: return ResultStatusCode.INFEASIBILITY_CERTIFICATE elif solsta == Enum.MSK_SOL_STA_PRIM_ILLPOSED_CER: return ResultStatusCode.NO_SOLUTION elif solsta == Enum.MSK_SOL_STA_DUAL_ILLPOSED_CER: return ResultStatusCode.REDUCTION_CERTIFICATE elif solsta == Enum.MSK_SOL_STA_INTEGER_OPTIMAL: return ResultStatusCode.FEASIBLE_POINT else: return ResultStatusCode.UNKNOWN_RESULT_STATUS def get_dualstatus(model): if model.m_is_dirty: return ResultStatusCode.NO_SOLUTION solsta = model.getsolsta() if solsta == Enum.MSK_SOL_STA_UNKNOWN: return ResultStatusCode.UNKNOWN_RESULT_STATUS elif solsta == Enum.MSK_SOL_STA_OPTIMAL: return ResultStatusCode.FEASIBLE_POINT elif solsta == Enum.MSK_SOL_STA_PRIM_FEAS: return ResultStatusCode.UNKNOWN_RESULT_STATUS elif solsta == Enum.MSK_SOL_STA_DUAL_FEAS: return ResultStatusCode.FEASIBLE_POINT elif solsta == Enum.MSK_SOL_STA_PRIM_AND_DUAL_FEAS: return ResultStatusCode.FEASIBLE_POINT elif solsta == Enum.MSK_SOL_STA_PRIM_INFEAS_CER: return ResultStatusCode.INFEASIBILITY_CERTIFICATE elif solsta == Enum.MSK_SOL_STA_DUAL_INFEAS_CER: return ResultStatusCode.NO_SOLUTION elif solsta == Enum.MSK_SOL_STA_PRIM_ILLPOSED_CER: return ResultStatusCode.REDUCTION_CERTIFICATE elif solsta == Enum.MSK_SOL_STA_DUAL_ILLPOSED_CER: return ResultStatusCode.NO_SOLUTION elif solsta == Enum.MSK_SOL_STA_INTEGER_OPTIMAL: return ResultStatusCode.NO_SOLUTION else: return ResultStatusCode.UNKNOWN_RESULT_STATUS solsta_names = { Enum.MSK_SOL_STA_UNKNOWN: "MSK_SOL_STA_UNKNOWN", Enum.MSK_SOL_STA_OPTIMAL: "MSK_SOL_STA_OPTIMAL", Enum.MSK_SOL_STA_PRIM_FEAS: "MSK_SOL_STA_PRIM_FEAS", Enum.MSK_SOL_STA_DUAL_FEAS: "MSK_SOL_STA_DUAL_FEAS", Enum.MSK_SOL_STA_PRIM_AND_DUAL_FEAS: "MSK_SOL_STA_PRIM_AND_DUAL_FEAS", Enum.MSK_SOL_STA_PRIM_INFEAS_CER: "MSK_SOL_STA_PRIM_INFEAS_CER", Enum.MSK_SOL_STA_DUAL_INFEAS_CER: "MSK_SOL_STA_DUAL_INFEAS_CER", Enum.MSK_SOL_STA_PRIM_ILLPOSED_CER: "MSK_SOL_STA_PRIM_ILLPOSED_CER", Enum.MSK_SOL_STA_DUAL_ILLPOSED_CER: "MSK_SOL_STA_DUAL_ILLPOSED_CER", Enum.MSK_SOL_STA_INTEGER_OPTIMAL: "MSK_SOL_STA_INTEGER_OPTIMAL", } termination_names = { Enum.MSK_RES_OK: "MSK_RES_OK", Enum.MSK_RES_TRM_MAX_ITERATIONS: "MSK_RES_TRM_MAX_ITERATIONS", Enum.MSK_RES_TRM_MAX_TIME: "MSK_RES_TRM_MAX_TIME", Enum.MSK_RES_TRM_OBJECTIVE_RANGE: "MSK_RES_TRM_OBJECTIVE_RANGE", Enum.MSK_RES_TRM_STALL: "MSK_RES_TRM_STALL", Enum.MSK_RES_TRM_USER_CALLBACK: "MSK_RES_TRM_USER_CALLBACK", Enum.MSK_RES_TRM_MIO_NUM_RELAXS: "MSK_RES_TRM_MIO_NUM_RELAXS", Enum.MSK_RES_TRM_MIO_NUM_BRANCHES: "MSK_RES_TRM_MIO_NUM_BRANCHES", Enum.MSK_RES_TRM_NUM_MAX_NUM_INT_SOLUTIONS: "MSK_RES_TRM_NUM_MAX_NUM_INT_SOLUTIONS", Enum.MSK_RES_TRM_MAX_NUM_SETBACKS: "MSK_RES_TRM_MAX_NUM_SETBACKS", Enum.MSK_RES_TRM_NUMERICAL_PROBLEM: "MSK_RES_TRM_NUMERICAL_PROBLEM", Enum.MSK_RES_TRM_LOST_RACE: "MSK_RES_TRM_LOST_RACE", Enum.MSK_RES_TRM_INTERNAL: "MSK_RES_TRM_INTERNAL", Enum.MSK_RES_TRM_INTERNAL_STOP: "MSK_RES_TRM_INTERNAL_STOP", } def get_rawstatusstring(model): if model.m_is_dirty: return "OPTIMIZE_NOT_CALLED" trm = model.last_solve_return_code if trm is None: return "OPTIMIZE_NOT_CALLED" elif trm == Enum.MSK_RES_OK: solsta = model.getsolsta() return solsta_names[solsta] else: return termination_names[trm] def get_terminationstatus(model): if model.m_is_dirty: return TerminationStatusCode.OPTIMIZE_NOT_CALLED trm = model.last_solve_return_code prosta = model.getprosta() solsta = model.getsolsta() if trm is None: return TerminationStatusCode.OPTIMIZE_NOT_CALLED elif trm == Enum.MSK_RES_OK: if prosta in (Enum.MSK_PRO_STA_PRIM_INFEAS, Enum.MSK_PRO_STA_ILL_POSED): return TerminationStatusCode.INFEASIBLE elif prosta == Enum.MSK_PRO_STA_DUAL_INFEAS: return TerminationStatusCode.DUAL_INFEASIBLE elif prosta == Enum.MSK_PRO_STA_PRIM_INFEAS_OR_UNBOUNDED: return TerminationStatusCode.INFEASIBLE_OR_UNBOUNDED elif solsta in (Enum.MSK_SOL_STA_OPTIMAL, Enum.MSK_SOL_STA_INTEGER_OPTIMAL): return TerminationStatusCode.OPTIMAL else: return TerminationStatusCode.OTHER_ERROR # ?? elif trm == Enum.MSK_RES_TRM_MAX_ITERATIONS: return TerminationStatusCode.ITERATION_LIMIT elif trm == Enum.MSK_RES_TRM_MAX_TIME: return TerminationStatusCode.TIME_LIMIT elif trm == Enum.MSK_RES_TRM_OBJECTIVE_RANGE: return TerminationStatusCode.OBJECTIVE_LIMIT elif trm == Enum.MSK_RES_TRM_MIO_NUM_RELAXS: return TerminationStatusCode.OTHER_LIMIT elif trm == Enum.MSK_RES_TRM_MIO_NUM_BRANCHES: return TerminationStatusCode.NODE_LIMIT elif trm == Enum.MSK_RES_TRM_NUM_MAX_NUM_INT_SOLUTIONS: return TerminationStatusCode.SOLUTION_LIMIT elif trm == Enum.MSK_RES_TRM_STALL: return TerminationStatusCode.SLOW_PROGRESS elif trm == Enum.MSK_RES_TRM_USER_CALLBACK: return TerminationStatusCode.INTERRUPTED elif trm == Enum.MSK_RES_TRM_MAX_NUM_SETBACKS: return TerminationStatusCode.OTHER_LIMIT elif trm == Enum.MSK_RES_TRM_NUMERICAL_PROBLEM: return TerminationStatusCode.SLOW_PROGRESS elif trm == Enum.MSK_RES_TRM_INTERNAL: return TerminationStatusCode.OTHER_ERROR elif trm == Enum.MSK_RES_TRM_INTERNAL_STOP: return TerminationStatusCode.OTHER_ERROR else: TerminationStatusCode.OTHER_ERROR model_attribute_get_func_map = { ModelAttribute.ObjectiveSense: lambda model: model.get_obj_sense(), ModelAttribute.DualObjectiveValue: lambda model: model.getdualobj(), ModelAttribute.ObjectiveValue: lambda model: model.getprimalobj(), ModelAttribute.SolveTimeSec: lambda model: model.get_raw_information_double( "MSK_DINF_OPTIMIZER_TIME" ), ModelAttribute.NumberOfThreads: lambda model: model.get_raw_parameter_int( "MSK_IPAR_NUM_THREADS" ), ModelAttribute.RelativeGap: lambda model: model.get_raw_parameter_double( "MSK_DPAR_MIO_REL_GAP_CONST" ), ModelAttribute.TimeLimitSec: lambda model: model.get_raw_parameter_double( "MSK_DPAR_OPTIMIZER_MAX_TIME" ), ModelAttribute.DualStatus: get_dualstatus, ModelAttribute.PrimalStatus: get_primalstatus, ModelAttribute.RawStatusString: get_rawstatusstring, ModelAttribute.TerminationStatus: get_terminationstatus, ModelAttribute.Silent: lambda model: model.silent, ModelAttribute.SolverName: lambda _: "MOSEK", ModelAttribute.SolverVersion: lambda model: model.version_string(), } def set_silent(model, value: bool): model.silent = value if value: model.disable_log() else: model.set_logging(lambda msg: print(msg, end="")) model_attribute_set_func_map = { ModelAttribute.ObjectiveSense: lambda model, v: model.set_obj_sense(v), ModelAttribute.NumberOfThreads: lambda model, v: model.set_raw_parameter_int( "MSK_IPAR_NUM_THREADS", v ), ModelAttribute.RelativeGap: lambda model, v: model.set_raw_parameter_double( "MSK_DPAR_MIO_REL_GAP_CONST", v ), ModelAttribute.TimeLimitSec: lambda model, v: model.set_raw_parameter_double( "MSK_DPAR_OPTIMIZER_MAX_TIME", v ), ModelAttribute.Silent: set_silent, } constraint_attribute_get_func_map = { ConstraintAttribute.Name: lambda model, constraint: model.get_constraint_name( constraint ), ConstraintAttribute.Primal: lambda model, constraint: model.get_constraint_primal( constraint ), ConstraintAttribute.Dual: lambda model, constraint: model.get_constraint_dual( constraint ), } constraint_attribute_set_func_map = { ConstraintAttribute.Name: lambda model, constraint, value: model.set_constraint_name( constraint, value ), } def tell_enum_type(name: str): if name.startswith("MSK_D"): return float elif name.startswith("MSK_I"): return int elif name.startswith("MSK_S"): return str else: raise ValueError(f"Unknown parameter name: {name}") class Model(RawModel): def __init__(self, env=None): if env is None: init_default_env() env = DEFAULT_ENV super().__init__(env) # We must keep a reference to the environment to prevent it from being garbage collected self._env = env self.last_solve_return_code: Optional[int] = None self.silent = False self.set_logging(lambda msg: print(msg, end="")) @staticmethod def supports_variable_attribute(attribute: VariableAttribute, settable=False): if settable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_model_attribute(attribute: ModelAttribute, settable=False): if settable: return attribute in model_attribute_set_func_map else: return attribute in model_attribute_get_func_map @staticmethod def supports_constraint_attribute(attribute: ConstraintAttribute, settable=False): if settable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map def get_variable_attribute(self, variable, attribute: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") value = _direct_get_entity_attribute( self, variable, attribute, variable_attribute_get_func_map, e, ) return value def set_variable_attribute(self, variable, attribute: VariableAttribute, value): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _direct_set_entity_attribute( self, variable, attribute, value, variable_attribute_set_func_map, e, ) def number_of_constraints(self, type: ConstraintType): if type not in {ConstraintType.Linear, ConstraintType.Quadratic}: raise ValueError(f"Unknown constraint type: {type}") return self.getnumcon() def number_of_variables(self): return self.getnumvar() def get_model_attribute(self, attribute: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") value = _direct_get_model_attribute( self, attribute, model_attribute_get_func_map, e, ) return value def set_model_attribute(self, attribute: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _direct_set_model_attribute( self, attribute, value, model_attribute_set_func_map, e, ) def get_constraint_attribute(self, constraint, attribute: ConstraintAttribute): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") value = _direct_get_entity_attribute( self, constraint, attribute, constraint_attribute_get_func_map, e, ) return value def set_constraint_attribute( self, constraint, attribute: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _direct_set_entity_attribute( self, constraint, attribute, value, constraint_attribute_set_func_map, e, ) def get_raw_parameter(self, param_name: str): param_type = tell_enum_type(param_name) get_function_map = { int: self.get_raw_parameter_int, float: self.get_raw_parameter_double, str: self.get_raw_parameter_string, } get_function = get_function_map[param_type] return get_function(param_name) def set_raw_parameter(self, param_name: str, value): param_type = tell_enum_type(param_name) set_function_map = { int: self.set_raw_parameter_int, float: self.set_raw_parameter_double, str: self.set_raw_parameter_string, } set_function = set_function_map[param_type] set_function(param_name, value) def get_raw_information(self, param_name: str): param_type = tell_enum_type(param_name) get_function_map = { int: self.get_raw_information_int, float: self.get_raw_information_double, } get_function = get_function_map[param_type] return get_function(param_name) def optimize(self): ret = super().optimize() self.last_solve_return_code = ret @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_linear_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_quadratic_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_quadratic_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_quadratic_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_quadratic_constraint(arg, *args, **kwargs) add_variables = make_variable_tupledict add_m_variables = make_variable_ndarray add_m_linear_constraints = add_matrix_constraints ================================================ FILE: src/pyoptinterface/_src/nlfunc.py ================================================ from .nlexpr_ext import ( ExpressionGraph, ExpressionHandle, UnaryOperator, BinaryOperator, TernaryOperator, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, ConstraintSense, ) from .comparison_constraint import ComparisonConstraint import functools import math import threading class ExpressionGraphContext: _thread_local = threading.local() def __enter__(self): _thread_local = self._thread_local graph = ExpressionGraph() if not hasattr(_thread_local, "_graph_stack"): _thread_local._graph_stack = [graph] else: _thread_local._graph_stack.append(graph) return graph def __exit__(self, exc_type, exc_val, exc_tb): self._thread_local._graph_stack.pop() @classmethod def current_graph_no_exception(cls): _thread_local = cls._thread_local if not hasattr(_thread_local, "_graph_stack"): return None stack = _thread_local._graph_stack if not stack: return None return stack[-1] @classmethod def current_graph(cls): _thread_local = cls._thread_local if not hasattr(_thread_local, "_graph_stack"): raise RuntimeError("No active expression graph context") stack = _thread_local._graph_stack if not stack: raise RuntimeError("No active expression graph context") return stack[-1] compare_op_map = { ConstraintSense.LessEqual: BinaryOperator.LessEqual, ConstraintSense.GreaterEqual: BinaryOperator.GreaterEqual, ConstraintSense.Equal: BinaryOperator.Equal, } def convert_to_expressionhandle(graph, expr): if isinstance(expr, ExpressionHandle): return expr elif isinstance(expr, (int, float)): return graph.add_constant(expr) elif isinstance(expr, VariableIndex): return graph.merge_variableindex(expr) elif isinstance(expr, ScalarAffineFunction): return graph.merge_scalaraffinefunction(expr) elif isinstance(expr, ScalarQuadraticFunction): return graph.merge_scalarquadraticfunction(expr) elif isinstance(expr, ExprBuilder): return graph.merge_exprbuilder(expr) elif isinstance(expr, ComparisonConstraint): lhs = convert_to_expressionhandle(graph, expr.lhs) rhs = convert_to_expressionhandle(graph, expr.rhs) if isinstance(lhs, ExpressionHandle) and isinstance(rhs, ExpressionHandle): compare_op = compare_op_map[expr.sense] expr = graph.add_binary(compare_op, lhs, rhs) return expr else: raise TypeError(f"Unsupported expression type in comparison constraint") else: return expr def to_nlexpr(expr): if isinstance(expr, ExpressionHandle): return expr graph = ExpressionGraphContext.current_graph() if isinstance(expr, (int, float)): return graph.add_constant(expr) elif isinstance(expr, VariableIndex): return graph.merge_variableindex(expr) elif isinstance(expr, ScalarAffineFunction): return graph.merge_scalaraffinefunction(expr) elif isinstance(expr, ScalarQuadraticFunction): return graph.merge_scalarquadraticfunction(expr) elif isinstance(expr, ExprBuilder): return graph.merge_exprbuilder(expr) else: raise TypeError(f"Unsupported expression type: {type(expr)}") # Implement unary mathematical functions def unary_mathematical_function(math_function, op: UnaryOperator): @functools.wraps(math_function) def f(expr): if isinstance(expr, (int, float)): return math_function(expr) graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if isinstance(expr, ExpressionHandle): new_expression = graph.add_unary(op, expr) return new_expression else: return NotImplemented return f sin = unary_mathematical_function(math.sin, UnaryOperator.Sin) cos = unary_mathematical_function(math.cos, UnaryOperator.Cos) tan = unary_mathematical_function(math.tan, UnaryOperator.Tan) asin = unary_mathematical_function(math.asin, UnaryOperator.Asin) acos = unary_mathematical_function(math.acos, UnaryOperator.Acos) atan = unary_mathematical_function(math.atan, UnaryOperator.Atan) abs = unary_mathematical_function(math.fabs, UnaryOperator.Abs) sqrt = unary_mathematical_function(math.sqrt, UnaryOperator.Sqrt) exp = unary_mathematical_function(math.exp, UnaryOperator.Exp) log = unary_mathematical_function(math.log, UnaryOperator.Log) log10 = unary_mathematical_function(math.log10, UnaryOperator.Log10) # Implement binary mathematical functions def binary_mathematical_function(math_function, op: BinaryOperator): @functools.wraps(math_function) def f(expr1, expr2): is_number1 = isinstance(expr1, (int, float)) is_number2 = isinstance(expr2, (int, float)) if is_number1 and is_number2: return math_function(expr1, expr2) graph = ExpressionGraphContext.current_graph() expr1 = convert_to_expressionhandle(graph, expr1) expr2 = convert_to_expressionhandle(graph, expr2) if isinstance(expr1, ExpressionHandle) and isinstance(expr2, ExpressionHandle): new_expression = graph.add_binary(op, expr1, expr2) return new_expression else: return NotImplemented return f pow = binary_mathematical_function(math.pow, BinaryOperator.Pow) def ifelse(condition, true_expr, false_expr): graph = ExpressionGraphContext.current_graph() if isinstance(condition, bool): if condition: return true_expr else: return false_expr condition = convert_to_expressionhandle(graph, condition) true_expr = convert_to_expressionhandle(graph, true_expr) false_expr = convert_to_expressionhandle(graph, false_expr) new_expression = graph.add_ternary( TernaryOperator.IfThenElse, condition, true_expr, false_expr ) return new_expression ================================================ FILE: src/pyoptinterface/_src/solver_common.py ================================================ # include some common methods for solver intergaces def _get_model_attribute( model, attribute, get_func_map, value_translate_map, error_callback ): get_function = get_func_map.get(attribute, None) if not get_function: raise error_callback(attribute) value = get_function(model) translate_function = value_translate_map.get(attribute, None) if translate_function: value = translate_function(value) return value def _direct_get_model_attribute(model, attribute, get_func_map, error_callback): get_function = get_func_map.get(attribute, None) if not get_function: raise error_callback(attribute) value = get_function(model) return value def _set_model_attribute( model, attribute, value, set_func_map, value_translate_map, error_callback, ): translate_function = value_translate_map.get(attribute, None) if translate_function: value = translate_function(value) set_function = set_func_map.get(attribute, None) if not set_function: raise error_callback(attribute) set_function(model, value) def _direct_set_model_attribute( model, attribute, value, set_func_map, error_callback, ): set_function = set_func_map.get(attribute, None) if not set_function: raise error_callback(attribute) set_function(model, value) def _get_entity_attribute( model, entity, attribute, get_func_map, value_translate_map, error_callback ): get_function = get_func_map.get(attribute, None) if not get_function: raise error_callback(attribute) value = get_function(model, entity) translate_function = value_translate_map.get(attribute, None) if translate_function: value = translate_function(value) return value def _direct_get_entity_attribute( model, entity, attribute, get_func_map, error_callback ): get_function = get_func_map.get(attribute, None) if not get_function: raise error_callback(attribute) value = get_function(model, entity) return value def _set_entity_attribute( model, entity, attribute, value, set_func_map, value_translate_map, error_callback, ): translate_function = value_translate_map.get(attribute, None) if translate_function: value = translate_function(value) set_function = set_func_map.get(attribute, None) if not set_function: raise error_callback(attribute) set_function(model, entity, value) def _direct_set_entity_attribute( model, entity, attribute, value, set_func_map, error_callback, ): set_function = set_func_map.get(attribute, None) if not set_function: raise error_callback(attribute) set_function(model, entity, value) ================================================ FILE: src/pyoptinterface/_src/tupledict.py ================================================ from typing import Iterable from itertools import product WILDCARD = "*" class tupledict(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__select_cache = None self.__scalar = False def __setitem__(self, key, value): super().__setitem__(key, value) self.__select_cache = None def __delitem__(self, key): super().__delitem__(key) self.__select_cache = None def __check_key_length(self): if len(self) == 0: return first_key = next(iter(self.keys())) if isinstance(first_key, tuple): key_len = len(first_key) for k in self.keys(): if len(k) != key_len: raise ValueError( "The length of keys in tupledict is not consistent" ) self.__key_len = key_len self.__scalar = False else: key_len = 1 self.__key_len = key_len self.__scalar = True def select(self, *keys, with_key=False): if len(keys) == 0: yield from () return if self.__scalar: if len(keys) != 1: raise ValueError( "The length of keys is not consistent with the scalar tupledict" ) key = keys[0] if key == WILDCARD: if with_key: yield from self.items() else: yield from self.values() else: if key in self: if with_key: yield key, self[key] else: yield self[key] else: yield from () else: if self.__select_cache is None: self.__check_key_length() self.__select_cache = dict() key_len = self.__key_len if len(keys) > key_len: raise ValueError( f"Too many keys for tupledict with {key_len}-tuple keys" ) no_wildcard_indices = tuple( i for i, key in enumerate(keys) if key != WILDCARD ) if len(no_wildcard_indices) == 0: if with_key: yield from self.items() else: yield from self.values() return no_wildcard_keys = tuple(keys[i] for i in no_wildcard_indices) select_cache = self.__select_cache indices_cache = select_cache.get(no_wildcard_indices, None) if indices_cache is None: indices_cache = dict() for k, v in self.items(): indices = tuple(k[i] for i in no_wildcard_indices) kv_cache = indices_cache.get(indices, None) if kv_cache is None: indices_cache[indices] = [(k, v)] else: kv_cache.append((k, v)) select_cache[no_wildcard_indices] = indices_cache kv_cache = indices_cache.get(no_wildcard_keys, None) if kv_cache is None: yield from () else: if with_key: yield from kv_cache else: for k, v in kv_cache: yield v def clean(self): self.__select_cache = None def map(self, func): return tupledict((k, func(v)) for k, v in self.items()) def flatten_tuple(t): # (1, (2, 3), (4, 5)) -> (1, 2, 3, 4, 5) for it in t: if isinstance(it, tuple) or isinstance(it, list): for element in it: yield element else: yield it def make_tupledict(*coords: Iterable, rule): d = {} assert len(coords) > 0 for coord in product(*coords): # (1, (2, 3), (4, 5)) -> (1, 2, 3, 4, 5) coord = tuple(flatten_tuple(coord)) value = rule(*coord) if len(coord) == 1: coord = coord[0] if value is not None: d[coord] = value return tupledict(d) ================================================ FILE: src/pyoptinterface/_src/xpress.py ================================================ import os import platform from pathlib import Path import logging from typing import Dict, Tuple, Union, overload from .xpress_model_ext import RawModel, Env, load_library, XPRS from .attributes import ( VariableAttribute, ConstraintAttribute, ModelAttribute, ResultStatusCode, TerminationStatusCode, ) from .core_ext import ( VariableIndex, ScalarAffineFunction, ScalarQuadraticFunction, ExprBuilder, VariableDomain, ConstraintType, ConstraintSense, ObjectiveSense, ) from .nlexpr_ext import ExpressionHandle from .nlfunc import ExpressionGraphContext, convert_to_expressionhandle from .comparison_constraint import ComparisonConstraint from .solver_common import ( _get_model_attribute, _set_model_attribute, _get_entity_attribute, _direct_get_entity_attribute, _set_entity_attribute, _direct_set_entity_attribute, ) from .aml import make_variable_tupledict, make_variable_ndarray from .matrix import add_matrix_constraints def detected_libraries(): libs = [] subdir = { "Linux": "lib", "Darwin": "lib", "Windows": "bin", }[platform.system()] libname = { "Linux": "libxprs.so", "Darwin": "libxprs.dylib", "Windows": "xprs.dll", }[platform.system()] # Environment home = os.environ.get("XPRESSDIR", None) if home and os.path.exists(home): lib = Path(home) / subdir / libname if lib.exists(): libs.append(str(lib)) # default names default_libname = libname libs.append(default_libname) return libs def autoload_library(): libs = detected_libraries() for lib in libs: ret = load_library(lib) if ret: logging.info(f"Loaded Xpress library: {lib}") return True return False autoload_library() # LP status codes (TerminationStatus, RawStatusString) _RAW_LPSTATUS_STRINGS = { XPRS.LPSTATUS.UNSTARTED: ( TerminationStatusCode.OPTIMIZE_NOT_CALLED, "LP problem optimization not started", ), XPRS.LPSTATUS.OPTIMAL: ( TerminationStatusCode.OPTIMAL, "Optimal LP solution found", ), XPRS.LPSTATUS.INFEAS: ( TerminationStatusCode.INFEASIBLE, "Infeasible LP problem", ), XPRS.LPSTATUS.CUTOFF: ( TerminationStatusCode.OBJECTIVE_LIMIT, "LP problem objective worse than cutoff value", ), XPRS.LPSTATUS.UNFINISHED: ( TerminationStatusCode.ITERATION_LIMIT, "LP problem optimization unfinished", ), XPRS.LPSTATUS.UNBOUNDED: ( TerminationStatusCode.DUAL_INFEASIBLE, "LP problem is unbounded", ), XPRS.LPSTATUS.CUTOFF_IN_DUAL: ( TerminationStatusCode.OBJECTIVE_LIMIT, "LP dual bound is worse than dual cutoff value", ), XPRS.LPSTATUS.UNSOLVED: ( TerminationStatusCode.NUMERICAL_ERROR, "LP problem could not be solved due to numerical issues", ), XPRS.LPSTATUS.NONCONVEX: ( TerminationStatusCode.INVALID_MODEL, "LP problem contains quadratic data which is not convex, consider using FICO Xpress Global", ), } # MIP status codes (TerminationStatus, RawStatusString) _RAW_MIPSTATUS_STRINGS = { XPRS.MIPSTATUS.NOT_LOADED: ( TerminationStatusCode.OPTIMIZE_NOT_CALLED, "MIP problem has not been loaded", ), XPRS.MIPSTATUS.LP_NOT_OPTIMAL: ( TerminationStatusCode.ITERATION_LIMIT, "MIP search incomplete, the initial continuous relaxation has not been solved and no integer solution has been found", ), XPRS.MIPSTATUS.LP_OPTIMAL: ( TerminationStatusCode.ITERATION_LIMIT, "MIP search incomplete, the initial continuous relaxation has been solved and no integer solution has been found", ), XPRS.MIPSTATUS.NO_SOL_FOUND: ( TerminationStatusCode.ITERATION_LIMIT, "MIP search incomplete, no integer solution found", ), XPRS.MIPSTATUS.SOLUTION: ( TerminationStatusCode.ITERATION_LIMIT, "MIP search incomplete, an integer solution has been found", ), XPRS.MIPSTATUS.INFEAS: ( TerminationStatusCode.INFEASIBLE, "MIP search complete, MIP is infeasible, no integer solution found", ), XPRS.MIPSTATUS.OPTIMAL: ( TerminationStatusCode.OPTIMAL, "MIP search complete, optimal integer solution found", ), XPRS.MIPSTATUS.UNBOUNDED: ( TerminationStatusCode.DUAL_INFEASIBLE, "MIP search incomplete, the initial continuous relaxation was found to be unbounded. A solution may have been found", ), } # NLP status codes (TerminationStatus, RawStatusString) _RAW_NLPSTATUS_STRINGS = { XPRS.NLPSTATUS.UNSTARTED: ( TerminationStatusCode.OPTIMIZE_NOT_CALLED, "Optimization unstarted", ), XPRS.NLPSTATUS.SOLUTION: ( TerminationStatusCode.LOCALLY_SOLVED, "Solution found", ), XPRS.NLPSTATUS.OPTIMAL: ( TerminationStatusCode.OPTIMAL, "Globally optimal", ), XPRS.NLPSTATUS.NOSOLUTION: ( TerminationStatusCode.ITERATION_LIMIT, "No solution found", ), XPRS.NLPSTATUS.INFEASIBLE: ( TerminationStatusCode.INFEASIBLE, "Proven infeasible", ), XPRS.NLPSTATUS.UNBOUNDED: ( TerminationStatusCode.DUAL_INFEASIBLE, "Locally unbounded", ), XPRS.NLPSTATUS.UNFINISHED: ( TerminationStatusCode.ITERATION_LIMIT, "Not yet solved to completion", ), XPRS.NLPSTATUS.UNSOLVED: ( TerminationStatusCode.NUMERICAL_ERROR, "Could not be solved due to numerical issues", ), } def get_terminationstatus(model): opt_type = model.get_optimize_type() if opt_type == XPRS.OPTIMIZETYPE.LP: raw_status = model.get_lp_status() status_string_pair = _RAW_LPSTATUS_STRINGS.get(raw_status, None) elif opt_type == XPRS.OPTIMIZETYPE.MIP: raw_status = model.get_mip_status() status_string_pair = _RAW_MIPSTATUS_STRINGS.get(raw_status, None) else: # NLP or LOCAL raw_status = model.get_nlp_status() status_string_pair = _RAW_NLPSTATUS_STRINGS.get(raw_status, None) if not status_string_pair: raise ValueError(f"Unknown termination status: {raw_status}") return status_string_pair[0] def get_primalstatus(model): opt_type = model.get_optimize_type() if opt_type == XPRS.OPTIMIZETYPE.LP: status = model.get_lp_status() if status == XPRS.LPSTATUS.OPTIMAL: return ResultStatusCode.FEASIBLE_POINT return ResultStatusCode.NO_SOLUTION elif opt_type == XPRS.OPTIMIZETYPE.MIP: status = model.get_mip_status() if model.get_raw_attribute_int_by_id(XPRS.MIPSOLS) > 0: return ResultStatusCode.FEASIBLE_POINT return ResultStatusCode.NO_SOLUTION else: # NLP or LOCAL status = model.get_nlp_status() if status in ( XPRS.NLPSTATUS.OPTIMAL, XPRS.NLPSTATUS.SOLUTION, XPRS.NLPSTATUS.UNBOUNDED, ): return ResultStatusCode.FEASIBLE_POINT return ResultStatusCode.NO_SOLUTION def get_dualstatus(model): opt_type = model.get_optimize_type() if opt_type != XPRS.OPTIMIZETYPE.LP: return ResultStatusCode.NO_SOLUTION status = model.get_lp_status() if status == XPRS.LPSTATUS.OPTIMAL: return ResultStatusCode.FEASIBLE_POINT return ResultStatusCode.NO_SOLUTION def get_rawstatusstring(model): opt_type = model.get_optimize_type() if opt_type == XPRS.OPTIMIZETYPE.LP: raw_status = model.get_lp_status() status_string_pair = _RAW_LPSTATUS_STRINGS.get(raw_status, None) elif opt_type == XPRS.OPTIMIZETYPE.MIP: raw_status = model.get_mip_status() status_string_pair = _RAW_MIPSTATUS_STRINGS.get(raw_status, None) else: # NLP or LOCAL raw_status = model.get_nlp_status() status_string_pair = _RAW_NLPSTATUS_STRINGS.get(raw_status, None) if not status_string_pair: raise ValueError(f"Unknown termination status: {raw_status}") return status_string_pair[1] # Variable maps variable_attribute_get_func_map = { # UB, LB, Name, etc VariableAttribute.Value: lambda model, v: model.get_variable_value(v), VariableAttribute.LowerBound: lambda model, v: model.get_variable_lowerbound(v), VariableAttribute.UpperBound: lambda model, v: model.get_variable_upperbound(v), VariableAttribute.PrimalStart: lambda model, v: model.get_variable_mip_start(v), VariableAttribute.Domain: lambda model, v: model.get_variable_type(v), VariableAttribute.Name: lambda model, v: model.get_variable_name(v), VariableAttribute.IISLowerBound: lambda model, v: model.get_variable_lowerbound_IIS( v ), VariableAttribute.IISUpperBound: lambda model, v: model.get_variable_upperbound_IIS( v ), VariableAttribute.ReducedCost: lambda model, v: model.get_variable_rc(v), } variable_attribute_set_func_map = ( { # Subset of the previous one about stuff that can be set VariableAttribute.LowerBound: lambda model, v, x: model.set_variable_lowerbound( v, x ), VariableAttribute.UpperBound: lambda model, v, x: model.set_variable_upperbound( v, x ), VariableAttribute.PrimalStart: lambda model, v, x: model.set_variable_mip_start( v, x ), VariableAttribute.Domain: lambda model, v, x: model.set_variable_type(v, x), VariableAttribute.Name: lambda model, v, x: model.set_variable_name(v, x), } ) constraint_attribute_get_func_map = { ConstraintAttribute.Name: lambda model, c: model.get_constraint_name(c), ConstraintAttribute.Primal: lambda model, c: model.get_normalized_rhs(c) - model.get_constraint_slack(c), ConstraintAttribute.Dual: lambda model, c: model.get_constraint_dual(c), ConstraintAttribute.IIS: lambda model, c: model.is_constraint_in_IIS(c), } constraint_attribute_set_func_map = { ConstraintAttribute.Name: lambda model, constraint, value: model.set_constraint_name( constraint, value ), } model_attribute_get_func_map = { ModelAttribute.Name: lambda model: model.get_problem_name(), ModelAttribute.ObjectiveSense: lambda model: model.get_raw_attribute_dbl_by_id( XPRS.OBJSENSE ), ModelAttribute.BarrierIterations: lambda model: model.get_raw_attribute_int_by_id( XPRS.BARITER ), ModelAttribute.DualObjectiveValue: lambda model: model.get_raw_attribute_dbl_by_id( XPRS.LPOBJVAL ), ModelAttribute.NodeCount: lambda model: model.get_raw_attribute_int_by_id( XPRS.NODES ), ModelAttribute.ObjectiveBound: lambda model: model.get_raw_attribute_dbl_by_id( XPRS.LPOBJVAL ), ModelAttribute.ObjectiveValue: lambda model: model.get_raw_attribute_dbl_by_id( XPRS.OBJVAL ), ModelAttribute.SimplexIterations: lambda model: model.get_raw_attribute_int_by_id( XPRS.SIMPLEXITER ), ModelAttribute.SolveTimeSec: lambda model: model.get_raw_attribute_dbl_by_id( XPRS.TIME ), ModelAttribute.NumberOfThreads: lambda model: model.get_raw_control_int( XPRS.THREADS ), ModelAttribute.RelativeGap: lambda model: model.get_raw_control_dbl_by_id( XPRS.MIPRELSTOP ), ModelAttribute.TimeLimitSec: lambda model: model.get_raw_contorl_dbl_by_id( XPRS.TIMELIMIT ), ModelAttribute.DualStatus: get_dualstatus, ModelAttribute.PrimalStatus: get_primalstatus, ModelAttribute.RawStatusString: get_rawstatusstring, ModelAttribute.TerminationStatus: get_terminationstatus, ModelAttribute.Silent: lambda model: model.get_raw_control_int_by_id(XPRS.OUTPUTLOG) == 0, ModelAttribute.SolverName: lambda _: "FICO Xpress", ModelAttribute.SolverVersion: lambda model: model.version_string(), } model_control_set_func_map = { ModelAttribute.Name: lambda model, value: model.set_problem_name(value), ModelAttribute.ObjectiveSense: lambda model, value: model.set_raw_control_dbl_by_id( XPRS.OBJSENSE, value ), ModelAttribute.NumberOfThreads: lambda model, value: model.set_raw_control_int_by_id( XPRS.THREADS, value ), ModelAttribute.TimeLimitSec: lambda model, value: model.set_raw_control_dbl_by_id( XPRS.TIMELIMIT, value ), ModelAttribute.Silent: lambda model, value: model.set_raw_control_int_by_id( XPRS.OUTPUTLOG, 0 if value else 1 ), } model_attribute_get_translate_func_map = { ModelAttribute.ObjectiveSense: lambda v: { XPRS.OBJ_MINIMIZE: ObjectiveSense.Minimize, XPRS.OBJ_MAXIMIZE: ObjectiveSense.Maximize, }[v], } model_attribute_set_translate_func_map = { ModelAttribute.ObjectiveSense: lambda v: { ObjectiveSense.Minimize: XPRS.OBJ_MINIMIZE, ObjectiveSense.Maximize: XPRS.OBJ_MAXIMIZE, }[v], } DEFAULT_ENV = None def init_default_env(): global DEFAULT_ENV if DEFAULT_ENV is None: DEFAULT_ENV = Env() class Model(RawModel): def __init__(self, env=None): # Initializing with raw model object if isinstance(env, RawModel): super().__init__(env) return if env is None: init_default_env() env = DEFAULT_ENV super().__init__(env) self.mip_start_values: Dict[VariableIndex, float] = dict() def optimize(self): if self._is_mip(): mip_start = self.mip_start_values if len(mip_start) != 0: variables = list(mip_start.keys()) values = list(mip_start.values()) self.add_mip_start(variables, values) mip_start.clear() super().optimize() @staticmethod def supports_variable_attribute(attribute: VariableAttribute, settable=False): if settable: return attribute in variable_attribute_set_func_map else: return attribute in variable_attribute_get_func_map @staticmethod def supports_model_attribute(attribute: ModelAttribute, settable=False): if settable: return attribute in model_control_set_func_map else: return attribute in model_attribute_get_func_map @staticmethod def supports_constraint_attribute(attribute: ConstraintAttribute, settable=False): if settable: return attribute in constraint_attribute_set_func_map else: return attribute in constraint_attribute_get_func_map def get_variable_attribute(self, variable, attribute: VariableAttribute): def e(attribute): raise ValueError(f"Unknown variable attribute to get: {attribute}") value = _direct_get_entity_attribute( self, variable, attribute, variable_attribute_get_func_map, e, ) return value def set_variable_attribute(self, variable, attribute: VariableAttribute, value): def e(attribute): raise ValueError(f"Unknown variable attribute to set: {attribute}") _direct_set_entity_attribute( self, variable, attribute, value, variable_attribute_set_func_map, e, ) def number_of_constraints(self, type: ConstraintType): if type in {ConstraintType.Linear, ConstraintType.Quadratic}: return self.get_raw_attribute_int_by_id(XPRS.ROWS) if type == ConstraintType.SOS: return self.get_raw_attribute_int_by_id(XPRS.SETS) raise ValueError(f"Unknown constraint type: {type}") def number_of_variables(self): return self.get_raw_attribute_int_by_id(XPRS.INPUTCOLS) def get_model_attribute(self, attribute: ModelAttribute): def e(attribute): raise ValueError(f"Unknown model attribute to get: {attribute}") value = _get_model_attribute( self, attribute, model_attribute_get_func_map, model_attribute_get_translate_func_map, e, ) return value def set_model_attribute(self, attribute: ModelAttribute, value): def e(attribute): raise ValueError(f"Unknown model attribute to set: {attribute}") _set_model_attribute( self, attribute, value, model_control_set_func_map, model_attribute_set_translate_func_map, e, ) def get_constraint_attribute(self, constraint, attribute: ConstraintAttribute): def e(attribute): raise ValueError(f"Unknown constraint attribute to get: {attribute}") value = _direct_get_entity_attribute( self, constraint, attribute, constraint_attribute_get_func_map, e, ) return value def set_constraint_attribute( self, constraint, attribute: ConstraintAttribute, value ): def e(attribute): raise ValueError(f"Unknown constraint attribute to set: {attribute}") _direct_set_entity_attribute( self, constraint, attribute, value, constraint_attribute_set_func_map, e, ) @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_linear_constraint( self, expr: Union[VariableIndex, ScalarAffineFunction, ExprBuilder], interval: Tuple[float, float], name: str = "", ): ... @overload def add_linear_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_linear_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_linear_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_linear_constraint(arg, *args, **kwargs) @overload def add_quadratic_constraint( self, expr: Union[ScalarQuadraticFunction, ExprBuilder], sense: ConstraintSense, rhs: float, name: str = "", ): ... @overload def add_quadratic_constraint( self, con: ComparisonConstraint, name: str = "", ): ... def add_quadratic_constraint(self, arg, *args, **kwargs): if isinstance(arg, ComparisonConstraint): return self._add_quadratic_constraint( arg.lhs, arg.sense, arg.rhs, *args, **kwargs ) else: return self._add_quadratic_constraint(arg, *args, **kwargs) @overload def add_nl_constraint( self, expr, sense: ConstraintSense, rhs: float, /, name: str = "", ): ... @overload def add_nl_constraint( self, expr, interval: Tuple[float, float], /, name: str = "", ): ... @overload def add_nl_constraint( self, con, /, name: str = "", ): ... def add_nl_constraint(self, expr, *args, **kwargs): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError("Expression should be convertible to ExpressionHandle") con = self._add_single_nl_constraint(graph, expr, *args, **kwargs) return con def add_nl_objective(self, expr): graph = ExpressionGraphContext.current_graph() expr = convert_to_expressionhandle(graph, expr) if not isinstance(expr, ExpressionHandle): raise ValueError("Expression should be convertible to ExpressionHandle") self._add_single_nl_objective(graph, expr) def set_callback(self, cb, where): def cb_wrapper(raw_model, ctx): # Warning: This is super hacky. We need to provide a complete Model # object to the callback (a RawModel is not enough). Xpress invokes # callbacks with thread-local problem pointers, so we reuse the # original object by swapping the pointers temporarily. This is # okay because we've serialized access to the model anyway (GIL # limitation). But all of this happens at the C++ level, here we # only need to provide the user callback with the original complete # Model object. So it looks like we're giving the original model to # the callbacks, but in reality we pull a switcheroo behind the # curtains. cb(self, ctx) super().set_callback(cb_wrapper, where) add_variables = make_variable_tupledict add_m_variables = make_variable_ndarray add_m_linear_constraints = add_matrix_constraints ================================================ FILE: src/pyoptinterface/copt.py ================================================ from pyoptinterface._src.copt import Model, autoload_library from pyoptinterface._src.copt_model_ext import ( EnvConfig, Env, COPT, load_library, is_library_loaded, ) __all__ = [ "Model", "EnvConfig", "Env", "COPT", "autoload_library", "load_library", "is_library_loaded", ] ================================================ FILE: src/pyoptinterface/gurobi.py ================================================ from pyoptinterface._src.gurobi import Model, Env, autoload_library from pyoptinterface._src.gurobi_model_ext import ( GRB, load_library, is_library_loaded, ) __all__ = [ "Model", "Env", "GRB", "autoload_library", "load_library", "is_library_loaded", ] ================================================ FILE: src/pyoptinterface/highs.py ================================================ from pyoptinterface._src.highs import Model, autoload_library from pyoptinterface._src.highs_model_ext import Enum, load_library, is_library_loaded __all__ = ["Model", "Enum", "autoload_library", "load_library", "is_library_loaded"] ================================================ FILE: src/pyoptinterface/ipopt.py ================================================ from pyoptinterface._src.ipopt import Model from pyoptinterface._src.ipopt_model_ext import ( ApplicationReturnStatus, load_library, is_library_loaded, ) __all__ = ["Model", "ApplicationReturnStatus", "load_library", "is_library_loaded"] ================================================ FILE: src/pyoptinterface/knitro.py ================================================ from pyoptinterface._src.knitro import Env, Model, autoload_library from pyoptinterface._src.knitro_model_ext import ( KN, load_library, is_library_loaded, has_valid_license, ) __all__ = [ "Env", "Model", "KN", "autoload_library", "load_library", "is_library_loaded", "has_valid_license", ] ================================================ FILE: src/pyoptinterface/mosek.py ================================================ from pyoptinterface._src.mosek import Model, autoload_library from pyoptinterface._src.mosek_model_ext import ( Env, Enum, load_library, is_library_loaded, ) __all__ = [ "Model", "Env", "Enum", "autoload_library", "load_library", "is_library_loaded", ] ================================================ FILE: src/pyoptinterface/nl.py ================================================ from pyoptinterface._src.nlfunc import ( ExpressionGraphContext as graph, to_nlexpr, sin, cos, tan, asin, acos, atan, abs, sqrt, exp, log, log10, pow, ifelse, ) ================================================ FILE: src/pyoptinterface/xpress.py ================================================ from pyoptinterface._src.xpress import Model, autoload_library from pyoptinterface._src.xpress_model_ext import ( Env, XPRS, load_library, is_library_loaded, license, beginlicensing, endlicensing, ) __all__ = [ "Model", "Env", "XPRS", "autoload_library", "load_library", "is_library_loaded", "license", "beginlicensing", "endlicensing", ] ================================================ FILE: tests/conftest.py ================================================ import pytest import platform from pyoptinterface import gurobi, xpress, copt, mosek, highs, ipopt, knitro nlp_model_dict = {} if ipopt.is_library_loaded(): def llvm(): return ipopt.Model(jit="LLVM") def c(): return ipopt.Model(jit="C") nlp_model_dict["ipopt_llvm"] = llvm system = platform.system() if system != "Darwin": # On macOS, loading dynamic library of Gurobi/Xpress/COPT/Mosek before loading libtcc will cause memory error # The reason is still unclear nlp_model_dict["ipopt_c"] = c if copt.is_library_loaded(): nlp_model_dict["copt"] = copt.Model if knitro.is_library_loaded() and knitro.has_valid_license(): nlp_model_dict["knitro"] = knitro.Model @pytest.fixture(params=nlp_model_dict.keys()) def nlp_model_ctor(request): name = request.param ctor = nlp_model_dict[name] return ctor model_interface_dict_full = {} if gurobi.is_library_loaded(): model_interface_dict_full["gurobi"] = gurobi.Model if xpress.is_library_loaded(): model_interface_dict_full["xpress"] = xpress.Model if copt.is_library_loaded(): model_interface_dict_full["copt"] = copt.Model if mosek.is_library_loaded(): model_interface_dict_full["mosek"] = mosek.Model if highs.is_library_loaded(): model_interface_dict_full["highs"] = highs.Model if knitro.is_library_loaded() and knitro.has_valid_license(): model_interface_dict_full["knitro"] = knitro.Model @pytest.fixture(params=model_interface_dict_full.keys()) def model_interface(request): name = request.param model_interface_class = model_interface_dict_full[name] return model_interface_class() model_interface_dict_oneshot = model_interface_dict_full.copy() if ipopt.is_library_loaded(): model_interface_dict_oneshot["ipopt"] = ipopt.Model @pytest.fixture(params=model_interface_dict_oneshot.keys()) def model_interface_oneshot(request): name = request.param model_interface_class = model_interface_dict_oneshot[name] return model_interface_class() ================================================ FILE: tests/simple_cb.py ================================================ import pyoptinterface as poi from pyoptinterface import gurobi, xpress GRB = gurobi.GRB XPRS = xpress.XPRS def simple_cb(f): model = f() x = model.add_variable(lb=0.0, ub=20.0) y = model.add_variable(lb=8.0, ub=20.0) model.set_variable_attribute(x, poi.VariableAttribute.Name, "x") model.set_variable_attribute(y, poi.VariableAttribute.Name, "y") obj = x * x + y * y model.set_objective(obj, poi.ObjectiveSense.Minimize) conexpr = x + y model.add_linear_constraint( conexpr, poi.ConstraintSense.GreaterEqual, 10.0, name="con1" ) def cb(model, where): runtime = 0.0 coldel = 0 rowdel = 0 if isinstance(model, gurobi.Model) and where == GRB.Callback.PRESOLVE: runtime = model.cb_get_info(GRB.Callback.RUNTIME) coldel = model.cb_get_info(GRB.Callback.PRE_COLDEL) rowdel = model.cb_get_info(GRB.Callback.PRE_ROWDEL) print(f"Runtime: {runtime}, Coldel: {coldel}, Rowdel: {rowdel}") if isinstance(model, xpress.Model) and where == XPRS.CB_CONTEXT.PRESOLVE: runtime = model.get_raw_attribute_dbl_by_id(XPRS.TIME) coldel = model.get_raw_attribute_int_by_id( XPRS.ORIGINALCOLS ) - model.get_raw_attribute_int_by_id(XPRS.COLS) rowdel = model.get_raw_attribute_int_by_id( XPRS.ORIGINALROWS ) - model.get_raw_attribute_int_by_id(XPRS.ROWS) print( f"CB[AFTER-PRESOLVE] >> Runtime: {runtime}, Coldel: {coldel}, Rowdel: {rowdel}" ) if isinstance(model, xpress.Model) and where == XPRS.CB_CONTEXT.MESSAGE: args = model.cb_get_arguments() print(f"CB[MESSAGE-{args.msgtype}] >> {args.msg}") if isinstance(model, gurobi.Model): model.set_callback(cb) elif isinstance(model, xpress.Model): model.set_callback(cb, XPRS.CB_CONTEXT.PRESOLVE | XPRS.CB_CONTEXT.MESSAGE) model.set_model_attribute(poi.ModelAttribute.Silent, False) model.optimize() if xpress.is_library_loaded(): simple_cb(xpress.Model) if gurobi.is_library_loaded(): simple_cb(gurobi.Model) ================================================ FILE: tests/test_basic.py ================================================ import pyoptinterface as poi import numpy as np from pytest import approx from pyoptinterface._src.core_ext import IntMonotoneIndexer def test_basic(): vars = [poi.VariableIndex(i) for i in range(10)] v = vars[0] assert v.index == 0 t = poi.ExprBuilder() t += v t += v * v assert t.degree() == 2 assert t.empty() == False t = poi.ExprBuilder(v) t.add_affine_term(vars[1], 2.0) t += 3.0 * vars[2] t -= 4.0 * vars[3] assert t.degree() == 1 assert t.empty() == False saf = poi.ScalarAffineFunction(t) saf.canonicalize() assert list(saf.variables) == [0, 1, 2, 3] assert np.allclose(saf.coefficients, [1.0, 2.0, 3.0, -4.0]) saf = 2.0 - 1.0 * saf * -4.0 / 2.0 + 1.0 saf.canonicalize() assert list(saf.variables) == [0, 1, 2, 3] assert np.allclose(saf.coefficients, [2.0, 4.0, 6.0, -8.0]) assert saf.constant == approx(3.0) sqf = v * v + 2.0 * v + 1.0 sqf = 2.0 - 1.0 * sqf * -4.0 / 2.0 + 1.0 sqf.canonicalize() assert list(sqf.variable_1s) == [0] assert list(sqf.variable_2s) == [0] assert np.allclose(sqf.coefficients, [2.0]) assert list(sqf.affine_part.variables) == [0] assert np.allclose(sqf.affine_part.coefficients, [4.0]) assert sqf.affine_part.constant == approx(5.0) t = poi.ExprBuilder(v) t *= 2.0 assert t.degree() == 1 t *= vars[0] assert t.degree() == 2 t /= 0.5 assert t.degree() == 2 t = poi.ExprBuilder(v * v + 2.0 * v + 1.0) t += 2.0 t -= v t *= 4.0 t /= 2.0 sqf = poi.ScalarQuadraticFunction(t) sqf.canonicalize() assert list(sqf.variable_1s) == [0] assert list(sqf.variable_2s) == [0] assert np.allclose(sqf.coefficients, [2.0]) assert list(sqf.affine_part.variables) == [0] assert np.allclose(sqf.affine_part.coefficients, [2.0]) assert sqf.affine_part.constant == approx(6.0) def test_affineexpr_from_numpy(): N = 25 coefs = np.arange(N, dtype=np.float64) vars = np.arange(N, dtype=np.int_) * 2 coefs.flags.writeable = False vars.flags.writeable = False expr = poi.ScalarAffineFunction.from_numpy(coefs, vars) assert list(expr.variables) == list(vars) assert np.allclose(expr.coefficients, coefs) constant = 3.0 expr = poi.ScalarAffineFunction.from_numpy(coefs, vars, constant) assert list(expr.variables) == list(vars) assert np.allclose(expr.coefficients, coefs) assert expr.constant == approx(constant) def test_monotoneindexer(): indexer = IntMonotoneIndexer() N = 200 for i in range(N): index = indexer.add_index() assert index == i for i in range(N - 1): indexer.delete_index(i) x = indexer.get_index(i + 1) assert x == 0 x = indexer.get_index(N - 1) assert x == N - 1 - (i + 1) x = indexer.get_index(0) assert x == -1 ================================================ FILE: tests/test_close.py ================================================ from pyoptinterface import gurobi, xpress, copt, mosek envs = [] models = [] if gurobi.is_library_loaded(): envs.append(gurobi.Env) models.append(gurobi.Model) if xpress.is_library_loaded(): envs.append(xpress.Env) models.append(xpress.Model) if copt.is_library_loaded(): envs.append(copt.Env) models.append(copt.Model) if mosek.is_library_loaded(): envs.append(mosek.Env) models.append(mosek.Model) def test_close(): for env, model in zip(envs, models): env_instance = env() model_instance = model(env_instance) model_instance.close() env_instance.close() ================================================ FILE: tests/test_compare_constraint.py ================================================ import pyoptinterface as poi import pytest def test_compare_constraint(model_interface): model = model_interface x = model.add_variable(lb=0.0) y = model.add_variable(lb=0.0) def t_expr(expr): con = model.add_linear_constraint(expr >= 1.0) model.set_objective(expr) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(1.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(expr <= 2.0) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(2.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(expr == 3.0) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(3.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(1.0 <= expr) model.set_objective(expr) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(1.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(2.0 >= expr) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(2.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(3.0 == expr) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(3.0, abs=1e-6) model.delete_constraint(con) def t_binary_expr(lhs, rhs): expr = lhs - rhs con = model.add_linear_constraint(lhs >= rhs + 1.0) model.set_objective(expr) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(1.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(lhs <= rhs + 2.0) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(2.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(lhs == rhs + 3.0) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(3.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(1.0 + rhs <= lhs) model.set_objective(expr) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(1.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(2.0 + rhs >= lhs) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(2.0, abs=1e-6) model.delete_constraint(con) con = model.add_linear_constraint(3.0 + rhs == lhs) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(3.0, abs=1e-6) model.delete_constraint(con) expr = x + y t_expr(expr) expr = poi.ExprBuilder(x + y) t_expr(expr) for lhs in [x, poi.ScalarAffineFunction(x), poi.ExprBuilder(x)]: for rhs in [y, poi.ScalarAffineFunction(y), poi.ExprBuilder(y)]: t_binary_expr(lhs, rhs) if hasattr(model, "add_quadratic_constraint"): con_expr = x * x + y * y expr = x + y con = model.add_quadratic_constraint(con_expr <= 18.0) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(6.0, abs=1e-5) model.delete_constraint(con) con = model.add_quadratic_constraint(32.0 >= con_expr) model.set_objective(expr, poi.ObjectiveSense.Maximize) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(8.0, abs=1e-5) model.delete_constraint(con) ================================================ FILE: tests/test_exp_cone.py ================================================ import pyoptinterface as poi from pytest import approx import pytest import math def test_exp_cone(model_interface): model = model_interface if not hasattr(model, "add_exp_cone_constraint"): pytest.skip("Model interface does not support exponential cone constraints") N = 4 x = model.add_variables(range(N), lb=1.0) t = model.add_variables(range(N), lb=0.0) one = model.add_variable(lb=1.0, ub=1.0, name="one") obj = poi.quicksum(t) model.set_objective(obj, poi.ObjectiveSense.Minimize) con = poi.make_tupledict( range(N), rule=lambda i: model.add_exp_cone_constraint([t[i], one, x[i]]) ) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = x.map(model.get_value) t_val = t.map(model.get_value) for i in range(N): assert x_val[i] == approx(1.0) assert t_val[i] == approx(math.e) obj_val = model.get_value(obj) assert obj_val == approx(N * math.e) model.delete_constraint(con[0]) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = x.map(model.get_value) t_val = t.map(model.get_value) for i in range(1, N): assert x_val[i] == approx(1.0) assert t_val[i] == approx(math.e) obj_val = model.get_value(obj) assert obj_val == approx((N - 1) * math.e) ================================================ FILE: tests/test_iis.py ================================================ import pyoptinterface as poi import pytest def test_constraint_iis(model_interface): model = model_interface if not hasattr(model, "computeIIS"): pytest.skip("Model interface does not support IIS computation") x = model.add_variable(lb=0.0, name="x") y = model.add_variable(lb=0.0, name="y") con1 = model.add_linear_constraint(x + y, poi.Geq, 5.0) con2 = model.add_linear_constraint(x + 2 * y, poi.Leq, 1.0) model.set_objective(x) model.computeIIS() con1_iis = model.get_constraint_attribute(con1, poi.ConstraintAttribute.IIS) con2_iis = model.get_constraint_attribute(con2, poi.ConstraintAttribute.IIS) assert con1_iis assert con2_iis def test_variable_iis(model_interface): model = model_interface if not hasattr(model, "computeIIS"): pytest.skip("Model interface does not support IIS computation") x = model.add_variable(lb=0.0, ub=2.0, name="x") y = model.add_variable(lb=0.0, ub=3.0, name="y") con1 = model.add_linear_constraint(x + y, poi.Geq, 6.0) model.set_objective(x) model.computeIIS() con1_iis = model.get_constraint_attribute(con1, poi.ConstraintAttribute.IIS) x_lb_iis = model.get_variable_attribute(x, poi.VariableAttribute.IISLowerBound) x_ub_iis = model.get_variable_attribute(x, poi.VariableAttribute.IISUpperBound) y_lb_iis = model.get_variable_attribute(y, poi.VariableAttribute.IISLowerBound) y_ub_iis = model.get_variable_attribute(y, poi.VariableAttribute.IISUpperBound) assert con1_iis assert not x_lb_iis assert x_ub_iis assert not y_lb_iis assert y_ub_iis ================================================ FILE: tests/test_in_constraint.py ================================================ import pyoptinterface as poi from pyoptinterface import nl, gurobi import pytest def test_linear_in_constraint(model_interface_oneshot): model = model_interface_oneshot if isinstance(model, gurobi.Model): pytest.skip("Gurobi does not support range linear constraints") x = model.add_variable(lb=0.0) y = model.add_variable(lb=0.0) expr = x + y model.add_linear_constraint(expr, (0, 1.0)) model.set_objective(expr) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(0.0, abs=1e-6) model.set_objective(-expr) model.optimize() expr_value = model.get_value(expr) assert expr_value == pytest.approx(1.0, rel=1e-6) def test_nl_in_constraint(nlp_model_ctor): model = nlp_model_ctor() x = model.add_variable(lb=0.0) y = model.add_variable(lb=0.0) with nl.graph(): expr = nl.exp(x) + nl.exp(y) model.add_nl_constraint(expr, (5.0, 10.0)) model.add_nl_objective(expr) model.optimize() objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert objective_value == pytest.approx(5.0, abs=1e-6) ================================================ FILE: tests/test_ipopt.py ================================================ import pytest import pyoptinterface as poi from pyoptinterface import ipopt, nl pytestmark = pytest.mark.skipif( not ipopt.is_library_loaded(), reason="IPOPT library not available" ) OPTIMIZE_RE = r"optimize\(\)" def _build_simple_model(): """Build a simple model: min x^2 s.t. x >= 0.5""" model = ipopt.Model() x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x**2) con = model.add_linear_constraint(x, poi.Geq, 0.5) return model, x, con class TestUnoptimizedModelRaises: """Accessing variable/constraint/objective values before calling optimize() should raise.""" def test_get_variable_value_before_optimize(self): model, x, _con = _build_simple_model() with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_value(x) def test_get_objective_value_before_optimize(self): model, _x, _con = _build_simple_model() with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) def test_get_constraint_primal_before_optimize(self): model, _x, con = _build_simple_model() with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_constraint_attribute(con, poi.ConstraintAttribute.Primal) def test_get_constraint_dual_before_optimize(self): model, _x, con = _build_simple_model() with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_constraint_attribute(con, poi.ConstraintAttribute.Dual) class TestDirtyAfterModificationRaises: """After a successful optimize(), modifying the model (adding variables/constraints) should make it dirty and re-querying values should raise.""" def test_dirty_after_add_variable(self): model, x, con = _build_simple_model() model.optimize() # Values should be accessible now model.get_value(x) model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) # Add a new variable to make the model dirty model.add_variable(lb=0.0, ub=10.0) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_value(x) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_constraint_attribute(con, poi.ConstraintAttribute.Primal) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_constraint_attribute(con, poi.ConstraintAttribute.Dual) def test_dirty_after_add_linear_constraint(self): model, x, _con = _build_simple_model() model.optimize() model.get_value(x) # Add a linear constraint to make the model dirty model.add_linear_constraint(x, poi.Leq, 5.0) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_value(x) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) def test_dirty_after_add_quadratic_constraint(self): model, x, _con = _build_simple_model() model.optimize() model.get_value(x) # Add a quadratic constraint to make the model dirty model.add_quadratic_constraint(x**2, poi.Leq, 25.0) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_value(x) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) def test_dirty_after_add_nl_constraint(self): model, x, _con = _build_simple_model() model.optimize() model.get_value(x) # Add a nonlinear constraint to make the model dirty with nl.graph(): model.add_nl_constraint(nl.exp(x), poi.Leq, 100.0) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_value(x) with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) def test_reoptimize_clears_dirty(self): """After re-optimizing, values should be accessible again.""" model, x, con = _build_simple_model() model.optimize() # Modify model model.add_linear_constraint(x, poi.Leq, 5.0) # Dirty – raises with pytest.raises(RuntimeError, match=OPTIMIZE_RE): model.get_value(x) # Re-optimize – should clear the dirty flag model.optimize() val = model.get_value(x) assert val == pytest.approx(0.5, abs=1e-6) obj = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj == pytest.approx(0.25, abs=1e-6) primal = model.get_constraint_attribute(con, poi.ConstraintAttribute.Primal) assert primal == pytest.approx(0.5, abs=1e-6) ================================================ FILE: tests/test_knitro.py ================================================ import pytest from pytest import approx from pyoptinterface import knitro import pyoptinterface as poi pytestmark = pytest.mark.skipif( not knitro.is_library_loaded() or not knitro.has_valid_license(), reason="KNITRO library is not loaded or license is not valid", ) def test_new_model_without_env(): """Test creating a model without an environment (default behavior).""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) y = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x + y, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x + y, poi.ConstraintSense.GreaterEqual, 5.0) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_value(x) y_val = model.get_value(y) assert x_val + y_val == approx(5.0) def test_new_model_with_env(): """Test creating a model with an environment.""" env = knitro.Env() model = knitro.Model(env=env) x = model.add_variable(lb=0.0, ub=10.0) y = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x + y, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x + y, poi.ConstraintSense.GreaterEqual, 5.0) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_value(x) y_val = model.get_value(y) assert x_val + y_val == approx(5.0) def test_multiple_models_single_env(): """Test creating multiple models sharing the same environment.""" env = knitro.Env() model1 = knitro.Model(env=env) model2 = knitro.Model(env=env) x1 = model1.add_variable(lb=0.0, ub=10.0) model1.set_objective(x1, poi.ObjectiveSense.Minimize) model1.add_linear_constraint(x1, poi.ConstraintSense.GreaterEqual, 3.0) model1.optimize() x2 = model2.add_variable(lb=0.0, ub=10.0) model2.set_objective(x2, poi.ObjectiveSense.Minimize) model2.add_linear_constraint(x2, poi.ConstraintSense.GreaterEqual, 5.0) model2.optimize() assert model1.get_value(x1) == approx(3.0) assert model2.get_value(x2) == approx(5.0) def test_multiple_models_multiple_envs(): """Test creating multiple models each with its own environment.""" env1 = knitro.Env() env2 = knitro.Env() model1 = knitro.Model(env=env1) model2 = knitro.Model(env=env2) x1 = model1.add_variable(lb=0.0, ub=10.0) model1.set_objective(x1, poi.ObjectiveSense.Minimize) model1.add_linear_constraint(x1, poi.ConstraintSense.GreaterEqual, 4.0) model1.optimize() x2 = model2.add_variable(lb=0.0, ub=10.0) model2.set_objective(x2, poi.ObjectiveSense.Minimize) model2.add_linear_constraint(x2, poi.ConstraintSense.GreaterEqual, 6.0) model2.optimize() assert model1.get_value(x1) == approx(4.0) assert model2.get_value(x2) == approx(6.0) def test_env_lifetime(): """Test that environment properly manages its lifetime.""" env = knitro.Env() model = knitro.Model(env=env) x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 2.0) model.optimize() assert model.get_value(x) == approx(2.0) model.close() try: del env except Exception as e: pytest.fail(f"Deleting environment raised an error: {e}") def test_env_close(): """Test that env.close() properly releases the license.""" env = knitro.Env() model = knitro.Model(env=env) x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 1.0) model.optimize() assert model.get_value(x) == approx(1.0) model.close() try: env.close() except Exception as e: pytest.fail(f"env.close() raised an error: {e}") def test_init_with_env(): """Test using init method with an environment.""" env = knitro.Env() model = knitro.Model() model.init(env) x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 1.0) model.optimize() assert model.get_value(x) == approx(1.0) model.close() del model del env def test_model_with_empty_env(): """Test that creating a model with an empty environment raises an error.""" env = knitro.Env(empty=True) assert env.is_empty with pytest.raises(RuntimeError, match="Empty environment"): knitro.Model(env=env) def test_model_init_with_empty_env_after_start(): """Test that initializing a model with an empty environment after starting raises an error.""" env = knitro.Env(empty=True) assert env.is_empty env.start() assert knitro.Model(env=env) is not None def test_model_dirty(): """Test the dirty method.""" model = knitro.Model() assert model.dirty() is True x = model.add_variable(lb=0.0, ub=10.0) assert model.dirty() is True model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 1.0) model.optimize() assert model.dirty() is False model.add_variable(lb=0.0, ub=5.0) assert model.dirty() is True def test_model_is_dirty(): """Test the is_dirty property.""" model = knitro.Model() assert model.is_dirty is True x = model.add_variable(lb=0.0, ub=10.0) assert model.is_dirty is True model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 1.0) model.optimize() assert model.is_dirty is False model.add_variable(lb=0.0, ub=5.0) assert model.is_dirty is True def test_model_empty(): """Test the empty() method.""" model = knitro.Model() assert model.empty() is False model.close() assert model.empty() is True def test_model_is_empty_property(): """Test the is_empty property.""" model = knitro.Model() assert model.is_empty is False model.close() assert model.is_empty is True def test_number_of_variables(): """Test number_of_variables() method.""" model = knitro.Model() assert model.number_of_variables() == 0 model.add_variable() assert model.number_of_variables() == 1 model.add_variable() model.add_variable() assert model.number_of_variables() == 3 def test_number_of_constraints(): """Test number_of_constraints() method.""" model = knitro.Model() x = model.add_variable(lb=-10.0, ub=10.0) y = model.add_variable(lb=-10.0, ub=10.0) assert model.number_of_constraints() == 0 assert model.number_of_constraints(poi.ConstraintType.Linear) == 0 assert model.number_of_constraints(poi.ConstraintType.Quadratic) == 0 model.add_linear_constraint(x + y, poi.ConstraintSense.LessEqual, 5.0) assert model.number_of_constraints() == 1 assert model.number_of_constraints(poi.ConstraintType.Linear) == 1 model.add_quadratic_constraint(x * x + y, poi.ConstraintSense.LessEqual, 10.0) assert model.number_of_constraints() == 2 assert model.number_of_constraints(poi.ConstraintType.Quadratic) == 1 def test_supports_attribute_methods(): """Test supports_*_attribute() static methods.""" assert knitro.Model.supports_variable_attribute(poi.VariableAttribute.Value) is True assert ( knitro.Model.supports_variable_attribute(poi.VariableAttribute.LowerBound) is True ) assert ( knitro.Model.supports_variable_attribute( poi.VariableAttribute.LowerBound, setable=True ) is True ) assert ( knitro.Model.supports_variable_attribute( poi.VariableAttribute.Value, setable=True ) is False ) assert ( knitro.Model.supports_constraint_attribute(poi.ConstraintAttribute.Primal) is True ) assert ( knitro.Model.supports_constraint_attribute(poi.ConstraintAttribute.Name) is True ) assert ( knitro.Model.supports_constraint_attribute( poi.ConstraintAttribute.Name, setable=True ) is True ) assert ( knitro.Model.supports_model_attribute(poi.ModelAttribute.ObjectiveValue) is True ) assert knitro.Model.supports_model_attribute(poi.ModelAttribute.SolverName) is True assert ( knitro.Model.supports_model_attribute(poi.ModelAttribute.Silent, setable=True) is True ) assert ( knitro.Model.supports_model_attribute( poi.ModelAttribute.ObjectiveValue, setable=True ) is False ) assert ( knitro.Model.supports_model_attribute( poi.ModelAttribute.NumberOfThreads, setable=True ) is True ) assert ( knitro.Model.supports_model_attribute( poi.ModelAttribute.TimeLimitSec, setable=True ) is True ) def test_model_attribute_solver_info(): """Test getting solver info model attributes.""" model = knitro.Model() solver_name = model.get_model_attribute(poi.ModelAttribute.SolverName) assert solver_name == "KNITRO" solver_version = model.get_model_attribute(poi.ModelAttribute.SolverVersion) assert isinstance(solver_version, str) assert len(solver_version) > 0 def test_model_attribute_after_solve(): """Test model attributes after solving.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) y = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x + 2 * y, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x + y, poi.ConstraintSense.GreaterEqual, 5.0) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL primal_status = model.get_model_attribute(poi.ModelAttribute.PrimalStatus) assert primal_status == poi.ResultStatusCode.FEASIBLE_POINT raw_status = model.get_model_attribute(poi.ModelAttribute.RawStatusString) assert isinstance(raw_status, str) assert "KNITRO" in raw_status obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_value == approx(5.0) obj_sense = model.get_model_attribute(poi.ModelAttribute.ObjectiveSense) assert obj_sense == poi.ObjectiveSense.Minimize solve_time = model.get_model_attribute(poi.ModelAttribute.SolveTimeSec) assert isinstance(solve_time, float) assert solve_time >= 0.0 iterations = model.get_model_attribute(poi.ModelAttribute.BarrierIterations) assert isinstance(iterations, int) assert iterations >= 0 def test_set_model_attribute_objective_sense(): """Test setting objective sense.""" model = knitro.Model() model.add_variable(lb=0.0, ub=10.0) model.set_model_attribute( poi.ModelAttribute.ObjectiveSense, poi.ObjectiveSense.Maximize ) assert ( model.get_model_attribute(poi.ModelAttribute.ObjectiveSense) == poi.ObjectiveSense.Maximize ) model.set_model_attribute( poi.ModelAttribute.ObjectiveSense, poi.ObjectiveSense.Minimize ) assert ( model.get_model_attribute(poi.ModelAttribute.ObjectiveSense) == poi.ObjectiveSense.Minimize ) def test_set_model_attribute_silent(): """Test setting silent mode (should not raise error).""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 1.0) model.set_model_attribute(poi.ModelAttribute.Silent, True) model.optimize() assert model.get_value(x) == approx(1.0) def test_set_model_attribute_threads(): """Test setting number of threads.""" model = knitro.Model() model.set_model_attribute(poi.ModelAttribute.NumberOfThreads, 2) threads = model.get_model_attribute(poi.ModelAttribute.NumberOfThreads) assert threads == 2 def test_set_model_attribute_time_limit(): """Test setting time limit (set only, get may not work due to param type mismatch).""" model = knitro.Model() model.set_model_attribute(poi.ModelAttribute.TimeLimitSec, 100.0) def test_variable_attribute_bounds(): """Test getting and setting variable bounds.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) lb = model.get_variable_attribute(x, poi.VariableAttribute.LowerBound) ub = model.get_variable_attribute(x, poi.VariableAttribute.UpperBound) assert lb == approx(0.0) assert ub == approx(10.0) model.set_variable_attribute(x, poi.VariableAttribute.LowerBound, 1.0) model.set_variable_attribute(x, poi.VariableAttribute.UpperBound, 5.0) lb = model.get_variable_attribute(x, poi.VariableAttribute.LowerBound) ub = model.get_variable_attribute(x, poi.VariableAttribute.UpperBound) assert lb == approx(1.0) assert ub == approx(5.0) def test_variable_attribute_name(): """Test getting and setting variable name.""" model = knitro.Model() x = model.add_variable(name="my_var") name = model.get_variable_attribute(x, poi.VariableAttribute.Name) assert name == "my_var" model.set_variable_attribute(x, poi.VariableAttribute.Name, "new_name") name = model.get_variable_attribute(x, poi.VariableAttribute.Name) assert name == "new_name" def test_variable_attribute_value(): """Test getting variable value after solve.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 3.0) model.optimize() value = model.get_variable_attribute(x, poi.VariableAttribute.Value) assert value == approx(3.0) def test_variable_attribute_primal_start(): """Test setting primal start value.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) model.set_variable_attribute(x, poi.VariableAttribute.PrimalStart, 5.0) model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 2.0) model.optimize() assert model.get_value(x) == approx(2.0) def test_variable_attribute_domain(): """Test getting and setting variable domain.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) # Default domain should be Continuous domain = model.get_variable_attribute(x, poi.VariableAttribute.Domain) assert domain == poi.VariableDomain.Continuous # Set to Integer and verify model.set_variable_attribute( x, poi.VariableAttribute.Domain, poi.VariableDomain.Integer ) domain = model.get_variable_attribute(x, poi.VariableAttribute.Domain) assert domain == poi.VariableDomain.Integer model.set_objective(x, poi.ObjectiveSense.Minimize) model.add_linear_constraint(x, poi.ConstraintSense.GreaterEqual, 2.5) model.optimize() assert model.get_value(x) == approx(3.0) # Test Binary domain y = model.add_variable(domain=poi.VariableDomain.Binary) domain = model.get_variable_attribute(y, poi.VariableAttribute.Domain) assert domain == poi.VariableDomain.Binary def test_constraint_attribute_name(): """Test getting and setting constraint name.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) con = model.add_linear_constraint( x, poi.ConstraintSense.LessEqual, 5.0, name="my_con" ) name = model.get_constraint_attribute(con, poi.ConstraintAttribute.Name) assert name == "my_con" model.set_constraint_attribute(con, poi.ConstraintAttribute.Name, "new_con") name = model.get_constraint_attribute(con, poi.ConstraintAttribute.Name) assert name == "new_con" def test_constraint_attribute_primal_dual(): """Test getting constraint primal and dual values after solve.""" model = knitro.Model() x = model.add_variable(lb=0.0, ub=10.0) y = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x + y, poi.ObjectiveSense.Minimize) con = model.add_linear_constraint(x + y, poi.ConstraintSense.GreaterEqual, 5.0) model.optimize() primal = model.get_constraint_attribute(con, poi.ConstraintAttribute.Primal) assert primal == approx(5.0) dual = model.get_constraint_attribute(con, poi.ConstraintAttribute.Dual) assert isinstance(dual, float) ================================================ FILE: tests/test_lukvle10.py ================================================ import pytest import pyoptinterface as poi from pyoptinterface import ipopt, nl, copt def test_nlp_lukvle10(nlp_model_ctor): # LUKSAN-VLCEK Problem 10 # # min sum[i=1..n] (x[2i]^2)^(x[2i+1]^2 + 1) + (x[2i+1]^2)^(x[2i]^2 + 1) # s.t. (3 - 2*x[i+1])*x[i+1] + 1 - x[i] - 2*x[i+2] = 0, i = 1,...,2n-2 # # Starting point: x[2i] = -1, x[2i+1] = 1 model = nlp_model_ctor() if isinstance(model, ipopt.Model): # LUKVLE10 is too large and IPOPT raises a bad_alloc error. pytest.skip("lukvle10 is too large to be supported with IPOPT") if isinstance(model, copt.Model): # LUKVLE10 is too large the current license of COpt supports up # to 2000 variables. pytest.skip("lukvle10 is too large to be supported with COpt") n = 1250 x = model.add_m_variables(2 * n, name="x") for i in range(2 * n - 2): model.add_quadratic_constraint( (3 - 2 * x[i + 1]) * x[i + 1] + 1 - x[i] - 2 * x[i + 2], poi.ConstraintSense.Equal, 0.0, name=f"c{i}", ) with nl.graph(): for i in range(n): model.add_nl_objective( nl.pow(x[2 * i] ** 2, (x[2 * i + 1] ** 2) + 1) + nl.pow(x[2 * i + 1] ** 2, (x[2 * i] ** 2) + 1) ) for i in range(n): model.set_variable_attribute(x[2 * i], poi.VariableAttribute.PrimalStart, -1.0) model.set_variable_attribute( x[2 * i + 1], poi.VariableAttribute.PrimalStart, 1.0 ) model.optimize() model.set_model_attribute(poi.ModelAttribute.Silent, False) termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMAL ================================================ FILE: tests/test_matrix_api.py ================================================ import pyoptinterface as poi import numpy as np from scipy.sparse import coo_array from pytest import approx def test_matrix_api(model_interface_oneshot): model = model_interface_oneshot N = 10 x = model.add_m_variables(N, lb=0.0) A = np.eye(N) ub = 3.0 lb = 1.0 A_sparse = coo_array(np.eye(N)) model.add_m_linear_constraints(A, x, poi.Leq, ub) model.add_m_linear_constraints(A_sparse, x, poi.Geq, lb) obj = poi.quicksum(x) model.set_objective(obj) model.optimize() obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_value == approx(N * lb) model.set_objective(-obj) model.optimize() obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_value == approx(-N * ub) def test_quicksum_ndarray(model_interface_oneshot): model = model_interface_oneshot N = 10 x = model.add_m_variables((N, 2 * N), lb=1.0, ub=3.0) obj = poi.quicksum(x) model.set_objective(obj) model.optimize() obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_value == approx(2 * N**2) ================================================ FILE: tests/test_nlp.py ================================================ import math import pytest import pyoptinterface as poi from pyoptinterface import ipopt, nl def test_easy_nlp(nlp_model_ctor): model = nlp_model_ctor() x = model.add_variable(lb=0.1, ub=10.0) y = model.add_variable(lb=0.1, ub=10.0) model.add_linear_constraint(x + y, poi.Eq, 1.0) with nl.graph(): model.add_nl_objective(nl.exp(x) + nl.exp(y)) with nl.graph(): z = x * x s = y * y model.add_nl_constraint(z, (0.36, 4.0)) model.add_nl_constraint(s, (0.04, 4.0)) nl_funcs = [ nl.abs, nl.acos, nl.asin, nl.atan, nl.cos, nl.exp, nl.log, nl.log10, nl.pow, nl.sin, nl.sqrt, nl.tan, ] B = 1e10 all_nlfuncs_con = [] with nl.graph(): for f in nl_funcs: if f == nl.pow: v = f(x, 2) else: v = f(x) con = model.add_nl_constraint(v, (-B, B)) all_nlfuncs_con.append(con) model.optimize() termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert ( termination_status == poi.TerminationStatusCode.LOCALLY_SOLVED or termination_status == poi.TerminationStatusCode.OPTIMAL ) x_value = model.get_value(x) y_value = model.get_value(y) assert x_value == pytest.approx(0.6) assert y_value == pytest.approx(0.4) obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_value == pytest.approx(math.exp(x_value) + math.exp(y_value)) con_values = [ model.get_constraint_attribute(con, poi.ConstraintAttribute.Primal) for con in all_nlfuncs_con ] correct_con_values = [] for f in nl_funcs: if f == nl.pow: correct_con_values.append(f(x_value, 2)) else: correct_con_values.append(f(x_value)) assert con_values == pytest.approx(correct_con_values) def test_nlfunc_ifelse(nlp_model_ctor): model = nlp_model_ctor() if not isinstance(model, ipopt.Model): pytest.skip("ifelse is only supported in IPOPT") for x_, fx in zip([0.2, 0.5, 1.0, 2.0, 3.0], [0.2, 0.5, 1.0, 4.0, 9.0]): model = nlp_model_ctor() x = model.add_variable(lb=0.0, ub=10.0) with nl.graph(): y = nl.ifelse(x > 1.0, x**2, x) model.add_nl_constraint(y, poi.Geq, fx) model.set_objective(x) model.optimize() x_value = model.get_value(x) assert x_value == pytest.approx(x_) @pytest.mark.skipif(not ipopt.is_library_loaded(), reason="IPOPT library not available") def test_ipopt_optimizer_not_called(): model = ipopt.Model() x = model.add_variable(lb=0.0, ub=10.0) termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMIZE_NOT_CALLED model.set_objective(x**2) model.optimize() termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.LOCALLY_SOLVED model.add_linear_constraint(x >= 0.5) termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMIZE_NOT_CALLED model.optimize() termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.LOCALLY_SOLVED model.add_quadratic_constraint(x**2 >= 0.36) termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMIZE_NOT_CALLED model.optimize() termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.LOCALLY_SOLVED with nl.graph(): model.add_nl_constraint(nl.exp(x) <= 100.0) termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMIZE_NOT_CALLED model.optimize() termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.LOCALLY_SOLVED with nl.graph(): model.add_nl_objective(nl.log(x)) termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.OPTIMIZE_NOT_CALLED model.optimize() termination_status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert termination_status == poi.TerminationStatusCode.LOCALLY_SOLVED if __name__ == "__main__": def c(): return ipopt.Model(jit="C") test_easy_nlp(c) test_nlfunc_ifelse(c) def llvm(): return ipopt.Model(jit="LLVM") test_easy_nlp(llvm) test_nlfunc_ifelse(llvm) ================================================ FILE: tests/test_nlp_bilinear.py ================================================ import pyoptinterface as poi import pytest def test_bilinear(nlp_model_ctor): model = nlp_model_ctor() x = model.add_m_variables(3, lb=0.0) obj = -(x[0] + x[1]) * (x[0] + x[2]) expr = (x[0] + x[1]) * (x[0] + x[1]) + (x[0] + x[2]) * (x[0] + x[2]) model.add_quadratic_constraint(expr == 4.0) model.set_objective(obj) model.optimize() objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert objective_value == pytest.approx(-2.0, abs=1e-8) ================================================ FILE: tests/test_nlp_clnlbeam.py ================================================ import pyoptinterface as poi from pyoptinterface import nl import pytest import numpy as np def test_clnlbeam(nlp_model_ctor): model = nlp_model_ctor() N = 100 h = 1 / N alpha = 350 t = model.add_m_variables(N + 1, lb=-1.0, ub=1.0) x = model.add_m_variables(N + 1, lb=-0.05, ub=0.05) u = model.add_m_variables(N + 1) for i in range(N): with nl.graph(): model.add_nl_objective( 0.5 * h * (u[i] * u[i] + u[i + 1] * u[i + 1]) + 0.5 * alpha * h * (nl.cos(t[i]) + nl.cos(t[i + 1])) ) model.add_nl_constraint( x[i + 1] - x[i] - 0.5 * h * (nl.sin(t[i]) + nl.sin(t[i + 1])) == 0.0 ) model.add_linear_constraint( t[i + 1] - t[i] - 0.5 * h * u[i + 1] - 0.5 * h * u[i] == 0.0 ) model.optimize() tv = np.zeros(N + 1) xv = np.zeros(N + 1) uv = np.zeros(N + 1) for i in range(N + 1): tv[i] = model.get_value(t[i]) xv[i] = model.get_value(x[i]) uv[i] = model.get_value(u[i]) con_expr_val = np.zeros(N) for i in range(N): con_expr_val[i] = ( xv[i + 1] - xv[i] - 0.5 * h * (np.sin(tv[i]) + np.sin(tv[i + 1])) ) assert np.allclose(con_expr_val, 0.0, atol=1e-8) lin_con_expr_val = np.zeros(N) for i in range(N): lin_con_expr_val[i] = tv[i + 1] - tv[i] - 0.5 * h * uv[i + 1] - 0.5 * h * uv[i] assert np.allclose(lin_con_expr_val, 0.0, atol=1e-8) obj_expr_val = 0.0 for i in range(N): obj_expr_val += 0.5 * h * (uv[i] * uv[i] + uv[i + 1] * uv[i + 1]) obj_expr_val += 0.5 * alpha * h * (np.cos(tv[i]) + np.cos(tv[i + 1])) objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert np.isclose(obj_expr_val, objective_value, atol=1e-8) assert objective_value == pytest.approx( 328.0967, abs=1e-4 ) or objective_value == pytest.approx(350.0, abs=1e-8) ================================================ FILE: tests/test_nlp_expression.py ================================================ import pyoptinterface as poi from pyoptinterface import nl import math import pytest from pytest import approx def test_nlp_expressiontree(model_interface): model = model_interface if not hasattr(model, "add_nl_constraint"): pytest.skip("Model interface does not support nonlinear constraint") x = model.add_variable(lb=0.0, ub=2.0) y = model.add_variable(lb=0.0, ub=2.0) with nl.graph(): z = nl.exp(x) + nl.exp(2 * y) model.add_nl_constraint(z <= 2 * math.exp(1.0)) z = x * x * x model.add_nl_constraint(z >= 0.8**3) model.set_objective(x + 2 * y, poi.ObjectiveSense.Maximize) model.optimize() x_value = model.get_value(x) y_value = model.get_value(y) # Note: with a feasibility tolerance defaulted to 1e-6 + the # effect of the internal solver scaling, x and y can assume # values relatively far away from the expected ones. # E.g.: x = 1.0005, y = 0.49975 assert x_value == approx(1.0, rel=1e-2) assert y_value == approx(0.5, rel=1e-2) def test_nlp_expressiontree_obj(model_interface): model = model_interface if not hasattr(model, "add_nl_constraint"): pytest.skip("Model interface does not support nonlinear constraint") if not hasattr(model, "add_nl_objective"): pytest.skip("Model interface does not support nonlinear objective") x = model.add_variable(lb=-2.0, ub=2.0) y = model.add_variable(lb=-2.0, ub=2.0) with nl.graph(): z = x**2 + y**2 model.add_nl_constraint(z, (-1.0, 1.0)) with nl.graph(): z = nl.exp(x**2) + nl.exp(y**2) model.add_nl_objective(z) model.optimize() x_value = model.get_value(x) y_value = model.get_value(y) assert x_value == approx(0.0, abs=1e-4) assert y_value == approx(0.0, abs=1e-4) ================================================ FILE: tests/test_nlp_hs071.py ================================================ import pyoptinterface as poi from pyoptinterface import nl import pytest def test_hs071(nlp_model_ctor): model = nlp_model_ctor() x = model.add_m_variables(4, lb=1.0, ub=5.0, name="x") model.set_variable_attribute(x[0], poi.VariableAttribute.PrimalStart, 1.0) model.set_variable_attribute(x[1], poi.VariableAttribute.PrimalStart, 5.0) model.set_variable_attribute(x[2], poi.VariableAttribute.PrimalStart, 5.0) model.set_variable_attribute(x[3], poi.VariableAttribute.PrimalStart, 1.0) with nl.graph(): model.add_nl_objective(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2]) with nl.graph(): model.add_nl_constraint(x[0] * x[1] * x[2] * x[3] >= 25.0) model.add_quadratic_constraint( x[0] ** 2 + x[1] ** 2 + x[2] ** 2 + x[3] ** 2 == 40.0 ) model.optimize() x1_val = model.get_value(x[0]) x2_val = model.get_value(x[1]) x3_val = model.get_value(x[2]) x4_val = model.get_value(x[3]) sum_sq = x1_val**2 + x2_val**2 + x3_val**2 + x4_val**2 product = x1_val * x2_val * x3_val * x4_val assert sum_sq == pytest.approx(40.0, rel=1e-6) assert 25.0 - 1e-6 <= product <= 100.0 + 1e-6 objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert objective_value == pytest.approx(17.014, rel=1e-3) ================================================ FILE: tests/test_nlp_multiple_run.py ================================================ from pyoptinterface import copt, ipopt, knitro, nl import pytest import math def test_nlp_reopt(nlp_model_ctor): model = nlp_model_ctor() x = model.add_variable(lb=0.1) y = model.add_variable(lb=0.1) with nl.graph(): model.add_nl_objective(x**2 + y**2) with nl.graph(): model.add_nl_constraint(x**2 <= 1.0) model.add_nl_constraint(y**2 <= 1.0) model.optimize() assert model.get_value(x) == pytest.approx(0.1, rel=1e-5) assert model.get_value(y) == pytest.approx(0.1, rel=1e-5) z = model.add_variable(lb=0.2) with nl.graph(): model.add_nl_objective(z**4) model.optimize() assert model.get_value(z) == pytest.approx(0.2) with nl.graph(): model.add_nl_constraint(nl.log(z) >= math.log(4.0)) model.optimize() print(model.get_value(z)) assert model.get_value(z) == pytest.approx(4.0, rel=1e-5) if __name__ == "__main__": def c(): return ipopt.Model(jit="C") test_nlp_reopt(c) def llvm(): return ipopt.Model(jit="LLVM") test_nlp_reopt(llvm) test_nlp_reopt(copt.Model) test_nlp_reopt(knitro.Model) ================================================ FILE: tests/test_nlp_opf.py ================================================ import math import pyoptinterface as poi from pyoptinterface import nl import pytest def test_acopf(nlp_model_ctor): model = nlp_model_ctor() branches = [ # (from, to, R, X, B, angmin, angmax, Smax) (0, 1, 0.00281, 0.0281, 0.00712, -30.0, 30.0, 4.00), (0, 3, 0.00304, 0.0304, 0.00658, -30.0, 30.0, 4.26), (0, 4, 0.00064, 0.0064, 0.03126, -30.0, 30.0, 4.26), (1, 2, 0.00108, 0.0108, 0.01852, -30.0, 30.0, 4.26), (2, 3, 0.00297, 0.0297, 0.00674, -30.0, 30.0, 4.26), (3, 4, 0.00297, 0.0297, 0.00674, -30.0, 30.0, 2.40), ] buses = [ # (Pd, Qd, Gs, Bs, Vmin, Vmax) (0.0, 0.0000, 0.0, 0.0, 0.9, 1.1), (3.0, 0.9861, 0.0, 0.0, 0.9, 1.1), (3.0, 0.9861, 0.0, 0.0, 0.9, 1.1), (4.0, 1.3147, 0.0, 0.0, 0.9, 1.1), (0.0, 0.0000, 0.0, 0.0, 0.9, 1.1), ] generators = [ # (bus, Pmin, Pmax, Qmin, Qmax, a, b, c) (0, 0.0, 0.4, -0.300, 0.300, 0.0, 1400, 0.0), (0, 0.0, 1.7, -1.275, 1.275, 0.0, 1500, 0.0), (2, 0.0, 5.2, -3.900, 3.900, 0.0, 3000, 0.0), (3, 0.0, 2.0, -1.500, 1.500, 0.0, 4000, 0.0), (4, 0.0, 6.0, -4.500, 4.500, 0.0, 1000, 0.0), ] slack_bus = 3 N_branch = len(branches) N_bus = len(buses) N_gen = len(generators) Pbr_from = model.add_m_variables(N_branch) Qbr_from = model.add_m_variables(N_branch) Pbr_to = model.add_m_variables(N_branch) Qbr_to = model.add_m_variables(N_branch) V = model.add_m_variables(N_bus, name="V") theta = model.add_m_variables(N_bus, name="theta") for i in range(N_bus): Vmin, Vmax = buses[i][4], buses[i][5] model.set_variable_bounds(V[i], Vmin, Vmax) model.set_variable_bounds(theta[slack_bus], 0.0, 0.0) P = model.add_variables(range(N_gen), name="P") Q = model.add_variables(range(N_gen), name="Q") for i in range(N_gen): model.set_variable_bounds(P[i], generators[i][1], generators[i][2]) model.set_variable_bounds(Q[i], generators[i][3], generators[i][4]) # nonlinear constraints for k in range(N_branch): with nl.graph(): branch = branches[k] R, X, Bc2 = branch[2], branch[3], branch[4] G = R / (R**2 + X**2) B = -X / (R**2 + X**2) Bc = Bc2 / 2 i = branch[0] j = branch[1] Vi = V[i] Vj = V[j] theta_i = theta[i] theta_j = theta[j] Pij = Pbr_from[k] Qij = Qbr_from[k] Pji = Pbr_to[k] Qji = Qbr_to[k] sin_ij = nl.sin(theta_i - theta_j) cos_ij = nl.cos(theta_i - theta_j) Pij_eq = G * Vi**2 - Vi * Vj * (G * cos_ij + B * sin_ij) - Pij Qij_eq = -(B + Bc) * Vi**2 - Vi * Vj * (G * sin_ij - B * cos_ij) - Qij Pji_eq = G * Vj**2 - Vi * Vj * (G * cos_ij - B * sin_ij) - Pji Qji_eq = -(B + Bc) * Vj**2 - Vi * Vj * (-G * sin_ij - B * cos_ij) - Qji model.add_nl_constraint( Pij_eq == 0.0, ) model.add_nl_constraint( Qij_eq == 0.0, ) model.add_nl_constraint( Pji_eq == 0.0, ) model.add_nl_constraint( Qji_eq == 0.0, ) # power balance constraints P_balance_eq = [poi.ExprBuilder() for i in range(N_bus)] Q_balance_eq = [poi.ExprBuilder() for i in range(N_bus)] for b in range(N_bus): Pd, Qd = buses[b][0], buses[b][1] Gs, Bs = buses[b][2], buses[b][3] Vb = V[b] P_balance_eq[b] -= poi.quicksum( Pbr_from[k] for k in range(N_branch) if branches[k][0] == b ) P_balance_eq[b] -= poi.quicksum( Pbr_to[k] for k in range(N_branch) if branches[k][1] == b ) P_balance_eq[b] += poi.quicksum( P[i] for i in range(N_gen) if generators[i][0] == b ) P_balance_eq[b] -= Pd P_balance_eq[b] -= Gs * Vb * Vb Q_balance_eq[b] -= poi.quicksum( Qbr_from[k] for k in range(N_branch) if branches[k][0] == b ) Q_balance_eq[b] -= poi.quicksum( Qbr_to[k] for k in range(N_branch) if branches[k][1] == b ) Q_balance_eq[b] += poi.quicksum( Q[i] for i in range(N_gen) if generators[i][0] == b ) Q_balance_eq[b] -= Qd Q_balance_eq[b] += Bs * Vb * Vb model.add_quadratic_constraint(P_balance_eq[b], poi.Eq, 0.0) model.add_quadratic_constraint(Q_balance_eq[b], poi.Eq, 0.0) for k in range(N_branch): branch = branches[k] i = branch[0] j = branch[1] theta_i = theta[i] theta_j = theta[j] angmin = branch[5] / 180 * math.pi angmax = branch[6] / 180 * math.pi model.add_linear_constraint(theta_i - theta_j, (angmin, angmax)) Smax = branch[7] Pij = Pbr_from[k] Qij = Qbr_from[k] Pji = Pbr_to[k] Qji = Qbr_to[k] model.add_quadratic_constraint(Pij * Pij + Qij * Qij, poi.Leq, Smax * Smax) model.add_quadratic_constraint(Pji * Pji + Qji * Qji, poi.Leq, Smax * Smax) cost = poi.ExprBuilder() for i in range(N_gen): a, b, c = generators[i][5], generators[i][6], generators[i][7] cost += a * P[i] * P[i] + b * P[i] + c model.set_objective(cost) model.optimize() terminattion_status = model.get_model_attribute( poi.ModelAttribute.TerminationStatus ) assert (terminattion_status == poi.TerminationStatusCode.LOCALLY_SOLVED) or ( terminattion_status == poi.TerminationStatusCode.OPTIMAL ) P_value = P.map(model.get_value) P_value_sum = sum(P_value.values()) total_load_p = sum(b[0] for b in buses) assert P_value_sum > total_load_p objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert objective_value == pytest.approx(1.7552e4, rel=1e-3) if __name__ == "__main__": from pyoptinterface import ipopt, copt, knitro test_acopf(ipopt.Model) test_acopf(copt.Model) test_acopf(knitro.Model) ================================================ FILE: tests/test_nlp_rocket.py ================================================ from pyoptinterface import nl import math import pytest def rocket_model(model, nh: int): h_0 = 1.0 v_0 = 0.0 m_0 = 1.0 g_0 = 1.0 T_c = 3.5 h_c = 500.0 v_c = 620.0 m_c = 0.6 c = 0.5 * math.sqrt(g_0 * h_0) m_f = m_c * m_0 D_c = 0.5 * v_c * (m_0 / g_0) T_max = T_c * m_0 * g_0 h = model.add_m_variables(nh, lb=1.0) v = model.add_m_variables(nh, lb=0.0) m = model.add_m_variables(nh, lb=m_f, ub=m_0) T = model.add_m_variables(nh, lb=0.0, ub=T_max) step = model.add_variable(lb=0.0) model.set_objective(-1.0 * h[-1]) for i in range(nh - 1): with nl.graph(): h1 = h[i] h2 = h[i + 1] v1 = v[i] v2 = v[i + 1] m1 = m[i] m2 = m[i + 1] T1 = T[i] T2 = T[i + 1] model.add_nl_constraint(h2 - h1 - 0.5 * step * (v1 + v2) == 0) D1 = D_c * v1 * v1 * nl.exp(-h_c * (h1 - h_0)) / h_0 D2 = D_c * v2 * v2 * nl.exp(-h_c * (h2 - h_0)) / h_0 g1 = g_0 * h_0 * h_0 / (h1 * h1) g2 = g_0 * h_0 * h_0 / (h2 * h2) dv1 = (T1 - D1) / m1 - g1 dv2 = (T2 - D2) / m2 - g2 model.add_nl_constraint(v2 - v1 - 0.5 * step * (dv1 + dv2) == 0) model.add_nl_constraint(m2 - m1 + 0.5 * step * (T1 + T2) / c == 0) # Boundary conditions model.set_variable_bounds(h[0], h_0, h_0) model.set_variable_bounds(v[0], v_0, v_0) model.set_variable_bounds(m[0], m_0, m_0) model.set_variable_bounds(m[-1], m_f, m_f) model.h = h def test_rocket(nlp_model_ctor): nh = 400 model = nlp_model_ctor() rocket_model(model, nh) model.optimize() obj = model.get_value(model.h[-1]) assert obj == pytest.approx(1.01283, rel=1e-4) if __name__ == "__main__": from pyoptinterface import copt, ipopt, knitro def c(): return ipopt.Model(jit="C") test_rocket(c) def llvm(): return ipopt.Model(jit="LLVM") test_rocket(llvm) test_rocket(copt.Model) test_rocket(knitro.Model) ================================================ FILE: tests/test_operator.py ================================================ from operator import add, sub, mul, truediv from pyoptinterface import ( VariableIndex, ExprBuilder, ScalarAffineFunction, ScalarQuadraticFunction, quicksum, quicksum_, ) from pytest import approx def evaluate(expr, var_value_map=None): if isinstance(expr, VariableIndex): return var_value_map[expr.index] elif isinstance(expr, ScalarAffineFunction): s = 0.0 if expr.constant is not None: s = expr.constant for index, coef in zip(expr.variables, expr.coefficients): val = var_value_map[index] s += coef * val return s elif isinstance(expr, ScalarQuadraticFunction): s = 0.0 if expr.affine_part is not None: s = evaluate(expr.affine_part, var_value_map) for index1, index2, coef in zip( expr.variable_1s, expr.variable_2s, expr.coefficients ): val1 = var_value_map[index1] val2 = var_value_map[index2] s += coef * val1 * val2 return s elif isinstance(expr, ExprBuilder): degree = expr.degree() if degree < 2: return evaluate(ScalarAffineFunction(expr), var_value_map) elif degree == 2: return evaluate(ScalarQuadraticFunction(expr), var_value_map) else: raise ValueError("Unsupported degree") elif isinstance(expr, (int, float)): return expr else: raise ValueError("Unsupported type") def degree(expr): if isinstance(expr, VariableIndex): return 1 elif isinstance(expr, ScalarAffineFunction): return 1 elif isinstance(expr, ScalarQuadraticFunction): return 2 elif isinstance(expr, ExprBuilder): return expr.degree() elif isinstance(expr, (int, float)): return 0 else: raise ValueError("Unsupported type") def test_evaluate(): assert evaluate(1.0) == approx(1.0) assert evaluate(2) == 2 N = 6 vars = [VariableIndex(i) for i in range(N)] var_value_map = {v.index: float(v.index) for v in vars} for i in range(N): assert evaluate(vars[i], var_value_map) == approx(i) expr = vars[0] + 1 * vars[1] + 2 * vars[2] assert evaluate(expr, var_value_map) == approx(5.0) expr = ExprBuilder(expr) assert evaluate(expr, var_value_map) == approx(5.0) expr = vars[0] + 2 * vars[1] + 5 * vars[3] * vars[2] + 6 * vars[4] * vars[4] answer = 2 + 5 * 3 * 2 + 6 * 4 * 4 assert evaluate(expr, var_value_map) == approx(answer) expr = ExprBuilder(expr) assert evaluate(expr, var_value_map) == approx(answer) def test_operator(): N = 6 vars = [VariableIndex(i) for i in range(N)] exprs = [ 1, 2.0, vars[4], 3 * vars[2], 2 * vars[1] + vars[3], 2 * vars[2] + vars[3] + 5.0, 13 * vars[4] * vars[4], 17 * vars[3] * vars[3] + 1.0, 17 * vars[3] * vars[5] + 3 * vars[1], 11 * vars[5] * vars[5] + 7 * vars[1] + 3.0, ] exprs += [ExprBuilder(e) for e in exprs] # For two quadratic polynomials, if their values equal on more than 3 points, then thir coefficients are the same. for i in (1, 2, 3, 4): var_value_map = {v.index: i * float(v.index) for v in vars} expr_values = [evaluate(e, var_value_map) for e in exprs] for op in [add, sub]: for i, ei in enumerate(exprs): for j, ej in enumerate(exprs): expr = op(ei, ej) value = evaluate(expr, var_value_map) assert value == approx(op(expr_values[i], expr_values[j])) op = mul for i, ei in enumerate(exprs): for j, ej in enumerate(exprs): total_degree = degree(ei) + degree(ej) if total_degree > 2: continue expr = op(ei, ej) value = evaluate(expr, var_value_map) # flag = value == approx(op(expr_values[i], expr_values[j])) # if not flag: # k = 1 assert value == approx(op(expr_values[i], expr_values[j])) op = truediv for i, ei in enumerate(exprs): for j, ej in enumerate(exprs): if not isinstance(ej, (int, float)): continue expr = op(ei, ej) value = evaluate(expr, var_value_map) assert value == approx(op(expr_values[i], expr_values[j])) def test_quicksum(): N = 6 vars = [VariableIndex(i) for i in range(N)] var_value_map = {v.index: float(v.index) for v in vars} vars_dict = {i: v for i, v in enumerate(vars)} expr = ExprBuilder() for v in vars: expr += v expr_sum = quicksum(vars_dict) assert evaluate(expr_sum, var_value_map) == approx(evaluate(expr, var_value_map)) f = lambda x: x * x expr = ExprBuilder() for v in vars: expr += f(v) expr_sum = quicksum(vars, f) assert evaluate(expr_sum, var_value_map) == approx(evaluate(expr, var_value_map)) c = 3.0 expr = ExprBuilder(c) for v in vars: expr += v expr_sum = ExprBuilder(c) quicksum_(expr_sum, vars) assert evaluate(expr_sum, var_value_map) == approx(evaluate(expr, var_value_map)) f = lambda x: x * x expr = ExprBuilder(c) for v in vars: expr += f(v) expr_sum = ExprBuilder(c) quicksum_(expr_sum, vars_dict, f) assert evaluate(expr_sum, var_value_map) == approx(evaluate(expr, var_value_map)) ================================================ FILE: tests/test_preopt.py ================================================ import pyoptinterface as poi def test_model_attribute_termination_before_solve(model_interface_oneshot): """Test termination status before solving.""" model = model_interface_oneshot x = model.add_variable(lb=0.0, ub=10.0) model.set_objective(x, poi.ObjectiveSense.Minimize) status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMIZE_NOT_CALLED ================================================ FILE: tests/test_qp.py ================================================ import pyoptinterface as poi from pytest import approx import numpy as np def test_simple_qp(model_interface_oneshot): model = model_interface_oneshot N = 6 x = model.add_variables(range(N), lb=0.0) model.add_linear_constraint(poi.quicksum(x), poi.Geq, N) s = poi.quicksum(x) s *= s model.set_objective(s, poi.ObjectiveSense.Minimize) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert ( status == poi.TerminationStatusCode.OPTIMAL or status == poi.TerminationStatusCode.LOCALLY_SOLVED ) obj_val = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_val == approx(N**2, rel=1e-5) # reported by https://github.com/metab0t/PyOptInterface/issues/59 def test_shuffle_qp_objective(model_interface_oneshot): model = model_interface_oneshot N = 3 weights = model.add_m_variables(N, lb=0) expected_returns = [0.05, 0.07, 0.12] min_return = 0.06 cov = [ (0, 0, 0.1), (0, 1, 0.1), (0, 2, 0.04), (1, 1, 0.4), (1, 2, 0.2), (2, 2, 0.9), ] cov_objs = [weights[i] * weights[j] * v for i, j, v in cov] model.add_linear_constraint(poi.quicksum(weights) == 1) model.add_linear_constraint( poi.quicksum(expected_returns[i] * weights[i] for i in range(N)) >= min_return ) trial = 120 obj_values = [] for _ in range(trial): import random random.shuffle(cov_objs) obj = poi.quicksum(cov_objs) model.set_objective(obj, poi.ObjectiveSense.Minimize) model.optimize() obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) obj_values.append(obj_value) obj_values = np.array(obj_values) # test all values are the same assert np.all(np.abs(obj_values - obj_values[0]) < 1e-8) def test_duplicated_quadratic_terms(model_interface_oneshot): model = model_interface_oneshot x = model.add_m_variables(2, lb=1.0) obj = ( x[0] * x[0] + x[0] * x[0] + x[1] * x[1] + 2 * x[1] * x[1] + 0.5 * x[0] * x[1] + 0.1 * x[1] * x[0] ) model.set_objective(obj) model.optimize() obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_value == approx(5.6) ================================================ FILE: tests/test_reducedcost.py ================================================ import pyoptinterface as poi import pytest from pytest import approx def test_simple_redcost(model_interface): model = model_interface if not model.supports_variable_attribute(poi.VariableAttribute.ReducedCost): pytest.skip("Model interface does not support ReducedCost attribute") x = model.add_variable(lb=0.0, ub=2.0) y = model.add_variable(lb=0.0, ub=2.5) model.add_linear_constraint(x + 2 * y >= 6.0) model.set_objective(x + y) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_redcost = model.get_variable_attribute(x, poi.VariableAttribute.ReducedCost) y_redcost = model.get_variable_attribute(y, poi.VariableAttribute.ReducedCost) assert x_redcost == approx(0.0, abs=1e-5) assert y_redcost == approx(-1.0, abs=1e-5) ================================================ FILE: tests/test_simple_opt.py ================================================ import pyoptinterface as poi from pytest import approx import pytest def test_simple_opt(model_interface): model = model_interface x = model.add_variable(lb=0.0, ub=20.0) y = model.add_variable() model.set_variable_bounds(y, 8.0, 20.0) model.set_variable_attribute(x, poi.VariableAttribute.Name, "x") model.set_variable_attribute(y, poi.VariableAttribute.Name, "y") obj = x * x + y * y model.set_objective(obj, poi.ObjectiveSense.Minimize) conexpr = x + y con1 = model.add_linear_constraint( conexpr - 10.0, poi.ConstraintSense.GreaterEqual, 0.0, name="con1" ) assert model.number_of_variables() == 2 assert model.number_of_constraints(poi.ConstraintType.Linear) == 1 model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_variable_attribute(x, poi.VariableAttribute.Value) y_val = model.get_variable_attribute(y, poi.VariableAttribute.Value) assert x_val == approx(2.0) assert y_val == approx(8.0) obj_val = model.get_value(obj) assert obj_val == approx(68.0) obj_val_attr = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_val_attr == approx(obj_val) conexpr_val = model.get_value(conexpr) assert conexpr_val == approx(10.0) assert model.pprint(x) == "x" assert model.pprint(y) == "y" assert model.pprint(obj) == "1*x*x+1*y*y" assert model.pprint(conexpr) == "1*x+1*y" model.delete_constraint(con1) assert model.number_of_constraints(poi.ConstraintType.Linear) == 0 con2 = model.add_linear_constraint(conexpr, poi.ConstraintSense.GreaterEqual, 20.0) assert model.number_of_constraints(poi.ConstraintType.Linear) == 1 model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_variable_attribute(x, poi.VariableAttribute.Value) y_val = model.get_variable_attribute(y, poi.VariableAttribute.Value) assert x_val == approx(10.0, abs=1e-3) assert y_val == approx(10.0, abs=1e-3) model.delete_constraint(con2) con3 = model.add_linear_constraint(conexpr, poi.ConstraintSense.GreaterEqual, 20.05) model.set_variable_attribute( x, poi.VariableAttribute.Domain, poi.VariableDomain.Integer ) model.set_objective(x + 2 * y, poi.ObjectiveSense.Minimize) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_variable_attribute(x, poi.VariableAttribute.Value) y_val = model.get_variable_attribute(y, poi.VariableAttribute.Value) assert x_val == approx(12.0) assert y_val == approx(8.05) model.delete_constraint(con3) con4 = model.add_linear_constraint(conexpr, poi.ConstraintSense.GreaterEqual, 10.0) model.set_variable_attribute( x, poi.VariableAttribute.Domain, poi.VariableDomain.Continuous ) model.set_variable_attribute(y, poi.VariableAttribute.LowerBound, 0.0) model.set_objective(obj, poi.ObjectiveSense.Minimize) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_variable_attribute(x, poi.VariableAttribute.Value) y_val = model.get_variable_attribute(y, poi.VariableAttribute.Value) assert x_val == approx(5.0) assert y_val == approx(5.0) model.set_normalized_rhs(con4, 16.0) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_variable_attribute(x, poi.VariableAttribute.Value) y_val = model.get_variable_attribute(y, poi.VariableAttribute.Value) assert x_val == approx(8.0) assert y_val == approx(8.0) def test_constant_objective(model_interface_oneshot): model = model_interface_oneshot x = model.add_variable(lb=0.0, ub=1.0) obj = 1.0 model.set_objective(obj, poi.ObjectiveSense.Minimize) model.optimize() obj_val = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_val == approx(1.0) model.set_objective(obj, poi.ObjectiveSense.Maximize) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert ( status == poi.TerminationStatusCode.OPTIMAL or status == poi.TerminationStatusCode.LOCALLY_SOLVED ) obj_val = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue) assert obj_val == approx(1.0) def test_constraint_primal_dual(model_interface_oneshot): model = model_interface_oneshot x = model.add_variable(lb=0.0, ub=1.0) y = model.add_variable(lb=0.0, ub=1.0) model.set_variable_attribute(x, poi.VariableAttribute.Name, "x") model.set_variable_attribute(y, poi.VariableAttribute.Name, "y") obj = x + y model.set_objective(obj, poi.ObjectiveSense.Minimize) conexpr = x + 2 * y con1 = model.add_linear_constraint(conexpr, poi.Geq, 1.0, name="con1") model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert ( status == poi.TerminationStatusCode.OPTIMAL or status == poi.TerminationStatusCode.LOCALLY_SOLVED ) primal_val = model.get_constraint_attribute(con1, poi.ConstraintAttribute.Primal) assert primal_val == approx(1.0) dual_val = model.get_constraint_attribute(con1, poi.ConstraintAttribute.Dual) assert dual_val == approx(0.5) def test_add_quadratic_expr_as_linear_throws_error(model_interface_oneshot): model = model_interface_oneshot xs = model.add_m_variables(10) x2_sum = poi.quicksum(x * x for x in xs.flat) with pytest.raises(RuntimeError, match="add_linear_constraint"): model.add_linear_constraint(x2_sum <= 1.0) def test_exprbuilder_self_operation(model_interface_oneshot): model = model_interface_oneshot x = model.add_m_variables(2, lb=1.0, ub=4.0) expr = poi.ExprBuilder(x[0] + 2.0 * x[1] + 3.0) expr += expr model.set_objective(expr) model.optimize() obj_value = model.get_value(expr) assert obj_value == approx(12.0) expr = poi.ExprBuilder(x[0] + 2.0 * x[1] + 3.0) expr -= expr model.set_objective(expr) model.optimize() obj_value = model.get_value(expr) assert obj_value == approx(0.0) expr = poi.ExprBuilder(x[0] + 2.0 * x[1] + 3.0) expr *= expr model.set_objective(expr) model.optimize() obj_value = model.get_value(expr) assert obj_value == approx(36.0) ================================================ FILE: tests/test_soc.py ================================================ import pyoptinterface as poi from pytest import approx import pytest def test_soc(model_interface): model = model_interface if not hasattr(model, "add_second_order_cone_constraint"): pytest.skip("Model interface does not support second order cone constraints") x = model.add_variable(lb=0.0, name="x") y = model.add_variable(lb=3.0, name="y") z = model.add_variable(lb=4.0, name="z") obj = x + y + z model.set_objective(obj, poi.ObjectiveSense.Minimize) con1 = model.add_second_order_cone_constraint([x, y, z]) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_value(x) y_val = model.get_value(y) z_val = model.get_value(z) assert x_val == approx(5.0, rel=1e-5) assert y_val == approx(3.0, rel=1e-5) assert z_val == approx(4.0, rel=1e-5) obj_val = model.get_value(obj) assert obj_val == approx(12.0, rel=1e-5) model.delete_constraint(con1) xx = model.add_variable(lb=0.0, name="xx") model.add_linear_constraint(xx - 2 * x, poi.ConstraintSense.Equal, 0.0) model.add_second_order_cone_constraint([xx, y, z]) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_value(x) y_val = model.get_value(y) z_val = model.get_value(z) assert x_val == approx(2.5, rel=1e-5) assert y_val == approx(3.0, rel=1e-5) assert z_val == approx(4.0, rel=1e-5) obj_val = model.get_value(obj) assert obj_val == approx(9.5, rel=1e-5) def test_rotated_soc(model_interface): model = model_interface if not hasattr(model, "add_second_order_cone_constraint"): pytest.skip("Model interface does not support second order cone constraints") x = model.add_variable(lb=0.0, name="x") y = model.add_variable(lb=0.0, name="y") z = model.add_variable(lb=4.0, ub=4.0, name="z") obj = x + 2 * y model.set_objective(obj, poi.ObjectiveSense.Minimize) con1 = model.add_second_order_cone_constraint([x, y, z], rotated=True) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_value(x) y_val = model.get_value(y) assert x_val == approx(4.0, rel=1e-3) assert y_val == approx(2.0, rel=1e-3) obj_val = model.get_value(obj) assert obj_val == approx(8.0, rel=1e-3) model.delete_constraint(con1) xx = model.add_variable(lb=0.0, name="xx") model.add_linear_constraint(xx - 4 * x, poi.ConstraintSense.Equal, 0.0) model.add_second_order_cone_constraint([xx, y, z], rotated=True) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL x_val = model.get_value(x) y_val = model.get_value(y) assert x_val == approx(2.0, rel=1e-3) assert y_val == approx(1.0, rel=1e-3) obj_val = model.get_value(obj) assert obj_val == approx(4.0, rel=1e-3) ================================================ FILE: tests/test_sos.py ================================================ import pyoptinterface as poi from pytest import approx import pytest # write a test to test if SOS1 and SOS2 constraint are functional def test_sos(model_interface): model = model_interface if not hasattr(model, "add_sos_constraint"): pytest.skip("Model interface does not support SOS constraints") N = 10 vars = [model.add_variable(lb=0.0, ub=1.0) for _ in range(N)] obj = poi.quicksum(vars) model.set_objective(obj, poi.ObjectiveSense.Maximize) con1 = model.add_sos_constraint(vars, poi.SOSType.SOS1) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL obj_val = model.get_value(obj) assert obj_val == approx(1.0) nz_indices = [i for i in range(N) if model.get_value(vars[i]) > 1e-3] assert len(nz_indices) == 1 model.delete_constraint(con1) con2 = model.add_sos_constraint(vars, poi.SOSType.SOS2) model.optimize() status = model.get_model_attribute(poi.ModelAttribute.TerminationStatus) assert status == poi.TerminationStatusCode.OPTIMAL obj_val = model.get_value(obj) assert obj_val == approx(2.0) nz_indices = [i for i in range(N) if model.get_value(vars[i]) > 1e-3] assert len(nz_indices) == 2 v1, v2 = nz_indices assert v1 + 1 == v2 ================================================ FILE: tests/test_tupledict.py ================================================ from pyoptinterface._src.tupledict import ( flatten_tuple, make_tupledict, tupledict, WILDCARD, ) def test_flatten_tuple(): assert list(flatten_tuple((1, (2, 3), (4, 5)))) == [1, 2, 3, 4, 5] assert list(flatten_tuple((1, 2, 3))) == [1, 2, 3] assert list(flatten_tuple(((1, 2), 3))) == [1, 2, 3] def test_make_tupledict(): assert make_tupledict([1, 2], [3, 4], rule=lambda x, y: x * y) == { (1, 3): 3, (1, 4): 4, (2, 3): 6, (2, 4): 8, } assert make_tupledict([1, 2], [3, 4], rule=lambda x, y: None) == {} assert make_tupledict([1, 2], rule=lambda x: x) == {1: 1, 2: 2} assert make_tupledict([1, 2], [(3, 3), (4, 4)], rule=lambda x, y, z: x) == { (1, 3, 3): 1, (1, 4, 4): 1, (2, 3, 3): 2, (2, 4, 4): 2, } def test_tupledict_select(): # Create a tupledict instance td = tupledict( { (1, 2): "a", (1, 3): "b", (2, 2): "c", (2, 3): "d", } ) # Test select with specific keys assert list(td.select(1, 2)) == ["a"] assert list(td.select(2, WILDCARD)) == ["c", "d"] # Test select with wildcard assert list(td.select(WILDCARD, 2)) == ["a", "c"] # Test select with all wildcards assert list(td.select(WILDCARD, WILDCARD)) == ["a", "b", "c", "d"] # Test select with non-existing key assert list(td.select(3, 2)) == [] # Test select with key and value assert list(td.select(1, 2, with_key=True)) == [((1, 2), "a")] assert list(td.select(2, WILDCARD, with_key=True)) == [((2, 2), "c"), ((2, 3), "d")] def test_tupledict_map(): td = tupledict([((i, i + 1), i) for i in range(10)]) td_m = td.map(lambda x: x**2) assert isinstance(td_m, tupledict) assert list(td_m.values()) == [i**2 for i in range(10)] assert list(td_m.keys()) == list(td.keys()) ================================================ FILE: tests/test_update.py ================================================ import pyoptinterface as poi from pytest import approx def test_update(model_interface): model = model_interface x = model.add_variables(range(3), lb=1.0, ub=2.0) model.delete_variable(x[1]) expr = x[0] - x[2] con = model.add_linear_constraint(expr, poi.Eq, 0.0) obj = x[0] - x[2] model.set_objective(obj) model.optimize() assert model.get_value(obj) == approx(0.0) model.delete_constraint(con) model.set_variable_attribute(x[0], poi.VariableAttribute.LowerBound, 2.0) assert model.get_variable_attribute( x[0], poi.VariableAttribute.LowerBound ) == approx(2.0) model.optimize() assert model.get_value(x[0]) == approx(2.0) assert model.get_value(x[2]) == approx(2.0) con = model.add_linear_constraint(expr, poi.Eq, 0.0) model.set_normalized_coefficient(con, x[2], -2.0) model.optimize() assert model.get_value(x[0]) == approx(2.0) assert model.get_value(x[2]) == approx(1.0) model.set_variable_attribute(x[0], poi.VariableAttribute.LowerBound, 1.5) model.set_variable_attribute(x[2], poi.VariableAttribute.LowerBound, 0.5) model.set_objective_coefficient(x[0], 2.0) model.set_objective_coefficient(x[2], 1.0) model.optimize() assert model.get_value(x[0]) == approx(1.5) assert model.get_value(x[2]) == approx(0.75) ================================================ FILE: tests/tsp_cb.py ================================================ # This file is adapted from the examples/python/tsp.py in Gurobi installation. # We use this file to ensure our callback implementation is correct and the result is compared with gurobipy/coptpy/xpress # this test is currently run manually # Copyright 2024, Gurobi Optimization, LLC from typing import List, Tuple import math import random import time from collections import defaultdict from itertools import combinations # Test what is available in the current system GUROBIPY_AVAILABLE = False COPTPY_AVAILABLE = False XPRESS_AVAILABLE = False try: import gurobipy as gp GUROBIPY_AVAILABLE = True except ImportError: print("Gurobipy not found.") try: import coptpy as cp COPTPY_AVAILABLE = True except ImportError: print("Coptpy not found.") try: import xpress as xp XPRESS_AVAILABLE = True except ImportError: print("Xpress Python Interface not found.") import pyoptinterface as poi from pyoptinterface import gurobi, copt, xpress GRB = gurobi.GRB COPT = copt.COPT XPRS = xpress.XPRS def shortest_subtour(edges: List[Tuple[int, int]]) -> List[int]: node_neighbors = defaultdict(list) for i, j in edges: node_neighbors[i].append(j) assert all(len(neighbors) == 2 for neighbors in node_neighbors.values()) # Follow edges to find cycles. Each time a new cycle is found, keep track # of the shortest cycle found so far and restart from an unvisited node. unvisited = set(node_neighbors) shortest = None while unvisited: cycle = [] neighbors = list(unvisited) while neighbors: current = neighbors.pop() cycle.append(current) unvisited.remove(current) neighbors = [j for j in node_neighbors[current] if j in unvisited] if shortest is None or len(cycle) < len(shortest): shortest = cycle assert shortest is not None return shortest if GUROBIPY_AVAILABLE: class GurobiTSPCallback: def __init__(self, nodes, x): self.nodes = nodes self.x = x def __call__(self, model, where): if where == GRB.Callback.MIPSOL: self.eliminate_subtours_gurobipy(model) def eliminate_subtours_gurobipy(self, model): values = model.cbGetSolution(self.x) edges = [(i, j) for (i, j), v in values.items() if v > 0.5] tour = shortest_subtour(edges) if len(tour) < len(self.nodes): # add subtour elimination constraint for every pair of cities in tour model.cbLazy( gp.quicksum(self.x[i, j] for i, j in combinations(tour, 2)) <= len(tour) - 1 ) def solve_tsp_gurobipy(nodes, distances): """ Solve a dense symmetric TSP using the following base formulation: min sum_ij d_ij x_ij s.t. sum_j x_ij == 2 forall i in V x_ij binary forall (i,j) in E and subtours eliminated using lazy constraints. """ m = gp.Model() x = m.addVars(distances.keys(), obj=distances, vtype=GRB.BINARY, name="e") x.update({(j, i): v for (i, j), v in x.items()}) # Create degree 2 constraints for i in nodes: m.addConstr(gp.quicksum(x[i, j] for j in nodes if i != j) == 2) m.Params.OutputFlag = 0 m.Params.LazyConstraints = 1 cb = GurobiTSPCallback(nodes, x) m.optimize(cb) edges = [(i, j) for (i, j), v in x.items() if v.X > 0.5] tour = shortest_subtour(edges) assert set(tour) == set(nodes) return tour, m.ObjVal if COPTPY_AVAILABLE: class COPTTSPCallback(cp.CallbackBase): def __init__(self, nodes, x): super().__init__() self.nodes = nodes self.x = x def callback(self): if self.where() == COPT.CBCONTEXT_MIPSOL: self.eliminate_subtours_coptpy() def eliminate_subtours_coptpy(self): values = self.getSolution(self.x) edges = [(i, j) for (i, j), v in values.items() if v > 0.5] tour = shortest_subtour(edges) if len(tour) < len(self.nodes): # add subtour elimination constraint for every pair of cities in tour self.addLazyConstr( cp.quicksum(self.x[i, j] for i, j in combinations(tour, 2)) <= len(tour) - 1 ) def solve_tsp_coptpy(nodes, distances): env = cp.Envr() m = env.createModel("TSP Callback Example") x = m.addVars(distances.keys(), vtype=COPT.BINARY, nameprefix="e") for (i, j), v in x.items(): v.setInfo(COPT.Info.Obj, distances[i, j]) for i, j in distances.keys(): x[j, i] = x[i, j] # Create degree 2 constraints for i in nodes: m.addConstr(cp.quicksum(x[i, j] for j in nodes if i != j) == 2) m.Param.Logging = 0 cb = COPTTSPCallback(nodes, x) m.setCallback(cb, COPT.CBCONTEXT_MIPSOL) m.solve() edges = [(i, j) for (i, j), v in x.items() if v.x > 0.5] tour = shortest_subtour(edges) assert set(tour) == set(nodes) return tour, m.objval if XPRESS_AVAILABLE: class XpressTSPCallback: def __init__(self, nodes, x): self.nodes = nodes self.x = x def __call__(self, prob, data, soltype, cutoff): """ Pre-integer solution callback: checks candidate solution and adds subtour elimination cuts. Args: soltype: Solution origin (0=B&B node, 1=heuristic, 2=user) cutoff: Current cutoff value Returns: (reject, new_cutoff): reject=1 to discard solution, new_cutoff to update solution cutoff value """ # Extract solution and identify active edges sol = prob.getCallbackSolution() edges = [(i, j) for i, j in self.x if sol[self.x[i, j].index] > 0.5] tour = shortest_subtour(edges) if len(tour) == len(self.nodes): # Complete tour return (0, None) if soltype != 0: # Can only add cuts at B&B nodes return (1, None) # Build and presolve SEC idxs = [self.x[i, j].index for i, j in combinations(tour, 2)] coeffs = [1.0] * len(idxs) rhs = len(tour) - 1 idxs, coeffs, rhs, status = prob.presolveRow("L", idxs, coeffs, rhs) if status < 0: # Presolve failed (dual reductions on?) return (1, None) # Add cut prob.addCuts([0], ["L"], [rhs], [0, len(idxs)], idxs, coeffs) return (0, None) # reject=1 would drop the node as well! def solve_tsp_xpress(nodes, distances): prob = xp.problem() x = prob.addVariables(distances.keys(), vartype=xp.binary, name="e") prob.setObjective(xp.Sum(dist * x[i, j] for (i, j), dist in distances.items())) x.update({(j, i): v for (i, j), v in x.items()}) # Create degree 2 constraints for i in nodes: prob.addConstraint(xp.Sum(x[i, j] for j in nodes if i != j) == 2) prob.controls.outputlog = 0 prob.controls.mipdualreductions = 0 cb = XpressTSPCallback(nodes, x) prob.addPreIntsolCallback(cb, None, 0) prob.optimize() edges = [(i, j) for (i, j), v in x.items() if prob.getSolution(v) > 0.5] tour = shortest_subtour(edges) assert set(tour) == set(nodes) return tour, prob.attributes.objval class POITSPCallback: def __init__(self, nodes, x): self.nodes = nodes self.x = x def run_gurobi(self, model, where): if where == GRB.Callback.MIPSOL: self.eliminate_subtours_poi(model) def run_copt(self, model, where): if where == COPT.CBCONTEXT_MIPSOL: self.eliminate_subtours_poi(model) def run_xpress(self, model, where): if where == XPRS.CB_CONTEXT.PREINTSOL: self.eliminate_subtours_poi(model) def eliminate_subtours_poi(self, model): edges = [] for (i, j), xij in self.x.items(): v = model.cb_get_solution(xij) if v > 0.5: edges.append((i, j)) tour = shortest_subtour(edges) if len(tour) < len(self.nodes): # add subtour elimination constraint for every pair of cities in tour model.cb_add_lazy_constraint( poi.quicksum(self.x[i, j] for i, j in combinations(tour, 2)), poi.Leq, len(tour) - 1, ) def solve_tsp_poi(f, nodes, distances): m = f() x = m.add_variables(distances.keys(), domain=poi.VariableDomain.Binary, name="e") m.set_objective(poi.quicksum(distances[k] * x[k] for k in distances)) for i, j in distances.keys(): x[j, i] = x[i, j] for i in nodes: m.add_linear_constraint( poi.quicksum(x[i, j] for j in nodes if i != j), poi.Eq, 2 ) m.set_model_attribute(poi.ModelAttribute.Silent, True) cb = POITSPCallback(nodes, x) if isinstance(m, gurobi.Model): m.set_raw_parameter("LazyConstraints", 1) m.set_callback(cb.run_gurobi) elif isinstance(m, copt.Model): m.set_callback(cb.run_copt, COPT.CBCONTEXT_MIPSOL) elif isinstance(m, xpress.Model): m.set_raw_control("XPRS_MIPDUALREDUCTIONS", 0) m.set_callback(cb.run_xpress, XPRS.CB_CONTEXT.PREINTSOL) m.optimize() # Extract the solution as a tour edges = [(i, j) for (i, j), v in x.items() if m.get_value(v) > 0.5] tour = shortest_subtour(edges) assert set(tour) == set(nodes) objval = m.get_model_attribute(poi.ModelAttribute.ObjectiveValue) return tour, objval def create_map(npoints, seed): # Create n random points in 2D random.seed(seed) nodes = list(range(npoints)) points = [(random.randint(0, 100), random.randint(0, 100)) for i in nodes] # Dictionary of Euclidean distance between each pair of points distances = { (i, j): math.sqrt(sum((points[i][k] - points[j][k]) ** 2 for k in range(2))) for i, j in combinations(nodes, 2) } return nodes, distances def test_gurobi(npoints_series, seed): for npoints in npoints_series: nodes, distances = create_map(npoints, seed) print(f"npoints = {npoints}") t0 = time.time() tour1, cost1 = solve_tsp_gurobipy(nodes, distances) t1 = time.time() print(f"\t gurobipy time: {t1 - t0:g} seconds") t0 = time.time() f = gurobi.Model tour2, cost2 = solve_tsp_poi(f, nodes, distances) t1 = time.time() print(f"\t poi time: {t1 - t0:g} seconds") assert tour1 == tour2 assert abs(cost1 - cost2) < 1e-6 def test_copt(npoints_series, seed): for npoints in npoints_series: nodes, distances = create_map(npoints, seed) print(f"npoints = {npoints}") t0 = time.time() tour1, cost1 = solve_tsp_coptpy(nodes, distances) t1 = time.time() print(f"\t coptpy time: {t1 - t0:g} seconds") t0 = time.time() f = copt.Model tour2, cost2 = solve_tsp_poi(f, nodes, distances) t1 = time.time() print(f"\t poi time: {t1 - t0:g} seconds") assert tour1 == tour2 assert abs(cost1 - cost2) < 1e-6 def test_xpress(npoints_series, seed): for npoints in npoints_series: nodes, distances = create_map(npoints, seed) print(f"npoints = {npoints}") t0 = time.time() tour1, cost1 = solve_tsp_xpress(nodes, distances) t1 = time.time() print(f"\t Xpress-Python cost: {cost1}, time: {t1 - t0:g} seconds") t0 = time.time() f = xpress.Model tour2, cost2 = solve_tsp_poi(f, nodes, distances) t1 = time.time() print(f"\t PyOptInterface cost: {cost2}, time: {t1 - t0:g} seconds") assert tour1 == tour2 assert abs(cost1 - cost2) < 1e-6 if __name__ == "__main__": seed = 987651234 X = range(10, 90, 10) if copt.is_library_loaded(): test_copt(X, seed) else: print("PyOptInterface did not find COPT.") if gurobi.is_library_loaded(): test_gurobi(X, seed) else: print("PyOptInterface did not find Gurobi.") if xpress.is_library_loaded(): test_xpress(X, seed) else: print("PyOptInterface did not find Xpress.") ================================================ FILE: tests/tsp_xpress.py ================================================ # TSP example using numpy functions (for efficiency) # # (C) Fair Isaac Corp., 1983-2025 from typing import List, Tuple import math import random import time from collections import defaultdict from itertools import combinations import pyoptinterface as poi from pyoptinterface import xpress import xpress as xp import numpy as np XPRS = xpress.XPRS def cb_preintsol(prob, data, soltype, cutoff): """Callback for checking if solution is acceptable""" n = data xsol = prob.getCallbackSolution() xsolf = np.array(xsol) xsol = xsolf.reshape(n, n) nextc = np.argmax(xsol, axis=1) i = 0 ncities = 1 while nextc[i] != 0 and ncities < n: ncities += 1 i = nextc[i] reject = False if ncities < n: if soltype != 0: reject = True else: unchecked = np.zeros(n) ngroup = 0 cut_mstart = [0] cut_ind = [] cut_coe = [] cut_rhs = [] nnz = 0 ncuts = 0 while np.min(unchecked) == 0 and ngroup <= n: """Seek a tour""" ngroup += 1 firstcity = np.argmin(unchecked) i = firstcity ncities = 0 while True: unchecked[i] = ngroup ncities += 1 i = nextc[i] if i == firstcity or ncities > n + 1: break S = np.where(unchecked == ngroup)[0].tolist() compS = np.where(unchecked != ngroup)[0].tolist() indices = [i * n + j for i in S for j in compS] if sum(xsolf[i] for i in indices) < 1 - 1e-3: mcolsp, dvalp = [], [] drhsp, status = prob.presolverow( rowtype="G", origcolind=indices, origrowcoef=np.ones(len(indices)), origrhs=1, maxcoefs=prob.attributes.cols, colind=mcolsp, rowcoef=dvalp, ) assert status == 0 nnz += len(mcolsp) ncuts += 1 cut_ind.extend(mcolsp) cut_coe.extend(dvalp) cut_rhs.append(drhsp) cut_mstart.append(nnz) if ncuts > 0: prob.addcuts( cuttype=[0] * ncuts, rowtype=["G"] * ncuts, rhs=cut_rhs, start=cut_mstart, colind=cut_ind, cutcoef=cut_coe, ) return (reject, None) def print_sol(p, n): """Print the solution: order of nodes and cost""" xsol = np.array(p.getSolution()).reshape(n, n) nextc = np.argmax(xsol, axis=1) i = 0 tour = [] while i != 0 or len(tour) == 0: tour.append(str(i)) i = nextc[i] print("->".join(tour), "->0; cost: ", p.attributes.objval, sep="") def create_initial_tour(n): """Returns a permuted trivial solution 0->1->2->...->(n-1)->0""" sol = np.zeros((n, n)) p = np.random.permutation(n) for i in range(n): sol[p[i], p[(i + 1) % n]] = 1 return sol.flatten() def solve_xpress(nodes, distances): n = len(nodes) nodes = range(n) p = xp.problem() p.controls.outputlog = 0 fly = np.array( [ p.addVariable(vartype=xp.binary, name=f"x_{i}_{j}") for i in nodes for j in nodes ], dtype=xp.npvar, ).reshape(n, n) # Outgoing constraints: sum of outgoing arcs from i equals 1 for i in nodes: p.addConstraint(xp.Sum(fly[i, :]) - fly[i, i] == 1) # Incoming constraints: sum of incoming arcs to i equals 1 for i in nodes: p.addConstraint(xp.Sum(fly[:, i]) - fly[i, i] == 1) # No self-loops for i in nodes: p.addConstraint(fly[i, i] == 0) p.setObjective(xp.Sum(fly[i, j] * distances[i, j] for i in nodes for j in nodes)) p.addcbpreintsol(cb_preintsol, n) p.controls.mipdualreductions = 0 for k in range(10): InitTour = create_initial_tour(n) p.addmipsol(solval=InitTour, name=f"InitTour_{k}") p.optimize() if p.attributes.solstatus not in [xp.SolStatus.OPTIMAL, xp.SolStatus.FEASIBLE]: print("Solve status:", p.attributes.solvestatus.name) print("Solution status:", p.attributes.solstatus.name) else: print_sol(p, n) xvals = np.array(p.getSolution()).reshape(n, n) edges = [(i, j) for i in nodes for j in nodes if xvals[i, j] > 0.5] print(edges) tour = shortest_subtour(edges) objval = p.attributes.objval return tour, objval def shortest_subtour(edges: List[Tuple[int, int]]) -> List[int]: node_neighbors = defaultdict(list) for i, j in edges: node_neighbors[i].append(j) # Follow edges to find cycles. Each time a new cycle is found, keep track # of the shortest cycle found so far and restart from an unvisited node. unvisited = set(node_neighbors) shortest = None while unvisited: cycle = [] neighbors = list(unvisited) while neighbors: current = neighbors.pop() cycle.append(current) unvisited.remove(current) neighbors = [j for j in node_neighbors[current] if j in unvisited] if shortest is None or len(cycle) < len(shortest): shortest = cycle assert shortest is not None return shortest def solve_poi(f, nodes, distances): n = len(nodes) m = f() fly = np.array( [ m.add_variable(name=f"x_{i}_{j}", domain=poi.VariableDomain.Binary) for i in nodes for j in nodes ], dtype=object, # Changed from xp.npvar ).reshape(n, n) # Outgoing constraints: sum of outgoing arcs from i equals 1 for i in nodes: m.add_linear_constraint(poi.quicksum(fly[i, :]) - fly[i, i], poi.Eq, 1) # Incoming constraints: sum of incoming arcs to i equals 1 for i in nodes: m.add_linear_constraint(poi.quicksum(fly[:, i]) - fly[i, i], poi.Eq, 1) # No self-loops for i in nodes: m.add_linear_constraint(fly[i, i], poi.Eq, 0) m.set_objective( poi.quicksum(fly[i, j] * distances[i, j] for i in nodes for j in nodes) ) def eliminate_subtours_poi(model): edges = [ (i, j) for (i, j), v in np.ndenumerate(fly) if model.cb_get_solution(v) > 0.5 ] tour = shortest_subtour(edges) if len(tour) < len(nodes): print(" Shortest subtour:", tour) print( f" Adding new cut with {len(tour)**2 - len(tour)} nonzeros." ) model.cb_add_lazy_constraint( poi.quicksum(fly[i, j] + fly[j, i] for i, j in combinations(tour, 2)), poi.Leq, len(tour) - 1, ) def cb(model, ctx): args = model.cb_get_arguments() if ctx == XPRS.CB_CONTEXT.MESSAGE and args.msgtype > 0: print(f"{ctx.name:>16}: {args.msg}") if ctx == XPRS.CB_CONTEXT.BARITERATION: print( f"{ctx.name:>16}: Barrier iter {model.get_raw_attribute("XPRS_BARITER")}, primal {model.get_raw_attribute("XPRS_BARPRIMALOBJ")}, dual {model.get_raw_attribute("XPRS_BARDUALOBJ")}, primal inf {model.get_raw_attribute("XPRS_BARPRIMALINF")}, dual inf{model.get_raw_attribute("XPRS_BARDUALINF")}, gap {model.get_raw_attribute("XPRS_BARCGAP")}" ) if ctx == XPRS.CB_CONTEXT.BARLOG: print( f"{ctx.name:>16}: Barrier iter {model.get_raw_attribute("XPRS_BARITER")}, primal {model.get_raw_attribute("XPRS_BARPRIMALOBJ")}, dual {model.get_raw_attribute("XPRS_BARDUALOBJ")}, primal inf {model.get_raw_attribute("XPRS_BARPRIMALINF")}, dual inf{model.get_raw_attribute("XPRS_BARDUALINF")}, gap {model.get_raw_attribute("XPRS_BARCGAP")}" ) if ctx == XPRS.CB_CONTEXT.AFTEROBJECTIVE: print( f"{ctx.name:>16}: Completed obj solve {model.get_raw_attribute("XPRS_SOLVEDOBJS")}" ) if ctx == XPRS.CB_CONTEXT.BEFOREOBJECTIVE: print( f"{ctx.name:>16}: Starting obj solve {model.get_raw_attribute("XPRS_SOLVEDOBJS")}" ) if ctx == XPRS.CB_CONTEXT.PRESOLVE: runtime = model.get_raw_attribute_dbl_by_id(XPRS.TIME) coldel = model.get_raw_attribute_int_by_id( XPRS.ORIGINALCOLS ) - model.get_raw_attribute_int_by_id(XPRS.COLS) rowdel = model.get_raw_attribute_int_by_id( XPRS.ORIGINALROWS ) - model.get_raw_attribute_int_by_id(XPRS.ROWS) print( f"{ctx.name:>16}: Runtime: {runtime}, Coldel: {coldel}, Rowdel: {rowdel}" ) if ctx == XPRS.CB_CONTEXT.CHECKTIME: print( f"{ctx.name:>16}: {model.get_raw_attribute("XPRS_TIME")} seconds have passed." ) if ctx == XPRS.CB_CONTEXT.CHGBRANCHOBJECT: print(f"{ctx.name:>16}: Not a lot to print here at the moment") if ctx == XPRS.CB_CONTEXT.CUTLOG: print( f"{ctx.name:>16}: You should see the cutlog somewhere near this message." ) if ctx == XPRS.CB_CONTEXT.CUTROUND: print( f"{ctx.name:>16}: The optimizer would have done another cut round? {args.ifxpresscuts} - Forcing it." ) args.p_action = 1 if ctx == XPRS.CB_CONTEXT.DESTROYMT: print(f"{ctx.name:>16}: Somewhere someone is killing a MIP Thread. RIP :(") if ctx == XPRS.CB_CONTEXT.GAPNOTIFY: obj = model.get_raw_attribute_dbl_by_id(XPRS.MIPOBJVAL) bound = model.get_raw_attribute_dbl_by_id(XPRS.BESTBOUND) gap = 0 if obj != 0 or bound != 0: gap = abs(obj - bound) / max(abs(obj), abs(bound)) print(f"{ctx.name:>16}: Current gap {gap}, next target set to {gap/2}") if ctx == XPRS.CB_CONTEXT.MIPLOG: print( f"{ctx.name:>16}: Node {model.get_raw_attribute("XPRS_CURRENTNODE")} with depth {model.get_raw_attribute("XPRS_NODEDEPTH")} has just been processed" ) if ctx == XPRS.CB_CONTEXT.INFNODE: print( f"{ctx.name:>16}: Infeasible node id {model.get_raw_attribute("XPRS_CURRENTNODE")}" ) if ctx == XPRS.CB_CONTEXT.INTSOL: print( f"{ctx.name:>16}: Integer solution value: {model.get_raw_attribute("XPRS_MIPOBJVAL")}" ) if ctx == XPRS.CB_CONTEXT.LPLOG: print( f"{ctx.name:>16}: At iteration {model.get_raw_attribute("XPRS_SIMPLEXITER")} objval is {model.get_raw_attribute("XPRS_LPOBJVAL")}" ) if ctx == XPRS.CB_CONTEXT.NEWNODE: print( f"{ctx.name:>16}: New node id {args.node}, parent node {args.parentnode}, branch {args.branch}" ) # if ctx == XPRS.CB_CONTEXT.MIPTHREAD: # print(f"{ctx.name:>16}: Not a lot to print here at the moment") if ctx == XPRS.CB_CONTEXT.NODECUTOFF: print(f"{ctx.name:>16}: Node {args.node} cut off.") if ctx == XPRS.CB_CONTEXT.NODELPSOLVED: obj = model.get_raw_attribute_dbl_by_id(XPRS.LPOBJVAL) print( f"{ctx.name:>16}: Solved relaxation at node {model.get_raw_attribute("XPRS_CURRENTNODE")}, lp obj {obj}" ) if ctx == XPRS.CB_CONTEXT.OPTNODE: obj = model.get_raw_attribute_dbl_by_id(XPRS.LPOBJVAL) print( f"{ctx.name:>16}: Finished processing node {model.get_raw_attribute("XPRS_CURRENTNODE")}, lp obj {obj}" ) if ctx == XPRS.CB_CONTEXT.PREINTSOL: print( f"{ctx.name:>16}: Candidate integer solution objective {model.get_raw_attribute("LPOBJVAL")}, soltype: {args.soltype}, p_reject: {args.p_reject}, p_cutoff: {args.p_cutoff}" ) eliminate_subtours_poi(model) if ctx == XPRS.CB_CONTEXT.PRENODE: print(f"{ctx.name:>16}: Node optimization is about to start...") if ctx == XPRS.CB_CONTEXT.USERSOLNOTIFY: print( f"{ctx.name:>16}: Solution {args.solname} was processed resulting in status {args.status}." ) m.set_callback( cb, XPRS.CB_CONTEXT.MESSAGE | XPRS.CB_CONTEXT.BARITERATION | XPRS.CB_CONTEXT.BARLOG | XPRS.CB_CONTEXT.AFTEROBJECTIVE | XPRS.CB_CONTEXT.BEFOREOBJECTIVE | XPRS.CB_CONTEXT.PRESOLVE | XPRS.CB_CONTEXT.CHECKTIME | XPRS.CB_CONTEXT.CHGBRANCHOBJECT | XPRS.CB_CONTEXT.CUTLOG | XPRS.CB_CONTEXT.CUTROUND | XPRS.CB_CONTEXT.DESTROYMT | XPRS.CB_CONTEXT.GAPNOTIFY | XPRS.CB_CONTEXT.MIPLOG | XPRS.CB_CONTEXT.INFNODE | XPRS.CB_CONTEXT.INTSOL | XPRS.CB_CONTEXT.LPLOG # |XPRS.CB_CONTEXT.MIPTHREAD | XPRS.CB_CONTEXT.NEWNODE | XPRS.CB_CONTEXT.NODECUTOFF | XPRS.CB_CONTEXT.NODELPSOLVED | XPRS.CB_CONTEXT.OPTNODE | XPRS.CB_CONTEXT.PREINTSOL | XPRS.CB_CONTEXT.PRENODE | XPRS.CB_CONTEXT.USERSOLNOTIFY, ) m.set_raw_control_int_by_id(XPRS.CALLBACKCHECKTIMEDELAY, 10) m.set_raw_control_dbl_by_id(XPRS.MIPRELGAPNOTIFY, 1.0) m.set_raw_control("XPRS_MIPDUALREDUCTIONS", 0) m.optimize() # Extract the solution as a tour edges = [(i, j) for (i, j), v in np.ndenumerate(fly) if m.get_value(v) > 0.5] tour = shortest_subtour(edges) objval = m.get_model_attribute(poi.ModelAttribute.ObjectiveValue) return tour, objval def create_map(npoints, seed): # Create n random points in 2D random.seed(seed) nodes = list(range(npoints)) points = [(random.randint(0, 100), random.randint(0, 100)) for _ in nodes] # Dictionary of Euclidean distance between each pair of points distances = { (i, j): math.sqrt(sum((points[i][k] - points[j][k]) ** 2 for k in range(2))) for i in nodes for j in nodes } return nodes, distances def test_xpress(npoints_series, seed): for npoints in npoints_series: nodes, distances = create_map(npoints, seed) print(f"npoints = {npoints}") t0 = time.time() f = xpress.Model _, cost2 = solve_poi(f, nodes, distances) t1 = time.time() print(f"\t poi time: {t1 - t0:g} seconds") print(f"POI solution value: {cost2}") t0 = time.time() _, cost1 = solve_xpress(nodes, distances) t1 = time.time() print(f"\t xpress time: {t1 - t0:g} seconds") print(f"Xpress solution value: {cost1}") if __name__ == "__main__": seed = 987651234 X = range(20, 10000, 10000) test_xpress(X, seed) ================================================ FILE: thirdparty/ankerl/stl.h ================================================ ///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// // A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. // Version 4.8.1 // https://github.com/martinus/unordered_dense // // Licensed under the MIT License . // SPDX-License-Identifier: MIT // Copyright (c) 2022 Martin Leitner-Ankerl // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #ifndef ANKERL_STL_H #define ANKERL_STL_H #include // for array #include // for uint64_t, uint32_t, std::uint8_t, UINT64_C #include // for size_t, memcpy, memset #include // for equal_to, hash #include // for initializer_list #include // for pair, distance #include // for numeric_limits #include // for allocator, allocator_traits, shared_ptr #include // for optional #include // for out_of_range #include // for basic_string #include // for basic_string_view, hash #include // for forward_as_tuple #include // for enable_if_t, declval, conditional_t, ena... #include // for forward, exchange, pair, as_const, piece... #include // for vector // includes , which fails to compile if // targeting GCC >= 13 with the (rewritten) win32 thread model, and // targeting Windows earlier than Vista (0x600). GCC predefines // _REENTRANT when using the 'posix' model, and doesn't when using the // 'win32' model. #if defined __MINGW64__ && defined __GNUC__ && __GNUC__ >= 13 && !defined _REENTRANT // _WIN32_WINNT is guaranteed to be defined here because of the // inclusion above. # ifndef _WIN32_WINNT # error "_WIN32_WINNT not defined" # endif # if _WIN32_WINNT < 0x600 # define ANKERL_MEMORY_RESOURCE_IS_BAD() 1 // NOLINT(cppcoreguidelines-macro-usage) # endif #endif #ifndef ANKERL_MEMORY_RESOURCE_IS_BAD # define ANKERL_MEMORY_RESOURCE_IS_BAD() 0 // NOLINT(cppcoreguidelines-macro-usage) #endif #if defined(__has_include) && !defined(ANKERL_UNORDERED_DENSE_DISABLE_PMR) # if __has_include() && !ANKERL_MEMORY_RESOURCE_IS_BAD() # define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage) # include // for polymorphic_allocator # elif __has_include() # define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage) # include // for polymorphic_allocator # endif #endif #if defined(_MSC_VER) && defined(_M_X64) # include # pragma intrinsic(_umul128) #endif #endif ================================================ FILE: thirdparty/ankerl/unordered_dense.h ================================================ ///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// // A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. // Version 4.8.1 // https://github.com/martinus/unordered_dense // // Licensed under the MIT License . // SPDX-License-Identifier: MIT // Copyright (c) 2022 Martin Leitner-Ankerl // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #ifndef ANKERL_UNORDERED_DENSE_H #define ANKERL_UNORDERED_DENSE_H // see https://semver.org/spec/v2.0.0.html #define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes #define ANKERL_UNORDERED_DENSE_VERSION_MINOR 8 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality #define ANKERL_UNORDERED_DENSE_VERSION_PATCH 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes // API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/ // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) #define ANKERL_UNORDERED_DENSE_NAMESPACE \ ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \ ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH) #if defined(_MSVC_LANG) # define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG #else # define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus #endif #if defined(__GNUC__) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__)) #elif defined(_MSC_VER) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) #endif // exceptions #if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) # define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 // NOLINT(cppcoreguidelines-macro-usage) #else # define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 // NOLINT(cppcoreguidelines-macro-usage) #endif #ifdef _MSC_VER # define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline) #else # define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline)) #endif #if defined(__clang__) && defined(__has_attribute) # if __has_attribute(__no_sanitize__) # define ANKERL_UNORDERED_DENSE_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK \ __attribute__((__no_sanitize__("unsigned-integer-overflow"))) # endif #endif #if !defined(ANKERL_UNORDERED_DENSE_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK) # define ANKERL_UNORDERED_DENSE_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK #endif #if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L # error ankerl::unordered_dense requires C++17 or higher #else # if !defined(ANKERL_UNORDERED_DENSE_STD_MODULE) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_STD_MODULE 0 # endif # if !ANKERL_UNORDERED_DENSE_STD_MODULE # include "stl.h" # endif # if __has_cpp_attribute(likely) && __has_cpp_attribute(unlikely) && ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L # define ANKERL_UNORDERED_DENSE_LIKELY_ATTR [[likely]] // NOLINT(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_UNLIKELY_ATTR [[unlikely]] // NOLINT(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) # else # define ANKERL_UNORDERED_DENSE_LIKELY_ATTR // NOLINT(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_UNLIKELY_ATTR // NOLINT(cppcoreguidelines-macro-usage) # if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) # define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1) // NOLINT(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage) # else # define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) # endif # endif namespace ankerl::unordered_dense { inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE { namespace detail { # if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other // inlinings more difficult. Throws are also generally the slow path. [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() { throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found"); } [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() { throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size"); } [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() { throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements"); } # else [[noreturn]] inline void on_error_key_not_found() { abort(); } [[noreturn]] inline void on_error_bucket_overflow() { abort(); } [[noreturn]] inline void on_error_too_many_elements() { abort(); } # endif } // namespace detail // hash /////////////////////////////////////////////////////////////////////// // This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash // No big-endian support (because different values on different machines don't matter), // hardcodes seed and the secret, reformats the code, and clang-tidy fixes. namespace detail::wyhash { inline void mum(std::uint64_t* a, std::uint64_t* b) { # if defined(__SIZEOF_INT128__) __uint128_t r = *a; r *= *b; *a = static_cast(r); *b = static_cast(r >> 64U); # elif defined(_MSC_VER) && defined(_M_X64) *a = _umul128(*a, *b, b); # else std::uint64_t ha = *a >> 32U; std::uint64_t hb = *b >> 32U; std::uint64_t la = static_cast(*a); std::uint64_t lb = static_cast(*b); std::uint64_t hi{}; std::uint64_t lo{}; std::uint64_t rh = ha * hb; std::uint64_t rm0 = ha * lb; std::uint64_t rm1 = hb * la; std::uint64_t rl = la * lb; std::uint64_t t = rl + (rm0 << 32U); auto c = static_cast(t < rl); lo = t + (rm1 << 32U); c += static_cast(lo < t); hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c; *a = lo; *b = hi; # endif } // multiply and xor mix function, aka MUM [[nodiscard]] inline auto mix(std::uint64_t a, std::uint64_t b) -> std::uint64_t { mum(&a, &b); return a ^ b; } // read functions. WARNING: we don't care about endianness, so results are different on big endian! [[nodiscard]] inline auto r8(const std::uint8_t* p) -> std::uint64_t { std::uint64_t v{}; std::memcpy(&v, p, 8U); return v; } [[nodiscard]] inline auto r4(const std::uint8_t* p) -> std::uint64_t { std::uint32_t v{}; std::memcpy(&v, p, 4); return v; } // reads 1, 2, or 3 bytes [[nodiscard]] inline auto r3(const std::uint8_t* p, std::size_t k) -> std::uint64_t { return (static_cast(p[0]) << 16U) | (static_cast(p[k >> 1U]) << 8U) | p[k - 1]; } [[maybe_unused]] [[nodiscard]] inline auto hash(void const* key, std::size_t len) -> std::uint64_t { static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f), UINT64_C(0xe7037ed1a0b428db), UINT64_C(0x8ebc6af09c88c6e3), UINT64_C(0x589965cc75374cc3)}; auto const* p = static_cast(key); std::uint64_t seed = secret[0]; std::uint64_t a{}; std::uint64_t b{}; if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) ANKERL_UNORDERED_DENSE_LIKELY_ATTR { if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) ANKERL_UNORDERED_DENSE_LIKELY_ATTR { a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U)); b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U)); } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) ANKERL_UNORDERED_DENSE_LIKELY_ATTR { a = r3(p, len); b = 0; } else { a = 0; b = 0; } } else { std::size_t i = len; if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) ANKERL_UNORDERED_DENSE_UNLIKELY_ATTR { std::uint64_t see1 = seed; std::uint64_t see2 = seed; do { seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1); see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2); p += 48; i -= 48; } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48)); seed ^= see1 ^ see2; } while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) ANKERL_UNORDERED_DENSE_UNLIKELY_ATTR { seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); i -= 16; p += 16; } a = r8(p + i - 16); b = r8(p + i - 8); } return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed)); } [[nodiscard]] inline auto hash(std::uint64_t x) -> std::uint64_t { return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15)); } } // namespace detail::wyhash template struct hash { auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) -> std::uint64_t { return std::hash{}(obj); } }; template struct hash::is_avalanching> { using is_avalanching = void; auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) -> std::uint64_t { return std::hash{}(obj); } }; template struct hash> { using is_avalanching = void; auto operator()(std::basic_string const& str) const noexcept -> std::uint64_t { return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size()); } }; template struct hash> { using is_avalanching = void; auto operator()(std::basic_string_view const& sv) const noexcept -> std::uint64_t { return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size()); } }; template struct hash { using is_avalanching = void; auto operator()(T* ptr) const noexcept -> std::uint64_t { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return detail::wyhash::hash(reinterpret_cast(ptr)); } }; template struct hash> { using is_avalanching = void; auto operator()(std::unique_ptr const& ptr) const noexcept -> std::uint64_t { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return detail::wyhash::hash(reinterpret_cast(ptr.get())); } }; template struct hash> { using is_avalanching = void; auto operator()(std::shared_ptr const& ptr) const noexcept -> std::uint64_t { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return detail::wyhash::hash(reinterpret_cast(ptr.get())); } }; template struct hash>> { using is_avalanching = void; auto operator()(Enum e) const noexcept -> std::uint64_t { using underlying = std::underlying_type_t; return detail::wyhash::hash(static_cast(e)); } }; template struct tuple_hash_helper { // Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest. // If it isn't an integral we need to hash it. template [[nodiscard]] constexpr static auto to64(Arg const& arg) -> std::uint64_t { if constexpr (std::is_integral_v || std::is_enum_v) { return static_cast(arg); } else { return hash{}(arg); } } [[nodiscard]] ANKERL_UNORDERED_DENSE_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK static auto mix64(std::uint64_t state, std::uint64_t v) -> std::uint64_t { return detail::wyhash::mix(state + v, std::uint64_t{0x9ddfea08eb382d69}); } // Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If // not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized // away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer. template [[nodiscard]] static auto calc_hash(T const& t, std::index_sequence /*unused*/) noexcept -> std::uint64_t { auto h = std::uint64_t{}; ((h = mix64(h, to64(std::get(t)))), ...); return h; } }; template struct hash> : tuple_hash_helper { using is_avalanching = void; auto operator()(std::tuple const& t) const noexcept -> std::uint64_t { return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); } }; template struct hash> : tuple_hash_helper { using is_avalanching = void; auto operator()(std::pair const& t) const noexcept -> std::uint64_t { return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); } }; // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) # define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \ template <> \ struct hash { \ using is_avalanching = void; \ auto operator()(T const& obj) const noexcept -> std::uint64_t { \ return detail::wyhash::hash(static_cast(obj)); \ } \ } # if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wuseless-cast" # endif // see https://en.cppreference.com/w/cpp/utility/hash ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char); # if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L && defined(__cpp_char8_t) ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t); # endif ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long); ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long); # if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic pop # endif // bucket_type ////////////////////////////////////////////////////////// namespace bucket_type { struct standard { static constexpr std::uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint static constexpr std::uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint std::uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash std::uint32_t m_value_idx; // index into the m_values vector. }; ANKERL_UNORDERED_DENSE_PACK(struct big { static constexpr std::uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint static constexpr std::uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint std::uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash std::size_t m_value_idx; // index into the m_values vector. }); } // namespace bucket_type namespace detail { struct nonesuch {}; struct default_container_t {}; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template