Skip to content

Commit

Permalink
NLW C API: Write obj grad #30
Browse files Browse the repository at this point in the history
Mechanism to write gradients: pass void* to std::function<void(int, double)>
  • Loading branch information
glebbelov committed Nov 14, 2023
1 parent f0e008c commit d431e0e
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 74 deletions.
50 changes: 48 additions & 2 deletions nl-writer2/examples/c/nlsol_ex_c_nl.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include <stdlib.h>
#include <assert.h>

#include "nlsol_ex_c_nl.h"

NLHeader_C CAPI_ex_Header(void* pex_void) {
CAPIExample* pex = (CAPIExample*)pex_void;
NLHeader_C hdr;
// Get default header
NLHeader_C hdr = MakeNLHeader_C_Default();

hdr.pi.num_vars = pex->n_var;
hdr.pi.num_algebraic_cons = pex->n_con;
Expand Down Expand Up @@ -122,23 +124,67 @@ NLHeader_C CAPI_ex_Header(void* pex_void) {
*/
hdr.pi.num_common_exprs_in_single_objs = 0;


// Technical
hdr.nli.format
= pex->binary_nl ? NL_FORMAT_BINARY : NL_FORMAT_TEXT;

hdr.nli.prob_name = "c_api_example_model";

return hdr;
}

const char* ObjDescription(void* p_user_data, int i) {
CAPIExample* pex = (CAPIExample*)p_user_data;
return pex->obj_name;
}

int ObjType(void* p_user_data, int i) {
CAPIExample* pex = (CAPIExample*)p_user_data;
return pex->obj_sense;
}

int ObjGradientNNZ(void* p_user_data, int i) {
CAPIExample* pex = (CAPIExample*)p_user_data;
return pex->n_obj_nz;
}

void FeedObjGradient(
void* p_user_data, int i, void* p_api_data_) {
CAPIExample* pex = (CAPIExample*)p_user_data;
assert(0==i);
for (int j=0; j<pex->n_obj_nz; ++j)
NLW2_WriteSparseDblEntry(p_api_data_,
pex->obj_linpart[j].index_,
pex->obj_linpart[j].value_);
}


NLFeeder2_C MakeNLFeeder2_C(
CAPIExample* pex, int binary) {
NLFeeder2_C result;
NLFeeder2_C result // Fill with default values
= NLW2_MakeNLFeeder2_C_Default();

result.p_user_data_ = pex;
pex->binary_nl = binary;

// Header feeder
result.Header = CAPI_ex_Header;

// Change some options
result.want_nl_comments_ = 1;

// Objective
result.ObjDescription = ObjDescription;
result.ObjType = ObjType;
result.ObjGradientNNZ = ObjGradientNNZ;
result.FeedObjGradient = FeedObjGradient;

return result;
}

void DestroyNLFeeder2_C(NLFeeder2_C* pf) {
pf->p_user_data_ = NULL;

NLW2_DestroyNLFeeder2_C_Default(pf);
}
36 changes: 30 additions & 6 deletions nl-writer2/include/api/c/nl-feeder2-c-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
#ifndef NLFEEDER2CIMPL_H
#define NLFEEDER2CIMPL_H

#include "api/c/nl-feeder2-c.h"
#include <functional>

#include "api/c/nl-feeder2-c.h" // C wrapper

#include "mp/nl-feeder2.h" // C++ base

#include "mp/nl-feeder2.h"

namespace mp {

/// Implementation:
/// Wrap NLFeeder2_C into a C++ class,
/// in order to interface it for NLWriter2
class NLFeeder2_C_Impl
Expand All @@ -30,6 +34,7 @@ class NLFeeder2_C_Impl
* technical parameters,
* such as text/binary NL format. */
NLHeader Header() {
assert(NLF().Header);
auto h_c = NLF().Header(NLF().p_user_data_);
NLHeader hdr;
*(ProblemInfo_C*)(&hdr) = h_c.pi;
Expand Down Expand Up @@ -71,26 +76,45 @@ class NLFeeder2_C_Impl
* (\a i in 0..num_objs-1).
* With WantNLComments()==true, this is
* written to text-format NL as a comment. */
const char* ObjDescription(int i) { return ""; }
const char* ObjDescription(int i) {
assert(NLF().ObjDescription);
return NLF().ObjDescription(NLF().p_user_data_, i);
}

/** Provide type of objective \a i.
* 0 - minimization;
* 1 - maximization. */
int ObjType(int i) { return {}; }
int ObjType(int i) {
assert(NLF().ObjType);
return NLF().ObjType(NLF().p_user_data_, i);
}

/** Feed gradient for objective \a i.
* Should include entries for all potentially
* nonzero elements (sparsity pattern).
*
* Implementation skeleton:
* if (obj_grad[i].size()) {
* auto sv = svw.MakeVectorWriter(obj_grad[i].size());
* auto sv = svwf.MakeVectorWriter(obj_grad[i].size());
* for (size_t j=0; j<obj_grad.size(); ++j)
* sv.Write(obj_grad[j].var_index, obj_grad[j].coef);
* }
*/
template <class ObjGradWriterFactory>
void FeedObjGradient(int i, ObjGradWriterFactory& ) { }
void FeedObjGradient(int i, ObjGradWriterFactory& svwf) {
assert(NLF().ObjGradientNNZ);
int nnz = NLF().ObjGradientNNZ(NLF().p_user_data_, i);
if (nnz) {
auto sv = svwf.MakeVectorWriter(nnz);
assert(NLF().FeedObjGradient);
// We need the callback to be callable from C
std::function<void(int, double)> svw
= [&sv](int i, double v){
sv.Write(i, v);
};
NLF().FeedObjGradient(NLF().p_user_data_, i, &svw);
}
}

/** Feed nonlinear expression of objective \a i.
*
Expand Down
146 changes: 83 additions & 63 deletions nl-writer2/include/api/c/nl-feeder2-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,82 @@
extern "C" {
#endif

typedef struct NLW2_ObjGradWriter NLW2_ObjGradWriter;
/// Declare callbacks

/// Write sparse vector entry
void NLW2_WriteSparseDblEntry(
void* p_api_data_, int index, double value);

/// \rst
/// Algebraic constraint bounds (for a single constraint):
/// either range (lb, ub),
/// or complementarity info (k, cvar), when k>0.
///
/// For a complementarity constraint to hold, if cvar is at
/// its lower bound, then body >= 0; if cvar is at its upper
/// bound, then body <= 0;
/// and if cvar is strictly between its bounds, then body = 0.
/// The integer k in a complementarity constraint line indicates
/// which bounds on cvar are finite: 1 and 3 imply a finite
/// lower bound; 2 and 3 imply a finite upper bound; 0 (which
/// should not occur) would imply no finite bounds, i.e.,
/// body = 0 must always hold.
///
/// Example:
///
/// .. code-block:: ampl
///
/// ampl: var x; var y; var z;
/// ampl: s.t. Compl1: x+y >= 3 complements x-z <= 15;
/// ampl: s.t. Compl2: -2 <= 2*y+3*z <= 13 complements 6*z-2*x;
/// ampl: expand;
/// subject to Compl1:
/// 3 <= x + y
/// complements
/// x - z <= 15;
///
/// subject to Compl2:
/// -2 <= 2*y + 3*z <= 13
/// complements
/// -2*x + 6*z;
///
/// ampl: solexpand;
/// Nonsquare complementarity system:
/// 4 complementarities including 2 equations
/// 5 variables
/// subject to Compl1.L:
/// x + y + Compl1$cvar = 0;
///
/// subject to Compl1.R:
/// -15 + x - z <= 0
/// complements
/// Compl1$cvar <= -3;
///
/// subject to Compl2.L:
/// 2*y + 3*z - Compl2$cvar = 0;
///
/// subject to Compl2.R:
/// -2*x + 6*z
/// complements
/// -2 <= Compl2$cvar <= 13;
///
/// \endrst
typedef struct AlgConRange_C {
double L, U;
int k, cvar; // k>0 means complementarity to cvar
} AlgConRange_C;


/** Wrap mp::NLFeeder2 for C API.
NLFeeder2_C: writes model details on request
via provided callback objects.
See the examples folder.
To fill some **default values and methods**,
e.g., options and some methods like name feeders,
call NLW2_MakeNLFeeder2_C_Default() / NLW2_Destroy...().
For the NL format, variables and constraints must have certain order.
**Variable ordering:**
Expand Down Expand Up @@ -50,6 +118,8 @@ typedef struct NLFeeder2_C {
* such as text/binary NL format. */
NLHeader_C (*Header)(void* p_user_data);

/// Options. Safe to leave as by the default generator.

/// NL comments?
int want_nl_comments_;

Expand All @@ -61,7 +131,7 @@ typedef struct NLFeeder2_C {
int output_precision_;

/// Write bounds first?
/// The default is yes in AMPL, controlled by
/// The default is 1 (yes) in AMPL, controlled by
/// (the value of option nl_permute) & 32
/// (the bit is 0 for yes).
/// Changing this option is deprecated, see
Expand Down Expand Up @@ -100,11 +170,12 @@ typedef struct NLFeeder2_C {
* nonzero elements (sparsity pattern).
*
* Implementation skeleton:
* for (size_t j=0; j<obj_grad.size(); ++j)
* NLW2_Write(&svw, obj_grad[j].var_index, obj_grad[j].coef);
* for (size_t j=0; j<obj_grad_size[i]; ++j)
* NLW2_WriteSparseDblEntry(p_api_data_,
* obj_grad_index[i][j], obj_grad_value[i][j]);
*/
void (*FeedObjGradient)(
void* p_user_data, int i, NLW2_ObjGradWriter* );
void* p_user_data, int i, void* p_api_data_);

/** Feed nonlinear expression of objective \a i.
*
Expand Down Expand Up @@ -173,64 +244,6 @@ typedef struct NLFeeder2_C {


///////////////// 5. CONSTRAINT BOUNDS & COMPLEMENTARITY ///////
/// \rst
/// Algebraic constraint bounds (for a single constraint):
/// either range (lb, ub),
/// or complementarity info (k, cvar), when k>0.
///
/// For a complementarity constraint to hold, if cvar is at
/// its lower bound, then body >= 0; if cvar is at its upper
/// bound, then body <= 0;
/// and if cvar is strictly between its bounds, then body = 0.
/// The integer k in a complementarity constraint line indicates
/// which bounds on cvar are finite: 1 and 3 imply a finite
/// lower bound; 2 and 3 imply a finite upper bound; 0 (which
/// should not occur) would imply no finite bounds, i.e.,
/// body = 0 must always hold.
///
/// Example:
///
/// .. code-block:: ampl
///
/// ampl: var x; var y; var z;
/// ampl: s.t. Compl1: x+y >= 3 complements x-z <= 15;
/// ampl: s.t. Compl2: -2 <= 2*y+3*z <= 13 complements 6*z-2*x;
/// ampl: expand;
/// subject to Compl1:
/// 3 <= x + y
/// complements
/// x - z <= 15;
///
/// subject to Compl2:
/// -2 <= 2*y + 3*z <= 13
/// complements
/// -2*x + 6*z;
///
/// ampl: solexpand;
/// Nonsquare complementarity system:
/// 4 complementarities including 2 equations
/// 5 variables
/// subject to Compl1.L:
/// x + y + Compl1$cvar = 0;
///
/// subject to Compl1.R:
/// -15 + x - z <= 0
/// complements
/// Compl1$cvar <= -3;
///
/// subject to Compl2.L:
/// 2*y + 3*z - Compl2$cvar = 0;
///
/// subject to Compl2.R:
/// -2*x + 6*z
/// complements
/// -2 <= Compl2$cvar <= 13;
///
/// \endrst
struct AlgConRange_C {
double L, U;
int k, cvar; // k>0 means complementarity to cvar
};

/** Bounds/complementarity for all algebraic constraints
* (\a num_algebraic_cons).
Expand Down Expand Up @@ -464,6 +477,13 @@ typedef struct NLFeeder2_C {

} NLFeeder2_C;


/// Return NLFeeder2_C with default options / methods
NLFeeder2_C NLW2_MakeNLFeeder2_C_Default();

/// Destroy NLFeeder2_C created by NLW2_MakeNLFeeder2_C_default()
void NLW2_DestroyNLFeeder2_C_Default(NLFeeder2_C* );

#ifdef __cplusplus
} // extern "C"
#endif
Expand Down
4 changes: 4 additions & 0 deletions nl-writer2/include/api/c/nl-writer2-misc-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ extern "C" {
/// NL writer and SOL reader utilities.
/// It provides facilities for logging
/// and error handling.
///
/// To fill **default methods**,
/// call NLW2_MakeNLUtils_C_Default() / NLW2_Destroy...().
///
/// The default error handler exit()s.
typedef struct NLUtils_C {

Expand Down
3 changes: 3 additions & 0 deletions nl-writer2/include/api/c/nlsol-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ extern "C" {
/// In particular, it does not store any intermediate
/// model representation.
///
/// To create / destroy,
/// use NLW2_MakeNLSOL_C / NLW2_Destroy...().
///
/// Usage: see the C API example and the below API.
typedef struct NLSOL_C {
/// Internal data
Expand Down
3 changes: 3 additions & 0 deletions nl-writer2/include/api/c/sol-handler2-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ extern "C" {
/// SOLHandler2_C: reads solution details on request
/// via provided callback objects.
/// See the examples folder.
///
/// To fill some **default methods**,
/// call NLW2_MakeSOLHandler2_C_Default() / NLW2_Destroy...().
typedef struct SOLHandler2_C {
/// User data
void* p_user_data_;
Expand Down
3 changes: 2 additions & 1 deletion nl-writer2/include/mp/nl-header.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ struct NLInfo : NLInfo_C {

/// Construct default
NLInfo() {
format = TEXT;
format = BINARY;
prob_name = "default_instance";
num_ampl_options = 3;
ampl_vbtol = 0.0;
arith_kind = NL_ARITH_IEEE_LITTLE_ENDIAN;
Expand Down
Loading

0 comments on commit d431e0e

Please sign in to comment.