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.singleflowdcrblockwhenSMSppMODis not empty (e.g.,make SMSppMOD=1), with clang++ 16 or later [seelib/makefile-modof SMS++] -
SingleFlowDCRBendersSolverreuses its cuts across the calls ofcompute()withintReuseCuts1 (0 by default): a call starts from the cuts of an earlier one when the instance is the same save for capacities and speeds that are not larger and costs that are not smaller, which is what makes them valid, as across the iterations of a Lagrangian method whose costs are the original ones plus nonnegative multipliers; the cuts can also be taken out and offered explicitly [seeget_cut_memory()andseed_cuts(), andBenBound::getCutMemory()andBenBound::seedCuts()] -
with
intWarmLambda1 (0 by default) the search for the multiplier ofDCRLagrangianSolverstarts from the critical pairs of the points solved earlier, re-evaluated with the present costs, instead of from 1, and the delay-feasible path found while doubling it is kept as the heuristic value [seeDCRLagrangianSolver::setWarmStart()] -
the C++20 module
smspp.singleflowdcrblock(src/SingleFlowDCRBlock.cppm), which exports every name that the headers declare at namespace scope and re-exportssmspp, so that a source file can writeimport smspp.singleflowdcrblock;in place of the#includeof the headers; it is built with the CMake optionSMSpp_BUILD_MODULE, as the targetSMS++::SingleFlowDCRBlock_module
-
the Modification of the module say what they change to the constructor of
Modification, sincechanges()is no longer virtual and returns that value, which whoever forwards them can refine withset_changes() -
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++] -
FACTOR, the oversubscription factor with whichMultiFlowDCRBlock::load()computes the mutual capacity of an arc out of the individual ones of a textual instance, is 1 (formerly 0.8), so that the mutual capacity is the sum of the individual ones; the multi-flow instances ofdata/are written anew with it, as the data version 2026-10-18, the single-flow ones being the same as before -
the instances of
data/are downloaded and extracted by the targetsdownload_dcr_<fmt>andextract_dcr_<fmt>, written as in every module that keeps its instances in the Package Registry, and the marker of the extraction carries the format in its name, so that a tree extracted before extracts once more
-
SingleFlowDCRBlockModsays again what it changes to the constructor ofModification: a merge had brought back itschanges(), which overrides nothing sinceModification::changes()is no longer virtual, and the module did not compile -
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++
0.2.0 - 2026-10-09
-
a tester of the module in
test/, needing nothing but the core SMS++ and run byctest -L SingleFlowDCRBlock, which on tiny graphs built in memory comparesSPT, both heuristics ofDCR_SPTand the two bounds ofDCRLagrangianSolverwith the enumeration of the paths, and checks the feasibility of given solutions, the netCDF round trip, the feasibility of the instance and the Modification issued by the changes of theSingleFlowDCRBlock, the edge cases (source equal to sink, no path, every path over the deadline, the deadline met with equality, ties, zero-cost, parallel and closed arcs) each on its own -
the instances of the module, as netCDF files downloaded from the Package Registry into
data/nc4[seedata/README.md], out of 307 networks in four sets (garr, sndlib, topo and waxman): 3070SingleFlowDCRBlock, the first 10 flows of each network on its own, and 1535MultiFlowDCRBlock, each network with its first 1 to 5 flows, which is what a formulation holding all of them solves exactly; the textual instances they are written from are intxt.tgzunder the same version, anddata/make-nc4rebuilds the former out of the latter -
tools/dcr2nc4, which writes aMultiFlowDCRBlockout of the multi-file textual format thatload()reads, or out of the raw one of the data sets it comes from, possibly keeping its first flows only, or with-stheSingleFlowDCRBlockof one of its flows, each arc with its own capacity and cost, working in a temporary directory, sinceload()writes its own files in the current one
-
MultiFlowDCRBlock::is_feasible()accepts the relative violationBlock::DefaultFeasTolof the core when no Configuration gives a tolerance, instead of none -
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
-
chg_costs(),chg_ucaps()andchg_dfcts()take their data as astd::span< const double >, whose length they check against the Range or the Subset instead of reading past the end, and the first two are registered in the methods factory in that form too; the forms taking an iterator stay, and defer to the span ones
-
a flow ColVariable fixed at a value other than 0 makes the SingleFlowDCRBlock throw, as documented, instead of closing the arc as if it were fixed at 0
-
SingleFlowDCRBlock::delay_feasible(),link_feasible()andmin_rate()take an arc as used when its x exceeds the tolerance of the check (1e-6 at least,used_threshold()), and check an unused one at x = 0: a solution of Gurobi leaves x of the order of 1e-6 on unused arcs, inside its integrality tolerance, which made r_min that small and the burst delay huge (topo/Heanet_9 ofbatch-instances), or violated rho x <= r, which the indicator constraints of the model read at x = 0 (topo/Shentel_4) -
load()read m (or n) entries of the capacities and of the delays when they were given empty, which its documentation allows and which means all infinite, or all 0: it looks at them only when they are there -
chg_ucap()did nothing on aSingleFlowDCRBlockwhose capacities are all infinite, the test on the new capacity being the other way round -
deserialize()over aSingleFlowDCRBlockthat had link delays kept them when the file has none, since they were not cleared with the rest -
is_feasible_instance()counted the closed arcs as usable: they are given an infinite cost, whichDCR_SPTnow reads as closed -
DCR_SPTrouted through the arcs closed withDCRcloseArcs(), whose infinite cost ERA-I could even return as the objective: both heuristics skip them, and ERA-H accepts a path that meets the deadline with equality, as ERA-I does, rather than only one that meets it strictly -
DCRLagrangianSolvercould not route through an arc of zero cost at lambda = 0, where its rate came out as sqrt( 0 / 0 ), a NaN the shortest path never takes, so that an instance whose paths all use one was found infeasible: an arc of zero cost reserves its capacity, as one of negative cost does; and after the firstLoadProblem()the heuristic value started at 0 instead of +Inf, so thatgetHeurVal()returned 0 whenever the delay constraint binds -
dcr2nc4did not build with MSVC, which has nomkdtemp(): the temporary directory of a run is made withstd::filesystem::create_directory()on a random name, tried again while the name is already there -
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 -
is_sol_feasible()declared infeasible a routing where an arc the flow does not use reserves a rate of the order of the tolerance of the Solver: r <= U x was checked against a tolerance relative to U x, i.e., an absolute one when x = 0, and it is now relative to U, the size of the terms of the constraint -
a
MultiFlowDCRBlockcould not be read back from the netCDF file it wrote:serialize()wrote the topology and the costs alone, leaving out the data of the flows, anddeserialize()built noSingleFlowDCRBlockand kept the previous instance, so that generating the abstract representation of what it read crashed. The file now holds the mutual capacities, the data of each flow and theSingleFlowDCRBlockof each flow in a group of its own, whichdeserialize()rebuilds; the matrices of the costs, of the capacities and of the deficits of an earlier file are still read, and no longer written, the costs being in the flows -
map_forward_Modification()passed a change of the deficits on to the otherSingleFlowDCRBlockas a change of its capacities, and, when this one had no capacities or no deficits, read them out of the empty vector rather than passing on the infinite capacities and the zero deficits it has -
BenBound.cppno longer includesunistd.h, which it does not use and which MSVC does not have
0.1.1 - 2026-09-13
is_sol_feasible()reads the DCRSolution, and the Variable are left alone
0.1.0 - 2026-09-12
-
the standard SMS++ module build system: the CMake project (with the package configuration files and the version derived from the git tag), the CI for both GitLab and GitHub, and the
makefile/makefile-c/makefile-striple of every other module -
SingleFlowDCRBlock::is_sol_feasible(), which checks the solution held by aDCRSolutionwithout touching theBlock, i.e., without requiring its abstract representation to exist at all -
the accessors and the setters of
DCRSolution, so that aSolvercan fill it directly out of its own data structures -
SingleFlowDCRBendersSolver::get_Solution(), which does exactly that: theDCRSolutionis filled out of what BenBound found, and theVariableof theBlockare not written at all -
SingleFlowDCRBlock::link_feasible()anddelay_feasible(), the two halves of the feasibility check that were nowhere to be found: the constraints tying the reserved rates to the routing decisions, and the end-to-end delay constraint
-
SingleFlowDCRBlock::is_feasible()checks all the constraints of the problem, the bounds of the variables and the two cone constraints comprised, and its "physical" branch reads the solution out of theVariableand checks it against the data, where it used to do nothing -
each of the four feasibility checks has a version taking the solution to be checked from the outside, which is what the "physical" version and
is_sol_feasible()both boil down to -
SingleFlowDCRBendersSolver::get_var_solution()also writes the two "aggregate" variablesr_minandtheta_min, which it used to compute and drop;is_DCR_feasible()asksSingleFlowDCRBlock::delay_feasible()rather than repeating the network calculus formula, and the data of theBlockare translated for BenBound in one place instead of two -
SingleFlowDCRBendersSolver::has_var_solution()says no unless the point BenBound stopped at is feasible for the DCR problem, which is now checked in full (the routing, the rates and the delay) and with a tolerance of 1e-6 rather than the 1e-2 of the delay alone;get_ub()accordingly returns +INF where there is no feasible solution, in place of the (possibly optimistic) relaxed value ofBenBound::getHeurVal(): neither that value nor the value of an infeasible point bounds the optimum -
SingleFlowDCRBendersSolver::get_var_value()computes the value of the solution BenBound found rather than of whatever theVariableof theBlockhappen to hold, which is what anybody could have written there -
MultiFlowDCRBlock::is_feasible()asks each commodity sub-Blockabout the commodity it describes, where it only checked the mutual capacity constraints of its own -
which of the two formulations is in use is asked for in the
Configurationof the static Constraint of theBlockConfig, the one of the staticVariablebeing still honoured as it used to be; the two methods that ask no longer disagree on the default, which is the SOCP formulation for both -
SingleFlowDCRBlock::load()takes the starting nodes before the ending ones, as its own documentation says -
get_NodeDelays()andget_LinkDelays()return a reference, as every other datum of theBlockdoes -
the timer of the DCR solvers is the
std::chrono-basedDCRtimer, which builds everywhere, in place of the POSIX-onlyOPTtimers -
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
-
the local copies of
OPTUtils.h,OPTtypes.handOPTvect.h, some 3700 lines of somebody else's code that the module carried around -
SingleFlowDCRBlock::is_feasible_flow(), which did exactly the same checks asis_feasible()under a name saying otherwise
-
the aggregate cone constraint was added to the abstract representation even when the "P/C" formulation was asked for, so that formulation was not linear at all and a
:MILPSolverthat does not do conic constraints could not solve it -
the debug output that the solvers wrote on
std::coutat every iteration, and theexit( 1 )calls with which they killed the whole process where they should throw -
is_feasible_instance()read the node deficits, the arc capacities and the delays out of vectors that are allowed to be empty, and left the source and the sink uninitialised when the instance had none -
DCR_SPT::DCRgetLink()fell off the end of the function when the arc was not there, andDCRLagrangianSolvercompared adoublethrough the integerabs() -
BenBoundmoved a trial value ofr_minthat it had already solved for by shrinking it multiplicatively, which walks past the left endpoint of the window where anr_mincan be feasible at all: it could therefore reserve less than the sustained rate of the flow, and hand back a point that is not a solution together with a value below the optimum. The point is now moved towards that endpoint, and stops there