Skip to content

Commit 2e0eb57

Browse files
committed
feat(core): add bonmin and couenne minlp solver support, optimize coinor build script
1 parent e9e8117 commit 2e0eb57

6 files changed

Lines changed: 639 additions & 43 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ node_modules/
44
# Build outputs
55
dist/
66
build/
7+
.build/
78
prebuilds/
89
coverage/
910
ctrf/
@@ -71,3 +72,8 @@ packages/morsel/.react-router/
7172
!packages/tree-sitter-modelica/tree-sitter-modelica.wasm
7273
packages/tree-sitter-modelica/examples/*/
7374
packages/ide/vscode-web/
75+
76+
# COIN-OR build artifacts
77+
Couenne/
78+
ThirdParty/
79+
f2ctmp/

packages/core/scripts/build-coinor.sh

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -53,36 +53,60 @@ if [ ! -d "$COIN_INSTALL/include/coin-or" ]; then
5353
export CXX=em++
5454
export AR=emar
5555
export RANLIB=emranlib
56-
57-
# Build CLP (includes CoinUtils)
58-
"$COINBREW" fetch Clp
59-
"$COINBREW" build Clp \
60-
--prefix="$COIN_INSTALL" \
61-
--no-prompt \
62-
--static \
63-
--disable-shared \
64-
--enable-static \
65-
--tests=none
66-
67-
# Build CBC (includes CGL)
68-
"$COINBREW" fetch Cbc
69-
"$COINBREW" build Cbc \
70-
--prefix="$COIN_INSTALL" \
71-
--no-prompt \
72-
--static \
73-
--disable-shared \
74-
--enable-static \
75-
--tests=none
76-
77-
# Build IPOPT with MUMPS
78-
"$COINBREW" fetch Ipopt
79-
"$COINBREW" build Ipopt \
80-
--prefix="$COIN_INSTALL" \
81-
--no-prompt \
82-
--static \
83-
--disable-shared \
84-
--enable-static \
85-
--tests=none
56+
export F77="fort77"
57+
export FC="fort77"
58+
export FFLAGS="-O2 -fPIC"
59+
export FCFLAGS="-O2 -fPIC"
60+
export LDFLAGS="-L$EMSDK/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten"
61+
62+
# 1. Shallow clone repositories explicitly to avoid slow complete fetches
63+
# and network drops from coinbrew, while remaining in batch mode.
64+
echo "Shallow cloning COIN-OR packages (latest master)..."
65+
fetch_shallow() {
66+
local proj=$1
67+
if [ ! -d "$BUILD_DIR/$proj" ]; then
68+
echo "--> Cloning $proj..."
69+
git clone --depth 1 "https://github.com/coin-or/$proj.git" "$BUILD_DIR/$proj"
70+
else
71+
echo "--> $proj already cloned."
72+
fi
73+
}
74+
75+
fetch_shallow Clp
76+
fetch_shallow Cbc
77+
fetch_shallow Ipopt
78+
fetch_shallow Bonmin
79+
fetch_shallow Couenne
80+
81+
# 2. Build solvers sequentially.
82+
# We do NOT use --reconfigure here, because it cascades and forces
83+
# coinbrew to redundantly rebuild all shared dependencies!
84+
# Pass the -L path to ALL builds uniformly so that Mumps (a transitive
85+
# dependency via Ipopt) can find libf2c.a in the Emscripten sysroot.
86+
COIN_LDFLAGS="-static -L$EMSDK/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten"
87+
88+
for pkg in Clp Cbc Ipopt Bonmin Couenne; do
89+
echo "--- Building $pkg ---"
90+
91+
# Ipopt and MINLP solvers need built-in LAPACK (no system LAPACK for WASM)
92+
EXTRA_FLAGS=""
93+
if [ "$pkg" = "Ipopt" ] || [ "$pkg" = "Bonmin" ] || [ "$pkg" = "Couenne" ]; then
94+
EXTRA_FLAGS="--with-lapack=BUILD"
95+
fi
96+
97+
"$COINBREW" build "$pkg" \
98+
--prefix="$COIN_INSTALL" \
99+
--disable-shared \
100+
--host=wasm32-unknown-emscripten \
101+
--enable-static \
102+
--tests=none \
103+
--reconfigure \
104+
LDFLAGS="$COIN_LDFLAGS" \
105+
LT_LDFLAGS="-all-static" \
106+
--no-prompt \
107+
--verbosity=2 \
108+
$EXTRA_FLAGS
109+
done
86110

87111
cd "$PACKAGE_DIR"
88112
fi
@@ -91,19 +115,24 @@ fi
91115
echo "Compiling COIN-OR WASM module..."
92116
mkdir -p "$WASM_DIR"
93117

94-
cat > "$BUILD_DIR/coinor_wasm_entry.c" << 'ENTRY_EOF'
118+
cat > "$BUILD_DIR/coinor_wasm_entry.cpp" << 'ENTRY_EOF'
95119
/*
96120
* COIN-OR WASM entry points.
97-
* Provides simplified C functions callable from JavaScript via Emscripten ccall().
121+
* Provides simplified C/C++ functions callable from JavaScript via Emscripten ccall().
98122
*/
99123
#include <stdlib.h>
100124
#include <string.h>
101125
102126
#define HAVE_IPOPT
103127
#define HAVE_CLP
104128
#define HAVE_CBC
129+
#define HAVE_BONMIN
130+
#define HAVE_COUENNE
105131
106132
#include "coinor-interface.c"
133+
#include "coinor-minlp-interface.cpp"
134+
135+
extern "C" {
107136
108137
/* IPOPT callback types from addFunction() */
109138
typedef int (*wasm_eval_f)(int n, double* x, int new_x, double* obj, void* ud);
@@ -238,23 +267,28 @@ int coinor_cbc_wasm(
238267
coinor_result_free(&result);
239268
return result.status;
240269
}
270+
} // extern "C"
241271
ENTRY_EOF
242272

