BAM user manual and installation guide v. 2026.5.28
May 28, 2026
For information about this software, contact us at info@minlp.com.
1 Introduction
The purpose of BAM (Branch-And-Model) is to solve optimization problems whose objective function or constraints are not available in algebraic form. BAM can be used to optimize models defined by black-box simulators or laboratory experiments. The solver is particularly suited for problems in which each simulator or experimental evaluation is expensive, making it important to limit the number of calls to the black box during the algorithm.
BAM was developed for applications such as optimizing complex simulation models, optimizing from laboratory data, and hyperparameter tuning of complex machine learning models.
The class of optimization problems addressed by BAM is commonly referred to as derivative-free, black-box, simulation, or data-driven optimization. Algorithms for this class of problems are typically classified as local- or global-search methods. Local search methods improve a current trial solution in its neighborhood, either through direct search or model-based approaches. Direct search methods select sample points with a prescribed geometry, as in Nelder-Mead and generalized pattern search. Model-based methods use evaluated points to construct surrogate models, typically linear or quadratic, and select new points by optimizing these models. Examples include trust-region methods and implicit filtering. Global-search methods include exploration steps to escape local optima. Examples include Bayesian optimization, genetic algorithms, and domain-partitioning algorithms.
Inspired by the success of branch-and-bound algorithms in algebraic optimization, BAM implements a domain-partitioning approach for data-driven optimization. The algorithm relies on box subdivision and is guaranteed to converge to a global minimum, including for problems with discontinuities, nonconvexities, and integer or categorical variables. BAM’s subdivision scheme enables flexible partitioning of the search space while exploiting previously evaluated points.
BAM approximates the function of interest using a collection of local surrogate models around evaluated points. These models are generated with Automated Learning of Algebraic MOdels (ALAMO), which constructs simple, accurate, and sparse algebraic models from a large set of candidate basis functions. This allows BAM to use surrogate models more general than quadratic models when appropriate.
Through model-based search, BAM exploits local trends in the objective function using information from evaluated points, often leading to rapid convergence during solution refinement. BAM uses the global optimization solver BARON to solve its local surrogate models to global optimality and to identify promising regions for further exploration at every iteration.
Compared with other data-driven optimization algorithms, BAM provides an effective search strategy, particularly for higher-dimensional problems.
BAM can:
-
•
solve optimization problems defined by simulation or experimental black-box systems
-
•
use previously collected data to initialize the search
-
•
call a user-specified function, such as a simulator, to collect measurements
-
•
enforce variable bounds
The problems addressed by BAM have been studied extensively in optimization, statistics, experimental design, and machine learning. Existing methods from these areas are commonly used to fit local or global models to data and to guide the search.
The main challenges addressed by BAM are the automatic selection of simulation or experimental points, the construction of suitable surrogate models, the optimization of these models, and the coordination of local refinement with global exploration. BAM performs these tasks automatically, without user intervention.
1.1 Licensing and software requirements
BAM is free to use for problems with up to two variables; no license file is needed in that case. For problems with three or more variables, a valid BAM license is required to run the software. Licenses are available from https://minlp.com/bam-licenses. Academic users and the U.S. Department of Energy are eligible for free licenses. Commercial users must obtain a paid license.
BAM has no third-party dependencies. The BAM distribution includes embedded versions of the ALAMO and BARON solvers. Users do not need separate licenses for these solvers or for any other software.
1.2 Installation
Python users.
The recommended way to use BAM from Python is the official bam-solver package:
pip install bam-solver
This installs a self-contained binary wheel for your platform: the BAM library and its run-time dependencies are bundled inside, so no compiler and no separate BAM installation are required. NumPy and CFFI are installed automatically. A BAM license is needed only for problems above the free-variable limit; it is configured at run time and is never part of the package. The Python API itself is described in Section 8.3, including how to point BAM at a license from Python.
Command-line, C, and Fortran users.
BAM is available for download from https://minlp.com/bam-downloads. Install BAM and the BAM license file in a directory of your choice, then add that directory to your system path.
On Windows, the BAM installer performs these steps automatically. On Linux and macOS systems, unzip the BAM download and place the extracted files in a directory included in your path.
Do not open the license file in an editor, as this may invalidate the license on some operating systems. On all operating systems, ensure that BAM and the BAM license file are readable by all intended users of the machine.
For a silent, non-interactive installation on Windows, download the installer and run the following command from the command line:
bam-windows64.exe /SILENT
An installation log file can be generated with:
bam-windows64.exe /SILENT /LOG=filename
where is the desired name of the log file. In both cases, Windows may still request permission to run the executable before launching the silent installer.
1.3 When to use BAM
BAM is well-suited for optimization problems with the following characteristics:
-
•
No algebraic objective. The objective is computed by a simulator, an experiment, or code whose internal form is not available for symbolic differentiation. BAM uses only function values; derivatives are not required.
-
•
Expensive evaluations. Each simulator call may require seconds to hours of wall time. BAM is designed for settings in which its per-iteration overhead, including surrogate fitting and global optimization of the surrogate models, is small relative to the simulation cost. If individual evaluations are inexpensive and the objective is smooth and unimodal, a simpler derivative-free optimization method may be preferable.
-
•
Bound-constrained variables. BAM enforces lower and upper bounds on variables. Continuous, integer, and categorical variables (encoded as integers) are supported. Nonlinear and equality constraints are not handled directly. A constraint of the form may be imposed by penalizing the objective in the callback or by returning the sentinel PRESET value for infeasible points; see Section 6).
-
•
Moderate dimensionality. BAM has been validated on problems with up to 470 variables. Its convergence properties remain valid in higher dimensions, and BAM remains data-efficient compared with other derivative-free optimization algorithms. As with all derivative-free methods, however, the required number of function evaluations generally increases with dimension.
-
•
Discontinuous, noisy, or nonsmooth functions. BAM does not require smoothness assumptions. Evaluations that cannot be completed by the simulator are counted and recorded (see PRESET and MAXPROFAILS). Evaluations that return NaN are recorded with objective value .
-
•
Warm starts. Previously collected evaluation data can be supplied through NEVALDATA, XEVALDATA, and ZEVALDATA. Candidate starting points for unevaluated candidates can be supplied via NDATA and XDATA.
-
•
Reproducibility-sensitive calculations. BAM is fully deterministic. It uses no random seeds or random initializations that vary from run to run. For the same input, BAM follows the same search path and returns the same solution. BAM is engineered so that solutions are expected to be identical across supported operating systems, including Windows, Unix, and macOS, on the same Intel hardware.
2 Quickstart
2.1 Illustrative example: camel6
This section presents three minimal BAM runs: one in Python, one on the command line, and one in C. All runs use the Six-Hump Camel function, also known as camel6, a standard two-variable nonlinear optimization benchmark. The function is defined for two variables, and , as
The search uses the input domain and . The function possesses two global minima at approximately and , both yielding a function value of approximately .
2.2 Solving from Python
The official bam-solver Python package (Section 1.2) provides the simplest way to use BAM. After running pip install bam-solver, users solve the Six-Hump Camel problem without input files, a compiler, or a separate BAM installation:
import bam
def camel6(x):
x1, x2 = x
return ((4 - 2.1*x1**2 + x1**4/3) * x1**2
+ x1*x2 + (-4 + 4*x2**2) * x2**2)
res = bam.minimize(camel6, x0=[0, 0],
bounds=[(-3, 3), (-1.5, 1.5)],
options={"max_evals": 80})
print(res.x, res.fun)
Section 8.3 describes the Python interface in full.
2.3 Solving from the command line
Place the following content in a file named camel6.bam in any directory:
! camel6 is a classic NLP benchmark nvars 2 xmin -3 -1.5 xmax 3 1.5 ndata 1 BEGIN_DATA 0 0 END_DATA dataprovider /usr/local/testlibs/dfo/all/camel6.exe datain input.in dataout output.out maxevals 80
The dataprovider entry must specify the absolute path of an executable. This executable reads the values of the two variables from input.in, one value per line, and writes the corresponding objective function value to output.out. The camel6 simulator used here, together with over 700 other ready-to-use dataproviders, can be downloaded from https://minlp.com/black-box-optimization-test-problems. From the directory containing camel6.bam, run:
bam camel6.bam
BAM solves the problem and reports the sequence of evaluations on the screen.
2.4 Solving from C
When BAM is run from the command line, the executable invokes the user-supplied dataprovider (or simulator) via system calls and exchanges information through data files. BAM can also be called entirely in memory through its callable API. This avoids file-based communication and reduces execution time, often by several orders of magnitude.
The complete C example below registers a callback that computes the same Six-Hump Camel function and asks BAM to solve the problem over the same domain.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "bam.h"
int main(void)
{
int nvars = 2, status;
double xbest[2], fbest;
double xmin[2] = {-3, -1.5};
double xmax[2] = { 3, 1.5};
status = bam_create(nvars);
status = bam_set_real_vector("xmin", xmin, nvars);
status = bam_set_real_vector("xmax", xmax, nvars);
status = bam_set_string("outfname", "camel6.lst");
status = bam_set_obj_fun(camel6, NULL);
xbest[0] = 0; xbest[1] = 0;
status = bam_solve(xbest, &fbest, /*start_at_xbest=*/1);
printf("Best f = %.15e\n", fbest);
status = bam_destroy();
return 0;
}
A complete version of this driver, including error checks and the camel6 callback definition, is given in Section 8.4.2. Compilation and linking instructions are given in Section 8.4.5.
3 Algorithms implemented
3.1 Overview
BAM starts from optional user-specified data, including previously evaluated points or proposed starting points. If no such data are supplied, BAM uses a space-filling sampling strategy specified by the SAMPLER option. At each iteration, BAM augments the set of evaluated points using both model-based and volume-based search.
Each iteration consists of four main steps:
-
1.
BAM partitions the search space into boxes using its box-subdivision scheme. Each box corresponds to the region of influence of one previously evaluated point, and each box contains exactly one evaluated point. BAM performs a dense search using the proposed subdivision. Dense sampling is essential to guarantee algorithm convergence.
-
2.
After partitioning the domain, BAM selects the most promising boxes after excluding boxes for which a Lipschitzian argument guarantees suboptimality (no Lipschitz constant is assumed by the algorithm). Boxes that pass this test are used to construct local surrogate models. This step avoids unnecessary model building and prevents delays by avoiding searching around non-global basins.
-
3.
BAM builds a collection of local surrogate models around the evaluated point in each selected box. Models are fitted using only information from other nearby evaluated points, requiring no further points to be evaluated. We use ALAMO for model construction, which builds simple yet accurate models by selecting a small subset of basis functions from a potentially large set.
-
4.
Based on the box sizes and the objective values obtained by minimizing local surrogate models with BARON, BAM identifies a list of candidate points for evaluation at each iteration. The newly sampled points are added to the collection utilized for the box subdivision at the next iteration.
The algorithm and its convergence properties are described in detail by Ma et al. [11]. Subsections 3.2–3.6 describe each step in turn.
3.2 Box subdivision
Let be the bound-constrained domain and the current set of evaluated points. BAM partitions into axis-aligned boxes with and . Each box is the area of influence of : it is the region around the points in where the local surrogate model anchored at will later be used.
To partition a box that contains a subset with , BAM applies the following splitting rule:
-
1.
For each coordinate , sort the -th coordinates of points in and let denote the largest gap between consecutive values, scaled by the side length of along that coordinate.
-
2.
Choose the splitting coordinate .
-
3.
Split at , the midpoint between the two consecutive values that produced .
-
4.
Recurse on the resulting sub-boxes that still contain more than one evaluated point.
The recursion terminates when each box holds exactly one evaluated point. Figure 1 illustrates one step of this rule on a 2D example: the box on the left contains five points; the largest scaled gap occurs along the horizontal coordinate, so the split places a vertical boundary midway between the two points that bound that gap, and the recursion proceeds on each side.
The subdivision is performed at every iteration on the full set of accumulated evaluations. The resulting boxes are axis-aligned and tile the domain. The tiling is dynamic, driven by measurements from the algorithm and those provided by the user. Because splits occur at the midpoint of the largest unexplored gap, the rule cannot produce vanishingly thin slices, a property that is used in the convergence proof of Section 3.6.
3.3 Lipschitzian filtering
After subdivision, BAM identifies a set of potentially optimal boxes for which it will build local surrogate models. The test is inspired by the DIRECT algorithm and does not assume a Lipschitz constant.
For each box , let denote the largest Euclidean distance from to any vertex of . If the (unknown) global Lipschitz constant of the objective is , then is a lower bound on over . Box is declared potentially optimal if there exists some such that
| (1) |
Equivalently, survives if and only if there is some hypothetical positive Lipschitz constant that makes the lower bound at no greater than the lower bound at any other box. Boxes for which no such exists are pruned. As in DIRECT, the test selects boxes that are simultaneously low in current value and large in size, but BAM applies it within its own subdivision and couples it with its unique dynamic partitioning and surrogate model-building processes.
The set of furthest vertices of each is computed during this pass and reused for size-based candidate generation in Section 3.5.
3.4 Local surrogate models
For each , BAM builds a local surrogate model on a neighborhood of . The neighborhood is the bordering set
| (2) |
where and are adjacent if their box closures intersect (i.e., they share at least one face). The model is fit to using ALAMO [3, 4], which selects a small subset of basis functions to minimize a Bayesian information criterion.
If the bordering set is unchanged from the previous iteration, BAM reuses the previous model rather than refitting. After fitting, BAM minimizes globally over using BARON [16, 10, 17]. The minimizer and the predicted minimum feed the candidate generation in Section 3.5.
The motivation for using many small models rather than a single global model is that, on a small box, the function is well approximated by a low-order polynomial, even when the global landscape is complicated.
3.5 Candidate generation
BAM proposes new evaluation points from three sources at each iteration:
-
1.
Model-based candidates. Sort the boxes in by predicted value ascending and evaluate the surrogate minimizers of the top boxes.
-
2.
Size-based candidates. Sort by box volume descending and evaluate the furthest vertex of the top boxes. Large boxes have less reliable models, so BAM explores the corners furthest from the anchor point.
-
3.
Density guarantee. In the single largest box overall, evaluate if not yet sampled; otherwise, the box center or a random interior point.
The values of and are internally controlled by BAM. The newly evaluated points are appended to , and the next iteration begins with a refreshed subdivision.
3.6 Convergence
Theorem 1 ([11]).
Let be a non-empty bound-constrained domain and continuous in a neighborhood of a global minimizer . For any , BAM eventually generates a point with .
The proof relies on two facts. First, at every iteration BAM evaluates a point inside the largest current box , so the largest-box vertex distance tends to zero as the iteration index . Second, the splitting rule of Section 3.2 prevents arbitrarily thin sub-boxes, so as , the entire domain is sampled densely. Continuity of near then yields the conclusion.
The convergence guarantee is independent of smoothness assumptions and applies even when is discontinuous away from or has integer-valued variables. BAM relies uniquely on advanced machine learning software (ALAMO) and optimization software (BARON). Both software products are embedded in the BAM distribution. For information on the algorithms used by ALAMO and BARON, see https://minlp.com.
4 BAM versus other global black-box algorithms
For decades, the science and engineering communities relied extensively on stochastic global search algorithms, such as simulated annealing, particle swarm, and genetic algorithms, to solve DFO problems (see [15, 1, 14] for reviews). Over the past two decades, there has been a nearly complete shift to Bayesian Optimization (BO).
Reference [15] benchmarked 22 solvers on 502 continuous DFO problems. Reference [14] compared 13 software implementations on 267 mixed-integer DFO problems. BAM did not exist when these studies were performed. At the time, the main conclusion was that deterministic solvers such as MCS [6] have an edge over the far more popular stochastic solvers. More recently, the study of [15] was extended to include over 40 software implementations, including BAM and BO [13], and found BAM to be far more data-efficient than all other DFO implementations tested. BAM excels at solving many more problems to global optimality and it does so much faster than other solvers.
The 502 continuous and 267 mixed-integer DFO problems of the above studies are available from https://minlp.com/black-box-optimization-test-problems. BAM’s free academic license provides full access to the software and permits benchmarking with other solvers. We encourage users to compare any desired test problems and report their results in the open literature.
Below, we highlight algorithmic differences between BAM and three other major global black-box optimization paradigms. Given BO’s current popularity, we contrast BAM to BO. Additionally, we discuss two deterministic algorithms that are closely related to BAM.
4.1 BAM vs. Bayesian Optimization (BO)
BAM and BO [12, 9, 5] are both model-based, surrogate-assisted, derivative-free global optimization methods, but they sit on opposite sides of nearly every design axis: deterministic algebraic surrogates vs. probabilistic Gaussian-process surrogates, partition-based “potentially optimal” pruning vs. acquisition-function maximization, a single mature commercial implementation vs. an open-source ecosystem of variants. More specifically:
-
•
BAM’s guarantee is deterministic and asymptotic: under continuity at the optimum, the sequence of evaluated points becomes everywhere dense in the search domain; hence, the best-so-far value converges to the global optimum. BO guarantees are probabilistic and quantitative, but conditional on the modeling assumptions, all of which assume the kernel family is correctly specified–an assumption that is rarely verifiable in practice.
-
•
BAM is deterministic by design; re-running on the same problem produces the same trajectory. BO is deterministic only if every randomized component is seeded; reproducibility across hardware and library upgrades is fragile.
-
•
BAM exposes very few algorithmic hyperparameters to the user. Unlike BO, in BAM, there is no kernel to choose, no acquisition function to choose, no length-scale to fit, and no acquisition optimizer to configure. BO requires all these; BO performance can vary by orders of magnitude depending on these choices.
4.2 BAM vs. DIRECT
BAM and DIRECT [8, 7] both descend from Lipschitzian global optimization for bound-constrained black-box problems, both are deterministic, both partition the box into hyperrectangles, and both achieve everywhere-dense convergence to the global optimum under mere continuity, with no need to know a Lipschitz constant. They differ in what each rectangle “thinks about.” DIRECT decides by looking only at one function value at the rectangle’s center, which makes it elegantly simple and very effective for low-dimensional problems (typically 2–6 variables) but slow to refine the solution to high accuracy and rapidly less competitive in higher dimensions or under noise. BAM keeps the Lipschitzian pruning idea, but, within each retained box, fits a local algebraic surrogate model with ALAMO (using only the points already evaluated) and globally minimizes that surrogate with BARON to propose the next point. This surrogate-guided exploitation step is what makes BAM extract more information per simulator call than DIRECT, and is the reason BAM’s published benchmarks show its advantage growing with dimension and robustness to noise, integer/categorical variables, and discontinuities.
4.3 BAM vs. Multilevel Coordinate Search (MCS)
BAM and MCS [6] are both partition-based, hybrid global/local DFO algorithms that converge to a global minimum under continuity alone via everywhere-dense sampling. They differ chiefly in how they exploit the partition: MCS splits boxes one coordinate at a time using a multilevel sweep that balances global and local exploration, and refines promising points with a local quadratic model and SQP-style line search. BAM partitions around evaluated points, selects DIRECT-style potentially optimal cells, and builds an ALAMO algebraic surrogate for each cell, which is then globally minimized by BARON. As a result, BAM extracts more information per iteration, handles mixed-integer and categorical variables natively, and is more robust to noise and discontinuities.
5 Running BAM from the command line
As an alternative to the callable API, users can also utilize BAM from the command line. If the BAM executable is used, it reads model data and algorithmic options from a simple-text file. Even though it is not required, it is strongly recommended that all BAM input files have the extension ‘.bam.’ If the input file is named ‘test.bam’ and the BAM executable is named ‘bam,’ issuing the command
bam test
or
bam test.bam
results in BAM parsing test.bam, and solving the problem. The BAM executable takes exactly one command-line argument, the input file. By default, BAM reports results only to the screen; to additionally obtain a listing file, supply the OUTFNAME option in the input file (a bare file name places the listing file in the execute directory, regardless of where the .bam file resides). Similarly, an evaluations file is produced when EVALSFNAME and PREVALS are supplied, and a trace file when TRACEFNAME is supplied; see Section 7.
During execution, BAM creates and uses a directory for storing various work files. BAM picks a unique, randomly named hidden directory inside the execute directory, creates it at the start of the run, and deletes it when the run finishes.
5.1 Example input file
The file below is referred to as ‘camel6.bam’ and solves problem camel6 of Section 2.1. It is written to exercise several features of the BAM input grammar: full-line comments introduced with any of *, #, %, or !; inline comments appended after an option; case-insensitive keywords and option names; and a long record (the xMax vector) split across two lines with a trailing &.
The problem has two variables (nVars ) with the indicated bounds, and a single user-supplied starting point at the origin (ndata ). The user provides a simulator camel6.exe that reads and (one per line) from input.in, evaluates the function, and writes the value to output.out. The run is named with proname (which labels it in the trace file), a listing file is requested with outfname, and the search is limited to 80 function evaluations with maxevals.
* a full-line comment introduced with *
# a full-line comment introduced with #
% a full-line comment introduced with %
! a full-line comment introduced with !
nVars 2 # inline comments are allowed after an option
xMin -3 -1.5 % keywords and option names are case insensitive
xMax 3 &
1.5
ndata 1 ! one user-supplied (unevaluated) starting point
BEGIN_DATA
0 0
END_DATA
dataprovider /usr/local/testlibs/dfo/all/camel6.exe
datain input.in
dataout output.out
proname camel6
outfname camel6.lst
maxevals 80
Over 700 additional examples of BAM input files can be found at https://minlp.com/black-box-optimization-test-problems.
5.2 Input file grammar
The following rules should be followed when preparing a BAM input file:
-
•
The name of the input file should include its full path if the file is not in the execution directory.
-
•
The name of the input file should not exceed 1000 characters in length.
-
•
The input is not case sensitive.
-
•
Most options are entered one per line, in the form of ‘keyword’ followed by ‘value’. Certain vector options are entered in multiple lines, starting with ‘BEGIN_keyword’, followed by the vector input, followed by ‘END_keyword’.
-
•
Certain options must appear first in the input file. This requirement is discussed in the option descriptions provided below.
-
•
With the exception of arguments involving paths, character-valued options should not contain spaces.
-
•
Blank lines, white space, and lines beginning with *, #, %, or ! are skipped. Inline comments preceded by !, #, or % are permitted in any line that contains alphanumeric options.
-
•
Blocks of comment lines are allowed using ‘BEGIN_COMMENT’, followed by the block of comment lines, followed by ‘END_COMMENT’; these comment blocks are entirely ignored by BAM.
-
•
Long records may be split across lines by ending an intermediate line with the character &. The continuation will be appended to the previous line. No more than 10000 characters are allowed on a single line.
-
•
The keyword NVARS may also be spelled NVAR.
6 BAM data and options specification statements
6.1 Required parameters
Users of the API must provide NVARS and install a callback to compute the objective function. As an alternative to callbacks, an executable may be utilized for objective function calculations. Below, this executable is referred to as DATAPROVIDER.
If the BAM executable is utilized instead of the API, both NVARS and the DATAPROVIDER must be provided in the BAM input file.
| Parameter | Description |
| NVARS | Number of model input variables. NVARS must be a positive integer and defines the number of optimization variables. NVARS must be provided before providing variable bounds and starting points. |
| DATAPROVIDER |
Complete path of an executable that will provide function values at points specified by BAM. The DATAPROVIDER executable must be capable of reading file DATAIN and writing file DATAOUT.
DATAIN format. BAM writes one variable value per line, in IEEE double precision (free-format), in the order . There is no header. DATAOUT format. The DATAPROVIDER must return a single line containing the value of the objective function at the evaluated point, in IEEE double precision. Working directory. BAM executes the DATAPROVIDER inside its scratch directory. The DATAPROVIDER should therefore use absolute paths for any auxiliary files or sub-programs it accesses. Failures and missing values. If the simulator cannot evaluate a point, it should write the sentinel value PRESET (default , see below) on the line in DATAOUT. If the simulator returns a NaN, BAM counts the call and records the evaluation with as the objective value. The number of consecutive DATAPROVIDER failures tolerated before BAM aborts is controlled by MAXPROFAILS. |
6.2 Optional vector parameters
The following parameters may be specified in the BAM input file or through the API after NVARS has already been specified.
| Parameter | Description |
| XMIN | Real vector specifying minimum values for each of the input variables. This should contain exactly NVARS entries. In the BAM input file, these entries must comprise a row of space-delimited elements. |
| XMAX | Real vector specifying maximum values for each of the input variables. This should contain exactly NVARS entries. In the BAM input file, these entries must comprise a row of space-delimited elements. |
| XISINT | An integer vector of 0/1 flags that specify which input variables, if any, BAM should treat as integers. For integer inputs, BAM’s sampling will be restricted to integer values. In the BAM input file, these entries must comprise a row of space-delimited elements. |
| RHO | Real vector specifying the smallest physically or numerically possible values for each of the input variables to be resolved in nearby measurements. BAM will avoid proposing new points that are too close to be resolved, i.e., points whose component distances are all smaller than the corresponding values of the resolution vector. RHO should contain exactly NVARS entries. In the BAM input file, these entries must comprise a row of space-delimited elements. If RHO is not provided by the user, BAM will set all entries of RHO equal to 1e-8 for continuous variables and 1 for discrete variables. All entries of RHO must be integers for integer variables. |
XMIN and XMAX define the domain of the optimization problem. If left unspecified, XMIN and XMAX will be initialized by BAM using BAM’s MAXBOUND parameter described below. If left unspecified, all values of XISINT are initialized to zero.
6.3 Optional data specifications
This section describes the optional parameters related to the particular problem being solved.
| Option | Description | Default |
| NDATA | Number of unevaluated starting points specified by the user. NDATA must be a nonnegative integer. | 0 |
| NEVALDATA | Number of evaluated starting points provided by the user. NEVALDATA must be a nonnegative integer. | 0 |
| PROPOSE | A indicator. If 1, BAM will propose evaluation points and terminate the run. | 0 |
| PRBESTFREQ | The frequency at which the incumbent values are printed in the trace file. PRBESTFREQ must be a nonnegative integer. If 0, only a blank is printed in the corresponding location in the trace file. | 50 |
| DATAIN | Name of input file for the DATAPROVIDER. BAM generates this file. | input.txt |
| DATAOUT | Name of the output file for the DATAPROVIDER. BAM expects the DATAPROVIDER to provide this file after each call. | output.txt |
| MAXEVALS | Maximum number of new function evaluations permitted during this run. MAXEVALS must be at least as large as NDATA. | 2500 |
| MAXNOGAIN | Maximum number of evaluations without improvement before stopping; disables. | |
| MAXTIME | Maximum total execution time allowed in seconds. This time includes all steps of the algorithm, including time to read the problem, preprocess the data, solve the optimization and machine learning subproblems, and print the results. | |
| MAXITER | Maximum number of algorithmic iterations permitted during this run. A value of implies that there is no limit on the number of algorithmic iterations (MAXEVALS and MAXTIME limits apply). | |
| MAXPROFAILS | Maximum number of DATAPROVIDER failures to tolerate before terminating. A value of implies that there is no limit on the number of DATAPROVIDER failures. | |
| MAXBOUND | The maximum absolute value allowed for variable bounds if XMIN/XMAX are not specified. If lower bounds for variables are not specified, they will be set equal to MAXBOUND. If upper bounds for variables are left unspecified, they will be set equal to MAXBOUND. | 10000 |
| BATCHSIZE | Maximum number of points BAM may hand to and receive from the objective callback at once. The callback receives up to BATCHSIZE many points to evaluate and returns up to BATCHSIZE many values. It is up to the user to determine: how the evaluations are performed (in parallel, sequentially, or a mix), how many evaluations are performed (fewer, more or as many as requested by BAM), and where evaluations are performed (at BAM’s requested or other points). The current release supports BATCHSIZE only, and larger requests are reduced to 1. | 1 |
| SAMPLER | Technique to be used for space-filling initial sampling. Possible values are 1 through 7, with the following meaning: 1. Latin hypercube sampling, 2. Sobol sampling, 3. Faure sampling, 4. Halton sampling, 5. Hammersley sampling, 6. Niederreiter2 sampling, 7. random sampling. | 2 |
| PRESET | A value indicating that the DATAPROVIDER was not able to compute a specific requested point. This value must be carefully chosen to be an otherwise unrealizable value for the function at hand. | -111111. |
| PRFREQ | An integer denoting the per-evaluation print frequency to the listing file. | 1 |
| OUTFNAME | Name of the listing file. By default, OUTFNAME is an empty string, whether BAM is used through the executable or through the API, and no listing file is produced. Supplying a nonempty OUTFNAME (in the input file, in a bam.opt file, or via a call to bam_set_string) turns on listing-file output: BAM mirrors the screen output to the named file (a bare file name places it in the execute directory). | empty |
| LICFNAME | Name of the BAM license file. At the start of every solve, BAM searches for this file in the current working directory, the directory containing the running executable, and finally each entry of the PATH environment variable. The default name is bamlice.txt; it may be set in the input .bam file, in a bam.opt file, or via a call to bam_set_string with keyword licfname. Unlike most options, LICFNAME may be set at any point in the API lifecycle (before bam_create, between solves, or after bam_destroy) and is intentionally preserved across bam_destroy / bam_create cycles, so a host application or wrapper that pins a per-installation license-file path only needs to set it once. The same value is also consulted by the bam_get_license_state and bam_get_license_path queries. If no readable license file is found, BAM runs in free mode (limited to bam_get_free_mode_nvars() variables); if the file is found but invalid, a warning is issued, and the run still proceeds in free mode for sufficiently small problems. | bamlice.txt |
| TRACEFNAME | Name of the trace file. By default, TRACEFNAME is an empty string, whether BAM is used through the executable or through the API, and no trace file is produced. Supplying a nonempty TRACEFNAME (in the input file, in a bam.opt file, or via a call to bam_set_string) turns on trace output: at the end of the run, BAM appends a succinct summary of the results to this file. The first time the file is created, a header line beginning with # is written that describes the comma-separated fields of each subsequent line: the problem name (PRONAME, see below), solver name and version in the form BAM-yyyy.m.d, total CPU time, best objective function value identified, total number of function evaluations, evaluation during which the best solution was identified, the incumbent progression (values every PRBESTFREQ evaluations), and BAM’s termination status. | empty |
| PRONAME | Problem name. This is the value written as the first field in the trace file, intended as a short, meaningful identifier for the problem being solved. If the user does not set it, PRONAME defaults to the basename of the input file when BAM is called through the executable, and to the literal string API-call when BAM is called through the API. In either case, the user may override it by supplying PRONAME in the input file, in a bam.opt file, or via a call to bam_set_string. Because the trace file is comma-separated, PRONAME should not contain commas. | Varies |
| OPTFNAME | Name of BAM options file. When BAM is called through its API, the user may provide an options file for BAM to read. By default, BAM will look for a file named bam.opt in the execute directory. The user may optionally suppress this look-up by setting OPTFNAME to an empty string via a call to bam_set_string. BAM will not complain if bam.opt is missing and will continue the run; it terminates only if OPTFNAME exists and is not readable. If used, the options file should follow the format of the BAM input file and be subject to its restrictions, including formatting specifications and the requirement that options not be repeated. Warning: an unintended bam.opt file in the current working directory will silently be read and may override settings made through the API. Set OPTFNAME to an empty string in API drivers that should ignore any local bam.opt. | bam.opt |
| EVALSFNAME | Name of the evaluations file (see PREVALS below). By default, EVALSFNAME is an empty string, whether BAM is used through the executable or through the API. If PREVALS is nonzero, EVALSFNAME must be set to a nonempty value; otherwise, BAM stops at the end of input parsing with a consistency error. | empty |
| PRINT_TO_SCREEN | A indicator. Output is directed to the screen if this option is set to 1; if set to 0, no output is sent to the screen. | 1 |
| PREVALS | In addition to the listing file, BAM optionally prints a file (named by EVALSFNAME) containing function evaluations. Each line of this file contains an evaluation number, followed by the current CPU time, the values of the problem variables, the corresponding objective function value, and the best objective function value found during the search. This file is printed only if PREVALS is positive, and printing occurs at every PREVALS function call. Whenever PREVALS is nonzero, EVALSFNAME must also be provided. | 0 |
| SIGNAL_HANDLER | A indicator. Controls whether BAM installs its own signal handlers for the duration of a solve. When set to 1 (the default), bam_solve saves the host application’s prior dispositions for SIGINT, SIGTERM, SIGSEGV, SIGABRT, SIGBUS, and SIGPIPE; installs BAM’s flag-and-poll handler for SIGINT/SIGTERM so a Ctrl-C unwinds the solver cleanly with termination code 50; installs a catastrophic-signal catcher for SIGSEGV/SIGABRT/SIGBUS that prints a brief banner before aborting; sets SIGPIPE to SIG_IGN so a broken pipe does not kill BAM; and restores all six prior dispositions before returning. Library users who wish to keep their own signal handlers in effect for the entire solve (e.g., to update a UI, post a heartbeat, or run a custom crash reporter) should set this option to 0. Even with the option disabled, Ctrl-C still terminates BAM via the subsolver-status path described in Section 8.5. | 1 |
The parser is not case sensitive. For example, nvars, nVARS, nVars, and NVARS are equivalent. Similarly, API calls involving strings are not case-sensitive.
If the parameter NDATA is set, then a data section must follow subsequently in the input file with precisely NDATA rows, one for each starting point (NVARS space-separate values for all problem variables) specified in the following form:
BEGIN_DATA END_DATA
If the parameter NEVALDATA is set, then a data section must follow subsequently in the input file with precisely NEVALDATA rows, one for each previously evaluated point (NVARS space-separated values for all problem variables, followed by space, followed by the corresponding objective function value) specified in the following form:
BEGIN_EVALDATA END_EVALDATA
7 BAM output
7.1 Screen output
The screen output below is obtained for problem camel6.bam.
*************************************************************************** BAM version 2026.5.28. Built: WIN-64 2026-05-28 22:33:16 Running on machine PONTIOS BAM is a product of The Optimization Firm. For more information and related optimization and data analytics products visit https://minlp.com. If you use this software, please cite: Ma, K., L. M. Rios, A. Bhosekar, N. V. Sahinidis and S. Rajagopalan, Branch-and-Model: A derivative-free global optimization algorithm, Computational Optimization and Applications, 85, 337-367, 2023. BAM is powered by the ALAMO and BARON software from http://www.minlp.com/. *************************************************************************** Reading input data Checking input consistency and initializing data structures Licensee: Nick Sahinidis at The Optimization Firm, LLC, niksah@minlp.com. *************************************************************************** * 1 0.06 0.00000 0.00000 2 0.08 0.00000 124.650 3 0.10 0.00000 0.562500E-01 4 0.13 0.00000 0.562500E-01 5 0.15 0.00000 1.44272 6 0.18 0.00000 13.5541 7 0.22 0.00000 2.14585 8 0.24 0.00000 8.35093 9 0.26 0.00000 2.78074 10 0.28 0.00000 3.00344 11 0.30 0.00000 0.163591 12 0.33 0.00000 0.351197E-02 13 0.35 0.00000 0.259889E-01 14 0.37 0.00000 0.135975 15 0.40 0.00000 0.351559E-04 16 0.42 0.00000 0.172178E-02 17 0.44 0.00000 0.239516E-02 * 18 0.48 -0.585197E-03 -0.585197E-03 19 0.50 -0.585197E-03 -0.561975E-03 * 20 0.52 -0.788847E-03 -0.788847E-03 21 0.54 -0.788847E-03 -0.706021E-03 22 0.58 -0.788847E-03 -0.526168E-03 23 0.96 -0.788847E-03 11.2062 24 0.98 -0.788847E-03 0.734318E-01 25 1.01 -0.788847E-03 -0.655217E-03 26 1.04 -0.788847E-03 -0.704539E-03 27 1.07 -0.788847E-03 0.723039E-01 28 1.09 -0.788847E-03 -0.788318E-03 * 29 1.14 -0.790032E-03 -0.790032E-03 * 30 1.17 -0.161182E-01 -0.161182E-01 * 31 1.20 -0.161197E-01 -0.161197E-01 * 32 1.24 -0.161237E-01 -0.161237E-01 * 33 1.29 -0.768957 -0.768957 34 1.32 -0.768957 -0.768942 * 35 1.37 -0.768971 -0.768971 36 1.41 -0.768971 11.5913 37 1.45 -0.768971 0.512115 * 38 1.48 -0.936642 -0.936642 * 39 1.52 -0.936645 -0.936645 40 1.56 -0.936645 -0.936623 41 1.59 -0.936645 0.296809 42 1.62 -0.936645 -0.531620E-01 43 1.68 -0.936645 -0.430716 44 1.71 -0.936645 -0.908600 * 45 1.75 -1.02401 -1.02401 * 46 1.78 -1.02402 -1.02402 * 47 1.82 -1.02402 -1.02402 * 48 1.85 -1.02979 -1.02979 * 49 1.90 -1.02979 -1.02979 50 1.94 -1.02979 -1.02979 * 51 2.00 -1.03156 -1.03156 * 52 2.04 -1.03156 -1.03156 53 2.08 -1.03156 -1.03156 * 54 2.12 -1.03163 -1.03163 * 55 2.16 -1.03163 -1.03163 56 2.21 -1.03163 -1.03163 * 57 2.25 -1.03163 -1.03163 58 2.29 -1.03163 -1.03163 59 2.33 -1.03163 -1.03163 60 2.38 -1.03163 -1.03163 61 2.42 -1.03163 -1.03163 62 2.47 -1.03163 -1.03163 63 2.52 -1.03163 -1.03163 64 2.57 -1.03163 -1.03163 65 2.61 -1.03163 -1.03163 66 3.94 -1.03163 -1.03046 67 4.00 -1.03163 -1.03028 68 4.04 -1.03163 -1.03163 69 4.08 -1.03163 -1.03101 70 4.12 -1.03163 -1.03163 71 4.16 -1.03163 -1.03101 72 4.21 -1.03163 -1.02402 73 4.28 -1.03163 -0.843580 74 4.34 -1.03163 -1.03163 75 4.40 -1.03163 105.762 76 4.47 -1.03163 -1.03158 77 4.52 -1.03163 1.17011 78 4.57 -1.03163 1.17009 79 4.62 -1.03163 1.17008 80 4.66 -1.03163 9.98123 Total number of evaluations: 80 Total execution time: 4.7 s Data provider time: 2.7 s BAM terminated with termination code 35 Solver reached limit on function calls. ***************************************************************************
The software first reports the version, platform, and compilation date of the executable, followed by credits. Then, a consistency check is run on the input problem data and, if passed, the data structures are initialized. Subsequently, information is provided for each function evaluation: the current cumulative CPU time, the current best-known objective function value for the problem, and the value of the objective function value computed in the most recent call to the DATAPROVIDER. At the end of the run, BAM provides a termination code and explanatory message.
A leading asterisk (*) on an evaluation row indicates that the call produced a new incumbent.
7.2 Listing file
If OUTFNAME is supplied, BAM mirrors the screen output to a listing file (named by OUTFNAME). The listing file additionally records the contents of the input file (or the API-set options).
7.3 Evaluations file
If PREVALS is positive, BAM writes one row per function evaluation to the evaluations file (named by EVALSFNAME, which must be supplied whenever PREVALS is nonzero). Each row contains, separated by whitespace: the evaluation index, cumulative CPU time at the moment of the call, the NVARS variable values used in the call, the objective value returned, and the best objective value found up to and including this evaluation. Rows are appended every PREVALS evaluations.
The evaluations file is one of two recommended sources for plotting convergence curves and for benchmarking BAM against other solvers. The second recommended source is the trace file.
7.4 Trace file
If TRACEFNAME is supplied, BAM writes one summary row per run to the trace file (named by TRACEFNAME). The first line of the file is a header that begins with # and names each comma-separated field: the problem name (PRONAME, which defaults to the basename of the input file for an executable call and to the literal API-call for an API call, unless the user sets it), solver name and version, total CPU time, best objective value, total number of function evaluations, the evaluation index at which the incumbent was found, the incumbent progression (values every PRBESTFREQ evaluations), and the termination code. Trace records from successive runs are appended to an existing trace file, making it easy to aggregate results across many problems.
7.5 Scratch directory
While a run is in progress, BAM uses a scratch directory inside the execute directory for intermediate files: BARON input and output, the DATAIN/DATAOUT files exchanged with the DATAPROVIDER, and surrogate-model coefficient dumps. BAM picks a unique, randomly named hidden directory for this purpose, creates it at the start of the run, and erases it at the end of a normal run. The execute directory should be on a local filesystem; placing it on a slow networked filesystem will substantially slow down the run.
8 BAM Application Programming Interface (API)
8.1 Introduction
The BAM library is written in high-performance Fortran 2023 and exposes a unified interface for setting up and solving black-box optimization problems from any programming language.
Most Python users should use the official bam-solver package (import name bam), which wraps the API in a Pythonic interface and provides the recommended way to use BAM from Python. Section 1.2 covers its installation and Section 8.3 covers its usage.
For other languages, and for Python users who prefer lower-level access, the native library API is available in two forms:
-
•
A C-compatible API using bind(C) routines.
-
•
A native Fortran API using routines with suffix _f.
Both APIs expose the same functionality, which Section 8.4 details. We recommend the C API for interoperability with C, C++, Julia, Rust, and Fortran users who prefer mixed-language interoperability. The native Fortran API provides convenience for Fortran users and relies on standard Fortran types.
Callback functions use a portable mechanism based on type(c_ptr) to pass user-defined data.
8.2 A second example: st_e36
The Six-Hump Camel example uses two continuous variables. In this section, we additionally illustrate BAM on st_e36, a mixed-integer problem with one continuous and one integer variable. We derive the problem from the st_e36 entry of MINLPLib version 2 [2] and illustrate it in detail in [14]. The bound-constrained formulation is:
The global minimum is at with objective value . The BAM driver below declares one continuous variable (xisint[0]=0) and one integer variable (xisint[1]=1). Users supply the starting point via xbest together with start_at_xbest=1.
8.3 Calling BAM from Python
BAM ships an official Python package, bam-solver (import name bam), that wraps the BAM library in a Pythonic interface. This interface is the recommended way to use BAM from Python and is distributed as pre-built binary wheels for Linux (x86_64 and aarch64), macOS (Intel and Apple Silicon), and Windows (x86_64). Section 1.2 describes installation (pip install bam-solver). Because each wheel bundles the BAM library and its runtime dependencies, Python users do not need a compiler, a toolchain, or a separate BAM installation; furthermore, they do not directly call the C or Fortran routines that this section describes next.
The package exposes three interfaces that drive the same solver and differ only in ergonomics: the SciPy-style minimize function (the recommended starting point), the full-control Bam class, and the AskTell interface for externally driven evaluations. Users need a license only for problems above the free-variable limit (Section 1.1); see “Licensing” below for how to set one from Python.
8.3.1 minimize: a SciPy-style interface
bam.minimize follows the scipy.optimize.minimize calling convention and returns an OptimizeResult. We solve the Six-Hump Camel problem of Section 5.1 using:
import bam
def camel6(x):
x1, x2 = x
return ((4 - 2.1*x1**2 + x1**4/3) * x1**2
+ x1*x2 + (-4 + 4*x2**2) * x2**2)
res = bam.minimize(camel6, x0=[0, 0],
bounds=[(-3, 3), (-1.5, 1.5)],
options={"max_evals": 120})
print(res.x, res.fun, res.message)
Users provide bounds as (low, high) pairs or as a scipy.optimize.Bounds object. They are important, yet optional: as in the C API and the full-control Bam class, omitting them lets BAM apply its default MAXBOUND box. When users omit bounds, minimize infers the number of variables from x0, and AskTell from its nvars argument. Users declare integer and categorical variables with integrality=[0, 1, …]. Because the signature is SciPy-compatible, users can also plug BAM into SciPy as a method: scipy.optimize.minimize(fun, x0, method=bam.minimize).
A mixed-integer example.
We solve the st_e36 problem of Section 8.2 in Python using:
import bam
def st_e36(x):
x1, x2 = x
return 2*x1**2 + 0.008*x2**3 - 3.2*x1*x2 - 2*x2
res = bam.minimize(st_e36, x0=[4.433315, 18],
bounds=[(3, 5.5), (15, 25)],
integrality=[0, 1],
options={"max_evals": 200})
print(res.x, res.fun) # -> [5.5, 25.0] -304.5
Migrating from scipy.optimize.
BAM is a derivative-free global solver, so a few habits differ from a local gradient-based method. It never asks for a Jacobian or Hessian; users set the evaluation budget with options={"max_evals": N} rather than maxiter; BAM users treat integer and categorical variables directly (integrality=…) instead of requiring a separate mixed-integer solver; and BAM operates as a fully deterministic, with no seed parameter, so the same problem solved twice produces the same trajectory. The result object carries the familiar fields x, fun, success, status, message, nfev, and nit.
Why minimize is the recommended starting point.
For most users it provides the simplest way to call BAM. A single function call sets up and solves the problem and returns an OptimizeResult; users do not need to create, configure, or destroy a solver object, or manage files. Bounds use (low, high) pairs, options use friendly names with sensible defaults, BAM forwards extra arguments to the objective through args, and the same call drops into SciPy via method=bam.minimize. The result carries x, fun, success, status, message, nfev, nit, and cpu_time, and options={"history": True} additionally records the full evaluation trajectory.
Watching progress: the incumbent callback.
minimize accepts a SciPy-style callback. BAM calls it with the current best point each time the search finds a new incumbent, that is, a new best-so-far objective value. This arrangement provides a convenient way to log or plot convergence as the run proceeds, and it does not affect the search:
def watch(x):
print("new incumbent:", x)
res = bam.minimize(camel6, x0=[0, 0], bounds=[(-3, 3), (-1.5, 1.5)],
callback=watch)
The callback is purely observational. The full-control Bam class described below does not have a separate progress hook; with it, users must fold the same check into the objective themselves.
8.3.2 Bam: the full-control class
bam.Bam is a thin, faithful mirror of BAM’s C API, for callers who want full control over every option and over the listing, trace, and evaluations files. The objective is a Python callable taking a NumPy array and returning a float; option names and meanings are exactly those of the command-line and C/Fortran interfaces (Section 6). The bounds xmin/xmax are optional, and users declare integer variables with set_int_vector("xisint", …).
By default the objective is single-point: set_objective(fn) registers a callable fn(x) that receives one point (a length-nvars array) and returns one value, exactly as minimize uses it. To match the simulator parallelism declared by the BATCHSIZE option, register a batch objective with set_objective(fn, batch=True): fn then receives a writable (k, nvars) array of k points and returns k objective values, where k is the value of BATCHSIZE. BAM currently caps BATCHSIZE at 1, so k remains 1 until developers raise that cap; the batch form ensures that callers can write code once and maintain compatibility later.
# library version number (year, month, day)
print("BAM library build:", bam.Bam.get_solver_version())
with bam.Bam(nvars=2) as solver:
solver.set_real_vector("xmin", [-3.0, -1.5])
solver.set_real_vector("xmax", [ 3.0, 1.5])
solver.set_real_vector("rho", [1e-3, 1e-3]) # per-variable resolution
solver.set_int("maxevals", 80)
solver.set_string("proname", "camel6") # names the run
solver.set_string("outfname", "camel6.lst") # mirror screen output
solver.set_string("tracefname", "camel6.trc") # one-line run summary
solver.set_objective(camel6)
result = solver.solve(x0=[0.0, 0.0])
print(result.x, result.fun, result.nfev)
By default the wrapper writes none of BAM’s output files and suppresses any local bam.opt. The scalar string setters above turn them on, with exactly the meaning the corresponding options carry from the command line: a listing file (outfname) that mirrors the screen output, and a trace file (tracefname) that receives one comma-separated summary line per run, appended on each subsequent run (see Section 7). The proname option names the run in that file. The rho vector sets the per-variable resolution, the smallest spacing BAM resolves between proposed points; the used here is why the best value below () is marginally coarser than camel6’s optimum near (the default resolution is ). For the solve above, camel6.trc contains:
#proname, BAMVersion, TotalTime, best_f, SolverEvals, best_eval, Incumbent progression, BAMStatus camel6, BAM-2026.5.21, 0.44615474, -1.0316264, 80, 51, -1.0316248, 35
The header names the comma-separated fields: the run name (proname), the BAM version, the total CPU time, the best objective value, the number of function evaluations, the evaluation at which the incumbent was found, the incumbent progression (one value every PRBESTFREQ evaluations, 50 by default), and the termination code (35 here, meaning the function-call limit was reached). Every field except the time is deterministic; TotalTime depends on the wall-clock and varies between machines and runs.
What the full-control class adds.
A few capabilities are reachable through Bam but not through minimize:
-
•
Querying the library build. The static bam.Bam.get_solver_version() used above returns the (year, month, day) of the libbam in use, the same date carried in the BAMVersion field of the trace. It requires no problem instance and callers may execute it before any solve.
-
•
Reusing one solver. A Bam object may be solved more than once: the option settings are replayed across the reset BAM performs between runs, and solver.n_calls() and solver.cpu_time may be queried between solves. minimize builds and discards a fresh solver on every call.
-
•
Setting any option by exact name and type. Scalars go through set_int / set_real / set_string and vector options through set_real_vector / set_int_vector; the xmin, xmax, and rho calls above are vector examples. Real-matrix options are set one column at a time with set_real_matrix_column, which is how several unevaluated starting points (ndata ) are supplied, something the single x0 of minimize cannot express.
8.3.3 AskTell: inverted control for external evaluators
The minimize function and the Bam class both work by calling the objective: the caller hands BAM a Python function, and BAM invokes that function whenever it needs an objective value. BAM owns the loop. This is the natural pattern when the objective is an ordinary computation that returns promptly.
Some objectives do not fit that pattern. The evaluation may be a physical experiment performed by a person over hours or days; it may be a simulation dispatched to a separate cluster, where a job is queued and results return later; or it may live in another process, another language, or a piece of laboratory equipment. In these cases there is no Python callable for BAM to invoke, and BAM cannot block inside a callback waiting for a result that is hours away.
The AskTell interface inverts the control flow for exactly these situations. Instead of BAM calling the objective, the caller repeatedly asks BAM which point to evaluate next, performs the evaluation by whatever means is appropriate, and then tells BAM the resulting value. The caller owns the loop; BAM only advises. Arbitrary time, and arbitrary work, may pass between an ask and the corresponding tell.
opt = bam.AskTell(nvars=2, bounds=[(-3, 3), (-1.5, 1.5)],
max_evals=120)
while not opt.is_done():
try:
x = opt.ask()
except StopIteration:
break
opt.tell(x, camel6(x))
res = opt.recommendation()
opt.close()
Here, camel6(x) is used only to keep the example self-contained; in practice the line opt.tell(x, camel6(x)) would be replaced by running the actual experiment or simulation on the point x and reporting the measured objective value. The interface consists of ask(), which returns the next point to evaluate as a NumPy array; tell(x, f), which reports that the objective value at x is f; is_done(), which reports whether BAM has finished; recommendation(), which returns the best point found so far as an OptimizeResult; and close(), which shuts the optimizer down and releases its resources. Using AskTell as a context manager calls close() automatically.
Realistic example: an external simulator.
The body of the while-loop can be anything. Here, each evaluation launches a separate program, and the context-manager form guarantees the worker thread is cleaned up even if something raises:
import bam, subprocess
def run_simulation(x):
# write inputs, run an external solver/simulation, read the result back
out = subprocess.run(
["./my_simulator", f"{x[0]:.10g}", f"{x[1]:.10g}"],
capture_output=True, text=True, check=True,
)
return float(out.stdout.strip())
with bam.AskTell(nvars=2, bounds=[(0, 10), (0, 10)], max_evals=200) as opt:
while not opt.is_done():
try:
x = opt.ask()
except StopIteration:
break
opt.tell(x, run_simulation(x)) # may take seconds, minutes, hours
res = opt.recommendation()
print("best:", res.x, res.fun, "after", res.nfev, "evaluations")
A simulation that fails to produce a value can be reported by telling BAM float(”nan”); BAM records that point as +inf and continues. Mixed-integer problems work the same way, with integrality=[0, 1, …] in the constructor.
How it works.
BAM’s underlying solve is a single blocking call: it runs the optimization to completion while driving a callback. AskTell turns that blocking call into a pausable one. On construction it starts the solve on a background worker thread and lets BAM proceed exactly as in an ordinary callback-driven run. The callback BAM invokes on that thread, however, does not itself evaluate anything: it passes the requested point out to the caller’s thread and then waits. The caller’s ask() receives that point; the caller performs the evaluation; the caller’s tell() passes the value back to the waiting callback, which returns it to BAM as though it had just been computed. BAM, unaware of the indirection, continues to its next iteration. At each moment only one side is running: BAM is frozen inside the callback while the caller evaluates, and the caller is blocked inside ask() while BAM works. The result is the same deterministic search that minimize would perform, on the same problem, with the same answer; only the direction of the calls is reversed. When close() is called before the search finishes, the callback raises StopIteration, which BAM receives through its callback termination flag and uses to stop the solve cleanly so that the worker thread can be joined.
Scope and limitations.
AskTell inverts control within a single, continuous Python process. The background worker thread holds the BAM solve, and all of its internal state, in memory for the lifetime of the optimizer. The interval between an ask and a tell may be long (days or weeks) but the process must remain running in your computer throughout: the optimization cannot be saved to disk, the process ended, and the search resumed later in a fresh process. AskTell also assumes a single solve at a time; it is not intended for running several optimizers concurrently in one process. For experimental campaigns that span days or weeks, in which the controlling computer is expected to be shut down and the optimization resumed later, a separate persistent interface is the appropriate tool; AskTell is the right choice when the optimization, however slow its individual evaluations, proceeds within one uninterrupted session.
8.3.4 Licensing
BAM runs in free mode for problems with at most bam.license_status()["free_limit_nvars"] variables and requires a license above that limit. The license is a run-time concern and is never part of the installed package. Point BAM at a license either with the environment variable BAM_LICENSE_FILE or programmatically:
import bam
bam.set_license_file("/path/to/bamlice.txt")
status = bam.license_status()
# status -> {"state": "licensed" | "free_no_license" | ...,
# "path": ..., "free_limit_nvars": 2}
set_license_file raises bam.BamLicenseError immediately if the file does not exist. In free mode, the wrapper emits a bam.BamLicenseWarning and, for a problem larger than the free limit, raises bam.BamLicenseError at solve time.
8.3.5 Diagnostics
bam.__version__ reports the version of the installed package. license_status() reports the live license state as above. Every solve returns an OptimizeResult whose status and message fields carry BAM’s termination code and its canonical text, queried from libbam so that they never drift from the installed library version. A callback that raises propagates the exception out of solve with the partial OptimizeResult attached; a callback that returns NaN is recorded by BAM as and the search continues.
8.3.6 Signal handling and determinism
The Python layer relies on a flag-and-poll interrupt architecture detailed in Section 8.5: a Ctrl-C during a solve raises bam.BamInterrupted carrying the best result found so far (for AskTell, the interruption is surfaced through recommendation() with termination code 50). BAM saves and restores the host’s signal dispositions around each solve, so a Python signal handler installed by the caller survives. The package is fully deterministic and has no seed parameter.
8.4 Calling BAM from the C and Fortran APIs
This section documents the native C and Fortran APIs. Python users do not need them: the bam-solver package of Section 8.3 automatically leverages the C API to run BAM.
8.4.1 Available routines
Each routine listed here is available in two versions:
-
•
A C-compatible routine: bam_xxx
-
•
A native Fortran routine: bam_xxx_f
Problem management functions
-
•
bam_create(nvars)
Create a new optimization problem with nvars variables. -
•
bam_destroy()
Destroy the current problem and free memory. -
•
bam_solve(x_best, f_best, start)
Solve the optimization problem.
Functions for setting options and data
-
•
bam_set_int(name, value)
-
•
bam_set_int_vector(name, values, n)
-
•
bam_set_real(name, value)
-
•
bam_set_real_vector(name, values, n)
-
•
bam_set_real_matrix_column(name, values, n, k)
-
•
bam_set_string(name, value)
-
•
bam_set_string_vector(name, values)
All these functions utilize native C data types. Each function has a corresponding _f version using native Fortran types.
Callback registration functions
-
•
bam_set_obj_fun(fproc, user_data)
Register an objective function using a C-interoperable callback. -
•
bam_set_obj_fun_f(fproc, user_data)
Register an objective function using a native Fortran callback.
The bam_set_obj_fun(fproc, user_data) callback has the signature:
subroutine f(n, k, x, fval, terminate, user_data) bind(C)
use iso_c_binding
integer(c_int), value :: n
integer(c_int) :: k
real(c_double) :: x(*)
real(c_double) :: fval(*)
integer(c_int) :: terminate
type(c_ptr), value :: user_data
end subroutine
The signature of bam_set_obj_fun_f(fproc, user_data) is similar:
subroutine f(n, k, x, fval, terminate, user_data)
use iso_c_binding
integer :: n, k
real :: x(*)
real :: fval(*)
integer :: terminate
type(c_ptr), value :: user_data
end subroutine
Here, n is the number of variables and k the number of points to evaluate: x holds n*k reals (point j occupying x((j-1)*n+1:j*n)) and may be modified by the callback, while fval holds the k objective values. k is capped by the BATCHSIZE option and is currently always 1; it is passed by reference so a future multi-point design may let the callback change it. The terminate argument is an output of the callback. On entry, it is 0; the callback writes the objective value(s) to fval and leaves terminate at 0 to ask BAM to continue. To request a clean termination from inside the callback, the user sets terminate to a nonzero value; fval is then ignored, BAM populates x_best/f_best with the best evaluation seen so far, and returns from bam_solve with termination code 51 (see Section 7 item 51). This termination condition is distinct from code 50 “Run interrupted by user”, which is reserved for OS-level signals such as SIGINT/SIGTERM.
Utility routines
-
•
real(c_double) function bam_get_time()
-
•
integer(c_int) function bam_get_calls()
-
•
void bam_get_version(yr,mo,da)
-
•
integer(c_int) function bam_get_status_count()
-
•
void bam_get_status_message(status, buffer, buflen)
-
•
integer(c_int) function bam_get_license_state()
-
•
void bam_get_license_path(buffer, buflen)
-
•
integer(c_int) function bam_get_free_mode_nvars()
The _f functions are native Fortran functions, i.e.:
-
•
real function bam_get_time_f()
-
•
integer function bam_get_calls_f()
-
•
subroutine bam_get_version_f(yr,mo,da)
-
•
integer function bam_get_status_count_f()
-
•
subroutine bam_get_status_message_f(status, message)
-
•
integer function bam_get_license_state_f()
-
•
subroutine bam_get_license_path_f(path)
-
•
integer function bam_get_free_mode_nvars_f()
The two bam_get_status_* routines let any caller query BAM’s termination/error code messages at runtime, so external code need not duplicate the message list maintained inside the library. bam_get_status_count returns the total number of codes (i.e., the upper bound on valid status arguments); bam_get_status_message writes the human-readable text for a given code into a caller-provided buffer (C: null-terminated; Fortran: blank-padded). Out-of-range codes (including 0, which BAM does not use) return the sentinel string Unknown status code.. The full list of messages is given in Section 9.
The three bam_get_license_* and the related bam_get_free_mode_nvars routines expose the licensing state to API callers without forcing them to scrape the listing file.
bam_get_license_state reports which of the three states applies given the currently configured license-file name: 0 for licensed (a valid license file was found), 1 for free mode with no license file configured anywhere on the search path, and 2 for free mode with a license file that was found but is unusable (cannot open, malformed, or for a different BAM version). The routine performs a fresh, quiet license check at call time and may therefore be invoked at any point in the API lifecycle, including before bam_create.
bam_get_license_path writes the resolved license-file path into a caller-provided buffer; when the file is found anywhere on the search path (CWD, executable directory, or $PATH) the buffer receives the full resolved path, and when not found, it receives the configured name unchanged, so callers can see what was searched for. The C variant null-terminates and truncates to fit; the Fortran variant blank-pads per Fortran assignment semantics.
bam_get_free_mode_nvars returns the maximum number of problem variables BAM allows in free (unlicensed) mode. At the time of this writing, that value is 2; reading it via this entry point insulates the host applications from future tier changes.
The license-file name BAM searches for is stored internally as licfname and defaults to bamlice.txt. Callers can override the name (or pin an absolute path) via
bam_set_string("licfname", "/absolute/or/altname.txt");
This is one of two keywords that bam_set_string accepts before bam_create (the other being optfname); calling it after bam_create also works.
Once a caller has set licfname, the value applies to every subsequent invocation of BAM in the same process until the caller changes it again with another bam_set_string("licfname", …) call. In particular, licfname is deliberately preserved across bam_destroy / bam_create cycles. It is a process-level configuration (which license file to read for this installation), not per-problem state. A host application or language wrapper that pins a custom license file path once does not have to reapply it on every solve.
The matching policy applied at solve time is: a licensed problem prints the Licensee: banner and runs; a free-with-no-file problem with at most bam_get_free_mode_nvars() variables runs silently while a larger one returns status 9 code 1; a free-with-invalid-file problem with at most bam_get_free_mode_nvars() variables emits a warning and runs, while a larger one returns status 9 code 2.
The options-file name BAM reads at the start of every solve is stored internally as optfname and defaults to bam.opt (looked up in the execute directory). Callers can override the name, point BAM at an alternate options file, or suppress the lookup entirely via
bam_set_string("optfname", "/absolute/path/or/altname.opt");
bam_set_string("optfname", ""); /* suppress bam.opt lookup */
This is the second of the two keywords that bam_set_string accepts before bam_create (the other being licfname); calling it after bam_create also works. Unlike every other bam_set_string keyword, optfname accepts an empty value, which is the recommended setting in API drivers that should ignore any local bam.opt file (see the OPTFNAME row of the options table in Section 6 and the Warning attached to it). The options it would have read are, in every case, settable through the API directly, so suppressing bam.opt does not lose functionality.
8.4.2 Calling the API from C
C users include the header (the contents of bam.h are reproduced in Appendix A):
#include "bam.h"
A typical usage pattern is:
int n = 2;
double xbest[2], fbest;
bam_create(n);
bam_set_real_vector("xmin", xmin, n);
bam_set_real_vector("xmax", xmax, n);
bam_set_obj_fun(myfun, mydata);
bam_solve(xbest, &fbest, 0);
bam_destroy();
The callback has the signature:
void myfun(int n, int *k, double *x, double *f,
int *terminate, void *user_data);
Here, n is the number of variables and *k is the number of points to evaluate: x holds n*(*k) doubles, point j occupying x[j*n .. j*n+n-1], and the callback writes one objective value per point into f[0 .. *k-1]. x is writable, so the callback may overwrite a point (for example, because it was only able to compute the objective at a nearby point). *k is capped by the BATCHSIZE option and is currently always 1 (BATCHSIZE is capped at 1 in this release); k is passed by reference so that a future multi-point design may let the callback modify it in case the user chooses to return fewer or more points than as many were requested by BAM. User data is passed as a void* and forwarded to the callback. The terminate argument is an output of the callback: on entry *terminate is 0; setting it to a nonzero value asks BAM to stop the run cleanly (f is then ignored), causing bam_solve to return termination code 51.
Complete example of calling the C API from C
The following driveroptimizes the Six-Hump Camel function. It illustrates the complete sequence of API calls, including full error checking.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "bam.h"
/* ================================================================
camel6 objective function callback
================================================================ */
void camel6(int n, int *k, double *x, double *f,
int *terminate, void *user_data)
{
(void)user_data;
/* Evaluate camel6 at each of the *k requested points (currently *k == 1).
Point j occupies x[j*n .. j*n+n-1]; its value goes into f[j]. */
for (int j = 0; j < *k; ++j) {
double x1 = x[j*n], x2 = x[j*n + 1];
f[j] = 4.*x1*x1 - 2.1*pow(x1, 4.) + 0.3333333333333333*pow(x1, 6.)
+ x1*x2 - 4.*x2*x2 + 4.*pow(x2, 4.);
}
*terminate = 0;
}
/* ================================================================
Main program
================================================================ */
int main(void)
{
int nvars = 2;
int status;
double xbest[2];
double fbest;
double xmin[2] = {-3, -1.5};
double xmax[2] = {3, 1.5};
int dummy = 0; /* user data */
void *pUser = &dummy;
/* --------------------------------------------------------------
Create BAM problem
-------------------------------------------------------------- */
status = bam_create(nvars);
if (status != 0) {
printf("bam_create failed, status=%d\n", status);
return 1;
}
/* --------------------------------------------------------------
Set bounds
-------------------------------------------------------------- */
status = bam_set_real_vector("xmin", xmin, nvars);
if (status != 0) {
printf("bam_set_real_vector failed, status=%d\n", status);
return 1;
}
status = bam_set_real_vector("xmax", xmax, nvars);
if (status != 0) {
printf("bam_set_real_vector failed, status=%d\n", status);
return 1;
}
/* --------------------------------------------------------------
Set filenames
-------------------------------------------------------------- */
status = bam_set_string("outfname", "camel6.lst");
if (status != 0) {
printf("bam_set_string failed, status=%d\n", status);
return 1;
}
status = bam_set_string("tracefname", "camel6.trc");
if (status != 0) {
printf("bam_set_string failed, status=%d\n", status);
return 1;
}
/* --------------------------------------------------------------
Register objective function
-------------------------------------------------------------- */
status = bam_set_obj_fun(camel6, pUser);
if (status != 0) {
printf("bam_set_obj_fun failed, status=%d\n", status);
return 1;
}
/* --------------------------------------------------------------
Initial guess
-------------------------------------------------------------- */
xbest[0] = 0;
xbest[1] = 0;
int start_at_xbest = 1;
/* --------------------------------------------------------------
Solve
-------------------------------------------------------------- */
status = bam_solve(xbest, &fbest, start_at_xbest);
int i;
printf("Status = %d\n", status);
printf("Best f = %.15e\n", fbest);
printf("Best x = ");
for (i = 0; i < nvars; i++) printf("%.15e ", xbest[i]);
printf("\n");
/* --------------------------------------------------------------
Destroy
-------------------------------------------------------------- /
status = bam_destroy();
if (status != 0) {
printf("bam_destroy failed, status=%d\n", status);
return 1;
}
return 0;
}
Querying the version, total time, and total number of calls
After bam_solve returns, the utility routines provide diagnostic information that is useful for benchmarking and reproducibility. They may be called as follows:
int yr, mo, da;
bam_get_version(&yr, &mo, &da);
printf("BAM version %d.%d.%d\n", yr, mo, da);
printf("Solved in %d calls and %.3f s\n",
bam_get_calls(), bam_get_time());
The same utility routines exist as native-Fortran functions bam_get_version_f, bam_get_calls_f, and bam_get_time_f.
8.4.3 Calling the C API from Fortran
Fortran users may call the C API directly by including the interface block in bam.inc (reproduced in Appendix B). A callback is written as:
subroutine camel6(n, k, x, f, terminate, user_data) bind(C)
use iso_c_binding
integer(c_int), value :: n
integer(c_int) :: k
real(c_double) :: x(*), f(*)
type(c_ptr), value :: user_data
integer(c_int) :: terminate
end subroutine
User data is passed using c_loc:
integer, target :: id = 7 call bam_set_obj_fun(c_funloc(camel6), c_loc(id))
Inside the callback, user data is recovered using c_f_pointer.
Complete example of calling the C API from Fortran
The following driveroptimizes the Six-Hump Camel function via the C API.
module camel6_callbacks
use iso_c_binding
contains
!======================================================================
! camel6 objective function (C interoperable)
!======================================================================
subroutine camel6(n, k, x, f, terminate, user_data) bind(C, name="camel6")
use iso_c_binding
implicit none
integer(c_int), value :: n
integer(c_int) :: k
real(c_double) :: x(*)
real(c_double) :: f(*)
integer(c_int), intent(out) :: terminate
type(c_ptr), value :: user_data
integer :: j, base
real(c_double) :: x1, x2
! Evaluate camel6 at each of the k requested points (currently k == 1).
! Point j occupies x((j-1)*n+1:j*n); its value goes into f(j).
do j = 1, k
base = (j-1)*n
x1 = x(base+1)
x2 = x(base+2)
f(j) = 4.d0*x1**2 - 2.1d0*x1**4 + 0.3333333333333333d0*x1**6 &
+ x1*x2 - 4.d0*x2**2 + 4.d0*x2**4
end do
terminate = 0
end subroutine camel6
end module camel6_callbacks
program main
use iso_c_binding
use camel6_callbacks
use iso_fortran_env, only: real64, int32
implicit none
include ’bam.inc’
!--------------------------------------------------------------
! Problem size
!--------------------------------------------------------------
integer(int32), parameter :: nvars = 2
! Variables
real(c_double) :: xbest(nvars), fbest
integer(c_int) :: status, start_at_xbest
! Bounds
real(c_double) :: xmin(nvars), xmax(nvars)
! Dummy user data
integer(int32), target :: dummy
type(c_ptr) :: pUser
! C strings
character(kind=c_char,len=5), parameter :: c_xmin = ’xmin’//c_null_char
character(kind=c_char,len=5), parameter :: c_xmax = ’xmax’//c_null_char
character(kind=c_char,len=9), parameter :: &
c_outfname = ’outfname’//c_null_char
character(kind=c_char,len=11), parameter :: &
c_tracefname = ’tracefname’//c_null_char
character(kind=c_char,len=11), parameter :: &
c_lst = ’camel6.lst’//c_null_char
character(kind=c_char,len=11), parameter :: &
c_trc = ’camel6.trc’//c_null_char
!--------------------------------------------------------------
! Create BAM problem
!--------------------------------------------------------------
status = bam_create(nvars)
if (status /= 0) then
print *, "bam_create failed, status=", status
stop
end if
!--------------------------------------------------------------
! Set bounds
!--------------------------------------------------------------
xmin(1) = -3d0
xmin(2) = -1.5d0
xmax(1) = 3d0
xmax(2) = 1.5d0
status = bam_set_real_vector(c_xmin, xmin, nvars)
if (status /= 0) then
print *, "bam_set_real_vector failed, status=", status
stop
end if
status = bam_set_real_vector(c_xmax, xmax, nvars)
if (status /= 0) then
print *, "bam_set_real_vector failed, status=", status
stop
end if
!--------------------------------------------------------------
! Set filenames
!--------------------------------------------------------------
status = bam_set_string(c_outfname, c_lst)
if (status /= 0) then
print *, "bam_set_string failed, status=", status
stop
end if
status = bam_set_string(c_tracefname, c_trc)
if (status /= 0) then
print *, "bam_set_string failed, status=", status
stop
end if
!--------------------------------------------------------------
! Register objective function
!--------------------------------------------------------------
pUser = c_loc(dummy)
status = bam_set_obj_fun(c_funloc(camel6), pUser)
if (status /= 0) then
print *, "bam_set_obj_fun failed, status=", status
stop
end if
!--------------------------------------------------------------
! Initial guess
!--------------------------------------------------------------
xbest(1:nvars) = 0d0
start_at_xbest = 1
!--------------------------------------------------------------
! Solve
!--------------------------------------------------------------
status = bam_solve(xbest, fbest, start_at_xbest)
print *, "Status = ", status
print *, "Best f = ", fbest
print *, "Best x = ", xbest
!--------------------------------------------------------------
! Destroy
!--------------------------------------------------------------
status = bam_destroy()
if (status /= 0) then
print *, "bam_destroy failed, status=", status
stop
end if
end program main
8.4.4 Using the native Fortran API
The native Fortran API provides routines with the suffix _f that use standard Fortran types:
-
•
integer function bam_create_f(nvars)
-
•
integer function bam_set_real_f(name,value)
-
•
integer function bam_solve_f(xbest,fbest,start)
A native Fortran callback has the signature:
subroutine myfun(n, k, x, f, terminate, user_data)
use iso_c_binding
implicit none
integer :: n, k, terminate
real :: x(*), f(*)
type(c_ptr), value :: user_data
end subroutine
User data is passed using c_loc exactly as in the C API. The terminate argument matches the C-API contract: setting it to a nonzero value asks BAM to stop the run cleanly and return code 51 from bam_solve; the value of f is then ignored.
The native API is convenient for Fortran-only users and avoids bind(C) procedures and data.
Complete example of calling the native Fortran API from Fortran
The following driveroptimizes the Six-Hump Camel function using the native Fortran API.
program main
use iso_c_binding
use iso_fortran_env, only: real64, int32
implicit none
C BAM API declarations
integer(int32) :: bam_create_f, bam_set_real_vector_f
integer(int32) :: bam_set_obj_fun_f
integer(int32) :: bam_set_string_f, bam_solve_f, bam_destroy_f
C Problem size
integer, parameter :: nvars = 2
C Variables
real(real64) :: xbest(nvars), fbest
integer(int32) :: status, start_at_xbest
C Bounds
real(real64) :: xmin(nvars), xmax(nvars)
C User data (unused here)
integer, target :: dummy
type(c_ptr) :: user_data = c_null_ptr
C External objective function
external :: camel6
C Create BAM problem
status = bam_create_f(nvars)
if (status /= 0) then
print *, ’bam_create_f failed, status=’, status
stop
endif
C Set bounds
xmin(1) = -3d0
xmin(2) = -1.5d0
xmax(1) = 3d0
xmax(2) = 1.5d0
status = bam_set_real_vector_f(’xmin’, xmin, nvars)
if (status /= 0) then
print *, ’bam_set_real_vector_f failed, status=’, status
stop
endif
status = bam_set_real_vector_f(’xmax’, xmax, nvars)
if (status /= 0) then
print *, ’bam_set_real_vector_f failed, status=’, status
stop
endif
C Initialize filenames
status = bam_set_string_f(’outfname’, ’camel6.lst’)
if (status /= 0) then
print *, ’bam_set_string_f failed, status=’, status
stop
endif
status = bam_set_string_f(’scratch’, ’camel6.scr’)
if (status /= 0) then
print *, ’bam_set_string_f failed, status=’, status
stop
endif
status = bam_set_string_f(’tracefname’, ’camel6.trc’)
if (status /= 0) then
print *, ’bam_set_string_f failed, status=’, status
stop
endif
C Register objective function (Fortran-native)
status = bam_set_obj_fun_f(camel6, user_data)
if (status /= 0) then
print *, ’bam_set_obj_fun_f failed, status=’, status
stop
endif
C Initial guess
xbest(1:nvars) = 0d0
start_at_xbest = 1
C Solve
status = bam_solve_f(xbest, fbest, start_at_xbest)
print *, ’Status = ’, status
print *, ’Best f = ’, fbest
print *, ’Best x = ’, xbest(1:nvars)
C Destroy
status = bam_destroy_f()
if (status /= 0) then
print *, ’bam_destroy_f failed, status=’, status
stop
endif
end
C======================================================================
C camel6 objective function
C======================================================================
subroutine camel6(n, k, x, f, terminate, user_data)
use iso_c_binding
use iso_fortran_env, only: real64, int32
implicit none
integer(int32) :: n, k, terminate, j, base
real(real64) :: x(*), f(*), x1, x2
type(c_ptr) :: user_data
C Evaluate camel6 at each of the k requested points (currently k == 1).
C Point j occupies x((j-1)*n+1:j*n); its value goes into f(j).
do j = 1, k
base = (j-1)*n
x1 = x(base+1)
x2 = x(base+2)
f(j) = 4.d0*x1**2 - 2.1d0*x1**4 + 0.3333333333333333d0*x1**6
$ + x1*x2 - 4.d0*x2**2 + 4.d0*x2**4
end do
terminate = 0
return
end
8.4.5 Compilation and linking
This subsection applies to the C and Fortran APIs only. The bam-solver Python package (Section 8.3) ships pre-compiled with the BAM library bundled inside the wheel, so Python users compile and link nothing.
The recommended commands below assume that the BAM include files (bam.h, bam.inc) and shared libraries (libbam.so on Linux, libbam.dylib on macOS, libbam.dll on Windows) are in the current working directory.
C with Intel icx
icx -g -O3 -fp-model=consistent -ftz -fimf-arch-consistency=true \
-static-intel -I. -L. \
camel6.c -lbam -lm \
-Wl,-rpath,’$ORIGIN’ -o camel6
Fortran (fixed form) with Intel ifx
ifx -g -O3 -fp-model=consistent -ftz -fimf-arch-consistency=true \
-heap-arrays 1024 -assume noold_maxminloc -static-intel \
-I. -L. \
camel6.f -lbam \
-Wl,-rpath,’$ORIGIN’ -o camel6
Fortran (fixed form) with GNU gfortran
gfortran -g -O3 -fno-fast-math -ffp-contract=off \
-fno-associative-math -fno-reciprocal-math \
-fmax-stack-var-size=1024 \
-I. -L. \
camel6.f -lbam \
-Wl,-rpath,’$ORIGIN’ -o camel6
Fortran 90 with Intel ifx
ifx -g -O3 -fp-model=consistent -ftz -fimf-arch-consistency=true \
-heap-arrays 1024 -assume noold_maxminloc -static-intel \
-I. -L. \
camel6.f90 -lbam \
-Wl,-rpath,’$ORIGIN’ -o camel6
The -Wl,-rpath,$ORIGIN linker flag embeds a relative runtime search path so that the resulting executable will find libbam.so in the same directory as the executable itself. This avoids the need to set LD_LIBRARY_PATH at run time.
8.4.6 Using the C/Fortran API to solve st_e36
This section illustrates the application of BAM API to the mixed-integer problem st_e36.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "bam.h"
/* ================================================================
st_e36 objective function callback
================================================================ */
void st_e36(int n, int *k, double *x, double *f,
int *terminate, void *user_data)
{
(void)user_data;
/* x[1] is integer */
#define _SQR(x) ((x)*(x))
/* Evaluate at each of the *k requested points (currently *k == 1). */
for (int j = 0; j < *k; ++j) {
double *xj = x + j*n;
f[j] = (double)(2*_SQR(xj[0]) + 0.008*pow(xj[1],3)
- 3.2*xj[0]*xj[1] - 2*xj[1]);
}
*terminate = 0;
}
/* ================================================================
Main program
================================================================ */
int main(void)
{
int nvars = 2, status;
double xbest[2], fbest;
double xmin[2] = {3, 15};
double xmax[2] = {5.5, 25};
int xisint[2] = {0, 1};
int dummy = 0;
void *pUser = &dummy;
status = bam_create(nvars);
if (status != 0) return 1;
status = bam_set_real_vector("xmin", xmin, nvars);
status = bam_set_real_vector("xmax", xmax, nvars);
status = bam_set_int_vector ("xisint", xisint, nvars);
status = bam_set_string("outfname", "st_e36.lst");
status = bam_set_string("tracefname","st_e36.trc");
status = bam_set_obj_fun(st_e36, pUser);
xbest[0] = 4.433315;
xbest[1] = 18;
int start_at_xbest = 1;
status = bam_solve(xbest, &fbest, start_at_xbest);
printf("Best f = %.15e\n", fbest);
printf("Best x = [%.15e, %.15e]\n", xbest[0], xbest[1]);
status = bam_destroy();
return 0;
}
The corresponding native Fortran and Fortran-90 drivers are provided as st_e36.f and st_e36.f90 at https://minlp.com.
8.5 Signal handling and user interrupt
BAM responds to a user-issued interrupt (Ctrl-C, or SIGINT/SIGTERM more generally) by terminating the search at the next safe point and returning normally to the caller with termination code 50, “Run interrupted by user.” The mechanism is a flag-and-poll architecture chosen so that BAM can be embedded in a host application as a library without disturbing the host’s own signal-handling policy any longer than necessary.
Architecture.
On entry to bam_solve, BAM saves the host application’s previous dispositions for SIGINT, SIGTERM, SIGSEGV, SIGABRT, SIGBUS, and SIGPIPE, then installs three kinds of handlers. SIGINT and SIGTERM go to an asynchronous-signal-safe handler that does nothing more than set an internal flag. SIGSEGV, SIGABRT, and SIGBUS go to a catastrophic-signal catcher (described below). SIGPIPE is set to SIG_IGN, so a broken pipe never terminates BAM. Throughout the run, BAM polls the interrupt flag at carefully defined safe points. When the flag is observed to be set, the main loop unwinds along the normal code return path, the listing file is finalized, and bam_solve returns termination code 50 to the caller. The interrupt handler itself does not allocate memory or invoke exit(); it is therefore safe even when the signal arrives mid-listing or mid-allocation. Before returning to the caller, BAM restores all six previously saved dispositions, leaving the host’s signal-handling policy outside the solve identical to what it was before.
Subsolvers.
BAM launches BARON via a system call, which, on POSIX systems, is required by the standard to ignore SIGINT in the parent for the entire duration of the wait. As a consequence, BAM’s own handler cannot fire while the solver is blocked in such a call. To make Ctrl-C effective during a long subsolver run, BAM also inspects BARON’s wait status when system() returns: if the child died from SIGINT, SIGTERM, or SIGQUIT, or if it exited with code 6 (BARON’s documented user-interrupt code), BAM raises the same internal flag and unwinds the loop at the next poll. In practice, this means an interruption is near-instant during a BARON call, since BARON itself catches Ctrl-C and exits with code 6 before BAM’s parent process sees the wait status.
Catastrophic signals.
SIGSEGV, SIGABRT, and SIGBUS are handled by a separate catastrophic-signal catcher. When invoked, the catcher prints a brief banner pointing the user to an minlp.com email address, sleeps for five seconds so the message is readable, and then calls abort(), producing a core dump if one has been requested by the host. The catcher uses only async-signal-safe primitives; it does not call into BAM’s own state, because that state may be in any condition when the signal is fired. The handler is installed with SA_RESETHAND, so the kernel restores the default disposition before the handler is entered: the abort() that follows therefore raises a SIGABRT that goes straight to the default handler and ends the process without re-entering the catcher. SIGPIPE is set to SIG_IGN rather than being routed to the catcher, because a broken pipe is not a catastrophic event and should not terminate BAM. As with SIGINT/SIGTERM, the host application’s previous dispositions for SIGSEGV, SIGABRT, SIGBUS, and SIGPIPE are saved on entry and restored on exit; a host application that has its own crash handler is not permanently displaced by linking against libbam.
Library safety.
Because the SIGINT/SIGTERM handler is async-signal-safe, the unwind goes through the normal code return chain, and the host’s previous signal dispositions for all six signals BAM touches are restored on exit. BAM is safe to embed inside applications that need to survive an interrupt and continue running. For example, this will work cleanly with a Python wrapper, a benchmarking harness, or a model-and-search outer loop. The host receives termination code 50 from bam_solve and may decide whether to retry, log, prompt the user, or exit. A second Ctrl-C is not converted to SIG_IGN by the handler, so if the polling loop is somehow stuck, the kernel-default action remains available as an emergency escape.
Opting out.
Hosts that have their own SIGINT handler and need it to remain in effect throughout the solve, for instance, to update a progress display, post a heartbeat, or trigger application-specific cleanup, can set SIGNAL_HANDLER to 0 via bam_set_int before calling bam_solve. With the option disabled, BAM neither installs nor restores any signal handler. Ctrl-C still terminates BAM through the subsolver-status path: SIGINT delivered to the foreground process group reaches the BARON child, BARON exits with code 6, and the post-system() check in BAM raises the flag. The trade-off is that an interrupt arriving between subsolver calls is not detected by BAM (the host’s handler runs, but BAM does not see it) until the next subsolver returns. Since subsolver evaluations dominate runtime, the resulting latency is small. The option has no effect on Ctrl-C handling in Microsoft Windows beyond the existing console handler registration.
Important constraints.
BAM’s signal-handling design assumes single-threaded entry into bam_solve. Concurrent calls from multiple threads are not supported and will race on the saved-disposition state. In a multi-threaded host, the calling thread must ensure SIGINT and SIGTERM are unmasked (via pthread_sigmask) for delivery to reach BAM’s handler. bam_solve must not be invoked from inside a signal handler.
8.6 Parallel solves
BAM is currently not thread-safe. Two concurrent calls to bam_solve from threads of the same process will overwrite each other’s state, race on the scratch files, and almost certainly abort. To run several BAM solves in parallel today, launch them as separate operating-system processes, one BAM per process, each with its own scratch directory, trace file, and listing file. A shell fan-out, a job scheduler, or a worker pool that fork/execs independent BAM invocations all work; in-process threading does not.
8.7 Summary
The BAM API provides:
-
•
A C interface for cross-language interoperability.
-
•
A native Fortran interface for convenience.
-
•
An official Python package, bam-solver, that wraps the C interface and offers three layers of ergonomics: the SciPy-style minimize function, the full-control Bam class, and the AskTell interface for external evaluators.
-
•
A unified callback mechanism that communicates user data to the callback.
Both C and Fortran users can pass arbitrary data to callbacks, and all three interfaces expose the same optimization functionality. Python users install the package using pip install bam-solver; pre-built wheels bundle all necessary binaries, eliminating the need for a compiler or a separate BAM installation.
9 Termination conditions and error messages
API errors result in nonzero return codes of the functions being called and must be addressed immediately by the caller before continuing execution. Errors in the input file are reported on the screen and/or the listing file as “warnings” and “severe errors.” BAM attempts to continue execution despite warnings. If the errors are severe, the program execution is stopped and, if relevant, the line of the input file where the fatal error occurred is displayed. The input file should be checked even if the warnings are not severe, as the problem might have been parsed differently than intended. Detailed error messages are provided in that case.
The textual messages listed below can also be queried at runtime through the BAM API: see bam_get_status_message (C) and bam_get_status_message_f (Fortran) in Section 8. Their companion bam_get_status_count returns the total number of codes. This is the recommended way for host applications and language wrappers to obtain BAM’s status text; the library is the single source of truth, so external code that queries it stays in sync across BAM releases automatically.
BAM may terminate with any of the following termination codes, all of which are self-explanatory:
-
1.
BAM is free to use for problems with up to two variables. Larger problems require a license file (free for academic users at https://minlp.com/bam-licenses).
-
2.
Licensing error. A valid license is required to run this software with more than two variables.
-
3.
BAM input file name must be no longer than 1000 characters.
-
4.
BAM input file not found.
-
5.
BAM listing file cannot be opened.
-
6.
Unable to open trace file.
-
7.
Insufficient memory to allocate data structures.
-
8.
BAM input file cannot be opened.
-
9.
The BAM input file must contain no keyword longer than 16 characters.
-
10.
No keyword may be specified more than once.
-
11.
Number of variables (NVARS) must be specified before specifying XMIN values.
-
12.
Number of variables (NVARS) must be specified before specifying XMAX values.
-
13.
Reserved for future use.
-
14.
Reserved for future use.
-
15.
Reserved for future use.
-
16.
Reserved for future use.
-
17.
Reserved for future use.
-
18.
Number of variables (NVARS) must be specified before specifying XISINT.
-
19.
END_DATA missing or incomplete DATA section in the input file.
-
20.
Number of data points (NDATA) must be specified before the DATA section of the input file.
-
21.
Number of variables (NVARS) must be specified before the DATA section of the input file.
-
22.
Keyword not recognized.
-
23.
Only one DATA section is allowed.
-
24.
Input data file missing required keyword(s).
-
25.
At least one of DATAPROVIDER and PROPOSE must be specified for the BAM executable.
-
26.
Error while attempting to access the BAM scratch directory.
-
27.
Error while attempting to access the BAM execution directory.
-
28.
XMAX-XMIN for all variables must be positive.
-
29.
XDATA must be in the range [XMIN, XMAX].
-
30.
XEVALDATA must be in the range [XMIN, XMAX].
-
31.
Premature end of input file.
-
32.
Each line of the input file must contain no more than 10000 characters. Longer data records may be split across multiple lines by using & at the end of a line to indicate continuation of the record on the next line.
-
33.
Syntax error in input file.
-
34.
Inline comments must be preceded by ! or #.
-
35.
Solver reached limit on function calls.
-
36.
Input value in error in the input file.
-
37.
Error while attempting to write the input file for the data provider.
-
38.
Error while attempting to read the output file of the data provider.
-
39.
Error while attempting to access the data provider.
-
40.
Error while trying to copy file to disk.
-
41.
Error while attempting to write file to disk.
-
42.
Error while attempting to read file from disk.
-
43.
Error while trying to run ALAMO.
-
44.
Error while trying to run BARON.
-
45.
Too many iterations without progress.
-
46.
Maximum CPU time (MAXTIME) exceeded.
-
47.
Numerical difficulties. Please report to info@minlp.com.
-
48.
NEVALDATA is nonzero, but XEVALDATA was not provided.
-
49.
NDATA is nonzero, but XDATA was not provided.
-
50.
Run interrupted by user.
-
51.
Termination requested by callback.
-
52.
Number of variables (NVARS) must be specified before specifying RHO.
-
53.
Resolution vector elements must be integers for integer variables.
-
54.
Search space evaluated conclusively.
-
55.
Maximum number of iterations reached.
-
56.
Too many iterations without new proposals.
-
57.
This function may not be called before the problem is initialized via a call to bam_create.
-
58.
Upon run completion, this function may not be called before the problem is initialized via a call to bam_create.
-
59.
The length of BAM keywords must be between 1 and 8192 characters.
-
60.
The length of this vector must equal NVARS.
-
61.
The length of this vector must equal NEVALDATA.
-
62.
Set the value of NEVALDATA before providing this vector.
-
63.
Before calling bam_solve, you must call bam_set_obj_fun or specify a DATAPROVIDER via a call to bam_set_string.
-
64.
The provided starting point contains NaNs. Fix it or set start_at_xbest to 0 and resubmit.
-
65.
Function bam_create must be called first before calling bam_set_obj_fun.
-
66.
The value of start_at_xbest must be 0 or 1.
-
67.
PREVALS is nonzero, but EVALSFNAME (the name of the evaluations file) was not specified.
-
68.
File almbaron is missing from your distribution. Please restore it in your installation folder.
-
69.
BAM parameter names may be no longer than 8192 characters.
-
70.
Error in location 293. Please report to info@minlp.com.
-
71.
Reserved for future use.
-
72.
The BAM executable must be called with exactly one command line argument (the BAM input file).
-
73.
The length of this vector must equal NDATA.
-
74.
Set the value of NDATA before providing this vector.
Appendix A: Header file bam.h
Appendix B: Include file bam.inc
The include file bam.inc contains the C API interfaces that Fortran users must include in their programs if they want to rely on this API rather than the native Fortran API. It has the following contents:
!--------------------------------------------------------------
! C API interfaces (Fortran users include in driver program)
!--------------------------------------------------------------
interface
!--------------------------------------------------------------
! bam_create
!--------------------------------------------------------------
integer(c_int) function bam_create(nvars) &
bind(C, name="bam_create")
import :: c_int
integer(c_int), value :: nvars
end function bam_create
!--------------------------------------------------------------
! bam_destroy
!--------------------------------------------------------------
integer(c_int) function bam_destroy() &
bind(C, name="bam_destroy")
import :: c_int
end function bam_destroy
!--------------------------------------------------------------
! bam_set_int
!--------------------------------------------------------------
integer(c_int) function bam_set_int(name, value) &
bind(C, name="bam_set_int")
import :: c_int, c_char
character(kind=c_char), dimension(*), intent(in) :: name
integer(c_int), value :: value
end function bam_set_int
!--------------------------------------------------------------
! bam_set_int_vector
!--------------------------------------------------------------
integer(c_int) function bam_set_int_vector(name, value, n) &
bind(C, name="bam_set_int_vector")
import :: c_int, c_char
character(kind=c_char), dimension(*), intent(in) :: name
integer(c_int), dimension(*), intent(in) :: value
integer(c_int), value :: n
end function bam_set_int_vector
!--------------------------------------------------------------
! bam_set_real
!--------------------------------------------------------------
integer(c_int) function bam_set_real(name, value) &
bind(C, name="bam_set_real")
import :: c_int, c_double, c_char
character(kind=c_char), dimension(*), intent(in) :: name
real(c_double), value :: value
end function bam_set_real
!--------------------------------------------------------------
! bam_set_real_vector
!--------------------------------------------------------------
integer(c_int) function bam_set_real_vector(name, value, n) &
bind(C, name="bam_set_real_vector")
import :: c_int, c_double, c_char
character(kind=c_char), dimension(*), intent(in) :: name
real(c_double), dimension(*), intent(in) :: value
integer(c_int), value :: n
end function bam_set_real_vector
!--------------------------------------------------------------
! bam_set_real_matrix_column
!--------------------------------------------------------------
integer(c_int) function bam_set_real_matrix_column(name, value, n, k) &
bind(C, name="bam_set_real_matrix_column")
import :: c_int, c_double, c_char
character(kind=c_char), dimension(*), intent(in) :: name
real(c_double), dimension(*), intent(in) :: value
integer(c_int), value :: n
integer(c_int), value :: k
end function bam_set_real_matrix_column
!--------------------------------------------------------------
! bam_set_string
!--------------------------------------------------------------
integer(c_int) function bam_set_string(name, value) &
bind(C, name="bam_set_string")
import :: c_int, c_char
character(kind=c_char), dimension(*), intent(in) :: name
character(kind=c_char), dimension(*), intent(in) :: value
end function bam_set_string
!--------------------------------------------------------------
! bam_set_string_vector
!--------------------------------------------------------------
integer(c_int) function bam_set_string_vector(name, value) &
bind(C, name="bam_set_string_vector")
import :: c_int, c_char
character(kind=c_char), dimension(*), intent(in) :: name
character(kind=c_char), dimension(*), intent(in) :: value
end function bam_set_string_vector
!--------------------------------------------------------------
! bam_get_version
!--------------------------------------------------------------
subroutine bam_get_version(yr, mo, da) &
bind(C, name="bam_get_version")
import :: c_int
integer(c_int) :: yr, mo, da
end subroutine bam_get_version
!--------------------------------------------------------------
! bam_get_time
!--------------------------------------------------------------
real(c_double) function bam_get_time() &
bind(C, name="bam_get_time")
import :: c_double
end function bam_get_time
!--------------------------------------------------------------
! bam_get_calls
!--------------------------------------------------------------
integer(c_int) function bam_get_calls() &
bind(C, name="bam_get_calls")
import :: c_int
end function bam_get_calls
!--------------------------------------------------------------
! bam_set_obj_fun
!--------------------------------------------------------------
integer(c_int) function bam_set_obj_fun(fproc, user_data) &
bind(C, name="bam_set_obj_fun")
import :: c_int, c_funptr, c_ptr
type(c_funptr), value :: fproc
type(c_ptr), value :: user_data
end function bam_set_obj_fun
!--------------------------------------------------------------
! bam_solve
!--------------------------------------------------------------
integer(c_int) function bam_solve(x_best, f_best, start_at_xbest) &
bind(C, name="bam_solve")
import :: c_int, c_double
real(c_double), dimension(*), intent(inout) :: x_best
real(c_double), intent(out) :: f_best
integer(c_int), value :: start_at_xbest
end function bam_solve
!--------------------------------------------------------------
! bam_get_status_count
!
! Returns the total number of BAM termination/error codes. Status
! codes used by BAM are 1..bam_get_status_count(); 0 is not used
! and is treated as out of range by bam_get_status_message.
!--------------------------------------------------------------
integer(c_int) function bam_get_status_count() &
bind(C, name="bam_get_status_count")
import :: c_int
end function bam_get_status_count
!--------------------------------------------------------------
! bam_get_status_message
!
! Writes the human-readable message for ‘status‘ into ‘buffer‘,
! which has capacity ‘buflen‘ bytes including the trailing NUL.
! The result is always null-terminated; messages longer than
! ‘buflen-1‘ characters are truncated. If ‘status‘ is outside
! [1, bam_get_status_count()] the sentinel "Unknown status code."
! is written instead. If ‘buflen <= 0‘ the call is a no-op.
!--------------------------------------------------------------
subroutine bam_get_status_message(status, buffer, buflen) &
bind(C, name="bam_get_status_message")
import :: c_int, c_char
integer(c_int), value :: status
character(kind=c_char), intent(out) :: buffer(*)
integer(c_int), value :: buflen
end subroutine bam_get_status_message
!--------------------------------------------------------------
! bam_get_license_state
!
! Report the wrapper’s license-state classification:
! 0 = licensed (license file found and valid)
! 1 = free mode, no license file configured
! 2 = free mode, license file present but invalid
!
! Performs a quiet license check at call time. May be invoked
! at any point in the API lifecycle, including before bam_create.
!--------------------------------------------------------------
integer(c_int) function bam_get_license_state() &
bind(C, name="bam_get_license_state")
import :: c_int
end function bam_get_license_state
!--------------------------------------------------------------
! bam_get_license_path
!
! Write the resolved license-file path (or the configured name
! when not found) into a caller-provided C buffer. NUL-terminated;
! truncates to fit; no-op when buflen <= 0.
!--------------------------------------------------------------
subroutine bam_get_license_path(buffer, buflen) &
bind(C, name="bam_get_license_path")
import :: c_int, c_char
character(kind=c_char), intent(out) :: buffer(*)
integer(c_int), value :: buflen
end subroutine bam_get_license_path
!--------------------------------------------------------------
! bam_get_free_mode_nvars
!
! Returns the maximum number of variables BAM permits in free
! (unlicensed) mode. Currently 2.
!--------------------------------------------------------------
integer(c_int) function bam_get_free_mode_nvars() &
bind(C, name="bam_get_free_mode_nvars")
import :: c_int
end function bam_get_free_mode_nvars
end interface
Bibliography
- [1] S. Amaran, N. V. Sahinidis, B. Sharda, and S. J. Bury. Simulation optimization: A review of algorithms and applications. Annals of Operations Research, 240:351–380, 2016.
- [2] M. R. Bussieck, A. S. Drud, and A. Meeraus. MINLPLib–A collection of test models for mixed-integer nonlinear programming. INFORMS Journal on Computing, 15:114–119, 2003.
- [3] A. Cozad, N. V. Sahinidis, and D. C. Miller. Learning surrogate models for simulation-based optimization. AIChE Journal, 60:2211–2227, 2014.
- [4] A. Cozad, N. V. Sahinidis, and D. C. Miller. A combined first-principles and data-driven approach to model building. Computers & Chemical Engineering, 73:116–127, 2015.
- [5] D. Eriksson and M. Jankowiak. High-dimensional bayesian optimization with sparse axis-aligned subspaces. In Proceedings of the 37th Conference on Uncertainty in Artificial Intelligence (UAI), 2021.
- [6] W. Huyer and A. Neumaier. Global optimization by multilevel coordinate search. Journal of Global Optimization, 14:331–355, 1999.
- [7] D. R. Jones and J. R. R. A. Martins. The DIRECT algorithm: 25 Years Later. Journal of Global Optimization, 79:521–566, 2021.
- [8] D. R. Jones, C. D. Perttunen, and B. E. Stuckman. Lipschitzian optimization without the Lipschitz constant. Journal of Optimization Theory and Application, 79:157–181, 1993.
- [9] D. R. Jones, M. Schonlau, and W. J. Welch. Efficient global optimization of expensive black-box functions. Journal of Global Optimization, 13:455–492, 1998.
- [10] A. Khajavirad and N. V. Sahinidis. A hybrid LP/NLP paradigm for global optimization relaxations. Mathematical Programming Computation, 10:383–421, 2018.
- [11] K. Ma, L. M. Rios, A. Bhosekar, N. V. Sahinidis, and S. Rajagopalan. Branch-and-Model: A derivative-free global optimization algorithm. Computational Optimization and Applications, 85:337–367, 2023.
- [12] J. Mockus, V. Tiesis, and A. Zilinskas. Towards Global Optimisation, volume 2, chapter The application of Bayesian methods for seeking the extremum, pages 117–128. North-Holland, Amsterdam, 1978.
- [13] D. Oh and N. V. Sahinidis. Algorithm-guided experimentation for autonomous AI systems in self-driving laboratories, 2026. American Chemical Society Spring Meeting, Atlanta, Georgia, March 2026, https://www.osti.gov/biblio/3031241.
- [14] N. Ploskas and N. V. Sahinidis. Review and comparison of algorithms and software for mixed-integer derivative-free optimization. Journal of Global Optimization, 82:433–462, 2022.
- [15] L. M. Rios and N. V. Sahinidis. Derivative-free optimization: A review of algorithms and comparison of software implementations. Journal of Global Optimization, 56:1247–1293, 2013.
- [16] M. Tawarmalani and N. V. Sahinidis. Global optimization of mixed-integer nonlinear programs: A theoretical and computational study. Mathematical Programming, 99:563–591, 2004.
- [17] Y. Zhang and N. V. Sahinidis. Solving continuous and discrete nonlinear programs with BARON. Computational Optimization and Applications, 92:1123–1161, 2025.