All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
the makefiles build the C++20 module
smspp.mcfclasssolverwhenSMSppMODis not empty (e.g.,make SMSppMOD=1), with clang++ 16 or later [seelib/makefile-modof SMS++] -
MCFClassSolver_tolerance_testchecks that eachMCFSolveroftest/MCFBSCfg-tol.txtsays infeasible on the infeasible instances with fractional coststest/data/infeas-frac-*.dmx, with a limit on the iterations so that one which pivots for ever fails instead of hanging, and so doesMCFSimplexoncetest/MCFBSCfg-tol-dual.txtturns it to the dual simplex -
the C++20 module
smspp.mcfclasssolver(src/MCFClassSolver.cppm), which exports every name that the headers declare at namespace scope and re-exportssmspp.mcfblock, so that a source file can writeimport smspp.mcfclasssolver;in place of the#includeof the headers; it is built with the CMake optionSMSpp_BUILD_MODULE, as the targetSMS++::MCFClassSolver_module
-
the pipeline builds the module also with its makefiles, after CMake, and runs on a change of the makefiles too [see
ci/makefileof the umbrella] -
SMSpp_BUILD_MODULEno longer stops the configuration when the C++20 module cannot be built, i.e., with a CMake older than 3.28, a generator other than Ninja or Visual Studio, a compiler other than Clang 16 (withclang-scan-deps) or MSVC 19.34, or a module it imports that is not there: the option is turned off with a warning that says why, and the build goes on with the headers [seecmake/CheckModule.cmakeof SMS++] -
the test
MCFClassSolver_run_dmx2nc4sets up the fixturedmx2nc4_generatedof the MCFBlock suite and shares its lockmcf_data
-
test/makefilecompilestest.cppwith MCFSimplex when MCFCplex is not there, as CMake does (WHICH_MCF=2), and so builds without CPLEX -
MCFClass compiles MCFCplex with its makefiles only if CPLEX is there, so that the makefiles build without CPLEX
-
the makefile defines
HAVE_CPLEXonly if MCFClass compilesMCFCplex, which it does only if CPLEX is there -
the makefiles of the tests and of the tools build also with g++: they take the option that stops at the first error and the one of the debugging information, which clang++ and g++ spell differently (
-ferror-limit=1and-glldbagainst-fmax-errors=1and-ggdb), fromSMSppSWERRandSMSppSWDBGoflib/makefile-OSof SMS++ -
MCFSimplexof the MCFClass submodule solves with a tolerance of the reduced costs not below the rounding of its potentials, which are of the order of the cost of its artificial arcs: with a smallerkEpsCstit pivoted for ever on an infeasible instance with fractional costs, e.g., the MCF R3Block of a CapacitatedFacilityLocationBlock after closing facilities -
MCFSimplexof the MCFClass submodule keeps its candidate lists when it is switched between primal and dual simplex (kAlgPrimal) on an instance already loaded: they were deleted with the structures of the old algorithm and the first pivot dereferenced a null pointer
0.3.0 - 2026-10-09
-
SPTreeof the MCFClass submodule has the parameterkNegCycl, whichMCFSolver< SPTree >gives as its own: with the defaultkNoNegCstthe costs are taken as nonnegative, withkNoNegCyclas giving no cycle of negative cost, and in both cases the bound of the check of a negative cycle is not computed;kMayNegCyclcomputes it again after each change of the costs, and it is the value to give when a negative cycle has to be found and reported as unbounded -
dblRelAccanddblFAccSolofMCFSolverare relative tolerances: if positive,compute()setskEpsCsttodblRelAcctimes the largest absolute value of a cost, andkEpsFlwtodblFAccSoltimes the largest among the finite capacities and the absolute deficits, computing them again only after a change of the data; the default 0 leaves the absolute tolerances of MCFClass -
the tests of this directory carry the label of the module, so that the pipeline, which selects with
ctest -L <module>, runs them: they were built and never run -
MCFSolver::get_var_direction(), which threw "not implemented yet": it writes one unit of flow along the cycle of negative cost and infinite capacity that the :MCFClass gives as the certificate of unboundedness [seeMCFClass::MCFGetUnbCycl()] and tells the MCFBlock that its flow Variable hold a direction;get_Solution()on an unbounded instance gives aMCFSolutionthat says it holds a direction rather than the flow of a solution that there is not. A :MCFClass that gives no certificate, which the base class allows, makes the former throw and the latter give nothing -
the certificate itself in two of the solvers of the MCFClass submodule:
MCFSimplexkeeps the cycle of the pivot whose step is infinite, whileSPTreereads it off its predecessor function
-
SPTreeof the MCFClass submodule computes the bound of its check of a negative cycle (the sum of the n - 1 most negative costs) again only after a change of the costs or of the arcs, rather than at each solve -
batch-lruns one seed of its three when$CIis set: the three are the same sweep with another random stream, while the whole of it takes 41 minutes on a machine of ours, which is more than a shared runner has to give a single test -
whoever links the module keeps it: the classes of a module register themselves in the factory from a static initialiser, and a linker that drops what looks unused takes the registration away with it, so the target now tells whoever links it to keep the symbol that forces the module in, and on ELF, where naming the symbol is not enough, the library as a whole
-
the makefile of the test asks for
-O3 -DNDEBUGand nothing else, the macro of the patch forboost::anyon macOS having no reason to be there since there is noboost::anyleft in the core
-
MCFClassSolver_run_dmx2nc4and the testrun_dmx2nc4of the MCFBlock suite of tests share aRESOURCE_LOCK: they build the same target, and underctest -jthey ran at the same time and wrote the same instances, leaving one of them empty, so thatbatch-lfailed on it -
the tables that map the parameters of
MCFSolver< SPTree >to those ofSPTreelackedintMaxThread,intEverykItanddblEveryTTm, so that the parameters after them went to the wrongSPTreeparameter (e.g.,dblAbsAccto none andkReoptread past the end of the table) -
MCFSolver<MCFSimplex>re-optimizing balanced the flow of the whole tree at each change of a single capacity or deficit and at each closed arc, rather than once before the next solve: after closing 1% of the arcs of an instance with 2^12 nodes, re-optimizing took about 8 times the instructions of a solve from scratch, and it now takes about 0.6 times -
MCFSolver<MCFSimplex>with the Dual Simplex re-optimizing after a change of the costs put the arcs out of the tree at the bound that their reduced cost under the potentials of before the change says, the potentials not being recomputed; and switching from the Primal to the Dual Simplex (or back) with an instance loaded deleted the modified balances that both use -
MCFSimplex::ChgDfcts()(MCFClass) bounded the range of the nodes by the number of arcs, and hence with the default range it wrote past the vector of the nodes (a caller ofMCFSolveris not affected, since it always passes the range) -
MCFSolver<*>::int_par_str2idx()looked a name that is not a parameter of the :MCFClass up among the double parameters of theCDASolver, so that an int parameter such asintMaxItergiven by name in aComputeConfigwas not found -
MCFSolver<MCFCplex>ignoredkReopt, the network simplex of CPLEX starting from the basis of the last solve in any case: withkReoptoff it now starts from the initial basis -
MCFSolver<SPTree>with a label-setting queue (Dijkstra or the heap, the default) stopped at the destinations also when there are negative costs, whose labels are not final then, giving wrong ones and missing negative cycles; with negative costs it now goes on until the queue is empty, as a label-correcting algorithm does, and stops as before otherwise -
MCFSolver<SPTree>found a directed cycle of negative cost only if the queue of SPTree is a FIFO one: a node scanned more than n times is the hint of a cycle, but with any other queue the predecessors need not close one then; a label below the sum of the n - 1 most negative arc costs, the cost of the most negative simple path, now proves one whatever the queue, the count of the scans staying as the early hint -
MCFSolver<MCFSimplex>withkReoptcould give a flow outside the bounds after a change of the capacities or of the deficits: the primal simplex of MCFClass, balancing the tree before re-optimizing, followed the thread while moving subtrees under its dummy root, and skipped the nodes the move took away; MCFClass now collects the sons of a node first, and on 5 instances times 5 seeds of 40 rounds of changes the runs that failed go from up to 11 in 25 to none -
on macOS a program linking the module lost the classes the module registers in the factories when the linker dropped the library, as it does under
-dead_strip_dylibs, which conda sets: the target now asks the linker for the symbol that forces the module in (-u), which ld64, unlike the ELF linker, counts as a use of the library -
SPTreeof the MCFClass submodule cycled for ever on a directed cycle of negative cost, which is what makes the instance unbounded: it stops on it and gives it as the certificate -
RelaxIVof the MCFClass submodule raised its finite stand-in for an infinite capacity only atLoadNet(), so a laterChgDfcts()asking for more flow than that could make it declare a feasible instance unfeasible, andChgDfcts()on a range of nodes wrote the node before the range -
SPTree::MCFArcs()of the same submodule gave the arcs of the graph mismatched with their names, reading the dictionary of the Forward Star the wrong way round
0.2.0 - 2026-09-12
MCFSolver::get_Solution(), which builds the MCFSolution out of the data structures of the MCFClass solver, without writing anything into the MCFBlock and therefore without requiring any Variable to exist
- the version of the module is the git tag of its repository, or the VERSION.txt of a release tarball, and the shared library carries it: its SONAME is major.minor while the major is 0, and it is installed with an RPATH relative to itself, so that an installed tree keeps working wherever it is moved