243-
emcc -O2 \
273+
em++ -O2 \
274+
-std=c++14 \
244275
-I"$COIN_INSTALL/include/coin-or" \
245276
-I"$SRC_DIR" \
246-
"$BUILD_DIR/coinor_wasm_entry.c" \
277+
"$BUILD_DIR/coinor_wasm_entry.cpp" \
247278
-L"$COIN_INSTALL/lib" \
279+
-lcouenne \
280+
-lbonmin \
248281
-lipopt \
249282
-lClp \
250283
-lCbc \
251-
-lCoinUtils \
252-
-lOsiClp \
253284
-lCgl \
285+
-lOsi \
286+
-lOsiClp \
287+
-lCoinUtils \
254288
-lm \
255289
-s MODULARIZE=1 \
256290
-s EXPORT_ES6=1 \
257-
-s EXPORTED_FUNCTIONS='["_coinor_ipopt_wasm","_coinor_clp_wasm","_coinor_cbc_wasm","_malloc","_free"]' \
291+
-s EXPORTED_FUNCTIONS='["_coinor_ipopt_wasm","_coinor_clp_wasm","_coinor_cbc_wasm","_coinor_bonmin_wasm","_coinor_couenne_wasm","_malloc","_free"]' \
258292
-s EXPORTED_RUNTIME_METHODS='["addFunction","removeFunction","ccall","cwrap"]' \
259293
-s ALLOW_TABLE_GROWTH=1 \
260294
-s ALLOW_MEMORY_GROWTH=1 \

packages/core/src/compiler/modelica/coinor-codegen.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import type { SolverOptions } from "./solver-options.js";
2525
export interface CoinorCodegenOptions {
2626
/** Model identifier (from FMU codegen). */
2727
modelIdentifier: string;
28-
/** Solver to use: "clp", "cbc", or "ipopt". */
29-
solver: "clp" | "cbc" | "ipopt";
28+
/** Solver to use: "clp", "cbc", "ipopt", "bonmin", or "couenne". */
29+
solver: "clp" | "cbc" | "ipopt" | "bonmin" | "couenne";
3030
/** NLP convergence tolerance (default: 1e-8). */
3131
tolerance?: number;
3232
/** Maximum iterations (default: 3000). */
@@ -212,6 +212,12 @@ export function generateLpMainC(problem: LpProblemDef, options: CoinorCodegenOpt
212212
*/
213213
export function generateNlpMainC(problem: NlpProblemDef, options: CoinorCodegenOptions): CoinorCodegenResult {
214214
const overrides = options.solverOptions;
215+
const solver =
216+
overrides?.optimizer === "bonmin" || overrides?.optimizer === "couenne" || overrides?.optimizer === "ipopt"
217+
? overrides.optimizer
218+
: options.solver === "bonmin" || options.solver === "couenne"
219+
? options.solver
220+
: "ipopt";
215221
const tolerance = overrides?.atol ?? options.tolerance ?? 1e-8;
216222
const maxIter = overrides?.maxNonlinearIterations ?? options.maxIterations ?? 3000;
217223
const printLevel = options.printLevel ?? 0;
@@ -244,9 +250,12 @@ export function generateNlpMainC(problem: NlpProblemDef, options: CoinorCodegenO
244250

245251
const lines: string[] = [];
246252

247-
lines.push("/* Auto-generated by ModelScript — COIN-OR IPOPT NLP driver */");
248-
lines.push("#define HAVE_IPOPT");
253+
lines.push(`/* Auto-generated by ModelScript — COIN-OR ${solver.toUpperCase()} NLP driver */`);
254+
lines.push(`#define HAVE_${solver.toUpperCase()}`);
249255
lines.push('#include "coinor-interface.c"');
256+
if (solver === "bonmin" || solver === "couenne") {
257+
lines.push('#include "coinor-minlp-interface.cpp"');
258+
}
250259
lines.push("#include <stdio.h>");
251260
lines.push("#include <stdlib.h>");
252261
lines.push("");
@@ -402,7 +411,7 @@ export function generateNlpMainC(problem: NlpProblemDef, options: CoinorCodegenO
402411
lines.push("");
403412

404413
lines.push(" CoinorResult result;");
405-
lines.push(" coinor_ipopt_solve(&cb, x0, &opts, &result);");
414+
lines.push(` coinor_${solver}_solve(&cb, x0, &opts, &result);`);
406415
lines.push("");
407416

408417
lines.push(' printf("Status: %d (%s)\\n", result.status, result.message);');
@@ -446,6 +455,20 @@ find_package(PkgConfig REQUIRED)
446455
pkg_check_modules(IPOPT REQUIRED ipopt)
447456
target_include_directories(\${PROJECT_NAME} PRIVATE \${IPOPT_INCLUDE_DIRS})
448457
target_link_libraries(\${PROJECT_NAME} PRIVATE \${IPOPT_LIBRARIES})
458+
`;
459+
case "bonmin":
460+
return `# BONMIN dependency
461+
find_package(PkgConfig REQUIRED)
462+
pkg_check_modules(BONMIN REQUIRED bonmin)
463+
target_include_directories(\${PROJECT_NAME} PRIVATE \${BONMIN_INCLUDE_DIRS})
464+
target_link_libraries(\${PROJECT_NAME} PRIVATE \${BONMIN_LIBRARIES})
465+
`;
466+
case "couenne":
467+
return `# COUENNE dependency
468+
find_package(PkgConfig REQUIRED)
469+
pkg_check_modules(COUENNE REQUIRED couenne)
470+
target_include_directories(\${PROJECT_NAME} PRIVATE \${COUENNE_INCLUDE_DIRS})
471+
target_link_libraries(\${PROJECT_NAME} PRIVATE \${COUENNE_LIBRARIES})
449472
`;
450473
default:
451474
return "";

0 commit comments

Comments
 (0)