From a338117bf0df5ae349df6dbb4048f06bd467770b Mon Sep 17 00:00:00 2001
From: Clara Witte <clara.witte@avt.rwth-aachen.de>
Date: Tue, 23 Jul 2024 13:57:13 +0200
Subject: [PATCH] Release version v0.8.0

---
 .gitignore                                    |    2 +
 .gitlab-ci.yml                                |    7 +-
 CMakeLists.txt                                |   18 +
 ReleaseNotes.txt                              |    6 +
 cmake/MAiNGOversion.cmake                     |    2 +-
 dep/babbase                                   |    2 +-
 dep/googletest                                |    2 +-
 dep/libale                                    |    2 +-
 dep/mcpp                                      |    2 +-
 dep/pybind11                                  |    2 +-
 doc/Doxyfile                                  |    2 +-
 doc/manual.dox                                |   93 +-
 examples/08_TwoStage/CHP_data.txt             |   14 +
 examples/08_TwoStage/CHP_sizing.h             |  272 ++
 examples/08_TwoStage/CHP_sizing.py            |  995 +++++++
 examples/08_TwoStage/CHP_sizing8.png          |  Bin 0 -> 94003 bytes
 examples/08_TwoStage/README                   |   59 +
 examples/08_TwoStage/get_demands.py           |   31 +
 examples/08_TwoStage/structure.png            |  Bin 0 -> 21394 bytes
 examples/MAiNGOSettings.txt                   |   31 +-
 examples/mainCppApi.cpp                       |   21 +-
 inc/MAiNGO.h                                  |   44 +-
 inc/TwoStageModel.h                           |  349 +++
 inc/bab.h                                     |  665 +++--
 inc/instrumentor.h                            |    7 +
 inc/lbp.h                                     |  952 +++----
 inc/lbpClp.h                                  |    2 +-
 inc/lbpCplex.h                                |    7 +-
 inc/lbpDagObj.h                               |   38 +-
 inc/lbpInterval.h                             |    2 +-
 inc/lbpTwoStage.h                             | 2366 +++++++++++++++++
 inc/logger.h                                  |    2 +-
 inc/mpiUtilities.h                            |   55 +-
 inc/settings.h                                |   75 +-
 inc/siblingResults.h                          |  434 +++
 inc/ubp.h                                     |   14 +-
 inc/ubpDagObj.h                               |   14 +-
 inc/ubpLazyQuadExpr.h                         |    6 +-
 inc/ubpTwoStage.h                             |  485 ++++
 inc/variableLister.h                          |    2 +-
 maingopy/__init__maingopy_and_melonpy.py.in   |  137 +-
 maingopy/__init__only_maingopy.py.in          |  118 +-
 maingopy/_maingopy.cpp                        |  186 +-
 .../tests/individualPythonTests/testSolver.py |   14 +
 src/MAiNGO.cpp                                |  510 ++--
 src/MAiNGOgetOption.cpp                       |   16 +-
 src/MAiNGOprintingFunctions.cpp               |   41 +-
 src/MAiNGOsetOption.cpp                       |  238 +-
 src/MAiNGOwritingFunctions.cpp                |   10 +-
 src/bab.cpp                                   | 1808 ++++++++++---
 src/babMpi.cpp                                |  153 +-
 src/lbp.cpp                                   |  170 +-
 src/lbpClp.cpp                                |    4 +-
 src/lbpCplex.cpp                              |   15 +-
 src/lbpDagObj.cpp                             |  146 +-
 src/lbpFactory.cpp                            |   17 +-
 src/lbpInterval.cpp                           |    9 +-
 src/lbpLinearizationStrats.cpp                |   12 +-
 src/logger.cpp                                |    3 +-
 src/ubp.cpp                                   |   13 +-
 src/ubpFactory.cpp                            |   24 +-
 tests/testProblems/main.cpp                   |   65 +-
 tests/testProblems/problem_CHP_sizing_Ns1.h   |  212 ++
 tests/testProblems/problem_case3_wnet.h       |    2 +-
 .../problem_growingDatasets_AVM.h             |    2 +-
 .../problem_growingDatasets_simple.h          |    2 +-
 tests/testProblems/problem_twoStageIP.h       |  116 +
 tests/unitTests/testMAiNGOException.cpp       |    4 +-
 tests/unitTests/testMAiNGOsetAndGetOption.cpp |  202 +-
 .../unitTests/testPointIsWithinNodeBounds.cpp |    2 +-
 70 files changed, 9784 insertions(+), 1549 deletions(-)
 create mode 100644 examples/08_TwoStage/CHP_data.txt
 create mode 100644 examples/08_TwoStage/CHP_sizing.h
 create mode 100644 examples/08_TwoStage/CHP_sizing.py
 create mode 100644 examples/08_TwoStage/CHP_sizing8.png
 create mode 100644 examples/08_TwoStage/README
 create mode 100644 examples/08_TwoStage/get_demands.py
 create mode 100644 examples/08_TwoStage/structure.png
 create mode 100644 inc/TwoStageModel.h
 create mode 100644 inc/instrumentor.h
 create mode 100644 inc/lbpTwoStage.h
 create mode 100644 inc/siblingResults.h
 create mode 100644 inc/ubpTwoStage.h
 create mode 100644 tests/testProblems/problem_CHP_sizing_Ns1.h
 create mode 100644 tests/testProblems/problem_twoStageIP.h

diff --git a/.gitignore b/.gitignore
index 4a7e4d2..314b5d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ _skbuild/
 *.egg-info/
 dist/
 MANIFEST
+*__pycache__*
+.vscode
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index de7611c..067e6c1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,11 +18,12 @@ build_windows:
         - mkdir build
         - cd build
         - '$env:path += ";C:\Program Files\cmake\bin"'
+        - $env:PYTHON_EXECUTABLE = $(python -c "import sys; print(sys.executable)")
         - cmake .. -G "Visual Studio 15 2017" -A x64 -D MAiNGO_use_mpi=FALSE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=TRUE -D MAiNGO_build_shared_c_api=TRUE -D MAiNGO_use_knitro=FALSE
         - cmake --build . --config Release -j4
         - move Release/maingo-test-problems.exe Release/maingo-test-problems-sequential.exe
         - move Release/unit-test-maingo.exe Release/unit-test-maingo-sequential.exe
-        - cmake .. -G "Visual Studio 15 2017" -A x64 -D MAiNGO_use_mpi=TRUE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=FALSE -D MAiNGO_build_parser=FALSE -D MAiNGO_build_shared_c_api=FALSE -D MAiNGO_use_melon=FALSE -D MAiNGO_use_knitro=FALSE
+        - cmake .. -G "Visual Studio 15 2017" -A x64 -D MAiNGO_use_mpi=TRUE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=FALSE -D MAiNGO_build_parser=FALSE -D MAiNGO_build_shared_c_api=FALSE -D MAiNGO_use_melon=FALSE
         - cmake --build . --config Release -j4
         - move Release/maingo-test-problems.exe Release/maingo-test-problems-parallel.exe
         - cd ..
@@ -66,11 +67,11 @@ build_linux:
     script:
         - mkdir build
         - cd build
-        - cmake -S .. -D MAiNGO_use_mpi=FALSE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=TRUE -D MAiNGO_build_shared_c_api=TRUE -D MAiNGO_use_knitro=FALSE
+        - cmake -S .. -D MAiNGO_use_mpi=FALSE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=TRUE -D MAiNGO_build_parser=TRUE -D MAiNGO_build_shared_c_api=TRUE
         - make -j4
         - mv maingo-test-problems maingo-test-problems-sequential
         - mv unit-test-maingo unit-test-maingo-sequential
-        - cmake -S .. -D MAiNGO_use_mpi=TRUE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=FALSE -D MAiNGO_build_parser=FALSE -D MAiNGO_build_shared_c_api=FALSE -D MAiNGO_use_melon=FALSE -D MAiNGO_use_knitro=FALSE
+        - cmake -S .. -D MAiNGO_use_mpi=TRUE -D MAiNGO_build_test=TRUE -D MAiNGO_build_python_interface=FALSE -D MAiNGO_build_parser=FALSE -D MAiNGO_build_shared_c_api=FALSE -D MAiNGO_use_melon=FALSE
         - make -j4
         - mv maingo-test-problems maingo-test-problems-parallel
         - cd ..
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0991349..59cca5c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,6 +13,7 @@ set(MAiNGO_use_cplex TRUE CACHE BOOL "Use CPLEX if it is available on the system
 set(MAiNGO_use_knitro TRUE CACHE BOOL "Use KNITRO if it is available on the system.")
 set(MAiNGO_use_mpi FALSE CACHE BOOL "Build parallel version of MAiNGO.")
 set(MAiNGO_use_growing_datasets FALSE CACHE BOOL "Build MAiNGO with growing datasets for solving parameter estimation problems.")
+set(MAiNGO_use_openmp FALSE CACHE BOOL "Use OpenMP for parallelelization of embarassingly parallel loops.")
 set(MAiNGO_build_python_interface FALSE CACHE BOOL "Build the Python package 'maingopy' that allows to call MAiNGO from Python.")
 set(MAiNGO_build_standalone FALSE CACHE BOOL "Build MAiNGOcpp executable as standalone solver with problem.h.")
 set(MAiNGO_build_shared_c_api FALSE CACHE BOOL "Build library version of MAiNGO with parser callable from C.")
@@ -119,6 +120,7 @@ if(MAiNGO_use_mpi)
     target_compile_definitions(maingo-core PUBLIC HAVE_MAiNGO_MPI) # Define pre-processor variable HAVE_MAiNGO_MPI
     target_link_libraries(maingo-core PUBLIC MPI::MPI_CXX)
 endif()
+
 if(MAiNGO_use_growing_datasets)
     message("Growing datasets will be used. See documentation for changes in model setup.")
     target_compile_definitions(maingo-core PUBLIC HAVE_GROWING_DATASETS) # Define pre-processor variable HAVE_GROWING_DATASETS
@@ -137,6 +139,22 @@ if(MAiNGO_use_gcov)
                        )
 endif()
 
+if(MAiNGO_use_openmp)
+    if(APPLE)
+        if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
+            # AppleClang Version 15.0.0 may be required!
+            # AppleClang does not support OpenMP out of the box.
+            # We tested with the libomp, provided by brew.
+            set(OpenMP_CXX "${CMAKE_CXX_COMPILER}" CACHE STRING "" FORCE)
+            set(OpenMP_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xpreprocessor -fopenmp")
+            set(OpenMP_CXX_LIB_NAMES "omp")
+            set(OpenMP_omp_LIBRARY omp)
+        endif()
+    endif()
+    find_package(OpenMP REQUIRED QUIET)
+    message("Found OpenMP. Will parallelize embarrassingly parrallel for loops.")
+    target_link_libraries(maingo-core PUBLIC OpenMP::OpenMP_CXX)
+endif()
 
 #----------------- If this is the top level, include dependencies  --------------------
 if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index 29fca04..51ea5fc 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -1,3 +1,9 @@
+Release version 0.8.0 (July 23th, 2024):
+    - New features & extensions: 
+	- New algorithm for specialized B&B algorithm for Two-Stage Stochastic Programming problems, see documentation under Two-Stage Stochastic Programming
+	- Extension for handling parameter estimation problems considering large datasets
+    - Small bugfixes
+
 Release version 0.7.4 (April 16th, 2024):
     - Bugfixes:
         - Update MathJax to version 3.0 to see latex formulas in the documentation on GitLab Pages
diff --git a/cmake/MAiNGOversion.cmake b/cmake/MAiNGOversion.cmake
index 391a4be..37b4674 100644
--- a/cmake/MAiNGOversion.cmake
+++ b/cmake/MAiNGOversion.cmake
@@ -1,3 +1,3 @@
 set(MAiNGO_VERSION
-0.7.4
+0.8.0
 )
diff --git a/dep/babbase b/dep/babbase
index 180a0ba..cab8c0e 160000
--- a/dep/babbase
+++ b/dep/babbase
@@ -1 +1 @@
-Subproject commit 180a0baf10b1d0443945c667d8f19de3e72bd43b
+Subproject commit cab8c0e50f0b033102031268206574f01cadc5cd
diff --git a/dep/googletest b/dep/googletest
index 53495a2..31993df 160000
--- a/dep/googletest
+++ b/dep/googletest
@@ -1 +1 @@
-Subproject commit 53495a2a7d6ba7e0691a7f3602e9a5324bba6e45
+Subproject commit 31993dfa6b47e11c7a6ef67cfa8af90892b9bd1c
diff --git a/dep/libale b/dep/libale
index 556283a..dfc0905 160000
--- a/dep/libale
+++ b/dep/libale
@@ -1 +1 @@
-Subproject commit 556283a03859824a6a826b793779bff50700cb66
+Subproject commit dfc09051539406ddd1f598c286ef045b96a457b5
diff --git a/dep/mcpp b/dep/mcpp
index fb61bbb..af99fd6 160000
--- a/dep/mcpp
+++ b/dep/mcpp
@@ -1 +1 @@
-Subproject commit fb61bbb8733b23a0b48cb86f016429594ee062c5
+Subproject commit af99fd6be1b711f7d1e1ab0c71b07bf56f590e72
diff --git a/dep/pybind11 b/dep/pybind11
index 19a6b9f..aa98d95 160000
--- a/dep/pybind11
+++ b/dep/pybind11
@@ -1 +1 @@
-Subproject commit 19a6b9f4efb569129c878b7f8db09132248fbaa1
+Subproject commit aa98d95717d1365b5c32d3942abd0292fbd19021
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 3bed689..e52addf 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -1783,7 +1783,7 @@ PAPER_TYPE             = a4
 # If left blank no extra packages will be included.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
-EXTRA_PACKAGES         =
+EXTRA_PACKAGES         = {amsmath} {amssymb} {bm}
 
 # The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
 # generated LaTeX document. The header should contain everything until the first
diff --git a/doc/manual.dox b/doc/manual.dox
index 9d00bff..7e6ab38 100644
--- a/doc/manual.dox
+++ b/doc/manual.dox
@@ -1896,8 +1896,9 @@ In this case you can reset it via <tt>\<mpiexec\> -remove</tt>.
 
 MAiNGO implements the extension for handling parameter estimation problems considering large datasets proposed in
 
-S. Sass, A. Mitsos, D. Bongartz, I. H. Bell, N. I. Nikolov, A. Tsoukalas: A branch-and-bound algorithm with growing datasets for large-scale parameter estimation 
-(<a href="https://doi.org/10.1016/j.ejor.2024.02.020">Sass et al. 2024</a>, <a href="https://doi.org/10.1080/10556788.2023.2205645">Sass et al. 2023</a>) .
+S. Sass, A. Mitsos, D. Bongartz, I. H. Bell, N. I. Nikolov, A. Tsoukalas (2024): (<a href="https://doi.org/10.1016/j.ejor.2024.02.020">A branch-and-bound algorithm with growing datasets for large-scale parameter estimation</a>), <b>European Journal of Operational Research</b>, 316(1), 36-35
+
+Sass, S., Mitsos, A., Nikolov, N. I., & Tsoukalas, A. (Submitted 2024):  Out-of-sample estimation for a branch-and-bound algorithm with growing datasets.
 
 This approach focuses on general nonconvex optimization problems of the form
 \htmlonly <style>div.image img[src="ParamEstProblem.PNG"]{width:6cm;align:left}</style> \endhtmlonly
@@ -1928,8 +1929,92 @@ It may look like this:
 |  Done.
 \endcode
 
-Additional settings allow to adapt this extension to the needs of the specific model solved, cf. settings with prefix <tt>growing</tt> in <tt>MAiNGOSettings.txt</tt>. These include, e.g., the choice when and how many data points to augment as well as the size of the initial dataset. For more details please refer to Sass et al. (Submitted in 2023) referenced above. 
-
+Additional settings allow to adapt this extension to the needs of the specific model solved, cf. settings with prefix <tt>growing</tt> in <tt>MAiNGOSettings.txt</tt>.
+These include, e.g., the choice when and how many data points to augment as well as the size of the initial dataset.
+For more details please refer to (<a href="https://doi.org/10.1016/j.ejor.2024.02.020">Sass et al. (2024)</a>) and Sass et al. (Submitted 2024) referenced above.
+
+@section two_stage MAiNGO - Two-Stage Stochastic Programming
+
+MAiNGO provides a specialized B&B algorithm for Two-Stage Stochastic Programming problems.
+Problems of the form
+\f{align*}{
+        \underset{\substack{\boldsymbol{x} \in \mathcal{X} \\ \boldsymbol{y} \in \mathcal{Y}}}{\min} \;
+                & f_\mathrm{I}(\boldsymbol{x}) + \sum_{s \in \mathcal{S}} w_s f_{\mathrm{II}}(\boldsymbol{x}, \boldsymbol{y}_s, \boldsymbol{p}_s) \\
+            \text{s.t:} \;
+                & \boldsymbol{g}_{\mathrm{I}}(\boldsymbol{x}) \leq \boldsymbol{0} \\
+            & \boldsymbol{g}_{\mathrm{II}}(\boldsymbol{x},\boldsymbol{y}_s, \boldsymbol{p}_s) \leq \boldsymbol{0} \quad \forall s \in \mathcal{S}
+\f}
+where \f$\mathcal{X} \subsetneq \mathbb{R}^{N_x}\f$, \f$\mathcal{Y}_s \subsetneq \mathbb{R}^{N_y}\f$, for each \f$s\in\mathcal{S}\f$, and \f$\mathcal{Y} \subsetneq \mathbb{R}^{N_s N_y}\f$, 
+such that \f$\mathcal{X}\f$ and \f$\mathcal{Y} = \times_{s \in \mathcal{S}} \mathcal{Y}_s\f$, are bounded hyperrectangles, and thus compact sets.
+The set of considered scenarios \f$\mathcal{S}\f$ is assumed to have finite cardinality \f$N_s = |\mathcal{S}| \geq 1\f$, and each element \f$s \in \mathcal{S}\f$ is assigned a probability \f$w_s \in (0, 1], \sum_{s\in\mathcal{S}}{w_s} = 1\f$.
+The vectors \f$\boldsymbol{x}\f$ and \f$\boldsymbol{y}_s\f$ capture the first-, and second-stage decisions.
+Note that above we used \f$\boldsymbol{y} = (\boldsymbol{y}_1, \cdots, \boldsymbol{y}_{\mathrm{N}_s}) = (y_{1,1}, \cdots, y_{1,\mathrm{N}_y}, \cdots, y_{\mathrm{N}_s,\mathrm{N}_y})\f$ for conciseness.
+
+The first-stage objective and constraints \f$f_\mathrm{I}\f$, and \f$\boldsymbol{g}_\mathrm{I}\f$ are functions of \f$\boldsymbol{x}\f$ only and the second-stage objective and constraints \f$f_\mathrm{II}\f$, and \f$\boldsymbol{g}_\mathrm{II}\f$ are functions of both \f$\boldsymbol{x}\f$ and \f$\boldsymbol{y}_s\f$, as well as the parameters \f$\boldsymbol{p}_s\f$
+(Note that in the above representation all constraints are assumed to be inequality constraints for ease of exposition). 
+
+
+Problems of this type can be specified via the \ref maingo::TwoStageModel class which is a specialization of the standard \ref maingo::MAiNGOmodel.
+Initializing a \ref maingo::TwoStageModel requires:
+
+- the number of first-stage variables \f$N_x\f$,
+- the number of second-stage variables per scenario \f$N_y\f$ (Note that currently we only handle problems with equal numbers of variables per scenario),
+- the vector of scenario weights \f$ \boldsymbol{w} \f$ (which must sum to 1),
+- the \ref maingo::TwoStageModel::data consisting of parameter vectors \f$\boldsymbol{p}_s\f$, used to parametrize second-stage objective and constraint functions.
+
+A \ref maingo::TwoStageModel instance requires the user to override the functions
+
+- \ref maingo::TwoStageModel::f1_func,
+  taking the vector of first stage variables `x`
+  and
+  returning the first-stage objective expression,
+- \ref maingo::TwoStageModel::f2_func,
+  taking vectors of first- and second-stage variables `x`, and `y_s`, as well as parameters `p_s`,
+  and
+  returning the objective expression for sceanrio `s`
+- \ref maingo::TwoStageModel::g1_func,
+  taking the vector of first stage variables `x`
+  and
+  returning a vector of vectors of named first-stage constraint expressions (`std::pair<Var, std::string>`) in the order:
+
+  -# inequalities,
+  -# squash inequalities,
+  -# equalities,
+  -# relaxation-only inequalities,
+    and
+  -# relaxation-only equalities
+
+- \ref maingo::TwoStageModel::g2_func,
+  taking vectors of first- and second-stage variables `x`, and `y_s`, as well as parameters `p_s`,
+  and
+  returning a vector of vectors of named second-stage constraint expressions (std::pair<Var, std::string>) in the order:
+
+  -# inequalities,
+  -# squash inequalities,
+  -# equalities,
+  -# relaxation-only inequalities,
+    and
+  -# relaxation-only equalities
+
+In the python interface this class is available as maingopy.TwoStageModel.
+Also see the examples in `examples/08_TwoStage`.
+
+The resulting problems are solved via a specialized variant of the default branch-and-bound algorithm, which exploits the problem structure for efficiency.
+In particular, the algorithm bounds the optimal solution value from above and below by solving separate subproblems for each scenario.
+As part of this algorithm, second-stage variables to be branched are selected via a variant of strong-branching that makes use of these separate subproblems.
+Additionally, the independence of subproblems allows to branch on multiple second-stage variables simultaneously, and create a combinatorial number of nodes from the results from strong-branching.
+To exploit the parallelism induced by the decomposition algorithm we recommend building MAiNGO with the `USE_OPENMP` flag turned `ON`.
+
+Caveats: Currently, using the Option `LBP_addAuxiliaryVars` with two stage problems does not work.
+Neither do we allow for two stage models that have variables which do not appear in any objective or constraint expression.
+
+Options the behavior of the algorithm can be adapted by the options:
+
+- \ref maingo::Settings::TS_useLowerBoundingSubsolvers
+- \ref maingo::Settings::TS_useUpperBoundingSubsolvers
+- \ref maingo::Settings::TS_parallelOBBT
+- \ref maingo::Settings::TS_maxBranchingPower
+- \ref maingo::Settings::TS_strongBranchingThreshold
 
 @section maingo_multistart Using MAiNGO as a multi-start local solver:
 
diff --git a/examples/08_TwoStage/CHP_data.txt b/examples/08_TwoStage/CHP_data.txt
new file mode 100644
index 0000000..52b942c
--- /dev/null
+++ b/examples/08_TwoStage/CHP_data.txt
@@ -0,0 +1,14 @@
+# Numerical data for CHP sizing problem
+Qdot_nom_ref     1     # [MW], reference nominal power
+c_ref            1.3e6 # [€], reference cost
+M                0.9   # [-], cost exponent
+c_m              0.05  # [-], maintenance coefficient, (fraction of investment cost)
+Qdot_rel_min     0.5   # [-] minimum output part load
+Qdot_nom_min     0.5   # [MW]
+Qdot_nom_max     2.3   # [MW]
+n                30    # lifetime in years
+i                0.05  # annualization interst rate
+T_OP             6000  # [h] annual operating time
+p_gas            80    # [€ / MWh]
+p_el_buy         250   # [€ / MWh]
+p_el_sell        100   # [€ / MWh]
\ No newline at end of file
diff --git a/examples/08_TwoStage/CHP_sizing.h b/examples/08_TwoStage/CHP_sizing.h
new file mode 100644
index 0000000..e822d1b
--- /dev/null
+++ b/examples/08_TwoStage/CHP_sizing.h
@@ -0,0 +1,272 @@
+// A simplified example problem of CHP sizing for demonstrating two-stage structure
+// We need to size a heat-controlled CHP for the satisfaction of heat and power demands, expressed
+// through multiple scenarios with different probabilities.
+// First stage decisions are the nominal size and second stage decision is the relative load.
+// From the relative load the required fuel and thus the operational costs can be calculated.
+
+// Author Marco Langiu
+#include "TwoStageModel.h"
+#include "MAiNGO.h"
+
+#include <filesystem>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+
+/**
+ * @brief Uncertain data for the CHP sizing problem
+ * heat and electricity demands
+ */
+std::vector<std::vector<double>> example_data = {
+// Qdot_dem, P_dem
+  {0.000000, 1.951245},
+  {0.000000, 0.900772},
+  {0.000000, 1.853530},
+  {0.665121, 0.454477},
+  {0.965798, 1.645523},
+  {1.141710, 1.572129},
+  {1.160934, 0.877757},
+  {1.287897, 1.394736}
+};
+
+/**
+ * @brief Vertex form of a * x^2 + b * x + c
+ */
+Var vertex_form(const Var &x, double a, double b, double c) {
+    return c - pow(b, 2) / (4 * a) + a * pow(x + b / (2 * a), 2);
+}
+
+
+/**
+ * @brief Example for user defined TwoStageModel
+ */
+struct CHP_sizing_problem : maingo::TwoStageModel {
+
+  CHP_sizing_problem() : TwoStageModel(1, 1, example_data) {
+    std::cout << "CHP sizing model with " << example_data.size() << " scenarios" << std::endl;
+
+    std::map<std::string, double CHP_sizing_problem::*> members;
+    members["Qdot_nom_ref"] = &CHP_sizing_problem::Qdot_nom_ref;
+    members["c_ref"] = &CHP_sizing_problem::c_ref;
+    members["M"] = &CHP_sizing_problem::M;
+    members["c_m"] = &CHP_sizing_problem::c_m;
+    members["Qdot_rel_min"] = &CHP_sizing_problem::Qdot_rel_min;
+    members["Qdot_nom_min"] = &CHP_sizing_problem::Qdot_nom_min;
+    members["Qdot_nom_max"] = &CHP_sizing_problem::Qdot_nom_max;
+    members["n"] = &CHP_sizing_problem::n;
+    members["i"] = &CHP_sizing_problem::i;
+    members["T_OP"] = &CHP_sizing_problem::T_OP;
+    members["p_gas"] = &CHP_sizing_problem::p_gas;
+    members["p_el_buy"] = &CHP_sizing_problem::p_el_buy;
+    members["p_el_sell"] = &CHP_sizing_problem::p_el_sell;
+
+    std::ifstream file;
+    file.open("CHP_data.txt");
+    if (file.is_open()) {
+        std::string line;
+        std::string word;
+        double number;
+        bool firstLine = true;
+        while (std::getline(file, line)) {    // Read file line by line
+            if (firstLine) {
+                // Check for BOM in UTF 8, BOM is ALWAYS at the beginning of a file -- NOTE: We only correctly handle UTF 8 setting files!
+                if (line.length() >= 3) {
+                    if (line[0] == (char)0XEF && line[1] == (char)0XBB && line[2] == (char)0XBF) {
+                        line.erase(0, 3);
+                    }
+                }
+                firstLine = false;
+            }
+            // If the line is not a comment, proceed (\r is for carriage return)
+            if ((line.find_first_not_of(' ') != std::string::npos) && !line.empty() && line[0] != '#' && line[0] != '\r') {
+                std::istringstream iss(line);    // This allows access to line as real std::string
+                iss >> word;
+                iss >> number;
+                this->*(members[word]) = number;
+            }
+        }
+      std::cout << "Found CHP_data.txt file, using values:\n";
+    }
+    else {    // File not found
+      std::cout << "No CHP_data.txt file found, using default values:\n";
+    }
+    for (const auto & elem : members) {
+      std::cout << elem.first << " = " << this->*(elem.second) << std::endl;
+    }
+    std::cout << std::endl;
+    file.close();
+
+    Qdot_eps   = 0.001 * Qdot_nom_max;
+    Qdot_mean  = (Qdot_nom_min + Qdot_nom_max) / 2;
+    af = (pow(1 + i, n) * i) / (pow(1 + i, n) - 1);  // annuity factor
+  };
+
+  double Qdot_nom_ref = 1;     // [MW], reference nominal power
+  double c_ref        = 1.3e6; // [€], reference cost
+  double M            = 0.9;   // [-], cost exponent
+  double c_m          = 0.05;  // [-], maintenance coefficient, (fraction of investment cost)
+  double Qdot_rel_min = 0.5;   // [-] minimum output part load
+  double Qdot_nom_min = 0.5;   // [MW], minimum nominal power
+  double Qdot_nom_max = 2.3;   // [MW], maximum nominal power
+  double Qdot_eps;
+  double Qdot_mean;
+  
+  double n = 30;   // lifetime in years
+  double i = 0.05; // annualization interst rate
+  double af;       // annuity factor
+
+  double T_OP      = 6000; // [h / a]
+  double p_gas     = 80;   // [€ / Mwh]
+  double p_el_buy  = 250;  // [€ / Mwh]
+  double p_el_sell = 100;  // [€ / Mwh]
+
+  /**
+   * @brief Compute the nominal thermal efficiency of the CHP
+   */
+  Var eff_th_nom(const Var &Qdot_nom) {
+      return 0.498 - 3.55e-2 * Qdot_nom;
+  }
+
+  /**
+   * @brief Compute the nominal electrical efficiency of the CHP
+   */
+  Var eff_el_nom(const Var &Qdot_nom) {
+      return 0.372 + 3.55e-2 * Qdot_nom;
+  }
+
+  /**
+   * @brief Compute the relative thermal efficiency of the CHP
+   */
+  Var eff_th_rel(const Var &Qdot_rel) {
+      return vertex_form(Qdot_rel, -0.0768, -0.0199, 1.0960);
+  }
+
+  /**
+   * @brief Compute the relative electrical efficiency of the CHP
+   */
+  Var eff_el_rel(const Var &Qdot_rel) {
+      return vertex_form(Qdot_rel, -0.2611, 0.6743, 0.5868);
+  }
+
+  /**
+   * @brief Compute the thermal efficiency of the CHP
+   */
+  Var eff_th(const Var &Qdot_nom, const Var &Qdot_rel) {
+      return eff_th_nom(Qdot_nom) * eff_th_rel(Qdot_rel);
+  }
+
+  /**
+   * @brief Compute the electrical efficiency of the CHP
+   */
+  Var eff_el(const Var &Qdot_nom, const Var &Qdot_rel) {
+      return eff_el_nom(Qdot_nom) * eff_el_rel(Qdot_rel);
+  }
+
+  /**
+   * @brief Function for getting the (Nx + Ns * Ny) optimization variables
+   */
+  std::vector<maingo::OptimizationVariable> get_variables() {
+    std::vector<maingo::OptimizationVariable> variables;
+    // Using branching priority ratio of 16:1
+    std::string name = "Qdot_nom";
+    variables.push_back({{Qdot_nom_min, Qdot_nom_max}, 16, name});
+    for (auto s = 0; s < Ns; ++s) {
+      name = "Qdot_rel_" + std::to_string(s);
+      variables.push_back({{0, 1}, 1, name});
+    }
+
+    for (auto &var : variables) {
+      std::cout << "  " << var.get_name() << " " << var.get_branching_priority() << std::endl;
+    }
+    std::cout << std::endl;
+    return variables;
+  };
+
+  std::vector<double> get_initial_point() {
+      return {
+          // Optimal solution obtained from dense sampling with 1000 points in each variable domain
+          1.671171171171171,
+          1,
+          0.6346346346346347,
+          1,
+          0.5005005005005005,
+          1,
+          0.9669669669669669,
+          0.6946946946946947,
+          0.8838838838838838,
+        };
+  }
+
+  /**
+   * @brief Annualized investment cost in million euros/a
+   */
+  Var f1_func(Varview x) {
+    const Var & Qdot_nom = x[0];
+    // investment cost of the component
+    Var ic = std::move(c_ref * pow(Qdot_nom / Qdot_nom_ref, M));
+    // fixed cost of the component
+    Var fc = std::move(c_m * ic);
+
+    return 1e-6 * (af * ic + fc);
+  }
+
+  /**
+   * @brief Annual operating cost in million euros/a
+   */
+  Var f2_func(Varview x, Varview y, Valview p) {
+    auto Qdot_nom = x[0];
+    auto Qdot_rel     = y[0];
+    auto Qdot_dem     = p[0];
+    auto P_dem        = p[1];
+    
+    auto Qdot_out = Qdot_nom * Qdot_rel;
+
+    auto Edot_in = Qdot_out / eff_th(Qdot_nom, Qdot_rel);
+    auto P_out = Edot_in * eff_el(Qdot_nom, Qdot_rel);
+    
+    // When allowing an electric heater
+    // auto Qdot_supplied_via_electricity = max(0, Qdot_dem - Qdot_out);
+    // Otherwise
+    auto Qdot_supplied_via_electricity = 0;
+
+    auto P_grid = P_dem - P_out + Qdot_supplied_via_electricity;
+
+    // Total variable cost = purchase for gas
+    // + purchase for missing elextricity
+    // or compensation (negative cost) for selling excess electricity
+    return 1e-6 * (
+        p_gas * Edot_in
+        + p_el_buy * max(0, P_grid)
+        - p_el_sell * max(0, -P_grid)
+    ) * T_OP;
+  }
+
+  /**
+   * @brief The vector of second stage constraints
+   */
+  std::vector<std::vector<std::pair<Var, std::string>>> g2_func(Varview x, Varview y, Valview p) {
+    auto & Qdot_nom        = x[0];
+    auto & Qdot_rel        = y[0];
+    auto min_partload_viol = vertex_form(Qdot_rel, -1, Qdot_rel_min + Qdot_eps, -Qdot_rel_min * Qdot_eps);
+
+    // If not using heater
+    auto Qdot_out      = Qdot_nom * Qdot_rel;
+    auto & Qdot_dem    = p[0];
+    auto dem_violation = Qdot_dem - Qdot_out;
+
+    return {
+      {
+        {min_partload_viol, "Minimum part load violation"},
+        {dem_violation, "Heat demand satisfaction"},
+      },  // ineq
+      {},  // squash
+      {},  // eq
+      {},  // ineqRelOnly
+      {},  // eqRelOnly
+    };
+  }
+
+};
+
+using Model = CHP_sizing_problem;
\ No newline at end of file
diff --git a/examples/08_TwoStage/CHP_sizing.py b/examples/08_TwoStage/CHP_sizing.py
new file mode 100644
index 0000000..8a0e49e
--- /dev/null
+++ b/examples/08_TwoStage/CHP_sizing.py
@@ -0,0 +1,995 @@
+import os
+import sys
+
+import numpy as np
+
+import maingopy
+
+FEASIBLE_POINT = maingopy.FEASIBLE_POINT
+GLOBALLY_OPTIMAL = maingopy.GLOBALLY_OPTIMAL
+LANG_ALE = maingopy.LANG_ALE
+LANG_GAMS = maingopy.LANG_GAMS
+LANG_NONE = maingopy.LANG_NONE
+
+
+def read_options(filename):
+    import re
+    settings_pattern = re.compile(r"\s*(\w+)\s*(\S+)\s*(?:#\.*)?")
+    comment_pattern = re.compile(r"\s*#\.*")
+    options = {}
+    with open(filename, 'r') as settings_file:
+        for line in settings_file:
+            match = re.match(settings_pattern, line)
+            if match:
+                setting, val = match.groups()
+                try:
+                    options[setting] = float(val)
+                except ValueError:
+                    raise ValueError(f"Couldn't parse value '{val}' "
+                                     f"for option '{setting}' as float!")
+                continue
+            if not line.strip() or re.match(comment_pattern, line):
+                continue
+            raise ValueError(f"Incorrect line\n'{line.strip()}'\n"
+                             "in settings file '{filename}': "
+                             "Lines need to be empty, contain "
+                             "a comment, starting with '#', "
+                             "or be of the form '<setting> <value> "
+                             "#<comment>'!")
+    return options
+
+
+DEFAULT_SETTINGS_FILE_NAME = "MAiNGOSettings.txt"
+
+
+# heat and electricity demands
+DEFAULT_DATA = [
+    [1.1,  0.5],
+    [1.1,  2.5],
+    [1.1,  4],
+    [1.2,  0.5],
+    [1.2,  4],
+    [1.3,  0.5],
+    [1.3,  2.5],
+    [1.3,  4],
+]
+
+
+def vertex_form(x, a, b, c):
+    """Compute the vertex form of a * x**2 + b * x + c."""
+    return c - b ** 2 / (4 * a) + a * (x + b / (2 * a)) ** 2
+
+
+class CHP_sizing_problem(maingopy.TwoStageModel):
+    """Initialize a MAiNGO Model for the CHP sizing Problem.
+
+    Arguments
+    ---------
+    Nx : number of first-stage variables
+    Ny : number of second-stage variables
+    w : list of float
+      weights for the Ns scenarios
+    data : list of list of float
+      values for the scenario-dependent parameters, the first
+      dimension specifies the scenario, the second the parameter
+    """
+
+    def __init__(self, data=DEFAULT_DATA, use_heater=False, reduced_space=False, sample=False):
+        CHP_data_file = os.path.join(os.path.dirname(
+            os.path.realpath(__file__)), "CHP_data.txt")
+        if os.path.exists(CHP_data_file):
+            print("Found CHP_data.txt file, using values:\n")
+            CHP_data = read_options(CHP_data_file)
+        else:
+            print("No CHP_data.txt file found, using default values:\n")
+            CHP_data = dict(
+                # Numerical data for CHP sizing problem
+                Qdot_nom_ref=1,  # [MW], reference nominal power
+                c_ref=1.3e6,  # [€], reference cost
+                M=0.9,  # [-], cost exponent
+                c_m=0.05,  # [-], maintenance cost relative to investment
+                Qdot_rel_min=0.5,  # [-] minimum output part load
+                Qdot_nom_min=0.5,  # [MW]
+                Qdot_nom_max=2.3,  # [MW]
+                n=30,  # lifetime in years
+                i=0.05,  # annualization interst rate
+                T_OP=6000,  # [h] annual operating time
+                p_gas=80,  # [€ / MWh]
+                p_el_buy=250,  # [€ / MWh]
+                p_el_sell=100,  # [€ / MWh]
+            )
+
+        for quantity, value in CHP_data.items():
+            print(quantity, '=', value)
+            setattr(CHP_sizing_problem, quantity, value)
+
+        self.Qdot_eps = 0.001 * self.Qdot_nom_max  # [MW]
+        self.Qdot_mean = (self.Qdot_nom_min + self.Qdot_nom_max) / 2  # [MW]
+        self.af = (((1 + self.i) ** self.n) * self.i) / \
+            (((1 + self.i) ** self.n) - 1)  # annuity factor
+        self.use_heater = use_heater
+        self.reduced_space = reduced_space
+        self.sample = sample
+
+        # Sanity checks
+        Qdot_dem = np.array(data)[:, 0]
+
+        # Demands larger than the maximum nominal power are not feasible
+        excess_demands = np.where(Qdot_dem > self.Qdot_nom_max)[0]
+        for s in excess_demands:
+            print(f"Warning: Heat demand for scenario {s}, {Qdot_dem[s]} MW "
+                  f"is larger than maximum nominal power {self.Qdot_nom_max} "
+                  f"MW! Correcting to {self.Qdot_nom_max} MW.")
+            data[s][0] = self.Qdot_nom_max
+
+        if not use_heater:
+            # ensure the ratio of all nonzero heat demands over
+            # the maximum heat demand is larger than Qdot_rel_min
+            nonzero_demands = np.nonzero(Qdot_dem)[0]
+            ratios = Qdot_dem[nonzero_demands] / np.max(Qdot_dem)
+            violated_ratios = ratios < self.Qdot_rel_min
+            min_partload_violations = \
+                nonzero_demands[np.where(violated_ratios)[0]]
+            for s in min_partload_violations:
+                print(f"Warning: Heat demand for scenario {s}, {Qdot_dem[s]} "
+                      "MW cannot be reached with a part load of "
+                      f"{self.Qdot_rel_min}! Correcting to 0 MW.")
+                data[s][0] = 0
+
+        if reduced_space:
+            Ny = 1
+        else:
+            Ny = 3
+        maingopy.TwoStageModel.__init__(
+            self, 1, Ny, data)
+
+    def eff_th_nom(self, Qdot_nom):
+        """Compute the nominal thermal efficiency of the CHP."""
+        return 0.498 - 3.55e-2 * Qdot_nom
+
+    def eff_el_nom(self, Qdot_nom):
+        """Compute the nominal electrical efficiency of the CHP."""
+        return 0.372 + 3.55e-2 * Qdot_nom
+
+    def eff_th_rel(self, Qdot_rel):
+        """Compute the relative electrical efficiency of the CHP."""
+        return vertex_form(Qdot_rel, -0.0768, -0.0199, 1.0960)
+
+    def eff_el_rel(self, Qdot_rel):
+        """Compute the relative electrical efficiency of the CHP."""
+        return vertex_form(Qdot_rel, -0.2611, 0.6743, 0.5868)
+
+    def eff_th(self, Qdot_nom, Qdot_rel):
+        """Compute the thermal efficiency of the CHP."""
+        return self.eff_th_nom(Qdot_nom) * self.eff_th_rel(Qdot_rel)
+
+    def eff_el(self, Qdot_nom, Qdot_rel):
+        """Compute the electrical efficiency of the CHP."""
+        return self.eff_el_nom(Qdot_nom) * self.eff_el_rel(Qdot_rel)
+
+    def get_variables(self):
+        """Get the MAiNGO variables."""
+        BRANCHING_FILE_NAME = 'prios.txt'
+
+        print()
+        if os.path.exists(BRANCHING_FILE_NAME):
+            prios = {k: int(v) for k, v in read_options(
+                BRANCHING_FILE_NAME).items()}
+            print(f"Using branching priorities from {BRANCHING_FILE_NAME}:")
+        else:
+            print("No 'prios.txt' file found in", os.getcwd(),
+                  "proceeding with default branching priorities:")
+            prios = {}
+        vars = [
+            maingopy.OptimizationVariable(
+                maingopy.Bounds(self.Qdot_nom_min, self.Qdot_nom_max),
+                prios.get("Qdot_nom", 1),
+                "Qdot_nom"
+            )
+        ]
+        if self.reduced_space:
+            vars.extend([
+                *(
+                    maingopy.OptimizationVariable(
+                        maingopy.Bounds(0, 1),
+                        prios.get(f"Qdot_rel_s{s}", 1),
+                        f"Qdot_rel_s{s}"
+                    )
+                    for s in range(self.Ns)
+                ),
+            ])
+        else:
+            for s in range(self.Ns):
+                vars.extend([
+                    maingopy.OptimizationVariable(
+                        maingopy.Bounds(0, 1),
+                        prios.get(f"Qdot_rel_s{s}", 1),
+                        f"Qdot_rel_s{s}"
+                    ),
+                    maingopy.OptimizationVariable(
+                        maingopy.Bounds(
+                            0, self.Qdot_nom_max /
+                            self.eff_th(self.Qdot_nom_max, 1)
+                        ),
+                        prios.get(f"Edot_in_s{s}", 1),
+                        f"Edot_in_s{s}"
+                    ),
+                    maingopy.OptimizationVariable(
+                        maingopy.Bounds(
+                            0, self.Qdot_nom_max *
+                            self.eff_el(self.Qdot_nom_max, 1) /
+                            self.eff_th(self.Qdot_nom_max, 1)
+                        ),
+                        prios.get(f"P_out_s{s}", 1),
+                        f"P_out_s{s}"
+                    )
+                ])
+
+        for var in vars:
+            print(" ", var.get_name(), var.get_branching_priority())
+        print()
+        return vars
+
+    def approximate_optimal_point(self, n=1000):
+        """Approximate the optimal point by evaluating the objective function"""
+        if not self.reduced_space:
+            raise NotImplementedError(
+                "Approximation of optimal point not implemented for full space")
+        
+        import warnings
+
+        def _nan_cols(arr):
+            """Find the indices of nan columns (infeasible x domain)."""
+            with warnings.catch_warnings():
+                # All-NaN slice encountered
+                warnings.simplefilter("ignore")
+                return np.isnan(np.nanmin(arr, axis=0))
+
+        x = np.linspace(self.Qdot_nom_min, self.Qdot_nom_max, n)
+        y = np.linspace(0, 1, n)
+        self.X, self.Y = np.meshgrid(x, y)
+        F = np.ones_like(self.X)  # feasibility mask, 1 or nan
+        Zx = self.f1_func([self.X])
+        self.Z = np.zeros_like(Zx)  # objective value
+
+        self.scenarioZs = []
+        self.scenarioSolutions = []
+        Fss = F * 0
+        FSs = []
+        for s in range(self.Ns):
+            ps = self.data[s]
+            Fs = np.ones_like(self.X)
+            for res in self.g2_func([self.X], [self.Y], ps)[0]:
+                Fs[res[0] >= 0] = float('nan')
+            F[:, _nan_cols(Fs)] = float('nan')
+            Zy = self.f2_func([self.X], [self.Y], ps)
+            Zs = (Zx + Zy) * Fs
+            Fss += np.nan_to_num(Fs, nan=0)
+            FSs.append(Fs)
+            feasible_x = ~_nan_cols(Zs)
+            min_y = np.nanargmin(Zs[:, feasible_x], axis=0)
+            self.Z[:, feasible_x] += self.w[s] * Zs[min_y, feasible_x]
+            self.scenarioZs.append(Zs)
+            self.scenarioSolutions.append((self.X[min_y, feasible_x], self.Y[min_y, feasible_x]))
+        self.Z *= F
+        with warnings.catch_warnings():
+            # All-NaN slice encountered
+            warnings.simplefilter("ignore")
+            self.Z *= Fss/Fss
+
+        xopt = self.X.flatten()[np.nanargmin(self.Z)]
+        yopt = [np.interp(xopt, *mins) for mins in self.scenarioSolutions]
+        return [xopt, *yopt]
+
+    def get_initial_point(self):
+        """Get the current variable values as the initial point."""
+        if self.sample:
+            return self.approximate_optimal_point(1000)
+
+        initial_point = [
+            self.Qdot_nom_max,
+        ]
+
+        if self.reduced_space:
+            initial_point.extend([1] * self.Ns)
+        else:
+            initial_point.extend([
+                1,
+                self.Qdot_nom_max / self.eff_th(self.Qdot_nom_max, 1),
+                self.Qdot_nom_max *
+                self.eff_el(self.Qdot_nom_max, 1) /
+                self.eff_th(self.Qdot_nom_max, 1)
+            ] * self.Ns)
+        return initial_point
+
+    def f1_func(self, x):
+        """Annualized investment cost in million euros/a."""
+        Qdot_nom = x[0]
+        # investment cost of the component
+        ic = self.c_ref * (Qdot_nom / self.Qdot_nom_ref) ** self.M
+        # fixed cost of the component
+        fc = self.c_m * ic
+
+        return 1e-6 * (self.af * ic + fc)
+
+    def f2_func(self, x, ys, ps):
+        """Annual operating cost in million euros/a."""
+        Qdot_nom = x[0]
+        Qdot_rel = ys[0]
+        if (isinstance(Qdot_nom, maingopy.FFVar)
+                and isinstance(Qdot_rel, maingopy.FFVar)):
+            max = maingopy.max
+        else:
+            try:
+                import sympy
+                if (isinstance(Qdot_nom, sympy.Symbol)
+                        and isinstance(Qdot_rel, sympy.Symbol)):
+                    max = sympy.Max
+                else:
+                    max = np.maximum
+            except ImportError:
+                max = np.maximum
+        Qdot_out = Qdot_nom * Qdot_rel
+
+        if self.reduced_space:
+            Edot_in = Qdot_out / \
+                self.eff_th(Qdot_nom=Qdot_nom,
+                            Qdot_rel=Qdot_rel)
+        else:
+            Edot_in = ys[1]
+
+        if self.reduced_space:
+            P_out = Edot_in * \
+                self.eff_el(Qdot_nom=Qdot_nom,
+                            Qdot_rel=Qdot_rel)
+        else:
+            P_out = ys[2]
+
+        Qdot_dem, P_dem = ps
+
+        if self.use_heater:
+            # When allowing an electric heater with 100% efficiency
+            Qdot_supplied_via_electricity = max(0, Qdot_dem - Qdot_out)
+        else:
+            # When not allowing an electric heater
+            Qdot_supplied_via_electricity = 0
+
+        P_grid = P_dem - P_out + Qdot_supplied_via_electricity
+
+        # Total variable cost = purchase for gas
+        # + purchase for missing electricity
+        # or compensation (negative cost) for selling excess electricity
+        return 1e-6 * (
+            self.p_gas * Edot_in
+            + self.p_el_buy * max(0, P_grid)
+            - self.p_el_sell * max(0, -P_grid)
+        ) * self.T_OP
+
+    def g2_func(self, x, ys, ps):
+        Qdot_rel = ys[0]
+        # quadratic function that is positive (indicating a violation)
+        # if we violate minimum part load requirements, i.e., if
+        # Qdot_eps <= Qdot_out <= Qdot_out_min
+        # min_partload_viol =
+        #   -(Qdot_rel - self.Qdot_rel_min) * (Qdot_rel - self.Qdot_eps)
+        # = -(Qdot_rel ** 2
+        #     - Qdot_rel * (self.Qdot_rel_min + self.Qdot_eps)
+        #     + self.Qdot_rel_min * self.Qdot_eps)
+        # = -1 * Qdot_rel ** 2
+        #   + (self.Qdot_rel_min + self.Qdot_eps) * Qdot_rel
+        #   - self.Qdot_rel_min * self.Qdot_eps
+        a = -1
+        b = self.Qdot_rel_min + self.Qdot_eps
+        c = -self.Qdot_rel_min * self.Qdot_eps
+        min_partload_viol = vertex_form(Qdot_rel, a, b, c)
+
+        definitions = []
+        if not self.reduced_space:
+            Qdot_nom = x[0]
+            Edot_in = ys[1]
+            P_out = ys[2]
+
+            Qdot_out = Qdot_nom * Qdot_rel
+            Edot_in_expr = Qdot_out / \
+                self.eff_th(Qdot_nom, Qdot_rel)
+            definitions.append(
+                ((Edot_in_expr - Edot_in), "Edot_in_definition"))
+
+            P_out_expr = Edot_in * \
+                self.eff_el(Qdot_nom, Qdot_rel)
+            definitions.append((P_out_expr - P_out, "P_out_definition"))
+
+        # functions indicating a violation for positive values
+        ineqs = [
+            [min_partload_viol, "Minimum part load requirement"],
+        ]
+        if not self.use_heater:
+            Qdot_nom = x[0]
+            Qdot_out = Qdot_nom * Qdot_rel
+            Qdot_dem = ps[0]
+            dem_violation = Qdot_dem - Qdot_out
+            ineqs.append([dem_violation, "Heat demand satisfaction"])
+
+        return [
+            ineqs,  # ineq
+            [],  # squash
+            definitions,  # eq
+            [],  # ineqRelOnly
+            [],  # eqRelOnly
+        ]
+
+    def solve(self, **options):
+        """Solve the problem with the given options.
+
+        Options
+        -------
+        epsilonA : double
+            Absolute optimality tolerance, i.e., termination when (UBD-LBD) <
+            BAB_epsilon_a.
+
+        epsilonR : double
+            Relative optimality tolerance, i.e., termination when (UBD-LBD) <
+            BAB_epsilon_r * UBD.
+
+        deltaIneq : double
+            Absolute feasibility tolerance for inequality constraints (i.e.,
+            constraint is considered satisfied if gi_(x)<=UBP_delta_ineq.
+
+        deltaEq : double
+            Absolute feasibility tolerance for equality constraints (i.e.,
+            constraint is considered satisfied if abs(hi_(x))<=UBP_delta_eq.
+
+        relNodeTol : double
+            Relative tolerance for minimum node size.
+
+        BAB_maxNodes : unsigned
+            Maximum number of nodes (i.e., solver terminates when more than
+            BAB_maxnodes are held in memory; used to avoid excessive branching)
+
+        BAB_maxIterations : unsigned
+            Maximum number of iterations (i.e., maximum number of nodes visited
+            in the Branch-and-Bound tree)
+
+        maxTime : unsigned
+            CPU time limit in seconds.
+
+        confirmTermination : bool
+            Whether to ask the user before terminating when reaching time,
+            node, or iteration limits.
+
+        terminateOnFeasiblePoint : bool
+            Whether to terminate as soon as the first feasible point was found
+            (no guarantee of global or local optimality!)
+
+        targetLowerBound : double
+            Target value for the lower bound on the optimal objective. MAiNGO
+            terminates once LBD>=targetLowerBound (no guarantee of global or
+            local optimality!)
+
+        targetUpperBound : double
+            Target value for the upper bound on the optimal objective. MAiNGO
+            terminates once UBD<=targetUpperBound (no guarantee of global or
+            local optimality!)
+
+        infinity : double
+            User definition of infinity (used to initialize UBD and LBD)
+            [currently cannot be set by the user via set_option].
+
+        PRE_maxLocalSearches : unsigned
+            Number of local searches in the multistart heuristic during
+            preprocessing at the root node.
+
+        PRE_obbtMaxRounds : unsigned
+            Maximum number of rounds of optimization-based range reduction
+            (OBBT; cf., e.g., Gleixner et al., J. Glob. Optim. 67 (2017) 731;
+            maximizing and minimizing each variable subject to relaxed
+            constraints) at the root node. If >=1 and a feasible point is
+            found during multistart, one round of OBBT using an objective cut
+            (f_cv<=UBD) is conducted as well.
+
+        PRE_pureMultistart : bool
+            Whether to perform a multistart only. A B&B tree will not be
+            constructed and no lower bounding problems will be solved.
+
+        BAB_nodeSelection : babBase::enums::NS
+            How to select the next node to process. See documentation of
+            babBase::enums::NS for possible values.
+
+        BAB_branchVariable : babBase::enums::BV
+            Which dimension to branch in for the current node. See
+            documentation of babBase::enums::BV for possible values.
+
+        BAB_alwaysSolveObbt : bool
+            Whether to solve OBBT (feasibility- and, once a feasible point has
+            been found, also optimality-based) at every BaB node.
+
+        BAB_dbbt : bool
+            Whether to do a single round of duality based bound tightening
+            (DBBT, cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19 (1995) 551). If
+            false, no DBBT is used. If true, multipliers from CPLEX are used to
+            tighten bounds (essentially for free). we tried additional rounds
+            but without reasonable improvement.
+
+        BAB_probing : bool
+            Whether to do probing (cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19
+            (1995) 551) at every node (can only be done if BAB_DBBT_maxrounds
+            >= 1)
+
+        BAB_constraintPropagation : bool
+            Whether to do constraint propagation. If false, no constraint
+            propagation is executed.
+
+        LBP_solver : lbp::LBP_SOLVER
+            Solver for solution of lower bounding problems.
+
+        LBP_linPoints : lbp::LINP
+            At which points to linearize for affine relaxation. See
+            documentation of lbp::LINP for possible values.
+
+        LBP_subgradientIntervals : bool
+            Whether to use the heuristic to improve McCormick relaxations by
+            tightening the range of each factor with the use of subgradients
+            (cf. Najman & Mitsos, JOGO 2019)
+
+        LBP_obbtMinImprovement : double
+            How much improvement needs to be achievable (relative to initial
+            diameter) to conduct OBBT for a variable.
+
+        LBP_activateMoreScaling : unsigned
+            Number of consecutive iterations without LBD improvement needed to
+            activate more aggressive scaling in LP solver (e.g., CPLEX)
+
+        LBP_addAuxiliaryVars : bool
+            Whether to add auxiliary variables for common factors in the lower
+            bounding DAG/problem.
+
+        LBP_minFactorsForAux : unsigned
+            Minimum number of common factors to add an auxiliary variable.
+
+        LBP_maxNumberOfAddedFactors : unsigned
+            Maximum number of added factor as auxiliaries.
+
+        MC_mvcompUse : bool
+            Whether to use multivariate composition theorem for computing
+            McCormick relaxations (see MC++ documentation for details)
+
+        MC_mvcompTol : double
+            (see MC++ documentation for details)
+
+        MC_envelTol : double
+            (see MC++ documentation for details)
+
+        UBP_solverPreprocessing : ubp::UBP_SOLVER
+            Solver to be used during pre-processing (i.e., multistart). See
+            documentation of ubp::UBP_SOLVER for possible values.
+
+        UBP_maxStepsPreprocessing : unsigned
+            Maximum number of steps the local solver is allowed to take in each
+            local run during multistart in pre-processing.
+
+        UBP_maxTimePreprocessing : double
+            Maximum CPU time the local solver is allowed to take in each local
+            run during multistart in pre-processing. Usually, this should only
+            be a fall-back option to prevent truly getting stuck in local
+            solution.
+
+        UBP_solverBab : ubp::UBP_SOLVER
+            Solver to be used during Branch-and-Bound. See documentation of
+            ubp::UBP_SOLVER for possible values.
+
+        UBP_maxStepsBab : unsigned
+            Maximum number of steps the local solver is allowed to take at each
+            BaB node.
+
+        UBP_maxTimeBab : double
+            Maximum CPU time the local solver is allowed to take at each BaB
+            node. Usually, this should only be a fall-back option to prevent
+            truly getting stuck in local solution.
+
+        UBP_ignoreNodeBounds : bool
+            Flag indicating whether the UBP solvers should ignore the box
+            constraints of the current node during the B&B (and consider only
+            the ones of the root node instead).
+
+        EC_nPoints : unsigned
+            Number of points on the Pareto front to be computed in
+            epsilon-constraint method (only available via the C++ API)
+
+        BAB_verbosity : VERB
+            How much output to print from Branch & Bound solver. Possible
+            values are VERB_NONE (=0), VERB_NORMAL (=1), VERB_ALL (=2)
+
+        LBP_verbosity : VERB
+            How much output to print from Lower Bounding Solver. Possible
+            values are VERB_NONE (=0), VERB_NORMAL (=1), VERB_ALL (=2)
+
+        UBP_verbosity : VERB
+            How much output to print from Upper Bounding Solver. Possible
+            values are VERB_NONE (=0), VERB_NORMAL (=1), VERB_ALL (=2)
+
+        BAB_printFreq : unsigned
+            After how many iterations to print progress on screen
+            (additionally, a line is printed when a new incumbent is found)
+
+        BAB_logFreq : unsigned
+            Like BAB_printFreq, but for log.
+
+        writeLog : bool
+            Whether to write a log file (named bab.log)
+
+        writeToLogSec : unsigned
+            Write to log file after a given ammount of CPU seconds.
+
+        writeResFile : bool
+            Whether to write an additional file containing non-standard
+            information about the solved model.
+
+        writeCsv : bool
+            Whether to write a csv-log file (named bab.csv). Currently, this
+            only include time, LBD, UBD, and final output.
+
+        PRE_printEveryLocalSearch : bool
+            Whether to print every run during multistart at the root node.
+
+        writeToOtherLanguage : PARSING_LANGUAGE
+            Write to a file in a different modeling language.
+
+        Returns
+        -------
+        solver : MAiNGO solver object
+            A solver object that can be queried for solve related information
+            and adjust different settings:
+
+            * solver.evaluate_additional_outputs_at_point(point)
+            * solver.evaluate_additional_outputs_at_solution_point()
+            * solver.evaluate_model_at_point(point)
+            * solver.evaluate_model_at_solution_point()
+            * solver.get_LBP_count()
+            * solver.get_UBP_count()
+            * solver.get_cpu_solution_time()
+            * solver.get_final_LBD()
+            * solver.get_final_abs_gap()
+            * solver.get_final_rel_gap()
+            * solver.get_iterations()
+            * solver.get_max_nodes_in_memory()
+            * solver.get_objective_value()
+            * solver.get_solution_point()
+            * solver.get_status()
+            * solver.get_wallclock_solution_time()
+            * solver.read_settings('settings.txt')
+            * solver.set_iterations_csv_file_name('iterations.csv')
+            * solver.set_json_file_name('results.json')
+            * solver.set_log_file_name('results.log')
+            * solver.set_model(myMAiNGOmodel)
+            * solver.set_option(option, value)
+            * solver.set_result_file_name('res.txt')
+            * solver.set_solution_and_statistics_csv_file_name('sol.csv')
+            * solver.solve()
+            * solver.write_model_to_file_in_other_language('ALE', 'prob.ale')
+
+        status : MAiNGO RETCODE
+            Return code for the solution, possible values are:
+
+            * GLOBALLY_OPTIMAL
+            * INFEASIBLE
+            * FEASIBLE_POINT
+            * NO_FEASIBLE_POINT_FOUND
+            * BOUND_TARGETS
+            * NOT_SOLVED_YET
+            * JUST_A_WORKER_DONT_ASK_ME
+        """
+        self.branching_priorities = options.pop('branching_priorities', {})
+        solver = maingopy.MAiNGO(self)
+        lang = options.pop('writeToOtherLanguage', LANG_NONE)
+        if lang is None:
+            lang = LANG_NONE
+        if lang not in {LANG_ALE, LANG_GAMS, LANG_NONE}:
+            try:  # whether a string was given
+                lang = globals().get(f'LANG_{lang.upper()}')
+            except KeyError:
+                raise ValueError(f'Language {lang} is not implemented! '
+                                 'Possible values for writeToOtherLanguage are'
+                                 ' ALE, GAMS or NONE!')
+        if lang != LANG_NONE:
+            ending = {LANG_ALE: '.ale', LANG_GAMS: '.gms'}[lang]
+            solver.write_model_to_file_in_other_language(lang,
+                                                         type(self).__name__ + ending)
+
+        # Handle special options for adjusting default file names
+        for file_name_option in [
+            'iterations_csv_file_name',
+            'json_file_name',
+            'log_file_name',
+            'result_file_name',
+            'solution_and_statistics_csv_file_name',
+            'bab_file_name',
+        ]:
+            file_name = options.pop(file_name_option, '')
+            if file_name:
+                getattr(solver, 'set_' + file_name_option)(file_name)
+
+        for option, value in options.items():
+            if not solver.set_option(option, value):
+                raise ValueError(f'Option "{option}" is not recognized!')
+        status = solver.solve()
+        return solver, status
+
+
+if __name__ == '__main__':
+    import argparse
+
+    parser = argparse.ArgumentParser(
+        description='Solve the CHP sizing problem.')
+    parser.add_argument(
+        '--Ns', type=int, default=-1,
+        help='Number of samples to generate for the random demands.'
+    )
+    parser.add_argument(
+        '--settings', type=str, default='',
+        help='Settings file to use.'
+    )
+    parser.add_argument(
+        '--debug', action='store_true',
+        help='Pause to allow attaching debugger.'
+    )
+    parser.add_argument(
+        '--plot', action='store_true',
+        help='Plot the feasible points in the domain and quit.'
+    )
+    parser.add_argument(
+        '--print', action='store_true',
+        help='Print the objective and constraint expressions and quit.'
+    )
+    parser.add_argument(
+        '--use_heater', action='store_true',
+        help='Consider an electric heater to satisfy unmet heat demand.'
+    )
+    parser.add_argument(
+        '--reduced_space', action='store_true',
+        help='Use a reduced space model for optimization.'
+    )
+    parser.add_argument(
+        '--sample', action='store_true',
+        help='Use sampling for initialization.'
+    )
+    args = parser.parse_args()
+
+    if maingopy.HAVE_MAiNGO_MPI():
+
+        import sys
+        try:
+            from mpi4py import MPI
+        except ImportError:
+            print("maingopy was built with MPI support, but mpi4py was not found.")
+            sys.exit(1)
+
+        comm = MPI.COMM_WORLD
+        rank = comm.Get_rank()
+        size = comm.Get_size()
+
+        buffers = maingopy.muteWorker()
+
+    if args.debug:
+        msg = "Pausing to allow attaching debugger, press enter to continue."
+        if maingopy.HAVE_MAiNGO_MPI():
+            if rank == 0:
+                input(msg)
+            comm.Barrier()
+        else:
+            input(msg)
+
+    import pandas as pd
+
+    if args.Ns > 0:
+        from get_demands import random_demands
+        data = random_demands(args.Ns)
+    else:
+        data = DEFAULT_DATA
+
+    mymodel = CHP_sizing_problem(
+        data, use_heater=args.use_heater, reduced_space=args.reduced_space, sample=args.sample)
+
+    if args.print:
+        import sympy
+        print("Objectives:")
+        if args.reduced_space:
+            x = [sympy.S('x')]
+            ys = [sympy.S(f'y_s')]
+        else:
+            x = [sympy.S('x')]
+            ys = [sympy.S(f'y1_s'), sympy.S(f'y2_s'), sympy.S(f'y3_s')]
+        ps = [sympy.S(f'p1_s'), sympy.S(f'p2_s')]
+        f1 = mymodel.f1_func(x)
+        f2s = mymodel.f2_func(x, ys, ps)
+        print(str(f1).lower())
+        print(str(f2s).lower())
+        print("Constraints:")
+        g2s = mymodel.g2_func(x, ys, ps)[0]
+        for g2si in g2s:
+            print(f'{g2si[1]}:', str(g2si[0]).lower())
+
+        Qdot_dem = [d[0] for d in data]
+        P_dem = [d[1] for d in data]
+
+        results = pd.DataFrame(index=range(mymodel.Ns), data=dict(
+            Qdot_dem=Qdot_dem,
+            P_dem=P_dem,
+        ))
+        print(results)
+        sys.exit(0)
+
+    if args.plot:
+        import matplotlib.pyplot as plt
+        import matplotlib.lines as mlines
+
+        if not args.reduced_space:
+            print("Plotting is only supported for the reduced space model, "
+                  "switching reduced_space flag!")
+            mymodel.reduced_space = True
+
+        # Render text with LaTeX (uncomment for faster plotting)
+        plt.rc('text', usetex=True)
+
+        xopt, *yopt = mymodel.approximate_optimal_point()
+
+        levels = np.linspace(
+            min(np.nanmin(Zs) for Zs in mymodel.scenarioZs),
+            max(np.nanmax(Zs) for Zs in mymodel.scenarioZs),
+            100
+        )
+
+        l = int(np.ceil((mymodel.Ns + 1) / 3))
+        fig, axs = plt.subplots(l, 3, figsize=(
+            9/0.8, 3 * l), sharex=True, sharey=True)
+        prop_cycle = plt.rcParams['axes.prop_cycle']
+        colors = prop_cycle.by_key()['color']
+
+        axes = iter(axs.flatten())
+        for s, (Zs, ax) in enumerate(zip(mymodel.scenarioZs, axes)):
+            c = colors[s % len(colors)]
+            ax.contourf(mymodel.X, mymodel.Y, Zs, levels=levels)
+            i = np.nanargmin(Zs)
+            label = f'(x^*_{s}, y^*_{s})'
+            if plt.rcParams['text.usetex']:
+                label = '$' + label + '$'
+            ax.scatter(mymodel.X.flatten()[i], mymodel.Y.flatten()[i], c=c, label=label)
+            
+            label = f'f_{{I}}(x) + f^*_{{II,{s}}}(x)'
+            # If rendered with LaTeX add $...$ around the label
+            if plt.rcParams['text.usetex']:
+                label = '$' + label + '$'
+            ax.plot(*mymodel.scenarioSolutions[s], ls='--', c=c, label=label)
+            # ax.plot(*mins, ls='--', c=c, label=label)
+            
+            title = f'f_{{I}}(x) + f_{{II,{s}}}(x,y)'
+            # If rendered with LaTeX add $...$ around the title
+            if plt.rcParams['text.usetex']:
+                title = '$' + title + '$'
+            ax.set_title(title)
+            if plt.rcParams['text.usetex']:
+                ax.set_xlabel('$x$')
+                ax.set_ylabel('$y$')
+            else:
+                ax.set_xlabel('x')
+                ax.set_ylabel('y')
+            # bottom left corner
+            ax.legend(loc=3)
+
+        ax = next(axes)
+
+        # ensure the cbar shows up
+        fig.subplots_adjust(right=0.8, wspace=0.1, hspace=0.25)
+        cbar_ax = fig.add_axes([0.85, 0.15, 0.025, 0.7])
+        fig.colorbar(
+            ax.contourf(mymodel.X, mymodel.Y, mymodel.Z, levels=levels),
+            cax=cbar_ax
+        )
+        label = f'x^*'
+        # If rendered with LaTeX add $...$ around the label
+        if plt.rcParams['text.usetex']:
+            label = '$' + label + '$'
+        ax.axvline(xopt, c='k', ls='-', label=label)
+        for s, mins in enumerate(mymodel.scenarioSolutions):
+            c = colors[s % len(colors)]
+            ax.plot(*mins, c=c, ls='--')
+            ax.scatter(xopt, yopt[s], c=c)
+        title = f'f_{{I}}(x) + \sum_s f^*_{{II,s}}(x)/{mymodel.Ns}'
+        # If rendered with LaTeX add $...$ around the title
+        if plt.rcParams['text.usetex']:
+            title = '$' + title + '$'
+        ax.set_title(title)
+        if plt.rcParams['text.usetex']:
+            ax.set_xlabel('$x$')
+            ax.set_ylabel('$y$')
+        else:
+            ax.set_xlabel('x')
+            ax.set_ylabel('y')
+
+        # modify legend
+        handles, labels = ax.get_legend_handles_labels()
+        yopt_label = f'y^*_s'
+        fsopt_label = 'f_{I}(x) + f^*_{II,s}(x)'
+        # If rendered with LaTeX add $...$ around the label
+        if plt.rcParams['text.usetex']:
+            yopt_label = '$' + yopt_label + '$'
+            fsopt_label = '$' + fsopt_label + '$'
+        # Add a black marker to the legend
+        yopt = mlines.Line2D([], [], color='k', marker='o',
+                                linestyle='None', label=yopt_label)
+        # Add a black dashed line
+        fsopt = mlines.Line2D([], [], color='k', linestyle='--',
+                                label=fsopt_label)
+        ax.legend(handles=[handles[0], yopt, fsopt])
+
+        # empty the remaining axes
+        for eax in axes:
+            eax.axis('off')
+
+        if args.use_heater:
+            figname = f'CHP_with_heater_{mymodel.Ns}_scenarios.pdf'
+        else:
+            figname = f'CHP_without_heater_{mymodel.Ns}_scenarios.pdf'
+        plt.savefig(figname)
+        plt.show()
+        sys.exit(0)
+
+    if args.settings:
+        try:
+            options = read_options(args.settings)
+        except Exception as e:
+            raise RuntimeError(
+                f"Trying to interpret argument '{args.settings}' "
+                f"as settings file name gives: {e}")
+    elif os.path.exists(DEFAULT_SETTINGS_FILE_NAME):
+        options = read_options(DEFAULT_SETTINGS_FILE_NAME)
+    else:
+        options = {}
+
+    if args.debug:
+        options["bab_file_name"] = 'bab.dot'
+    solver, status = mymodel.solve(**options)
+
+    Qdot_dem = [d[0] for d in data]
+    P_dem = [d[1] for d in data]
+    Qdot_nom, *op_vars = solver.get_solution_point()
+    if args.reduced_space:
+        Qdot_rel = op_vars
+    else:
+        Qdot_rel = np.array([op_vars[i]
+                             for i in range(0, 3 * mymodel.Ns, 3)])
+        Edot_in = np.array([op_vars[i] for i in range(1, 3 * mymodel.Ns, 3)])
+        P_out = np.array([op_vars[i] for i in range(2, 3 * mymodel.Ns, 3)])
+    Qdot_rel = np.array(Qdot_rel)
+    Qdot_out = Qdot_nom * Qdot_rel
+    Qdot_diff = Qdot_out - Qdot_dem
+    Qdot_diss = np.maximum(0, Qdot_diff)
+    Qdot_el = np.maximum(0, -Qdot_diff)
+    if args.reduced_space:
+        Edot_in = Qdot_out / \
+            mymodel.eff_th(Qdot_nom=Qdot_nom,
+                           Qdot_rel=Qdot_rel)
+        P_out = Edot_in * \
+            mymodel.eff_el(Qdot_nom=Qdot_nom,
+                           Qdot_rel=Qdot_rel)
+    P_buy = np.maximum(0, P_dem - P_out)
+    P_sell = np.maximum(0, P_out - P_dem)
+
+    N_s = len(data)
+
+    results = pd.DataFrame(index=range(N_s), data=dict(
+        Qdot_dem=Qdot_dem,
+        P_dem=P_dem,
+        Qdot_nom=[Qdot_nom] * N_s,
+        Qdot_rel=Qdot_rel,
+        Qdot_out=Qdot_out,
+        Qdot_el=Qdot_el,
+        Qdot_diss=Qdot_diss,
+        Edot_in=Edot_in,
+        P_out=P_out,
+        P_buy=P_buy,
+        P_sell=P_sell,
+    ))
+    print(results)
+
+    if maingopy.HAVE_MAiNGO_MPI():
+        maingopy.unmuteWorker(buffers)
diff --git a/examples/08_TwoStage/CHP_sizing8.png b/examples/08_TwoStage/CHP_sizing8.png
new file mode 100644
index 0000000000000000000000000000000000000000..36b00a4c6b2e33fe7cbfc65ea35b6e995b5eeac2
GIT binary patch
literal 94003
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%0D2aXRb
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
bfi&^2p5>s9OT#1cP>^a*S3j3^P6<r_bftK=

literal 0
HcmV?d00001

diff --git a/examples/08_TwoStage/README b/examples/08_TwoStage/README
new file mode 100644
index 0000000..5543b9c
--- /dev/null
+++ b/examples/08_TwoStage/README
@@ -0,0 +1,59 @@
+# Nonlinear Two-Stage Stochastic Programming Problems
+
+This example showcases the usage of MAiNGO for solving nonlinear two-stage stochastic programming problems.
+The subalgorithm adressing these kinds of problems in MAiNGO is called MUSE-BB, and will be described in detail, in a forthcoming publication.
+
+## Structure
+For the implementation, we currently only allow for problem formulations where second stage objective and constraints only differ in numeric values, not in structure, i.e., the deterministic equivalent (DE) takes the form:
+```
+min  f_{I}(x) + sum(w[s] * f_{II}(x, y[s], data[s]), s in S)
+s.t. g_{I}(x) <= 0
+     g_{II}(x, y[s], data[s]) <= 0, s in S
+```
+Note, however, that the above form indirectly also allows for different expression structures, e.g., by setting certain elements of `data` which are multiplied to expressions to 0.
+This renders the above form equivalent to the apparently more generic case:
+```
+min  f_{I}(x) + sum(w[s] * f_{II_s}(x, y[s]), s in S)
+s.t. g_{I}(x) <= 0
+     g_{II,s}(x, y[s]) <= 0
+```
+
+The above problem formulations exhibit a staircase structure, visualized below:
+
+![Two-Stage Problem Structure](structure.png)
+
+## Specification
+The assumption that the different instances of functions `f_{II,s}` and `g_{II,s}` only differ in some numeric parameter values allows users to define `DE` in a compact manner, by providing functions `fII_func` and `gII_func`, which take appropriate arguments, instead of explicitly constructing the each individual instance of `fII,s` and `gII,s`.
+Furthermore, this allows to easily scale the problem by simply increasing the number of entries in `w` and `data`.
+
+In the problem definition users thus need to specify:
+- `x`: the `Nx` first stage variables
+- `y`: the `Ny` second stage variables
+- `fI`: the first stage objective
+- `fII_func`: the function that can be used to generate second stage objectives for each scenario
+- `gI`: the `NgI` first stage constraints
+- `gII_func`: the function that can be used to generate the `NgII` second stage constraints for each scenario
+
+The functions `fII_func` and `gII_func` implicitly define `Np` parameters that may vary with the scenario.
+Specific instances of a particular problem can therefore be instantiated by specifying:
+- `w`: the `Ns` sceanrio weights (which should sum to 1)
+- `data`: the `Ns` * `Np` values of the `Np` parameter vectors, used in the second stage objective and constraints.
+
+## Example: Combined Heat and Power Unit Sizing
+
+This directory contains code for running a simplified CHP sizing problem as an example.
+
+- `CHP_sizing.h` is a basic variant of this example problem, with a fixed number of 8 scenarios and a branching priority ratio of 16:1, showcasing the basic usage in C++.
+- `CHP_sizing.py` is a more elaborate implementation, using the `maingopy` python interface.
+It allows for additional features like changing the number of scenarios (using pseudorandomly generated demands), and branching priorities, switching between full- or reduced-space variants of the problem, and optionally considering an additional electric heater.
+Furthermore, running this example is possible for both the default implementation of MAiNGO (via `python CHP_sizing.py [options]`) as well as the MPI parallelized implementation (via `mpiexec -n [numper of processors] python CHP_sizing.py [options]`).
+For the reduced-space formulation an initialization by simple sampling is implemented, which results in initial points extremely close to the global optima.
+Finally, the python implementation also provides functionality for debugging, printing the resulting objective and constraint expressions, and plotting the resulting feasible set and objective valiues.
+
+The example in `CHP_sizing.h` matches the results for running `CHP_sizing.py` with the options `--Ns 8 --reduced_space --sample` and branching priorities of 16:1, corresponding to a `prios.txt` file containing the line `Qdot_nom 16`.
+An example plot for this case is given below.
+
+![CHP_sizing8.png](CHP_sizing8.png)
+ 
+
+
diff --git a/examples/08_TwoStage/get_demands.py b/examples/08_TwoStage/get_demands.py
new file mode 100644
index 0000000..1b14507
--- /dev/null
+++ b/examples/08_TwoStage/get_demands.py
@@ -0,0 +1,31 @@
+"""Generate random demands for the CHP sizing problem."""
+import numpy as np
+
+
+def random_demands(Ns, seed=42, /, Qdot_bounds=(0, 1.5), P_bounds=(0, 2)):
+    """Generate Ns random demands for the CHP sizing problem.
+
+    Parameters
+    ----------
+    Ns : int
+        Number of scenarios.
+    seed : int, optional
+        Seed for the random number generator, by default 0
+    Qdot_bounds, P_bounds : tuple, optional
+        Bounds for the relative demands, by default (0, 1.5) and (0, 2)
+
+    Returns
+    -------
+    demands : np.ndarray
+        Demands for heat and power in each scenario.
+    """
+    rng = np.random.default_rng(seed)
+    demands = rng.uniform(
+        (Qdot_bounds[0], P_bounds[0]),
+        (Qdot_bounds[1], P_bounds[1]),
+        size=(Ns, 2)
+    )
+    # Sort the demands by combined demand
+    # return demands[np.argsort(np.sum(demands, axis=1))]
+    # Sort the demands by heat demand
+    return demands[np.argsort(demands[:, 0])]
diff --git a/examples/08_TwoStage/structure.png b/examples/08_TwoStage/structure.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d370e9492614bbf094ab92e6b50315231060f63
GIT binary patch
literal 21394
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
N!PC{xWt~$(698@TD+&Mr

literal 0
HcmV?d00001

diff --git a/examples/MAiNGOSettings.txt b/examples/MAiNGOSettings.txt
index 21c217d..0310c93 100644
--- a/examples/MAiNGOSettings.txt
+++ b/examples/MAiNGOSettings.txt
@@ -208,26 +208,41 @@
 #-------------------------------------------------------------------------------------------------------------------------------
 #---------------Settings for extension "B&B alorithm with growing datasets for large-scale parameter estimation"----------------
 
-#relative tolerance for considering dataset as the full dataset    (default: 0.9)
+#which approach to use: GROW_APPR_DETERMINISTIC = 0, GROW_APPR_SSEHEURISTIC = 1, GROW_APPR_MSEHEURISTIC = 2    (default: 0)
+#growing_approach                       0
+
+#maximum CPU time in seconds for post-processing in heuristic approaches, has to be at least >=0   (default: 60)
+#if set to 0, post-processing will be skipped
+#growing_maxTimePostprocessing          60
+
+#relative tolerance for considering dataset as the full dataset, has to be > 0 and < 1    (default: 0.9)
 #growing_dataSizeTol                    0.9
 
-#size of the smallest reduced dataset is x times size of full dataset    (default: 0.1)
+#size of the smallest reduced dataset is x times size of full dataset, has to be > 0 and < 1     (default: 0.1)
 #at least one data point will be used
 #growing_dataSizeInit                   0.1
 
-#rule when to augment the dataset: AUG_RULE_CONST = 0, AUG_RULE_SCALING = 1, AUG_RULE_SCALCST = 2    (default: 2)
-#growing_augmentRule                    2
+#whether to use resampling for the calculation of the lower bound based on the initial dataset, has to be 0 for FALSE and 1 for TRUE    (default: 0)
+#growing_useResampling                  0
+
+#size of new dataset to be added when augmenting relative to number of points of the full dataset    (default: 0.25)
+#growing_augmentPercentage              0.25
+
+#rule when to augment the dataset: AUG_RULE_CONST = 0, AUG_RULE_SCALING = 1, AUG_RULE_VALID = 2, AUG_RULE_COMBI = 3, AUG_RULE_TOL = 4, AUG_RULE_SCALCST = 5, AUG_RULE_VALCST = 6, AUG_RULE_COMBICST = 7, AUG_RULE_TOLCST = 8    (default: 8)
+#when using GROW_APPR_MSEHEURISTIC rules including SCAL(ING) cannot be used
+#growing_augmentRule                    8
 
 #augment dataset in nodes whose depth is a multiple of this freq    (default: 10)
-#this setting will only be used when AUG_RULE_CONST or AUG_RULE_SCALCST has been chosen
+#this setting will only be used when CONST (CST) is part of the augmentation rule
 #growing_augmentFreq                    10
 
 #weighting factor of heuristic lower bound, has to be > 0 and <= 1    (default: 1)
-#this setting will only be used when AUG_RULE_SCALING or AUG_RULE_SCALCST has been chosen
+#this setting will only be used when SCAL(ING) is part of the augmentation rule
 #growing_augmentWeight                  1
 
-#size of new dataset to be added when augmenting relative to number of points of the full dataset    (default: 0.25)
-#growing_augmentPercentage              0.25
+#absolute and relative tolerance on lower bound for augmenting, has to be > min(epsilonA,epsilonR)    (default: 1e-1)
+#this setting will only be used when TOL is part of the augmentation rule
+#growing_augmentTol                     1e-1
 
 
 #-------------------------------------------------------------------------------------------------------------------------------
diff --git a/examples/mainCppApi.cpp b/examples/mainCppApi.cpp
index 294e468..b0c556c 100644
--- a/examples/mainCppApi.cpp
+++ b/examples/mainCppApi.cpp
@@ -10,6 +10,7 @@
  **********************************************************************************/
 
 #include "MAiNGO.h"
+#include "instrumentor.h"
 #include "mpiUtilities.h"
 
 #include "01_BasicExample/problem.h"
@@ -18,10 +19,10 @@
 
 // #include "03_Biobjective/problemEpsCon.h"
 
-/*
-* The following examples require that the CMake flag MAiNGO_build_melon is set to true.
-* Note that the MeLOn toolbox is not compatible with Intel compilers due to missing C++17 features.
-*/
+/**
+ * The following examples require that the CMake flag MAiNGO_build_melon is set to true.
+ * Note that the MeLOn toolbox is not compatible with Intel compilers due to missing C++17 features.
+ */
 // #include "04_ArtificalNeuralNetwork/problemReducedSpace.h"
 // #include "04_ArtificalNeuralNetwork/problemFullSpace.h"
 
@@ -33,11 +34,13 @@
 // #include "06_BayesianOptimization/problemBayesianOptimizationReducedSpace.h"
 // #include "06_BayesianOptimization/problemBayesianOptimizationFullspace.h"
 
-/*
-* The following example requires that the CMake flag MAiNGO_use_growing_datasets is set to true.
-*/
+/**
+ * The following example requires that the CMake flag MAiNGO_use_growing_datasets is set to true.
+ */
 // #include "07_GrowingDatasets/problem_growingDatasets_simple.h"
 
+// #include "08_TwoStage/CHP_sizing.h"
+
 #include <memory>
 
 
@@ -49,7 +52,7 @@
 int
 main(int argc, char *argv[])
 {
-
+    PROFILE_SESSION("MAiNGOcpp");
 #ifdef HAVE_MAiNGO_MPI
     // Initialize MPI and corresponding variable
     MPI_Init(&argc, &argv);
@@ -121,7 +124,7 @@ main(int argc, char *argv[])
         MAiNGO_IF_BAB_MANAGER
             // myMAiNGO->write_model_to_file_in_other_language(maingo::WRITING_LANGUAGE::LANG_ALE,"my_problem_file_MAiNGO.txt","dummySolverName(onlyUsedWhenWritingGAMS)",/*useMinMax*/true,/*useTrig*/true,/*ignoreBoundingFuncs*/true,/*useRelOnly*/false);
         MAiNGO_END_IF
-	maingoStatus = myMAiNGO->solve();
+	    maingoStatus = myMAiNGO->solve();
         // Use this function instead of solve() for solving bi-objective problems using the epsilon-constraint method (don't forget to include the example problem in problemEpsCon.h):
         // maingoStatus = myMAiNGO->solve_epsilon_constraint();
     }
diff --git a/inc/MAiNGO.h b/inc/MAiNGO.h
index e10158d..bd4b5e9 100644
--- a/inc/MAiNGO.h
+++ b/inc/MAiNGO.h
@@ -13,6 +13,7 @@
 
 #include "MAiNGOdebug.h"
 #include "MAiNGOmodel.h"
+#include "TwoStageModel.h"
 #include "constraint.h"
 #include "logger.h"
 #include "returnCodes.h"
@@ -166,6 +167,13 @@ class MAiNGO {
         */
     void set_json_file_name(const std::string &jsonFileName) { _jsonFileName = jsonFileName; }
 
+    /**
+     *  @brief Sets name of the dot file that contains the structure of the branch and bound tree.
+     *
+     *  @param[in] babFileName is the file name.
+     */
+    void set_bab_file_name(const std::string &babFileName) { _babFileName = babFileName; }
+
     /**
         *  @brief Writes MAiNGO model to a a file in a different modeling language.
         *
@@ -381,6 +389,13 @@ class MAiNGO {
         */
     void _initialize_dataset();
 
+    /**
+        *  @brief Auxiliary function for get random initial datasets
+        *
+        *  @param[in] seed is the fixed random seed
+        *  @param[in,out] dataset holds the generated dataset
+        */
+    void _sample_initial_dataset(const unsigned seed, std::set<unsigned int> &dataset);
 #endif
 
     /**
@@ -446,6 +461,13 @@ class MAiNGO {
         *  @brief Prints additional model output on screen
         */
     void _print_additional_output();
+
+#ifdef HAVE_GROWING_DATASETS
+    /**
+        *  @brief Prints statistics of post-processing for heuristic B&B algorithm with growing datasets on screen
+        */
+    void _print_statistics_postprocessing();
+#endif    // HAVE_GROWING_DATASETS
     /**@}*/
 
     /**
@@ -656,6 +678,7 @@ class MAiNGO {
     unsigned _nconstantOutputVariables;                                  /*!< number of constant output variables */
     std::vector<std::string> _outputNames;                               /*!< strings for output variables */
     std::shared_ptr<MAiNGOmodel> _myFFVARmodel;                          /*!< pointer to a MAiNGOmodel object which will be evaluated with mc::FFVar variables */
+    std::shared_ptr<maingo::TwoStageModel> _myTwoStageFFVARmodel;        /*!< pointer to a TwoStageModel object which will be evaluated with mc::FFVar variables */
     EvaluationContainer _modelOutput;                                    /*!< object holding the actual modelOutput in mc::FFVar, it is needed to not lose information on pointers */
     bool _modelSpecified;                                                /*!< flag storing whether a model has been successfully specified */
     bool _DAGconstructed;                                                /*!< flag storing whether the DAG has already been constructed */
@@ -669,8 +692,9 @@ class MAiNGO {
     std::shared_ptr<std::vector<Constraint>> _constantOutputs;           /*!< vector holding all constant outputs */
     std::shared_ptr<std::vector<Constraint>> _nonconstantOutputs;        /*!< vector holding all non-constant outputs */
 #ifdef HAVE_GROWING_DATASETS
-    std::shared_ptr<std::vector<std::set<unsigned int>>> _datasets; /*!< pointer to a vector containing all available datasets. Note: first data point has index 0 */
-#endif                                                              // HAVE_GROWING_MAiNGO
+    std::shared_ptr<std::vector<std::set<unsigned int>>> _datasets;      /*!< pointer to a vector containing all available datasets. Note: first data point has index 0 */
+    std::shared_ptr<std::set<unsigned int>> _datasetResampled;           /*!< pointer to resampled initial dataset. Note: first data point has index 0 */
+#endif    // HAVE_GROWING_MAiNGO
     /**@}*/
 
     /**
@@ -692,11 +716,14 @@ class MAiNGO {
     /**@{*/
     std::vector<double> _solutionPoint;      /*!< vector holding the solution point */
     double _solutionValue;                   /*!< double holding the solution value */
-    double _solutionTime;                    /*!< double holding the solution time in CPU s */
     double _preprocessTime;                  /*!< double holding the solution time in CPU s for pre-processing only */
+    double _preprocessTimeWallClock;         /*!< double holding the solution time in wall clock s for pre-processing only */
+    double _solutionTime;                    /*!< double holding the solution time in CPU s */
+    double _solutionTimeWallClock;           /*!< double holding the solution time in wall clock s */
     double _babTime;                         /*!< double holding the solution time in CPU s for B&B only */
+    double _babTimeWallClock;                /*!< double holding the solution time in wall clock s for B&B only */
     double _outputTime;                      /*!< double holding the time in CPU s for final output only */
-    double _solutionTimeWallClock;           /*!< double holding the solution time in wall clock s */
+    double _outputTimeWallClock;             /*!< double holding the time in wall clock s for final output only */
     RETCODE _maingoStatus;                   /*!< flag storing the return status of MAiNGO */
     PROBLEM_STRUCTURE _problemStructure;     /*!< flag storing the problem structure */
     TIGHTENING_RETCODE _rootObbtStatus;      /*!< flag indicating whether optimization-based bound tightening at the root node found problem to be infeasible */
@@ -733,9 +760,10 @@ class MAiNGO {
     /**@{*/
     std::istream* _inputStream             = &std::cin;                                 /*!< stream from which user input may be read during solution */
     std::shared_ptr<Logger> _logger        = std::make_shared<Logger>(_maingoSettings); /*!< object taking care of printing and saving information to logs */
-    std::string _jsonFileName              = "statisticsAndSolution.json";              /*!< name of the json file into which information about the problem and solution may be written */
-    std::string _resultFileName            = "MAiNGOresult.txt";                        /*!< name of the text file into which the results (solution point, constraints residuals etc.) may be written */
-    std::string _csvSolutionStatisticsName = "statisticsAndSolution.csv";               /*!< name of the csv file into which the solution as well as statistics may be written */
+    std::string _jsonFileName              = "statisticsAndSolution.json"; /*!< name of the json file into which information about the problem and solution may be written */
+    std::string _babFileName               = "";                           /*!< name of the dot file that contains the structure of the branch and bound tree. An empty sting (default) disables writing this file. */
+    std::string _resultFileName            = "MAiNGOresult.txt";           /*!< name of the text file into which the results (solution point, constraints residuals etc.) may be written */
+    std::string _csvSolutionStatisticsName = "statisticsAndSolution.csv";  /*!< name of the csv file into which the solution as well as statistics may be written */
     /**@}*/
 
     /**
@@ -761,4 +789,4 @@ class MAiNGO {
 };    // end of class MAiNGO
 
 
-}    // end of namespace maingo
\ No newline at end of file
+}    // end of namespace maingo
diff --git a/inc/TwoStageModel.h b/inc/TwoStageModel.h
new file mode 100644
index 0000000..cac235e
--- /dev/null
+++ b/inc/TwoStageModel.h
@@ -0,0 +1,349 @@
+/**********************************************************************************
+ * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ **********************************************************************************/
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>     // std::advance
+#include <utility>      // std::pair
+
+#include "MAiNGOmodel.h"
+#include "MAiNGOException.h"
+
+
+using Var = mc::FFVar;
+using NamedVar = std::pair<Var, std::string>;
+
+namespace maingo {
+
+/**
+  * @brief Class defining the abstract TwoStageModel to be specialized by the user
+  * In contrast to the typical specialization of a maingo::MAiNGOmodel the specialization does not require
+  * overriding the evaluate method, but instead the four functions specifying objective and constraints:
+  * - f1_func : first stage objective function
+  * - f2_func : second stage objective function
+  * - g1_func : first stage constraints vector
+  * - g2_func : second stage constraints vector
+  */
+class TwoStageModel: public MAiNGOmodel {
+
+  public:
+    typedef const std::vector<Var> & Varview;
+    typedef const std::vector<double> & Valview;
+  
+  private:
+    /**
+     * @brief validation for w. It must contain at least one entry and the sum of all entries needs to be 1
+     *
+     * @param[in] w weights of the considered scenarios
+     */
+    Valview _validate(Valview w) const {
+      if ( w.size() == 0 ) {
+        throw std::invalid_argument("w must have at least one entry!");
+      }
+      double sum = 0;
+      for (auto & weight : w)
+        sum += weight;
+      if (!mc::isequal(sum, 1., 1e-6, 1e-6)) {
+        throw std::invalid_argument("The sum of weight entries in w must be equal to 1, but is "
+                                    + std::to_string(sum) + " instead!");
+      }
+      return w;
+    };
+  
+    /**
+     * @brief validation for data. It must be the same length as w and all entries must be of the same length.
+     *
+     * @param[in] data a vector the same length as w. Each entry is a vector of parameter values for the corresponding scenario.
+     */
+    const std::vector<std::vector<double>> &_validate(const std::vector<std::vector<double>> &data) const {
+      if ( data.size() != Ns ) {
+        throw std::invalid_argument("w and data must have the same length! "
+                                    "Have " + std::to_string(w.size())
+                                    + " and " + std::to_string(data.size()) + "!");
+      }
+      if ( Ns > 1 ) {
+        auto len = data[0].size();
+        for (auto & data_s : data)
+          if ( data_s.size() != len )
+            throw std::invalid_argument("All entries of data must have same length!");
+      }
+      return data;
+    };
+
+  public:
+    const unsigned int Nx;  // Number of first stage variables
+    const unsigned int Ny;  // Number of second stage variables / scenario
+    const unsigned int Ns;  // number of scenarios
+
+    unsigned int Nineq1;         // number of first stage ineq
+    unsigned int Nsquash1;       // number of first stage squash
+    unsigned int Neq1;           // number of first stage eq
+    unsigned int NineqRelOnly1;  // number of first stage ineqRelOnly
+    unsigned int NeqRelOnly1;    // number of first stage eqRelOnly
+
+    unsigned int Nineq2;         // number of second stage ineq
+    unsigned int Nsquash2;       // number of second stage squash
+    unsigned int Neq2;           // number of second stage eq
+    unsigned int NineqRelOnly2;  // number of second stage ineqRelOnly
+    unsigned int NeqRelOnly2;    // number of second stage eqRelOnly
+
+    // weights for the considered scenarios
+    const std::vector<double> w;
+    // vectors of parameter values for each scenario
+    const std::vector<std::vector<double>> data;
+
+    /**
+     * @brief Constructor for uniformly weighted scenarios
+     *
+     * @param[in] Nx number of first-stage variables
+     * @param[in] Ny number of second-stage variables
+     * @param[in] data |S| vectors with parameters values
+     */
+    TwoStageModel(
+      const unsigned int Nx,
+      const unsigned int Ny,
+      const std::vector<std::vector<double>> &data
+    ): Nx(Nx), Ny(Ny), data{_validate(data)}, w(data.size(), 1.0 / data.size()), Ns{static_cast<unsigned int>(data.size())} {}
+
+    /**
+     * @brief Generic constructor
+     *
+     * @param[in] Nx number of first-stage variables
+     * @param[in] Ny number of second-stage variables
+     * @param[in] data |S| vectors with parameters values
+     * @param[in] w weights for the considered |S| scenarios
+     */
+    TwoStageModel(
+      const unsigned int Nx, 
+      const unsigned int Ny,
+      const std::vector<std::vector<double>> &data,
+      Valview w
+    ): Nx(Nx), Ny(Ny), data{_validate(data)}, w{_validate(w)}, Ns{static_cast<unsigned int>(w.size())} {}
+
+    /**
+     * @brief The first stage objective function
+     */
+    virtual Var f1_func(Varview x) {
+      return 0;
+    }
+
+    /**
+     * @brief The second stage objective function
+     */
+    virtual Var f2_func(Varview x, Varview ys, Valview ps) {
+      return 0;
+    };
+
+    /**
+     * @brief The vector of first stage constraints
+     */
+    virtual std::vector<std::vector<NamedVar>> g1_func(Varview x) {
+      return {
+        {},  // ineq
+        {},  // squash
+        {},  // eq
+        {},  // ineqRelOnly
+        {},  // eqRelOnly
+      };
+    }
+
+    /**
+     * @brief The vector of second stage constraints
+     */
+    virtual std::vector<std::vector<NamedVar>> g2_func(Varview x, Varview ys, Valview ps) {
+      return {
+        {},  // ineq
+        {},  // squash
+        {},  // eq
+        {},  // ineqRelOnly
+        {},  // eqRelOnly
+      };
+    }
+  
+    /**
+     * @brief Main function that can be used as a callback by external code to update FFVars
+     *
+     * @param[in] optVars is the optimization variables vector
+     */
+    virtual void _update(Varview optVars) {
+    }
+
+    /**
+     * @brief Main function used to evaluate the model and construct a directed acyclic graph
+     *
+     * @param[in] optVars is the optimization variables vector
+     */
+    maingo::EvaluationContainer evaluate(Varview optVars){
+
+      _update(optVars);
+
+      if (optVars.size() != Nx + Ns * Ny) {
+        throw MAiNGOException("You seem to have specified a redundant variable that is removed before preprocessing (check console output).\nWe can currently not handle this case, please reformulate your problem so that all variables appear in at least one constraint or the objective!");
+      }
+
+      // Prepare output
+      maingo::EvaluationContainer result;
+
+      std::vector<Var> x = std::vector<Var>();
+      x.reserve(Nx);
+	  x.insert(x.begin(), optVars.begin(), optVars.begin() + Nx);
+
+      // TODO: The moves currently don't do anything, as there's no move assignment implemented!
+      Var f1 = std::move(f1_func(x));
+      auto obj(f1);
+
+      result.output.push_back(maingo::OutputVariable(f1, "f1"));
+
+      std::vector<std::vector<NamedVar>> g1 = g1_func(x);
+      auto & g1_ineq = g1[0];
+      auto & g1_squash = g1[1];
+      auto & g1_eq = g1[2];
+      auto & g1_ineqRO = g1[3];
+      auto & g1_eqRO = g1[4];
+
+      Nineq1 = g1_ineq.size();
+      Nsquash1 = g1_squash.size();
+      Neq1 = g1_eq.size();
+      NineqRelOnly1 = g1_ineqRO.size();
+      NeqRelOnly1 = g1_eqRO.size();
+
+      // TODO: result.ineq is NOT actually a vector... bad docstring!
+      // result.ineq.insert(result.ineq.end(), g1.begin(), g1.end());
+      for (auto i = 0; i < g1_ineq.size(); i++) {
+        const auto &lhs = g1_ineq[i];
+        result.ineq.push_back(lhs.first, lhs.second);
+      }
+      for (auto i = 0; i < g1_squash.size(); i++) {
+        const auto &lhs = g1_squash[i];
+        result.ineqSquash.push_back(lhs.first, lhs.second);
+      }
+      for (auto i = 0; i < g1_eq.size(); i++) {
+        const auto &lhs = g1_eq[i];
+        result.eq.push_back(lhs.first, lhs.second);
+      }
+      for (auto i = 0; i < g1_ineqRO.size(); i++) {
+        const auto &lhs = g1_ineqRO[i];
+        result.ineqRelaxationOnly.push_back(lhs.first, lhs.second);
+      }
+      for (auto i = 0; i < g1_eqRO.size(); i++) {
+        const auto &lhs = g1_eqRO[i];
+        result.eqRelaxationOnly.push_back(lhs.first, lhs.second);
+      }
+
+      for (auto s = 0; s < Ns; s++) {
+        auto s_string = std::to_string(s + 1);
+
+        const auto &result_s = evaluate(optVars, s);
+
+        auto & f2s = result_s.objective.value[0];
+        obj += w[s] * f2s;
+        
+        result.output.push_back(maingo::OutputVariable(f2s, "f2_" + s_string));
+    
+        auto & g2s_ineq = result_s.ineq.value;
+        auto & g2s_ineq_names = result_s.ineq.name;
+        auto & g2s_squash = result_s.ineqSquash.value;
+        auto & g2s_squash_names = result_s.ineqSquash.name;
+        auto & g2s_eq = result_s.eq.value;
+        auto & g2s_eq_names = result_s.eq.name;
+        auto & g2s_ineqRO = result_s.ineqRelaxationOnly.value;
+        auto & g2s_ineqRO_names = result_s.ineqRelaxationOnly.name;
+        auto & g2s_eqRO = result_s.eqRelaxationOnly.value;
+        auto & g2s_eqRO_names = result_s.eqRelaxationOnly.name;
+
+        // TODO: result.ineq is NOT actually a vector... bad docstring!
+        // result.ineq.insert(result.ineq.end(), g1.begin(), g1.end());
+        for (auto i = 0; i < g2s_ineq.size(); i++) {
+          result.ineq.push_back(g2s_ineq[i], g2s_ineq_names[i] + '_' + s_string);
+        }
+        for (auto i = 0; i < g2s_squash.size(); i++) {
+          result.ineqSquash.push_back(g2s_squash[i], g2s_squash_names[i] + '_' + s_string);
+        }
+        for (auto i = 0; i < g2s_eq.size(); i++) {
+          result.eq.push_back(g2s_eq[i], g2s_eq_names[i] + '_' + s_string);
+        }
+        for (auto i = 0; i < g2s_ineqRO.size(); i++) {
+          result.ineqRelaxationOnly.push_back(g2s_ineqRO[i], g2s_ineqRO_names[i] + '_' + s_string);
+        }
+        for (auto i = 0; i < g2s_eqRO.size(); i++) {
+          result.eqRelaxationOnly.push_back(g2s_eqRO[i], g2s_eqRO_names[i] + '_' + s_string);
+        }
+
+        if (s == 0) {  // NOTE: we assume subproblems to have equal structure, so we only need to do this once
+          Nineq2 = g2s_ineq.size();
+          Nsquash2 = g2s_squash.size();
+          Neq2 = g2s_eq.size();
+          NineqRelOnly2 = g2s_ineqRO.size();
+          NeqRelOnly2 = g2s_eqRO.size();
+        }
+      }
+      result.objective = obj;
+
+      return result;
+    };
+
+    /**
+     * @brief Main function used to evaluate the model and construct a directed acyclic graph
+     *
+     * @param[in] optVars is the vector of all variables
+     * @param[in] s is the scenario index
+     */
+    maingo::EvaluationContainer evaluate(const std::vector<Var> &optVars, unsigned int s){
+
+      // Prepare output
+      maingo::EvaluationContainer result_s;
+
+      std::vector<Var> x = std::vector<Var>();
+      x.reserve(Nx);
+      x.insert(x.begin(), optVars.begin(), optVars.begin() + Nx);
+    
+      std::vector<Var> ys = std::vector<Var>();
+      ys.reserve(Ny);
+      ys.insert(ys.begin(), optVars.begin() + Nx + s * Ny, optVars.begin() + Nx + (s + 1) * Ny); 
+      
+      Valview ps = data[s];
+
+      auto f2s = f2_func(x, ys, ps);
+
+      result_s.objective = f2s;
+      auto s_string = std::to_string(s);
+      result_s.output.push_back(maingo::OutputVariable(f2s, "f2_" + s_string));
+
+      auto g2s = g2_func(x, ys, ps);
+      auto & g2s_ineq = g2s[0];
+      auto & g2s_squash = g2s[1];
+      auto & g2s_eq = g2s[2];
+      auto & g2s_ineqRO = g2s[3];
+      auto & g2s_eqRO = g2s[4];
+
+      // TODO: result.ineq is NOT actually a vector... bad docstring!
+      // result.ineq.insert(result.ineq.end(), g1.begin(), g1.end());
+      for (auto i = 0; i < g2s_ineq.size(); i++) {
+        result_s.ineq.push_back(g2s_ineq[i].first, g2s_ineq[i].second);
+      }
+      for (auto i = 0; i < g2s_squash.size(); i++) {
+        result_s.ineqSquash.push_back(g2s_squash[i].first, g2s_squash[i].second);
+      }
+      for (auto i = 0; i < g2s_eq.size(); i++) {
+        result_s.eq.push_back(g2s_eq[i].first, g2s_eq[i].second);
+      }
+      for (auto i = 0; i < g2s_ineqRO.size(); i++) {
+        result_s.ineqRelaxationOnly.push_back(g2s_ineqRO[i].first, g2s_ineqRO[i].second);
+      }
+      for (auto i = 0; i < g2s_eqRO.size(); i++) {
+        result_s.eqRelaxationOnly.push_back(g2s_eqRO[i].first, g2s_eqRO[i].second);
+      }
+
+      return result_s;
+    };
+  };
+};
\ No newline at end of file
diff --git a/inc/bab.h b/inc/bab.h
index 905158e..8e88b1b 100644
--- a/inc/bab.h
+++ b/inc/bab.h
@@ -19,30 +19,32 @@
 #endif
 
 #include "babBrancher.h"
+#include "siblingResults.h"
 
 #include <cmath>
 #include <map>
 #include <memory>
 #include <string>
 #include <vector>
+#include <array>
 
 
 namespace maingo {
 
 
 namespace lbp {
-class LowerBoundingSolver;
-struct LbpDualInfo;
+  class LowerBoundingSolver;
+  struct LbpDualInfo;
 }    // namespace lbp
 namespace ubp {
-class UpperBoundingSolver;
+  class UpperBoundingSolver;
 }    // namespace ubp
 
 
 /**
-*   @namespace maingo::bab
-*   @brief namespace holding everything related to the actual branch-and-bound algorithm
-*/
+ *   @namespace maingo::bab
+ *   @brief namespace holding everything related to the actual branch-and-bound algorithm
+ */
 namespace bab {
 
 
@@ -55,112 +57,163 @@ namespace bab {
  * node of the BAB tree, as well as duality-based bounds tightening (DBBT) and probing (cf. Ryoo&Sahinidis, Comput. Chem. Eng. 19 (1995) 551).
  * It also contains a multi-start local search from randomly generated initial points at the root node. Lower and upper bounding are conducted by the respective lower and upper bounding solvers (LBS / UBS).
 */
-class BranchAndBound {
+class BranchAndBound : public babBase::FathomObserver {
 
   public:
     /**
-        * @brief Constructor, stores information on problem and settings
-        *
-        * @param[in] variables is a vector containing the initial optimization variables defined in problem.h
-        * @param[in] LBSIn is a pointer to the LowerBoundingSolver object
-        * @param[in] UBSIn is a pointer to the UpperBoundingSolver object
-        * @param[in] settingsIn is a pointer to an object containing the settings for the Branch-and-Bound solvers
-        * @param[in] loggerIn is a pointer to the MAiNGO logger object
-        * @param[in] nvarWOaux is the number of optimization variables without the additional auxiliary variables added by the LBP_addAuxiliaryVars option
-        * @param[in] inputStream is the address of the input stream from which user input may be read during solution
-        */
+     * @brief Constructor, stores information on problem and settings
+     *
+     * @param[in] variables is a vector containing the initial optimization variables defined in problem.h
+     * @param[in] LBSIn is a pointer to the LowerBoundingSolver object
+     * @param[in] UBSIn is a pointer to the UpperBoundingSolver object
+     * @param[in] settingsIn is a pointer to an object containing the settings for the Branch-and-Bound solvers
+     * @param[in] loggerIn is a pointer to the MAiNGO logger object
+     * @param[in] nvarWOaux is the number of optimization variables without the additional auxiliary variables added by the LBP_addAuxiliaryVars option
+     * @param[in] inputStream is the address of the input stream from which user input may be read during solution
+     * @param[in] babFileName is the name of the file to which the B&B tree is written in dot format
+     */
     BranchAndBound(const std::vector<babBase::OptimizationVariable> &variables, std::shared_ptr<lbp::LowerBoundingSolver> LBSIn, std::shared_ptr<ubp::UpperBoundingSolver> UBSIn,
-                   std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, const unsigned nvarWOaux, std::istream *const inputStream = &std::cin);
+                   std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, const unsigned nvarWOaux, std::istream *const inputStream = &std::cin, const std::string & babFileName = "");
 
     /**
-        * @brief Destructor
-        */
-    ~BranchAndBound() {}
+     * @brief Main function to solve the optimization problem
+     * @param[in] rootNodeIn Root node to start Branch&Bound on.
+     * @param[in,out] solutionValue Objective value of best feasible point found (empty if no feasible point was found); Also used for communicating objective value of initial feasible point.
+     * @param[in,out] solutionPoint Solution point, i.e., (one of) the point(s) at which the best objective value was found (empty if no feasible point was found); Also used for communicating initial feasible point.
+     * @param[in] preprocessTime Is the CPU time spent in pre-processing before invoking this solve routine (needed for correct output of total CPU time during B&B)
+     * @param[in] preprocessWallTime Is the wall time spent in pre-processing before invoking this solve routine (needed for correct output of total wall time during B&B)
+     * @param[out] timePassed Is the CPU time spent in B&B (especially useful if time is >24h)
+     * @param[out] wallTimePassed Is the wall time spent in B&B (especially useful if time is >24h)
+     * @return Return code summarizing the solution status.
+     */
+    babBase::enums::BAB_RETCODE solve(babBase::BabNode &rootNodeIn, double &solutionValue, std::vector<double> &solutionPoint, const double preprocessTime, const double preprocessWallTime, double &timePassed, double &wallTimePassed);
 
+#ifdef HAVE_GROWING_DATASETS
     /**
-        * @brief Main function to solve the optimization problem
-        * @param[in] rootNodeIn Root node to start Branch&Bound on.
-        * @param[in,out] solutionValue Objective value of best feasible point found (empty if no feasible point was found); Also used for communicating objective value of initial feasible point.
-        * @param[in,out] solutionPoint Solution point, i.e., (one of) the point(s) at which the best objective value was found (empty if no feasible point was found); Also used for communicating initial feasible point.
-        * @param[in] preprocessTime Is the CPU time spent in pre-processing before invoking this solve routine (needed for correct output of total CPU time during B&B)
-        * @param[out] timePassed Is the CPU time spent in B&B (especially useful if time is >24h)
-        * @return Return code summarizing the solution status.
+        * @brief Function for post-processing nodes in heuristic approach with growing datasets
+        *
+        * @param[in] finalUBD Is the objective value of the best feasible point found by the B&B algorithm
         */
-    babBase::enums::BAB_RETCODE solve(babBase::BabNode &rootNodeIn, double &solutionValue, std::vector<double> &solutionPoint, const double preprocessTime, double &timePassed);
+    void postprocess(const double &finalUBD);
+#endif    // HAVE_GROWING_DATASETS
 
     /**
-        *  @brief Function returning the number of iterations
-        */
+     *  @brief Function returning the number of iterations
+     */
     double get_iterations() { return _iterations; }
 
     /**
-        *  @brief Function returning the maximum number of nodes in memory
-        */
+     *  @brief Function returning the maximum number of nodes in memory
+     */
     double get_max_nodes_in_memory() { return _nNodesMaxInMemory; }
 
     /**
-        *  @brief Function returning number of UBD problems solved
-        */
+     *  @brief Function returning number of UBD problems solved
+     */
     double get_UBP_count() { return _ubdcnt; }
 
     /**
-        *  @brief Function returning number of LBD problems solved
-        */
+     *  @brief Function returning number of LBD problems solved
+     */
     double get_LBP_count() { return _lbdcnt; }
 
     /**
-        *  @brief Function returning the final LBD
-        */
+     *  @brief Function returning the final LBD
+     */
     double get_final_LBD() { return _lbd; }
 
     /**
-        *  @brief Function returning the final absolute gap
-        */
+     *  @brief Function returning the final absolute gap
+     */
     double get_final_abs_gap() { return _ubd - _lbd; }
 
     /**
-        *  @brief Function returning the final relative gap
-        */
+     *  @brief Function returning the final relative gap
+     */
     double get_final_rel_gap() { return ((_ubd == 0) ? (get_final_abs_gap()) : ((_ubd - _lbd) / std::fabs(_ubd))); }
 
     /**
-        *  @brief Function returning the ID of the node where the incumbent was first found
-        */
+     *  @brief Function returning the ID of the node where the incumbent was first found
+     */
     double get_first_found() { return _firstFound; }
 
     /**
-        *  @brief Function returning the number of nodes left after termination of B&B
-        */
+     *  @brief Function returning the number of nodes left after termination of B&B
+     */
     double get_nodes_left() { return _nNodesLeft; }
 
 #ifdef HAVE_GROWING_DATASETS
     /**
-    * @brief Function for passing pointer of vector containing datasets to B&B
-    *
-    * @param[in] datasetsIn is a pointer to a vector containing all available datasets
-    */
+        *  @brief Function returning the number of nodes tracked for postprocessing the heuristic B&B algorithm with growing datasets
+        */
+    double get_nodes_tracked_for_postprocessing() { return _nNodesTrackedPost; }
+
+    /**
+        *  @brief Function returning the number of nodes processed in postprocessing of the heuristic B&B algorithm with growing datasets
+        */
+    double get_nodes_processed_in_postprocessing() { return _nNodesProcessedPost; }
+
+    /**
+        *  @brief Function returning the final lower bound after postprocessing the heuristic B&B algorithm with growing datasets
+        */
+    double get_lower_bound_after_postprocessing() { return _lbdPostpro; }
+
+    /**
+        *  @brief Function returning the CPU time spent for postprocessing the heuristic B&B algorithm with growing datasets
+        */
+    double get_time_for_postprocessing() { return _timePostpro; }
+
+    /**
+     * @brief Function for passing pointer of vector containing datasets to B&B
+     *
+     * @param[in] datasetsIn is a pointer to a vector containing all available datasets
+     */
     void pass_datasets_to_bab(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasetsIn)
     {
         _datasets = datasetsIn;
     }
-#endif    // HAVE_GROWING_DATASETS
-#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS)
+  #ifdef MAiNGO_DEBUG_MODE
     /**
-	* @brief Function for passing pointer of LBS with full dataset to B&B
-	*
-	* @param[in] LBSFullIn is a pointer to an LBS object using the full dataset only
-	*/
+	 * @brief Function for passing pointer of LBS with full dataset to B&B
+	 *
+	 * @param[in] LBSFullIn is a pointer to an LBS object using the full dataset only
+	 */
     void pass_LBSFull_to_bab(const std::shared_ptr<lbp::LowerBoundingSolver> LBSFullIn)
     {
         _LBSFull = LBSFullIn;
     }
-#endif
+  #endif
+#endif    // HAVE_GROWING_DATASETS
+
+    /**
+     * @brief Setup the use of _two_stage_branching.
+     * 
+     * @param[in] Nx Number of first-stage variables.
+     * @param[in] Ny Number of second-stage variables.
+     * @param[in] w Vector of weights for the scenarios.
+     * @param[in] solve_sibling_subproblems Callable for solving LBP subproblems of sibling nodes.
+     * @param[in] alpha Strong branching score threshold.
+     * @param[in] k_max Base 2 log of the maximum number of nodes generated during multi section branching.
+     */
+    void setup_two_stage_branching(
+        unsigned Nx, unsigned Ny, const std::vector<double> &w,
+        std::vector<double> &subproblemBounds,
+        std::function<
+            void(
+                lbp::SiblingResults & siblingResults,
+                double ubd,
+                int obbtType
+            )
+        > solve_sibling_subproblems,
+        double alpha,
+        unsigned int k_max
+    );
 
   private:
     /**
-        * @enum _TERMINATION_TYPE
-        * @brief Enum for representing different termination types in B&B
-        */
+     * @enum _TERMINATION_TYPE
+     * @brief Enum for representing different termination types in B&B
+     */
     enum _TERMINATION_TYPE {
         _TERMINATED = 0,            /*!< termination condition has been reached and no worker is processing any nodes */
         _TERMINATED_WORKERS_ACTIVE, /*!< termination condition has been reached, but there are still nodes being processed by workers */
@@ -168,118 +221,205 @@ class BranchAndBound {
     };
 
     /**
-        * @brief Function processing the current node
+     * @brief Function processing the current node
+     *
+     * @param[in,out] currentNodeInOut  The node to be processed
+     */
+    std::tuple<bool, bool, int, int, double, std::vector<double>, bool, double, std::vector<double>> _process_node(babBase::BabNode &currentNodeInOut);
+
+    /**
+     * @brief Function to process sibling nodes obtained from second-stage branching.
+     * 
+     * @param[in] siblingResults The struct storing the results of the sibling iteration.
+     */
+    void _process_siblings(lbp::SiblingResults &siblingResults);
+
+    /**
+     * @brief Function for pre-processing the current node. Includes bound tightening and OBBT.
+     *
+     * @param[in,out] currentNodeInOut  The node to be processed
+     * @return Flag indicating whether the node was proven to be infeasible
+     */
+    bool _preprocess_node(babBase::BabNode &currentNodeInOut);
+
+    /**
+     * @brief Function invoking the LBS to solve the lower bounding problem
+     *
+     * @param[in] currentNode  The node to be processed
+     * @return Tuple consisting of flags for whether the node is infeasible and whether it is converged, the lower bound, the lower bounding solution point, and dual information for DBBT
+     */
+    std::tuple<bool, bool, double, std::vector<double>, lbp::LbpDualInfo> _solve_LBP(const babBase::BabNode &currentNode);
+
+    /**
+     * @brief Function invoking the UBS to solve the upper bounding problem
+     *
+     * @param[in] currentNode  The node to be processed
+     * @param[in,out] ubpSolutionPoint  On input: initial point for local search. On output: solution point.
+     * @param[in] currentLBD Lower bound of current Node. Needed for sanity check.
+     * @return Tuple consisting of flags indicating whether a new feasible point has been found and whether the node converged, and the optimal objective value of the new point
+     */
+    std::tuple<bool, bool, double> _solve_UBP(const babBase::BabNode &currentNode, std::vector<double> &ubpSolutionPoint, const double currentLBD);
+
+    /**
+     * @brief Function for post-processing the current node. Includes bound DBBT and probing
+     *
+     * @param[in,out] currentNodeInOut  The node to be processed
+     * @param[in] lbpSolutionPoint  Solution point of the lower bounding problem
+     * @param[in] dualInfo is a struct containing information from the LP solved during LBP
+     * @return Flag indicating whether the node has converged
+     */
+    bool _postprocess_node(babBase::BabNode &currentNodeInOut, const std::vector<double> &lbpSolutionPoint, const lbp::LbpDualInfo &dualInfo);
+
+    /**
+     * @brief Function for updating the incumbent and fathoming accordingly
+     *
+     * @param[in] solval is the value of the processed solution
+     * @param[in] sol is the solution point
+     * @param[in] currentNodeID is the ID of the new node holding the incumbent (it is used instead of directly giving the node to match the parallel implementation)
+     */
+    void _update_incumbent_and_fathom(const double solval, const std::vector<double> sol, const unsigned int currentNodeID);
+
+    /**
+     * @brief Function for updating the global lower bound
+     */
+    void _update_lowest_lbd();
+
+#ifdef HAVE_GROWING_DATASETS
+    /**
+        * @brief Auxiliary function for calling augmentation rule CONST
         *
-        * @param[in,out] currentNodeInOut  The node to be processed
+        * @param[in] depth is the depth of the node calling the augmentation rule
+        * @param[out] boolean indicating whether to augment (true) or branch (false)
         */
-    std::tuple<bool, bool, int, int, double, std::vector<double>, bool, double, std::vector<double>> _process_node(babBase::BabNode &currentNodeInOut);
+    bool _call_augmentation_rule_const(const int depth);
 
     /**
-        * @brief Function for pre-processing the current node. Includes bound tightening and OBBT.
+        * @brief Auxiliary function for calling augmentation rule SCALING
         *
-        * @param[in,out] currentNodeInOut  The node to be processed
-        * @return Flag indicating whether the node was proven to be infeasible
+        * @param[in] indexDataset is the index of the the dataset of the node calling the augmentation rule
+        * @param[in] currentLBD is the current lower bound used for pruning
+        * @param[out] boolean indicating whether to augment (true) or branch (false)
         */
-    bool _preprocess_node(babBase::BabNode &currentNodeInOut);
+    bool _call_augmentation_rule_scaling(const int indexDataset, const double currentLBD);
 
     /**
-        * @brief Function invoking the LBS to solve the lower bounding problem
+        * @brief Function for evaluating solution point of lower bounding problem with validation set
         *
-        * @param[in] currentNode  The node to be processed
-        * @return Tuple consisting of flags for whether the node is infeasible and whether it is converged, the lower bound, the lower bounding solution point, and dual information for DBBT
+        * @param[in] currentNode is the node to be processed
+        * @param[in] lbpSolutionPoint is the solution point of the LBP of the current node
+        * @param[in] currentLBD is the objective value of the LBP of the current node
         */
-    std::tuple<bool, bool, double, std::vector<double>, lbp::LbpDualInfo> _solve_LBP(const babBase::BabNode &currentNode);
+    double _evaluate_lower_bound_for_validation_set(const babBase::BabNode& currentNode, const std::vector<double>& lbpSolutionPoint, const double currentLBD);
 
     /**
-        * @brief Function invoking the UBS to solve the upper bounding problem
+        * @brief Function for calculating approximated lower bound as combination of reduced and validated lower bound
         *
-        * @param[in] currentNode  The node to be processed
-        * @param[in,out] ubpSolutionPoint  On input: initial point for local search. On output: solution point.
-        * @param[in] currentLBD Lower bound of current Node. Needed for sanity check.
-        * @return Tuple consisting of flags indicating whether a new feasible point has been found and whether the node converged, and the optimal objective value of the new point
+        * @param[in] currentLBD is the objective value of the LBP of the current node
+        * @param[in] validatedLBD is the evaluation of the LBP with the validation dataset at the optimal lower bounding point
+        * @param[in] indexDataset is the index of the dataset of the current node
         */
-    std::tuple<bool, bool, double> _solve_UBP(const babBase::BabNode &currentNode, std::vector<double> &ubpSolutionPoint, const double currentLBD);
+    double _calculate_combined_lower_bound(const double currentLBD, const double validatedLBD, const unsigned int indexDataset);
 
     /**
-        * @brief Function for post-processing the current node. Includes bound DBBT and probing
+        * @brief Auxiliary function for calling augmentation rule VALID
         *
-        * @param[in,out] currentNodeInOut  The node to be processed
-        * @param[in] lbpSolutionPoint  Solution point of the lower bounding problem
-        * @param[in] dualInfo is a struct containing information from the LP solved during LBP
-        * @return Flag indicating whether the node has converged
+        * @param[in] validatedLBD is the evaluation of the LBP with the validation dataset at the optimal lower bounding point
+        * @param[out] boolean indicating whether to augment (true) or branch (false)
         */
-    bool _postprocess_node(babBase::BabNode &currentNodeInOut, const std::vector<double> &lbpSolutionPoint, const lbp::LbpDualInfo &dualInfo);
+    bool _call_augmentation_rule_valid(const double validatedLBD);
 
     /**
-        * @brief Function for updating the incumbent and fathoming accordingly
+        * @brief Auxiliary function for calling augmentation rule COMBI
         *
-        * @param[in] solval is the value of the processed solution
-        * @param[in] sol is the solution point
-        * @param[in] currentNodeID is the ID of the new node holding the incumbent (it is used instead of directly giving the node to match the parallel implementation)
+        * @param[in] currentLBD is the objective value of the LBP of the current node
+        * @param[in] validatedLBD is the evaluation of the LBP with the validation dataset at the optimal lower bounding point
+        * @param[in] indexDataset is the index of the dataset of the current node
         */
-    void _update_incumbent_and_fathom(const double solval, const std::vector<double> sol, const unsigned int currentNodeID);
+    bool _call_augmentation_rule_combi(const double currentLBD, const double validatedLBD, const unsigned int indexDataset);
 
     /**
-        * @brief Function for updating the global lower bound
+        * @brief Auxiliary function for calling augmentation rule TOL
+        *
+        * @param[in] currentLBD is the objective value of the LBP of the current node
+        * @param[in] validatedLBD is the evaluation of the LBP with the validation dataset at the optimal lower bounding point
+        * @param[in] indexDataset is the index of the dataset of the current node
         */
-    void _update_lowest_lbd();
+    bool _call_augmentation_rule_tol(const double currentLBD, const double validatedLBD, const unsigned int indexDataset);
 
-#ifdef HAVE_GROWING_DATASETS
     /**
     * @brief Function which checks whether to augment the dataset
     *
     * @param[in] currentNode is the node to be processed
     * @param[in] lbpSolutionPoint is the solution point of the LBP of the current node
     * @param[in] currentLBD is the objective value of the LBP of the current node
+    * @param[in] validatedLBD is the evaluation of the LBP with the validation dataset at the optimal lower bounding point
     */
-    bool _check_whether_to_augment(const babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const double currentLBD);
+    bool _check_whether_to_augment(const babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const double currentLBD, const double validatedLBD);
 
     /**
-    * @brief Function for augmenting dataset of node
-    *
-    * @param[in] current index of dataset
-    * @param[out] new index of dataset after augmentation
-    */
+     * @brief Function for augmenting dataset of node
+     *
+     * @param[in] current index of dataset
+     * @param[out] new index of dataset after augmentation
+     */
     unsigned int _augment_dataset(babBase::BabNode &currentNode);
 #endif    // HAVE_GROWING_DATASETS
 
     /**
-        * @brief Function which checks whether it is necessary to activate scaling within the LBD solver. This is a heuristic approach, which does not affect any deterministic optimization assumptions
-        */
+     * @brief Function which checks whether it is necessary to activate scaling within the LBD solver. This is a heuristic approach, which does not affect any deterministic optimization assumptions
+     */
     void _check_if_more_scaling_needed();
 
     /**
-        * @brief Function for checking if the B&B algorithm terminated
-        */
+     * @brief Function for checking if the B&B algorithm terminated
+     */
     _TERMINATION_TYPE _check_termination();
 
+#ifdef HAVE_GROWING_DATASETS
     /**
-        * @brief Function for printing the current progress on the screen and appending it to the internal log to be written to file later
-        *
-        * @param[in] currentNodeLBD is the lower bound for the current node
-        * @param[in] currentNode is the current node
+        * @brief Function for checking if post-processing of heuristic approach terminated
         */
+    _TERMINATION_TYPE _check_termination_postprocessing();
+#endif     // HAVE_GROWING_DATASETS
+
+    /**
+     * @brief Function for printing the current progress on the screen and appending it to the internal log to be written to file later
+     *
+     * @param[in] currentNodeLBD is the lower bound for the current node
+     * @param[in] currentNode is the current node
+     */
     void _display_and_log_progress(const double currentNodeLBD, const babBase::BabNode &currentNode);
 
 #if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS)
     /**
-		* @brief Function for printing the current node to a separate log file
-		*
-		* @param[in] currentNode is the current node
-		* @param[in] currentNodeLbd is the lower bound calculated in the current node
-		* @param[in] currentNodeLbdFull is the lower bound calculated based on the full dataset in the current node
-		* @param[in] currentNodeUbdFull is the upper bound of the current node
-		* @param[in] lbpSolutionPoint is the solution point of the lower bound calculated und used in the current node
-		* @param[in] lbpSolutionPointFullis the solution point of the lower bound calculated based on the full dataset in the current node
-		*/
+	 * @brief Function for printing the current node to a separate log file
+	 *
+	 * @param[in] currentNode is the current node
+	 * @param[in] currentNodeLbd is the lower bound calculated in the current node
+	 * @param[in] currentNodeLbdFull is the lower bound calculated based on the full dataset in the current node
+	 * @param[in] currentNodeUbdFull is the upper bound of the current node
+	 * @param[in] lbpSolutionPoint is the solution point of the lower bound calculated und used in the current node
+	 * @param[in] lbpSolutionPointFullis the solution point of the lower bound calculated based on the full dataset in the current node
+	 */
     void _log_nodes(const babBase::BabNode &currentNode, const double currentNodeLbd, const double currentNodeLbdFull, const double currentNodeUbdFull, const std::vector<double> lbpSolutionPoint, const std::vector<double> lbpSolutionPointFull);
 #endif
 
+#ifdef HAVE_GROWING_DATASETS
     /**
-        * @brief Function printing a termination message
+        * @brief Function for printing post-processing information of one node
         *
-        * @param[in] message is a string holding the message to print
+        * @param[in] ID is the id of the node
+        * @param[in] oldLBD is the lower bound of the node before post-processing
+        * @param[in] newLBD is the lower bound of the node after post-processing
         */
+    void _print_postprocessed_node(const int ID, const double oldLBD, const double newLBD);
+#endif // HAVE_GROWING_DATASETS
+
+    /**
+     * @brief Function printing a termination message
+     *
+     * @param[in] message is a string holding the message to print
+     */
     void _print_termination(std::string message);
 
     /**
@@ -292,128 +432,161 @@ class BranchAndBound {
 
 #ifdef HAVE_MAiNGO_MPI
     /**
-        * @name MPI management and communication functions of manager
-        */
+     * @name MPI management and communication functions of manager
+     */
     /**@{*/
     /**
-        * @brief Function for dealing with exceptions (informing workers etc.)
-        *
-        * @param[in] e is the exception to be handled
-        */
+     * @brief Function for dealing with exceptions (informing workers etc.)
+     *
+     * @param[in] e is the exception to be handled
+     */
     void _communicate_exception_and_throw(const maingo::MAiNGOMpiException &e);
 
     /**
-        * @brief Auxiliary function for receiving solved problems from workers
-        *
-        * @param[out] node is the node corresponding to the solved problem
-        * @param[out] lbd is the new lowerbound for the node
-        * @param[out] lbdSolutionPoint is the solution point of the node
-        * @param[out] lbdcnt is the number of lower bounding problems that were solved during solving the node
-        * @param[out] ubdcnt is the number of upper bounding problems that were solved during solving the node
-        * @param[in] status is the status of the node after solving it (NORMAL, CONVERGED, INFEASIBLE)
-        * @param[in] src is the worker who solved the problem
-        */
-    void _recv_solved_problem(babBase::BabNode &node, double &lbd, std::vector<double> &lbdSolutionPoint, unsigned &lbdcnt,
+     * @brief Auxiliary function for receiving solved problems from workers
+     *
+     * @param[out] node is the node corresponding to the solved problem
+     * @param[out] sibling is the sibling node of the solved node
+     * @param[out] siblingResults is the results from solving the sibling nodes
+     * @param[out] lbd is the new lowerbound for the node
+     * @param[out] lbdSolutionPoint is the solution point of the node
+     * @param[out] lbdcnt is the number of lower bounding problems that were solved during solving the node
+     * @param[out] ubdcnt is the number of upper bounding problems that were solved during solving the node
+     * @param[in] status is the status of the node after solving it (NORMAL, CONVERGED, INFEASIBLE)
+     * @param[in] src is the worker who solved the problem
+     */
+    void _recv_solved_problem(babBase::BabNode &node, 
+                              babBase::BabNode &sibling, lbp::SiblingResults &siblingResults,
+                              double &lbd, std::vector<double> &lbdSolutionPoint, unsigned &lbdcnt,
                               unsigned &ubdcnt, const COMMUNICATION_TAG status, const int src);
 
     /**
-        * @brief Auxiliary function for sending a new problem to a worker
-        *
-        * @param[in] node is the node that is sent to the worker for solving
-        * @param[in] dest is the worker to whom the problem is sent
-        */
+     * @brief Auxiliary function for sending a new problem to a worker
+     *
+     * @param[in] node is the node that is sent to the worker for solving
+     * @param[in] dest is the worker to whom the problem is sent
+     */
     void _send_new_problem(const babBase::BabNode &node, const int dest);
 
+    /**
+     * @brief Auxiliary function for sending a new problem corresponding to a sibling iteration to a worker
+     *
+     * @param[in] lower_sibling is the lower sibling node
+     * @param[in] upper_sibling is the upper sibling node
+     * @param[in] siblingResults is a pointer to the siblingResults object
+     */
+    void _send_new_sibling_problem(const babBase::BabNode &lower_sibling, const babBase::BabNode &upper_sibling, const int dest);
+
+    /**
+     * @brief Auxiliary function for sending the incumbent to a worker   
+     * 
+     * @param[in] dest is the worker to whom the incumbent is sent  
+     */
+    inline void send_incumbent(const int dest);
+
 #ifdef HAVE_GROWING_DATASETS
     /**
-    * @brief Auxiliary function for sending new dataset to a worker
-    *
-    * @param[in] newDataset is the dataset that is sent to the worker for appending the datasets vector
-    * @param[in] dest is the worker to whom the problem is sent
-    */
+     * @brief Auxiliary function for sending new dataset to a worker
+     *
+     * @param[in] newDataset is the dataset that is sent to the worker for appending the datasets vector
+     * @param[in] dest is the worker to whom the problem is sent
+     */
     void _send_new_dataset(const std::set<unsigned int> &newDataset, const int dest);
 #endif    // HAVE_GROWING_DATASETS
 
     /**
-        * @brief Auxillary function for informing workers about occuring events
-        *
-        * @param[in] eventTag is the tag corresponding to the event the workers should be informed of
-        * @param[in] blocking is a flag indicating if the communication should be performed in a blocking or non-blocking manner
-        */
+     * @brief Auxillary function for informing workers about occuring events
+     *
+     * @param[in] eventTag is the tag corresponding to the event the workers should be informed of
+     * @param[in] blocking is a flag indicating if the communication should be performed in a blocking or non-blocking manner
+     */
     void _inform_worker_about_event(const BCAST_TAG eventTag, const bool blocking);
     /**@}*/
 
     /**
-        * @name MPI management and communication functions of worker
-        */
+      * @name MPI management and communication functions of worker
+      */
     /**@{*/
     /**
-        * @brief Auxiliary function for receiving a new problem from the manager
-        *
-        * @param[out] node is the node that is received from the manager for solving
-        */
-    void _recv_new_problem(babBase::BabNode &node);
+     * @brief Auxiliary function for receiving a new problem from the manager
+     *
+     * @param[out] node is the node that is received from the manager for solving
+     * @param[out] sibling is the (upper) sibling node if the two nodes are siblings
+     */
+    babBase::enums::ITERATION_TYPE _recv_new_problem(babBase::BabNode &node, babBase::BabNode &sibling);
 
 #ifdef HAVE_GROWING_DATASETS
     /**
-    * @brief Auxiliary function for receiving a new dataset from the manager
-    */
+     * @brief Auxiliary function for receiving a new dataset from the manager
+     */
     void _recv_new_dataset();
 #endif    // HAVE_GROWING_DATASETS
 
     /**
-        * @brief Auxiliary function for sending a new incumbent to the manager
-        *
-        * @param[in] ubd is the objective value of the found incumbent
-        * @param[in] incumbent is the found incumbent point
-        * @param[in] incumbentID is the ID of the node which holds the found incumbent
-        */
+     * @brief Auxiliary function for sending a new incumbent to the manager
+     *
+     * @param[in] ubd is the objective value of the found incumbent
+     * @param[in] incumbent is the found incumbent point
+     * @param[in] incumbentID is the ID of the node which holds the found incumbent
+     */
     void _send_incumbent(const double ubd, const std::vector<double> incumbent, const unsigned incumbentID);
 
     /**
-        * @brief Auxiliary function for sending a solved problem to the manager
-        *
-        * @param[in] node is the solved node which is sent to the manager
-        * @param[in] lbd is the new lbd found for the node
-        * @param[in] lbdSolutionPoint is the solution point of the node
-        * @param[in] lbdcnt is the number of lower bounding problems that were solved during solving the node
-        * @param[in] ubdcnt is the number of upper bounding problems that were solved during solving the node
-        * @param[in] status is the status of the node after solving it (NORMAL, CONVERGED, INFEASIBLE)
-        */
+     * @brief Auxiliary function for sending a solved problem to the manager
+     *
+     * @param[in] node is the solved node which is sent to the manager
+     * @param[in] lbd is the new lbd found for the node
+     * @param[in] lbdSolutionPoint is the solution point of the node
+     * @param[in] lbdcnt is the number of lower bounding problems that were solved during solving the node
+     * @param[in] ubdcnt is the number of upper bounding problems that were solved during solving the node
+     * @param[in] status is the status of the node after solving it (NORMAL, CONVERGED, INFEASIBLE)
+     */
     void _send_solved_problem(const babBase::BabNode node, const double lbd, const std::vector<double> lbdSolutionPoint,
                               const unsigned lbdcnt, const unsigned ubdcnt, const COMMUNICATION_TAG status);
 
     /**
-        * @brief Auxiliary function for synchronizing with the master (e.g., to manage termination, exceptions etc.)
-        *
-        * @param[in] req is the pending request for which the worker awaits an answer
-        */
+     * @brief Auxiliary function for sending a solved problem to the manager
+     *
+     * @param[in] siblingResults is the object containing results from solving the sibling nodes
+     * @param[in] lbdcnt is the number of lower bounding problems that were solved during solving the node
+     * @param[in] ubdcnt is the number of upper bounding problems that were solved during solving the node
+     * @param[in] status is the status of the node after solving it (NORMAL, CONVERGED, INFEASIBLE)
+     */
+    void _send_solved_sibling_problem(const lbp::SiblingResults &siblingResults,
+                                      const unsigned lbdcnt, const unsigned ubdcnt, const COMMUNICATION_TAG status);
+
+    /**
+     * @brief Auxiliary function for synchronizing with the master (e.g., to manage termination, exceptions etc.)
+     *
+     * @param[in] req is the pending request for which the worker awaits an answer
+     */
     void _sync_with_master(MPI_Request &req);
 
     /**
-        * @brief Auxiliary function for synchronizing with the master (e.g., to manage termination, exceptions etc.)
-        *
-        * @param[in] req is the pending request for which the worker awaits an answer
-        * @param[out] terminate is a flag that indicates if the worker should terminate the B&B loop
-        */
+     * @brief Auxiliary function for synchronizing with the master (e.g., to manage termination, exceptions etc.)
+     *
+     * @param[in] req is the pending request for which the worker awaits an answer
+     * @param[out] terminate is a flag that indicates if the worker should terminate the B&B loop
+     */
     void _sync_with_master(MPI_Request &req, bool &terminate);
     /**@}*/
-#endif
+#endif    // HAVE_MAiNGO_MPI
 
     std::unique_ptr<babBase::Brancher> _brancher;   /*!< pointer to brancher object that holds and manages the branch-and-bound tree */
     std::shared_ptr<ubp::UpperBoundingSolver> _UBS; /*!< pointer to upper bounding solver */
     std::shared_ptr<lbp::LowerBoundingSolver> _LBS; /*!< pointer to lower bounding solver */
 #if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS)
     std::shared_ptr<lbp::LowerBoundingSolver> _LBSFull; /*!< pointer to lower bounding solver using the full dataset only */
-    double _currentLBDFull;                             /*!< lower bound of the current node when using the full dataset */
-    std::vector<double> _lbpSolutionPointFull;          /*!< solution point of the LBP in the current node when using the full dataset */
+    double _currentLBDFull;                         /*!< lower bound of the current node when using the full dataset */
+    std::vector<double> _lbpSolutionPointFull;      /*!< solution point of the LBP in the current node when using the full dataset */
+    double _currentLBDValid;                        /*!< lower bound of the current node when using the evaluation called for augmentation rule VALID */
 #endif
 
     std::shared_ptr<Settings> _maingoSettings; /*!< pointer to object storing settings */
 
     /**
-        * @name Internal variables for storing problem parameters
-        */
+     * @name Internal variables for storing problem parameters
+     */
     /**@{*/
     std::vector<babBase::OptimizationVariable> _originalVariables; /*!< vector holding the optimization variables */
     const unsigned _nvar;                                          /*!< stores number of optimization parameters */
@@ -426,13 +599,16 @@ class BranchAndBound {
     /**@}*/
 
     /**
-        * @name Internal variables for storing solution information
-        */
+     * @name Internal variables for storing solution information
+     */
     /**@{*/
     std::vector<double> _incumbent;      /*!< vector storing solution (p^*) */
     std::vector<double> _initialPoint;   /*!< vector storing initial point */
     double _ubd;                         /*!< incumbent upper bound */
     double _lbd;                         /*!< lowest lower bound */
+#ifdef HAVE_GROWING_DATASETS
+    double _lbdPostpro;                  /*!< final lower bound after post-processing the heuristic B&B algorithm with growing datasets */
+#endif // HAVE_GROWING_DATASETS
     double _bestLbdFathomed;             /*!< this is the lowest lower bound of a node that has been fathomed by value dominance so far (needed to compute the final optimality gap correctly) */
     bool _foundFeas;                     /*!< if a feasible point has been found */
     unsigned _firstFound;                /*!< first node to find incumbent */
@@ -441,8 +617,8 @@ class BranchAndBound {
     /**@}*/
 
     /**
-        * @name Internal variables for heuristic approaches
-        */
+     * @name Internal variables for heuristic approaches
+     */
     /**@{*/
     double _lbdOld;             /*!< lowest lower bound before update in _update_lowest_lbd() */
     unsigned _lbdNotChanged;    /*!< counter on iterations where the lowest lbd did not change */
@@ -450,30 +626,95 @@ class BranchAndBound {
     /**@}*/
 
     /**
-        * @name Internal variables to store statistics
-        */
+     * @name Internal variables for two stage problems
+     */
+    /**@{*/
+    std::vector<double> _w = {};                                                               /*!< vector storing the weights for the scenarios of two-stage problems */
+    unsigned _Ns = 0;                                                                          /*!< number of scenarios for two-stage problems */
+    unsigned _Nx = 0;                                                                          /*!< number of first-stage variables for two-stage problems */
+    unsigned _Ny = 0;                                                                          /*!< number of second-stage variables for two-stage problems */
+    std::shared_ptr<std::vector<double>> _subproblemBounds = nullptr;                          /*!< storage for subproblem objective bounds */
+    std::map<int, std::pair<std::vector<std::vector<double>>, bool>> _parentSubproblemBounds;  /*!< dictionary storing the bounds obtained using subsolvers for each node and a bool indicating whether to keep the entry after lookup */
+
+    /**
+     * @brief Function for updating an entry in _parentSubproblemBounds
+     * 
+     * @param[in] it is a valid iterator to the entry in _parentSubproblemBounds that should be updated
+     * 
+     * @return bool indicating whether the entry was removed
+     */
+    inline bool _update_psb_entry(std::map<int, std::pair<std::vector<std::vector<double>>, bool>>::iterator &it);
+
+    /**
+     * @brief Function for setting _subproblemBounds based on the entry in _parentSubproblemBounds
+     * 
+     * @param[in] n is the node for which the bounds should be set
+     * @param[in] sibling_iteration is a flag indicating whether the current iteration is a sibling iteration
+     */
+    void _retreive_parent_subproblem_bounds(const babBase::BabNode &n, const bool sibling_iteration = false);
+
+    /**
+     * @brief Function for updating an entry in _parentSubproblemBounds after observing the fathoming of a node 
+     * 
+     * @param[in] id is the ID of the node for which the bounds should be updated
+     */
+    void observe_fathoming(const babBase::BabNode &n) override;
+
+    std::function<
+        void(
+            lbp::SiblingResults &siblingResults,
+            double ubd,
+            int obbtType
+        )
+    > _solve_sibling_subproblems; /*!< function that solves the lower bounding subproblems for sibling nodes */
+    std::function<
+        int(
+            babBase::BabNode &orthantNode,
+            const std::vector<unsigned int> &bits,
+            const std::array<std::vector<double>, 2> &subproblemBounds,
+            const std::array<std::vector<lbp::LbpDualInfo>, 2> &subproblemDualInfo,
+            const std::array<std::vector<std::vector<double>>, 2> &subproblemSolutions,
+            const std::vector<double> &parentSubproblemBounds,
+            double _ubd,
+            bool newUbd
+        )
+    > _postprocess_orthant;       /*!< function that sets the data for orthant nodes */
+    /**@}*/
+
+    /**
+     * @name Internal variables to store statistics
+     */
     /**@{*/
     unsigned _nNodesTotal;       /*!< total nodes created in Iset */
     unsigned _nNodesLeft;        /*!< nodes left in Iset */
     unsigned _nNodesMaxInMemory; /*!< maximum number of nodes held in memory so far */
     unsigned _nNodesDeleted;     /*!< nodes deleted in Iset */
     unsigned _nNodesFathomed;    /*!< nodes fathomed in Iset */
+#ifdef HAVE_GROWING_DATASETS
+    unsigned _nNodesTrackedPost;   /*!< nodes tracked for post-processing heuristic approach */
+    unsigned _nNodesProcessedPost; /*!< nodes actually processed in post-processing before hitting CPU time limit */
+#endif    // HAVE_GROWING_DATASETS
     /**@}*/
 
     /**
-        * @name Counters
-        */
+     * @name Counters
+     */
     /**@{*/
-    unsigned _lbdcnt;       /*!< total number of LBPs solved */
-    unsigned _ubdcnt;       /*!< total number of UBPs solved */
-    double _timePassed;     /*!< total CPU time in seconds */
-    double _timePreprocess; /*!< CPU time in seconds used for preprocessing */
-    unsigned _daysPassed;   /*!< number of full days */
+    unsigned _lbdcnt;                   /*!< total number of LBPs solved */
+    unsigned _ubdcnt;                   /*!< total number of UBPs solved */
+    double _timePassed;                 /*!< total CPU time in seconds */
+    double _wtimePassed;                /*!< total wall time in seconds */
+    double _timePreprocess;             /*!< CPU time in seconds used for preprocessing */
+    double _wtimePreprocess;            /*!< wall time in seconds used for preprocessing */
+    unsigned _daysPassed, _wdaysPassed; /*!< number of full days */
+#ifdef HAVE_GROWING_DATASETS
+    double _timePostpro;                /*!< CPU time for post-processing the heuristic B&B algorithm with growing datasets */
+#endif    // HAVE_GROWING_DATASETS
     /**@}*/
 
     /**
-        * @name Internal variables used for printing
-        */
+     * @name Internal variables used for printing
+     */
     /**@{*/
     unsigned _linesprinted;          /*!< number of lines printed */
     unsigned _iterations;            /*!< number of iterations */
@@ -486,8 +727,8 @@ class BranchAndBound {
 
 #ifdef HAVE_MAiNGO_MPI
     /**
-        * @name Internal variables used for MPI communication
-        */
+     * @name Internal variables used for MPI communication
+     */
     /**@{*/
     int _rank;             /*!< rank of process*/
     int _nProcs;           /*!< number of processes*/
@@ -496,8 +737,8 @@ class BranchAndBound {
     /**@}*/
 
     /**
-        * @name Internal variables used for MPI management
-        */
+     * @name Internal variables used for MPI management
+     */
     /**@{*/
     std::vector<bool> _informedWorkerAboutIncumbent;           /*!< stores information about which worker already knows about the current incumbent */
     bool _checkForNodeWithIncumbent;                           /*!< used to properly track the incumbent when a new one is found within the B&B tree */
@@ -506,22 +747,22 @@ class BranchAndBound {
     std::vector<std::pair<bool, double>> _nodesGivenToWorkers; /*!< vector holding whether worker i currently has a node and the double value of the lbd of this node */
 #ifdef HAVE_GROWING_DATASETS
     std::vector<bool> _informedWorkerAboutDataset; /*!< stores information about which worker already knows about the current dataset vector */
-#endif                                             // HAVE_GROWING_DATASETS
+#endif    // HAVE_GROWING_DATASETS
     /**@}*/
 
     /**
-        * @name Internal variables used by worker processes
-        */
+     * @name Internal variables used by worker processes
+     */
     /**@{*/
     bool _pendingIncumbentUpdate; /*!< flag determining whether the workers should be informed about new incumbent */
     MPI_Request _incumbentReq;    /*!< MPI request handle for new incumbent */
     /**@}*/
 
-#endif
-};
+#endif    // HAVE_MAiNGO_MPI
+    };
 
 
 }    // end namespace bab
 
 
-}    // end namespace maingo
\ No newline at end of file
+}    // end namespace maingo
diff --git a/inc/instrumentor.h b/inc/instrumentor.h
new file mode 100644
index 0000000..d5a276d
--- /dev/null
+++ b/inc/instrumentor.h
@@ -0,0 +1,7 @@
+/**
+ * @file instrumentor.h
+ * @brief Instrumentor used for profiling in internal development (do not delete)
+ */
+#define PROFILE_SESSION(name)
+#define PROFILE_SCOPE(name)
+#define PROFILE_FUNCTION()
diff --git a/inc/lbp.h b/inc/lbp.h
index 0ccabe3..353c071 100644
--- a/inc/lbp.h
+++ b/inc/lbp.h
@@ -18,6 +18,7 @@
 #include "logger.h"
 #include "returnCodes.h"
 #include "settings.h"
+#include "TwoStageModel.h"
 
 #include "babNode.h"
 #include "babUtils.h"
@@ -35,9 +36,9 @@ namespace lbp {
 
 
 /**
-* @enum OBBT
-* @brief Enum for communicating whether Optimization-Based Bound Tightening should consider only feasibility or also optimality
-*/
+ * @enum OBBT
+ * @brief Enum for communicating whether Optimization-Based Bound Tightening should consider only feasibility or also optimality
+ */
 enum OBBT {
     OBBT_FEAS = 0, /*!< Consider feasibility only, i.e., maximize and minimize each variable only subject to the relaxed (and linearized) constraints */
     OBBT_FEASOPT   /*!< Consider both feasibility and optimality, i.e., including the objective function cut f_cv<=UBD */
@@ -45,10 +46,10 @@ enum OBBT {
 
 
 /**
-* @struct LbpDualInfo
-* @brief Container for information from the LBP that is needed in DBBT and probing, used for communicating the results via bab.
-*
-*/
+ * @struct LbpDualInfo
+ * @brief Container for information from the LBP that is needed in DBBT and probing, used for communicating the results via bab.
+ *
+ */
 struct LbpDualInfo {
     std::vector<double> multipliers; /*!< Vector containing the multipliers of bound constraints for each variable in the LBP */
     double lpLowerBound;             /*!< Lower bound obtained from the solution of the lower bounding LP. This may be different from the lower bound returned by solve_LBP in case the interval bound is better than that obtained from the LP. */
@@ -56,31 +57,35 @@ struct LbpDualInfo {
 
 
 /**
-* @class LowerBoundingSolver
-* @brief Wrapper for handling the lower bounding problems as well as optimization-based bounds tightening (OBBT)
-*
-* This class provides an interface between the Branch-and-Bound solver, the problem definition used by BigMC, and the actual sub-solver used for lower bounding (currently CPLEX).
-* It manages the CPLEX problem and solver instance(s), evaluates the Model using MC++ to obtain relaxations and subgradients, constructs the respective LP relaxations, and calls CPLEX to solve either the lower bounding problems (LBP) or OBBT as queried by the B&B solver.
-*/
+ * @class LowerBoundingSolver
+ * @brief Wrapper for handling the lower bounding problems as well as optimization-based bounds tightening (OBBT)
+ *
+ * This class provides an interface between the Branch-and-Bound solver, the problem definition used by BigMC, and the actual sub-solver used for lower bounding (currently CPLEX).
+ * It manages the CPLEX problem and solver instance(s), evaluates the Model using MC++ to obtain relaxations and subgradients, constructs the respective LP relaxations, and calls CPLEX to solve either the lower bounding problems (LBP) or OBBT as queried by the B&B solver.
+ */
 class LowerBoundingSolver {
+  
+  template<class subsolver_class>
+  friend class LbpTwoStage;
+
   public:
     /**
-        * @brief Constructor, stores information on the problem and constructs an own copy of the directed acyclic graph
-        *
-        * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
-        * @param[in] DAGvars are the variables corresponding to the DAG
-        * @param[in] DAGfunctions are the functions corresponding to the DAG
-        * @param[in] variables is a vector containing the optimization variables
-        * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly
-        * @param[in] nineqIn is the number of inequality constraints
-        * @param[in] neqIn is the number of equality
-        * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem
-        * @param[in] neqRelaxationOnlyIn is the number of equality constraints for use only in the relaxed problem
-        * @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
-        * @param[in] settingsIn is a pointer to the MAiNGO settings
-        * @param[in] loggerIn is a pointer to the MAiNGO logger object
-        * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
-        */
+     * @brief Constructor, stores information on the problem and constructs an own copy of the directed acyclic graph
+     *
+     * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
+     * @param[in] DAGvars are the variables corresponding to the DAG
+     * @param[in] DAGfunctions are the functions corresponding to the DAG
+     * @param[in] variables is a vector containing the optimization variables
+     * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly
+     * @param[in] nineqIn is the number of inequality constraints
+     * @param[in] neqIn is the number of equality
+     * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem
+     * @param[in] neqRelaxationOnlyIn is the number of equality constraints for use only in the relaxed problem
+     * @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
+     * @param[in] settingsIn is a pointer to the MAiNGO settings
+     * @param[in] loggerIn is a pointer to the MAiNGO logger object
+     * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
+     */
     LowerBoundingSolver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions,
                         const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear,
                         const unsigned nineqIn, const unsigned neqIn,
@@ -88,72 +93,83 @@ class LowerBoundingSolver {
                         std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn);
 
     /**
-        * @brief Virtual destructor, only needed to make sure the correct destructor of the derived classes is called
-        */
+     * @brief Virtual destructor, only needed to make sure the correct destructor of the derived classes is called
+     */
     virtual ~LowerBoundingSolver(){};
 
     /**
-        * @brief Function called by B&B solver for solving the lower bounding problem on the current node
-        *
-        * @param[in] currentNode is the B&B node for which the lower bounding problem should be solved
-        * @param[out] lowerBound is the lower bound on the optimal objective value obtained for the current node
-        * @param[out] solutionPoint is the point at which lowerBound was achieved
-        * @param[out] dualInfo is a struct containing information from the LP solver needed for DBBT and probing
-        * @return Return code, either RETCODE_FEASIBLE or RETCODE_INFEASIBLE
-        */
+     * @brief Function called by B&B solver for solving the lower bounding problem on the current node
+     *
+     * @param[in] currentNode is the B&B node for which the lower bounding problem should be solved
+     * @param[out] lowerBound is the lower bound on the optimal objective value obtained for the current node
+     * @param[out] solutionPoint is the point at which lowerBound was achieved
+     * @param[out] dualInfo is a struct containing information from the LP solver needed for DBBT and probing
+     * @return Return code, either RETCODE_FEASIBLE or RETCODE_INFEASIBLE
+     */
     SUBSOLVER_RETCODE solve_LBP(const babBase::BabNode &currentNode, double &lowerBound, std::vector<double> &solutionPoint, LbpDualInfo &dualInfo);
 
+#ifdef HAVE_GROWING_DATASETS
     /**
-        * @brief Function called by B&B solver for optimality-based range reduction (cf., e.g., Gleixner et al., J. Glob. Optim. 67 (2017) 731)
+        * @brief Function for evaluating the lower bounding problem on the current node at a given point
         *
-        * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved; if OBBT is successful in tightening bounds, currentNode will be modified accordingly
-        * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); It is used for the objective function cut if reductionType==OBBT_FEASOPT
-        * @param[in] reductionType determines whether OBBT should include only feasibility or also optimality (i.e., an objective function cut f_cv<=currentUBD)
-        * @param[in] includeLinearVars determines whether OBBT runs should also be conducted for variables occurring only linearly
-        * @return Return code, see enum TIGHTENING_RETCODE
+        * @param[in] currentNode is the B&B node for which the lower bounding problem should be build
+        * @param[in] evaluationPoint is the point at which the lower bounding problem should be evaluated
+        * @param[out] resultValue is the function value obtained from the respective evaluation
         */
-    TIGHTENING_RETCODE solve_OBBT(babBase::BabNode &currentNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars = false);
+    void evaluate_LBP(const babBase::BabNode& currentNode, const std::vector<double>& evaluationPoint, double& resultValue);
+#endif    // HAVE_GROWING_DATASETS
 
     /**
-        * @brief Function called by B&B solver for constraint propagation. This function is virtual as it may be overwritten for certain LBD solvers.
-        *        The defaults for constraint propagation are 10 rounds and at least 1% improvement
-        *
-        * @param[in,out] currentNode is the B&B node for which constraint propagation should be executed; if constraint propagation is successful in tightening bounds, currentNode will be modified accordingly
-        * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); it is used for the upper bound of the objective constraint interval
-        * @param[in] pass is the maximum number of consecutive propagation runs
-        * @return Return code, see enum TIGHTENING_RETCODE
-        */
+     * @brief Function called by B&B solver for optimality-based range reduction (cf., e.g., Gleixner et al., J. Glob. Optim. 67 (2017) 731)
+     *
+     * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved; if OBBT is successful in tightening bounds, currentNode will be modified accordingly
+     * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); It is used for the objective function cut if reductionType==OBBT_FEASOPT
+     * @param[in] reductionType determines whether OBBT should include only feasibility or also optimality (i.e., an objective function cut f_cv<=currentUBD)
+     * @param[in] includeLinearVars determines whether OBBT runs should also be conducted for variables occurring only linearly
+     * @return Return code, see enum TIGHTENING_RETCODE
+     */
+    virtual TIGHTENING_RETCODE solve_OBBT(babBase::BabNode &currentNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars = false);
+
+    /**
+     * @brief Function called by B&B solver for constraint propagation. This function is virtual as it may be overwritten for certain LBD solvers.
+     *        The defaults for constraint propagation are 10 rounds and at least 1% improvement
+     *
+     * @param[in,out] currentNode is the B&B node for which constraint propagation should be executed; if constraint propagation is successful in tightening bounds, currentNode will be modified accordingly
+     * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); it is used for the upper bound of the objective constraint interval
+     * @param[in] pass is the maximum number of consecutive propagation runs
+     * @return Return code, see enum TIGHTENING_RETCODE
+     */
     virtual TIGHTENING_RETCODE do_constraint_propagation(babBase::BabNode &currentNode, const double currentUBD, const unsigned pass = 3);
 
     /**
-        * @brief Function called by B&B solver for DBBT and probing (for each variable depending on where the LBD solution lies)
-        *
-        * @param[in,out] currentNode is the B&B node for which constraint propagation should be executed; if DBBT or probing are successful in tightening bounds, currentNode will be modified accordingly
-        * @param[in] lbpSolutionPoint is a vector containing the solution point of the LBP for currentNode
-        * @param[in] dualInfo is a struct containing information from the LP solved during LBP
-        * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); it is used for the upper bound in DBBT / probing
-        * @return Return code, see enum TIGHTENING_RETCODE
-        */
-    TIGHTENING_RETCODE do_dbbt_and_probing(babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const LbpDualInfo &dualInfo, const double currentUBD);
+     * @brief Function called by B&B solver for DBBT and probing (for each variable depending on where the LBD solution lies)
+     *
+     * @param[in,out] currentNode is the B&B node for which constraint propagation should be executed; if DBBT or probing are successful in tightening bounds, currentNode will be modified accordingly
+     * @param[in] lbpSolutionPoint is a vector containing the solution point of the LBP for currentNode
+     * @param[in] dualInfo is a struct containing information from the LP solved during LBP
+     * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); it is used for the upper bound in DBBT / probing
+     * @return Return code, see enum TIGHTENING_RETCODE
+     */
+    virtual TIGHTENING_RETCODE do_dbbt_and_probing(babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const LbpDualInfo &dualInfo, const double currentUBD);
 
     /**
-        * @brief Function called by the B&B solver to update the incumbent and the ID of the node currently holding it, which is needed by some linearization heuristics
-        *
-        * @param[in] incumbentBAB is a vector containing the current incumbent
-        */
-    void update_incumbent_LBP(const std::vector<double> &incumbentBAB);
+     * @brief Function called by the B&B solver to update the incumbent and the ID of the node currently holding it, which is needed by some linearization heuristics
+     *
+     * @param[in] incumbentBAB is a vector containing the current incumbent
+     */
+    virtual void update_incumbent_LBP(const std::vector<double> &incumbentBAB);
 
     /**
-        * @brief Function called by the B&B solver to heuristically activate more scaling in the LBS
-        */
+     * @brief Function called by the B&B solver to heuristically activate more scaling in the LBS
+     */
     virtual void activate_more_scaling();
 
     /**
-        * @brief Function called by the B&B in preprocessing in order to check the need for specific options, currently for subgradient intervals & CPLEX no large values
-        *
-        * @param[in] rootNode is the rootNode on which some options are checked
-        */
-    void preprocessor_check_options(const babBase::BabNode &rootNode);
+     * @brief Function called by the B&B in preprocessing in order to check the need for specific options, currently for subgradient intervals & CPLEX no large values
+     *
+     * @param[in] rootNode is the rootNode on which some options are checked
+     */
+    virtual void preprocessor_check_options(const babBase::BabNode &rootNode);
 
 #ifdef HAVE_GROWING_DATASETS
     /**
@@ -161,485 +177,504 @@ class LowerBoundingSolver {
 	*
 	* @param[in] indexDataset is the index number of the (reduced) dataset to be used
 	*/
-    void change_growing_objective(const unsigned int indexDataset);
+    void change_growing_objective(const int indexDataset);
 
     /**
-	* @brief Function for passing dataset vector and position of data points to solver
-	*
-	* @param[in] datasets is a pointer to a vector containing all available datasets
-    * @param[in] indexFirstData is the position of the first objective per data in MAiNGO::_DAGfunctions
-	*/
+    * @brief Function for calling respective function of DagObj
+    */
+    void change_growing_objective_for_resampling();
+
+    /**
+	 * @brief Function for passing dataset vector and position of data points to solver
+	 *
+	 * @param[in] datasets is a pointer to a vector containing all available datasets
+     * @param[in] indexFirstData is the position of the first objective per data in MAiNGO::_DAGfunctions
+	 */
     void pass_data_position_to_solver(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasets, const unsigned int indexFirstData);
 
-#ifdef HAVE_MAiNGO_MPI
     /**
-    * @brief Function for updating dataset vector and adding subgraph after adding new dataset, needed for parallel version
+    * @brief Function for passing resampled initial dataset to solver
     *
-    * @param[in] newDataset is the new dataset to be added
+    * @param[in] dataset is a pointer to the resampled dataset
     */
+    void pass_resampled_dataset_to_solver(const std::shared_ptr<std::set<unsigned int>> datasetIn);
+
+    /**
+    * @brief Function for telling solver whether mean squared error or summed squared error is used as objective function
+    *
+    * @param[in] useMse is the boolean to be passed
+    */
+    void pass_use_mse_to_solver(const bool useMse);
+
+  #ifdef HAVE_MAiNGO_MPI
+    /**
+     * @brief Function for updating dataset vector and adding subgraph after adding new dataset, needed for parallel version
+     *
+     * @param[in] newDataset is the new dataset to be added
+     */
     void update_datasets_and_storedSubgraphs(std::set<unsigned int> &newDataset);
-#endif    //HAVE_MAiNGO_MPI
+  #endif    //HAVE_MAiNGO_MPI
 #endif    //HAVE_GROWING_DATASETS
 
   protected:
     /**
-        * @brief Calls the proper function for computing linearization points and modifying the coefficients of the LP problem
-        *        The function is virtual, since other solver types, e.g., an interval solver does not need to always compute McCormick relaxations
-        *
-        * @param[in] currentNode is current node of the branch-and-bound tree
-        * @return returns a LINEARIZATION_RETCODE defining whether the final problem was already solved/proven infeasible during linearization
-        */
+     * @brief Calls the proper function for computing linearization points and modifying the coefficients of the LP problem
+     *        The function is virtual, since other solver types, e.g., an interval solver does not need to always compute McCormick relaxations
+     *
+     * @param[in] currentNode is current node of the branch-and-bound tree
+     * @return returns a LINEARIZATION_RETCODE defining whether the final problem was already solved/proven infeasible during linearization
+     */
     virtual LINEARIZATION_RETCODE _update_LP(const babBase::BabNode &currentNode);
 
     /**
-        * @brief Virtual function for setting the bounds of variables
-        *
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        */
+     * @brief Virtual function for setting the bounds of variables
+     *
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     */
     virtual void _set_variable_bounds(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief Virtual auxiliary function for updating LP objective, i.e., processing the linearization of the objective function
-        *
-        * @param[in] resultRelaxation is the McCormick object holding relaxation of objective iObj at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        * @param[in] iObj is the number of the objective function
-        */
+     * @brief Virtual auxiliary function for updating LP objective, i.e., processing the linearization of the objective function
+     *
+     * @param[in] resultRelaxation is the McCormick object holding relaxation of objective iObj at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     * @param[in] iObj is the number of the objective function
+     */
     virtual void _update_LP_obj(const MC &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                 const std::vector<double> &upperVarBounds, unsigned const &iLin, unsigned const &iObj);
 
     /**
-        * @brief Virtual auxiliary function for updating LP inequalities, i.e., processing the linearization of the inequality
-        *
-        * @param[in] resultRelaxation is the McCormick object holding relaxation of inequality iIneq at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        * @param[in] iIneq is the number of the inequality function
-        */
+     * @brief Virtual auxiliary function for updating LP inequalities, i.e., processing the linearization of the inequality
+     *
+     * @param[in] resultRelaxation is the McCormick object holding relaxation of inequality iIneq at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     * @param[in] iIneq is the number of the inequality function
+     */
     virtual void _update_LP_ineq(const MC &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                  const std::vector<double> &upperVarBounds, unsigned const &iLin, unsigned const &iIneq);
 
     /**
-        * @brief Virtual auxiliary function for updating LP equalities, i.e., processing the linearization of the equality
-        *
-        * @param[in] resultRelaxationCv is the McCormick object holding relaxation of equality iEq at linearizationPoint used for the convex part
-        * @param[in] resultRelaxationCc is the McCormick object holding relaxation of equality iEq at linearizationPoint used for the concave part
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        * @param[in] iEq is the number of the equality function
-        */
+     * @brief Virtual auxiliary function for updating LP equalities, i.e., processing the linearization of the equality
+     *
+     * @param[in] resultRelaxationCv is the McCormick object holding relaxation of equality iEq at linearizationPoint used for the convex part
+     * @param[in] resultRelaxationCc is the McCormick object holding relaxation of equality iEq at linearizationPoint used for the concave part
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     * @param[in] iEq is the number of the equality function
+     */
     virtual void _update_LP_eq(const MC &resultRelaxationCv, const MC &resultRelaxationCc, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                const std::vector<double> &upperVarBounds, unsigned const &iLin, unsigned const &iEq);
 
     /**
-        * @brief Virtual auxiliary function for updating LP relaxation only inequalities, i.e., processing the linearization of the relaxation only inequality
-        *
-        * @param[in] resultRelaxation is the McCormick object holding relaxation of relaxation only inequality iIneqRelaxationOnly at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        * @param[in] iIneqRelaxationOnly is the number of the relaxation only inequality function
-        */
+     * @brief Virtual auxiliary function for updating LP relaxation only inequalities, i.e., processing the linearization of the relaxation only inequality
+     *
+     * @param[in] resultRelaxation is the McCormick object holding relaxation of relaxation only inequality iIneqRelaxationOnly at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     * @param[in] iIneqRelaxationOnly is the number of the relaxation only inequality function
+     */
     virtual void _update_LP_ineqRelaxationOnly(const MC &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                                const std::vector<double> &upperVarBounds, unsigned const &iLin, unsigned const &iIneqRelaxationOnly);
 
     /**
-        * @brief Virtual auxiliary function for updating LP relaxation only equalities, i.e., processing the linearization of the relaxation only equality
-        *
-        * @param[in] resultRelaxationCv is the McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the convex part
-        * @param[in] resultRelaxationCc is the McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the concave part
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        * @param[in] iEqRelaxationOnly is the number of the relaxation only equality function
-        */
+     * @brief Virtual auxiliary function for updating LP relaxation only equalities, i.e., processing the linearization of the relaxation only equality
+     *
+     * @param[in] resultRelaxationCv is the McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the convex part
+     * @param[in] resultRelaxationCc is the McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the concave part
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     * @param[in] iEqRelaxationOnly is the number of the relaxation only equality function
+     */
     virtual void _update_LP_eqRelaxationOnly(const MC &resultRelaxationCv, const MC &resultRelaxationCc, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                              const std::vector<double> &upperVarBounds, unsigned const &iLin, unsigned const &iEqRelaxationOnly);
 
 
     /**
-        * @brief Virtual auxiliary function for updating LP squash inequalities, i.e., processing the linearization of the squash inequality
-        *        No tolerances are allowed for squash inequalities!
-        *
-        * @param[in] resultRelaxation is the McCormick object holding relaxation of inequality iIneqSquash at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        * @param[in] iIneqSquash is the number of the inequality function
-        */
+     * @brief Virtual auxiliary function for updating LP squash inequalities, i.e., processing the linearization of the squash inequality
+     *        No tolerances are allowed for squash inequalities!
+     *
+     * @param[in] resultRelaxation is the McCormick object holding relaxation of inequality iIneqSquash at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     * @param[in] iIneqSquash is the number of the inequality function
+     */
     virtual void _update_LP_ineq_squash(const MC &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                         const std::vector<double> &upperVarBounds, unsigned const &iLin, unsigned const &iIneqSquash);
     /**
-        * @brief Virtual auxiliary function for updating whole LP at once
-        *
-        * @param[in] resultRelaxation is the vector holding McCormick relaxations at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iLin is the number of the linearization point
-        */
+     * @brief Virtual auxiliary function for updating whole LP at once
+     *
+     * @param[in] resultRelaxation is the vector holding McCormick relaxations at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iLin is the number of the linearization point
+     */
     void _update_whole_LP_at_linpoint(const std::vector<MC> &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                       const std::vector<double> &upperVarBounds, unsigned const &iLin);
 
     /**
-        * @brief Virtual auxiliary function for updating LP objective, i.e., processing the linearization of the objective function for vector McCormick relaxations
-        *
-        * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of objective iObj at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iObj is the number of the objective function
-        */
+     * @brief Virtual auxiliary function for updating LP objective, i.e., processing the linearization of the objective function for vector McCormick relaxations
+     *
+     * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of objective iObj at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iObj is the number of the objective function
+     */
     virtual void _update_LP_obj(const vMC &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                 const std::vector<double> &upperVarBounds, unsigned const &iObj);
 
     /**
-        * @brief Virtual auxiliary function for updating LP inequalities, i.e., processing the linearization of the inequality for vector McCormick relaxations
-        *
-        * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of inequality iIneq at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iIneq is the number of the inequality function
-        */
+     * @brief Virtual auxiliary function for updating LP inequalities, i.e., processing the linearization of the inequality for vector McCormick relaxations
+     *
+     * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of inequality iIneq at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iIneq is the number of the inequality function
+     */
     virtual void _update_LP_ineq(const vMC &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                  const std::vector<double> &upperVarBounds, unsigned const &iIneq);
 
     /**
-        * @brief Virtual auxiliary function for updating LP equalities, i.e., processing the linearization of the equality for vector McCormick relaxations
-        *
-        * @param[in] resultRelaxationCvVMC is the vector McCormick object holding relaxation of equality iEq at linearizationPoint used for the convex part
-        * @param[in] resultRelaxationCcVMC is the vector McCormick object holding relaxation of equality iEq at linearizationPoint used for the concave part
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iEq is the number of the equality function
-        */
+     * @brief Virtual auxiliary function for updating LP equalities, i.e., processing the linearization of the equality for vector McCormick relaxations
+     *
+     * @param[in] resultRelaxationCvVMC is the vector McCormick object holding relaxation of equality iEq at linearizationPoint used for the convex part
+     * @param[in] resultRelaxationCcVMC is the vector McCormick object holding relaxation of equality iEq at linearizationPoint used for the concave part
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iEq is the number of the equality function
+     */
     virtual void _update_LP_eq(const vMC &resultRelaxationCvVMC, const vMC &resultRelaxationCcVMC, const std::vector<std::vector<double>> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                const std::vector<double> &upperVarBounds, unsigned const &iEq);
 
     /**
-        * @brief Virtual auxiliary function for updating LP relaxation only inequalities, i.e., processing the linearization of the relaxation only inequality for vector McCormick relaxations
-        *
-        * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of relaxation only inequality iIneqRelaxationOnly at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iIneqRelaxationOnly is the number of the relaxation only inequality function
-        */
+     * @brief Virtual auxiliary function for updating LP relaxation only inequalities, i.e., processing the linearization of the relaxation only inequality for vector McCormick relaxations
+     *
+     * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of relaxation only inequality iIneqRelaxationOnly at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iIneqRelaxationOnly is the number of the relaxation only inequality function
+     */
     virtual void _update_LP_ineqRelaxationOnly(const vMC &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                                const std::vector<double> &upperVarBounds, unsigned const &iIneqRelaxationOnly);
 
     /**
-        * @brief Virtual auxiliary function for updating LP relaxation only equalities, i.e., processing the linearization of the relaxation only equality for vector McCormick relaxations
-        *
-        * @param[in] resultRelaxationCvVMC is the vector McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the convex part
-        * @param[in] resultRelaxationCcVMC is the vector McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the concave part
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iEqRelaxationOnly is the number of the relaxation only equality function
-        */
+     * @brief Virtual auxiliary function for updating LP relaxation only equalities, i.e., processing the linearization of the relaxation only equality for vector McCormick relaxations
+     *
+     * @param[in] resultRelaxationCvVMC is the vector McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the convex part
+     * @param[in] resultRelaxationCcVMC is the vector McCormick object holding relaxation of relaxation only equality iEqRelaxationOnly at linearizationPoint used for the concave part
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iEqRelaxationOnly is the number of the relaxation only equality function
+     */
     virtual void _update_LP_eqRelaxationOnly(const vMC &resultRelaxationCvVMC, const vMC &resultRelaxationCcVMC, const std::vector<std::vector<double>> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                              const std::vector<double> &upperVarBounds, unsigned const &iEqRelaxationOnly);
 
 
     /**
-        * @brief Virtual auxiliary function for updating LP squash inequalities, i.e., processing the linearization of the squash inequality for vector McCormick relaxations
-        *        No tolerances are allowed for squash inequalities!
-        *
-        * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of inequality iIneqSquash at linearizationPoint
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] iIneqSquash is the number of the inequality function
-        */
+     * @brief Virtual auxiliary function for updating LP squash inequalities, i.e., processing the linearization of the squash inequality for vector McCormick relaxations
+     *        No tolerances are allowed for squash inequalities!
+     *
+     * @param[in] resultRelaxationVMC is the vector McCormick object holding relaxation of inequality iIneqSquash at linearizationPoint
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] iIneqSquash is the number of the inequality function
+     */
     virtual void _update_LP_ineq_squash(const vMC &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoint, const std::vector<double> &lowerVarBounds,
                                         const std::vector<double> &upperVarBounds, unsigned const &iIneqSquash);
 
     /**
-        * @brief Virtual auxiliary function for updating whole LP at once
-        *
-        * @param[in] resultRelaxationVMC is the vector holding vector McCormick relaxations at linearizationPoints
-        * @param[in] linearizationPoints is the vector holding the linearization points
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        */
+     * @brief Virtual auxiliary function for updating whole LP at once
+     *
+     * @param[in] resultRelaxationVMC is the vector holding vector McCormick relaxations at linearizationPoints
+     * @param[in] linearizationPoints is the vector holding the linearization points
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     */
     void _update_whole_LP_at_vector_linpoints(const std::vector<vMC> &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoints, const std::vector<double> &lowerVarBounds,
                                               const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief Function for equilibrating a line in an LP
-        *
-        * @param[in,out] coefficients is the vector holding the coefficients aij in the corresponding LP line j: sum_i a_ji xi <= bj
-        * @param[in,out] rhs is the right-hand side bj in the corresponding LP line j: sum_i a_ji xi <= bj
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        * @return Returns the scaling factor used for the present LP line
-        */
+     * @brief Function for equilibrating a line in an LP
+     *
+     * @param[in,out] coefficients is the vector holding the coefficients aij in the corresponding LP line j: sum_i a_ji xi <= bj
+     * @param[in,out] rhs is the right-hand side bj in the corresponding LP line j: sum_i a_ji xi <= bj
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     * @return Returns the scaling factor used for the present LP line
+     */
     double _equilibrate_and_relax(std::vector<double> &coefficients, double &rhs, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief Virtual function for solving the currently constructed linear program.
-        *        This function also internally sets the _solutionPoint, _multipliers, and the _LPstatus.
-        *
-        * @param[in] currentNode is the currentNode, needed for throwing exceptions or similar
-        */
-    virtual void _solve_LP(const babBase::BabNode &currentNode);
+     * @brief Virtual function for solving the currently constructed linear program.
+     *        This function also internally sets the _solutionPoint, _multipliers, and the _LPstatus.
+     *
+     * @param[in] currentNode is the currentNode, needed for throwing exceptions or similar
+     */
+    virtual LP_RETCODE _solve_LP(const babBase::BabNode &currentNode);
 
     /**
-        * @brief Virtual function returning the current status of the last solved linear program.
-        *
-        * @return Returns the current status of the last solved linear program.
-        */
+     * @brief Virtual function returning the current status of the last solved linear program.
+     *
+     * @return Returns the current status of the last solved linear program.
+     */
     virtual LP_RETCODE _get_LP_status();
 
     /**
-        * @brief Virtual function for setting the solution to the solution point of the lastly solved LP.
-        *
-        * @param[in, out] solution is modified to hold the solution point of the lastly solved LP
-        * @param[in, out] etaVal is modified to hold the value of eta variable of the lastly solved LP
-        */
+     * @brief Virtual function for setting the solution to the solution point of the lastly solved LP.
+     *
+     * @param[in, out] solution is modified to hold the solution point of the lastly solved LP
+     * @param[in, out] etaVal is modified to hold the value of eta variable of the lastly solved LP
+     */
     virtual void _get_solution_point(std::vector<double> &solution, double &etaVal);
 
     /**
-        * @brief Virtual function returning the objective value of the lastly solved LP.
-        *
-        * @return Returns the objective value of the lastly solved LP.
-        */
+     * @brief Virtual function returning the objective value of the lastly solved LP.
+     *
+     * @return Returns the objective value of the lastly solved LP.
+     */
     double _get_objective_value();
 
     /**
-        * @brief Virtual function returning the objective value of the lastly solved LP for a specific solver.
-        *
-        * @return Returns the objective value of the lastly solved LP.
-        */
+     * @brief Virtual function returning the objective value of the lastly solved LP for a specific solver.
+     *
+     * @return Returns the objective value of the lastly solved LP.
+     */
     virtual double _get_objective_value_solver();
 
     /**
-        * @brief Virtual function for setting the multipliers of the lastly solved LP.
-        *
-        * @param[in] multipliers is modified to hold the reduced costs of the lastly solved LP
-        */
+     * @brief Virtual function for setting the multipliers of the lastly solved LP.
+     *
+     * @param[in] multipliers is modified to hold the reduced costs of the lastly solved LP
+     */
     virtual void _get_multipliers(std::vector<double> &multipliers);
 
     /**
-        * @brief Virtual function deactivating all objective rows in the LP for feasibility OBBT.
-        */
+     * @brief Virtual function deactivating all objective rows in the LP for feasibility OBBT.
+     */
     virtual void _deactivate_objective_function_for_OBBT();
 
     /**
-        * @brief Virtual function modifying the LP for feasibility-optimality OBBT.
-        *
-        * @param[in] currentUBD is the current upper bound
-        * @param[in,out] toTreatMax is the list holding variables indices for maximization
-        * @param[in,out] toTreatMin is the list holding variables indices for minimization
-        */
+     * @brief Virtual function modifying the LP for feasibility-optimality OBBT.
+     *
+     * @param[in] currentUBD is the current upper bound
+     * @param[in,out] toTreatMax is the list holding variables indices for maximization
+     * @param[in,out] toTreatMin is the list holding variables indices for minimization
+     */
     virtual void _modify_LP_for_feasopt_OBBT(const double &currentUBD, std::list<unsigned> &toTreatMax, std::list<unsigned> &toTreatMin);
 
     /**
-        * @brief Virtual function for setting the optimization sense of variable iVar in OBBT.
-        *
-        * @param[in] iVar is the number of variable of which the optimization sense will be changed.
-        * @param[in] optimizationSense describes whether the variable shall be maximized or minimized 1: minimize, 0: ignore,  -1: maximize.
-        */
+     * @brief Virtual function for setting the optimization sense of variable iVar in OBBT.
+     *
+     * @param[in] iVar is the number of variable of which the optimization sense will be changed.
+     * @param[in] optimizationSense describes whether the variable shall be maximized or minimized 1: minimize, 0: ignore,  -1: maximize.
+     */
     virtual void _set_optimization_sense_of_variable(const unsigned &iVar, const int &optimizationSense);
 
     /**
-        * @brief Virtual function for restoring proper coefficients and options in the LP after OBBT.
-        */
+     * @brief Virtual function for restoring proper coefficients and options in the LP after OBBT.
+     */
     virtual void _restore_LP_coefficients_after_OBBT();
 
     /**
-        * @brief Virtual function for fixing a variable to one of its bounds.
-        *
-        * @param[in] iVar is the number of variable which will be fixed.
-        * @param[in] fixToLowerBound describes whether the variable shall be fixed to its lower or upper bound.
-        */
+     * @brief Virtual function for fixing a variable to one of its bounds.
+     *
+     * @param[in] iVar is the number of variable which will be fixed.
+     * @param[in] fixToLowerBound describes whether the variable shall be fixed to its lower or upper bound.
+     */
     virtual void _fix_variable(const unsigned &iVar, const bool fixToLowerBound);
 
     /**
-        * @brief Virtual function for checking if the current linear program is really infeasible by, e.g., resolving it with different algorithms.
-        *
-        * @return Returns true if the linear program is indeed infeasible, false if and optimal solution was found
-        */
+     * @brief Virtual function for checking if the current linear program is really infeasible by, e.g., resolving it with different algorithms.
+     *
+     * @return Returns true if the linear program is indeed infeasible, false if and optimal solution was found
+     */
     virtual bool _check_if_LP_really_infeasible();
 
     /**
-        * @brief Auxiliary function for calling the proper function to linearize functions at chosen linearization point
-        *
-        * @param[out] resultRelaxation is the vector holding McCormick relaxation after they have been evaluated at the linearization point
-        * @param[in] linearizationPoint is the vector holding the linearization point
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] subgraph is the subgraph holding the list of operations of the underlying function(s) to be evaluated
-        * @param[in] functions is the vector holding the FFVar pointers to function(s) to be evaluated
-        */
+     * @brief Auxiliary function for calling the proper function to linearize functions at chosen linearization point
+     *
+     * @param[out] resultRelaxation is the vector holding McCormick relaxation after they have been evaluated at the linearization point
+     * @param[in] linearizationPoint is the vector holding the linearization point
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] subgraph is the subgraph holding the list of operations of the underlying function(s) to be evaluated
+     * @param[in] functions is the vector holding the FFVar pointers to function(s) to be evaluated
+     */
     void _linearize_functions_at_linpoint(std::vector<MC> &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds,
                                           mc::FFSubgraph &subgraph, std::vector<mc::FFVar> &functions);
 
     /**
-        * @brief Auxiliary function for calling the proper function to linearize functions at precomputed vector linearization point.
-        *        The precomputed vector linearization point has to be saved in _DAGobj.vMcPoint.
-        *
-        * @param[out] resultRelaxationVMC is the vector holding vector McCormick relaxation after they have been evaluated at the vector linearization point
-        * @param[in] linearizationPoints is the vector holding linearization points used for subgradient heuristic
-        * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
-        * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
-        * @param[in] subgraph is the subgraph holding the list of operations of the underlying function(s) to be evaluated
-        * @param[in] functions is the vector holding the FFVar pointers to function(s) to be evaluated
-        */
+     * @brief Auxiliary function for calling the proper function to linearize functions at precomputed vector linearization point.
+     *        The precomputed vector linearization point has to be saved in _DAGobj.vMcPoint.
+     *
+     * @param[out] resultRelaxationVMC is the vector holding vector McCormick relaxation after they have been evaluated at the vector linearization point
+     * @param[in] linearizationPoints is the vector holding linearization points used for subgradient heuristic
+     * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+     * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+     * @param[in] subgraph is the subgraph holding the list of operations of the underlying function(s) to be evaluated
+     * @param[in] functions is the vector holding the FFVar pointers to function(s) to be evaluated
+     */
     void _linearize_functions_at_preset_vector_linpoint(std::vector<vMC> &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoints,
                                                         const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds,
                                                         mc::FFSubgraph &subgraph, std::vector<mc::FFVar> &functions);
 
 
     /**
-        * @brief This function linearizes each function of the model at the middle point of the underlying box
-        *
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
-        */
+     * @brief This function linearizes each function of the model at the middle point of the underlying box
+     *
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
+     */
     LINEARIZATION_RETCODE _linearize_model_at_midpoint(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief This function linearizes each function of the model at the incumbent if it is contained in the current node. Otherwise
-        *        each function is linearized at the middle point of the underlying box
-        *
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
-        */
+     * @brief This function linearizes each function of the model at the incumbent if it is contained in the current node. Otherwise
+     *        each function is linearized at the middle point of the underlying box
+     *
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
+     */
     LINEARIZATION_RETCODE _linearize_model_at_incumbent_or_at_midpoint(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief This function adds linearizations to LP with the use of an adapted version of Kelley's algorithm.
-        *        The number of points equals at most _nvar+2.
-        *        This function requires the solution of auxiliary LPs.
-        *        Linear functions will be computed only once, since McCormick returns envelopes.
-        *        Superfluous rows in the resulting LP will be set to 0.
-        *
-        * @param[in] currentNode is the current node in the B&B
-        * @return Returns LINEARIZATION_UNKNOWN if the problem was not solved completely during linearization,
-        *         returns LINEARIZATION_INFEASIBLE if the problem was found to be infeasible,
-        *         returns LINEARIZATION_FEASIBLE if the problem was solved during linearization
-        */
+     * @brief This function adds linearizations to LP with the use of an adapted version of Kelley's algorithm.
+     *        The number of points equals at most _nvar+2.
+     *        This function requires the solution of auxiliary LPs.
+     *        Linear functions will be computed only once, since McCormick returns envelopes.
+     *        Superfluous rows in the resulting LP will be set to 0.
+     *
+     * @param[in] currentNode is the current node in the B&B
+     * @return Returns LINEARIZATION_UNKNOWN if the problem was not solved completely during linearization,
+     *         returns LINEARIZATION_INFEASIBLE if the problem was found to be infeasible,
+     *         returns LINEARIZATION_FEASIBLE if the problem was solved during linearization
+     */
     LINEARIZATION_RETCODE _linearization_points_Kelley(const babBase::BabNode &currentNode);
 
     /**
-        * @brief This function linearizes each function of the model at (_nvar+2)/2 points (except for the linear ones).
-        *        The points are computed by using the precomputed simplex vertices from _compute_and_rotate_simplex
-        *
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
-        */
+     * @brief This function linearizes each function of the model at (_nvar+2)/2 points (except for the linear ones).
+     *        The points are computed by using the precomputed simplex vertices from _compute_and_rotate_simplex
+     *
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
+     */
     LINEARIZATION_RETCODE _linearization_points_Simplex(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief This function linearizes each function of the model at (_nvar+2)/2 random points (except for the linear ones)
-        *
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
-        */
+     * @brief This function linearizes each function of the model at (_nvar+2)/2 random points (except for the linear ones)
+     *
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     * @return Returns LINEARIZATION_UNKNOWN, since the problem is not solved in this function
+     */
     LINEARIZATION_RETCODE _linearization_points_random(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief This function adds linearizations to LP with the use of an adapted version of Kelley's algorithm.
-        *        The number of points equals at most the size of the chosen linpoints vector +3.
-        *        This function requires the solution of auxiliary LPs.
-        *        Linear functions will be computed only once, since McCormick returns envelopes.
-        *        Superfluous rows in the resulting LP will be set to 0.
-        *
-        * @param[in] currentNode is the current node in the B&B
-        * @return Returns LINEARIZATION_UNKNOWN if the problem was not solved completely during linearization,
-        *         returns LINEARIZATION_INFEASIBLE if the problem was found to be infeasible,
-        *         returns LINEARIZATION_FEASIBLE if the problem was solved during linearization
-        */
+     * @brief This function adds linearizations to LP with the use of an adapted version of Kelley's algorithm.
+     *        The number of points equals at most the size of the chosen linpoints vector +3.
+     *        This function requires the solution of auxiliary LPs.
+     *        Linear functions will be computed only once, since McCormick returns envelopes.
+     *        Superfluous rows in the resulting LP will be set to 0.
+     *
+     * @param[in] currentNode is the current node in the B&B
+     * @return Returns LINEARIZATION_UNKNOWN if the problem was not solved completely during linearization,
+     *         returns LINEARIZATION_INFEASIBLE if the problem was found to be infeasible,
+     *         returns LINEARIZATION_FEASIBLE if the problem was solved during linearization
+     */
     LINEARIZATION_RETCODE _linearization_points_Kelley_Simplex(const babBase::BabNode &currentNode);
 
     /**
-        * @brief This function properly builds the LP using previously determined nonlinear and linear functions
-        *
-        * @param[in] resultRelaxationVMCNonlinear is the vector of VMC objects holding relaxations of nonlinear functions
-        * @param[in] resultRelaxationLinear is the vector of MC objects holding relaxations of linear functions
-        * @param[in] linearizationPoint is the point where the subgradient heuristic was performed and acts as a reference point for linear functions
-        * @param[in] scaledPoints are the simplex/random points scaled back to its original domain
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        */
+     * @brief This function properly builds the LP using previously determined nonlinear and linear functions
+     *
+     * @param[in] resultRelaxationVMCNonlinear is the vector of VMC objects holding relaxations of nonlinear functions
+     * @param[in] resultRelaxationLinear is the vector of MC objects holding relaxations of linear functions
+     * @param[in] linearizationPoint is the point where the subgradient heuristic was performed and acts as a reference point for linear functions
+     * @param[in] scaledPoints are the simplex/random points scaled back to its original domain
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     */
     void _update_LP_nonlinear_linear(const std::vector<vMC> &resultRelaxationVMCNonlinear, const std::vector<MC> &resultRelaxationLinear, const std::vector<double> &linearizationPoint,
                                      const std::vector<std::vector<double>> &scaledPoints, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief This function properly builds the LP using previously determined nonlinear functions
-        *
-        * @param[in] resultRelaxationNonlinear is the vector of MC objects holding relaxations of nonlinear functions
-        * @param[in] linearizationPoint is the point where the subgradient heuristic was performed
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        * @param[in] iLin is the number of linearization
-        */
+     * @brief This function properly builds the LP using previously determined nonlinear functions
+     *
+     * @param[in] resultRelaxationNonlinear is the vector of MC objects holding relaxations of nonlinear functions
+     * @param[in] linearizationPoint is the point where the subgradient heuristic was performed
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     * @param[in] iLin is the number of linearization
+     */
     void _update_LP_nonlinear(const std::vector<MC> &resultRelaxationNonlinear, const std::vector<double> &linearizationPoint,
                               const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds, const unsigned iLin);
 
     /**
-        * @brief The function resets the LP, meaning it sets all rhs to 1e19 and coefficients to 0. Eta coefficients are -1
-        *
-        * @param[in] linearizationPoint is a dummy point to save computation time
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        */
+     * @brief The function resets the LP, meaning it sets all rhs to 1e19 and coefficients to 0. Eta coefficients are -1
+     *
+     * @param[in] linearizationPoint is a dummy point to save computation time
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     */
     void _reset_LP(const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief Function for the computation of simplex points lying on a sphere with radius sphereRadius rotated by angleIn
-        *
-        * @param[in] dim denotes the dimension of the current optimization problem
-        * @param[in] angleIn is the rotation angle of the simplex, the simplex will be rotated alternating by angleIn and 180°+angleIn
-        * @param[in] sphereRadius is the radius of the dim-dimensional ball on which the simplex vertices lie
-        * @param[in] simplexPoints holds the computed points of the simplex
-        */
+     * @brief Function for the computation of simplex points lying on a sphere with radius sphereRadius rotated by angleIn
+     *
+     * @param[in] dim denotes the dimension of the current optimization problem
+     * @param[in] angleIn is the rotation angle of the simplex, the simplex will be rotated alternating by angleIn and 180°+angleIn
+     * @param[in] sphereRadius is the radius of the dim-dimensional ball on which the simplex vertices lie
+     * @param[in] simplexPoints holds the computed points of the simplex
+     */
     void _compute_and_rotate_simplex(const unsigned int dim, const double angleIn, const double sphereRadius, std::vector<std::vector<double>> &simplexPoints);
 
     /**
-        * @brief Heuristical determination of good linearization points. This function is in testing phasing and is not used
-        */
+     * @brief Heuristical determination of good linearization points. This function is in testing phasing and is not used
+     */
     void _choose_good_lin_points(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds, bool firstTime = true);
 
     /**
-        * @brief Virtual function for checking if a given node is feasible with the use of interval arithmetics.
-        *        It is needed in cases where the LBD solver may return something misleading, e.g., says sth is optimal but it can't be verified.
-        *
-        * @param[in,out] newLBD ist the lower bound obtained through intervals by the fallback function
-        * @return Returns whether the node is feasible in interval arithmetic or not
-        */
+     * @brief Virtual function for checking if a given node is feasible with the use of interval arithmetics.
+     *        It is needed in cases where the LBD solver may return something misleading, e.g., says sth is optimal but it can't be verified.
+     *
+     * @param[in,out] newLBD ist the lower bound obtained through intervals by the fallback function
+     * @return Returns whether the node is feasible in interval arithmetic or not
+     */
     virtual SUBSOLVER_RETCODE _fallback_to_intervals(double &newLBD);
 
     /**
-        * @brief Virtual function for checking if a specific option has to be turned off for a given lower bounding solver, e.g., interval-based solvers can't use OBBT
-        */
+     * @brief Virtual function for checking if a specific option has to be turned off for a given lower bounding solver, e.g., interval-based solvers can't use OBBT
+     */
     virtual void _turn_off_specific_options();
 
     /**
-        *  @brief Function used for truncation of value digits which are not guaranteed to be correct
-        *
-        *  @param[in] value ist the double to be truncated
-        *  @param[in] tolerance is a given tolerance up to which the value will be truncated, e.g., 10e9 means that 9 digits after comma will be cut off
-        */
+     *  @brief Function used for truncation of value digits which are not guaranteed to be correct
+     *
+     *  @param[in] value ist the double to be truncated
+     *  @param[in] tolerance is a given tolerance up to which the value will be truncated, e.g., 10e9 means that 9 digits after comma will be cut off
+     */
     void _truncate_value(double &value, const double tolerance)
     {
         value = std::trunc(value * (tolerance)) / (tolerance);
@@ -647,63 +682,63 @@ class LowerBoundingSolver {
 
 #ifdef LP__OPTIMALITY_CHECK
     /**
-        * @brief Virtual function for checking if the solution point returned by the LP solver is really infeasible
-        *
-        * @param[in] currentNode is holding the current node in the branch-and-bound tree
-        * @return Returns whether the problem was confirmed to be infeasible or not
-        */
+     * @brief Virtual function for checking if the solution point returned by the LP solver is really infeasible
+     *
+     * @param[in] currentNode is holding the current node in the branch-and-bound tree
+     * @return Returns whether the problem was confirmed to be infeasible or not
+     */
     virtual SUBSOLVER_RETCODE _check_infeasibility(const babBase::BabNode &currentNode);
 
     /**
-        * @brief Virtual function for checking if the solution point returned by the LP solver is really feasible
-        *
-        * @param[in] solution is holding the solution point to check
-        * @return Returns whether the given solution was confirmed to be feasible or not
-        */
+     * @brief Virtual function for checking if the solution point returned by the LP solver is really feasible
+     *
+     * @param[in] solution is holding the solution point to check
+     * @return Returns whether the given solution was confirmed to be feasible or not
+     */
     virtual SUBSOLVER_RETCODE _check_feasibility(const std::vector<double> &solution);
 
     /**
-        * @brief Virtual function for checking if the solution point returned by the LP solver is really optimal
-        *
-        * @param[in] currentNode is holding the current node in the branch-and-bound tree
-        * @param[in] newLBD is the value of the solution point to check
-        * @param[in] solution is holding the solution point to check
-        * @param[in] etaVal is holding the value of eta at the solution point
-        * @param[in] multipliers is holding the dual multipliers of the solution
-        * @return Returns whether the given solution was confirmed to be optimal or not
-        */
+     * @brief Virtual function for checking if the solution point returned by the LP solver is really optimal
+     *
+     * @param[in] currentNode is holding the current node in the branch-and-bound tree
+     * @param[in] newLBD is the value of the solution point to check
+     * @param[in] solution is holding the solution point to check
+     * @param[in] etaVal is holding the value of eta at the solution point
+     * @param[in] multipliers is holding the dual multipliers of the solution
+     * @return Returns whether the given solution was confirmed to be optimal or not
+     */
     virtual SUBSOLVER_RETCODE _check_optimality(const babBase::BabNode &currentNode, const double newLBD, const std::vector<double> &solution,
                                                 const double etaVal, const std::vector<double> &multipliers);
 
     /**
-        * @brief Function for printing the current LP stored in _MatrixA, _MatrixA_eqs, _rhsB, _rhsB_eqs
-        *
-        * @param[in] lowerVarBounds is the vector of lower bounds on your variables
-        * @param[in] upperVarBounds is the vector of upper bounds on your variables
-        */
+     * @brief Function for printing the current LP stored in _MatrixA, _MatrixA_eqs, _rhsB, _rhsB_eqs
+     *
+     * @param[in] lowerVarBounds is the vector of lower bounds on your variables
+     * @param[in] upperVarBounds is the vector of upper bounds on your variables
+     */
     void _print_LP(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds);
 
     /**
-        * @brief Checks if a branch-and-bound node contains the current incumbent (if any). Also writes output to the logger.
-        *
-        * @param[in] node is the BabNode to be checked
-        */
+     * @brief Checks if a branch-and-bound node contains the current incumbent (if any). Also writes output to the logger.
+     *
+     * @param[in] node is the BabNode to be checked
+     */
     bool _contains_incumbent(const babBase::BabNode &node);
 
 #endif
 
 #ifdef LP__WRITE_CHECK_FILES
     /**
-        * @brief Function writing the current linear program to file
-        *
-        * @param[in] fileName is the name of the written file
-        */
+     * @brief Function writing the current linear program to file
+     *
+     * @param[in] fileName is the name of the written file
+     */
     virtual void _write_LP_to_file(const std::string &fileName);
 #endif
 
     /**
-        * @name Objects for dual optimality condition check, i.e., c^T * x = y^T * b
-        */
+     * @name Objects for dual optimality condition check, i.e., c^T * x = y^T * b
+     */
     /**@{*/
     std::vector<std::vector<std::vector<double>>> _matrixObj;                /*!< objective(s) times _nLinObj times variables */
     std::vector<std::vector<std::vector<double>>> _matrixIneq;               /*!< inequalities times _nLinIneq times variables */
@@ -727,8 +762,8 @@ class LowerBoundingSolver {
     std::vector<std::vector<double>> _objectiveScalingFactors; /*!< scaling factors used in the linearizations of the objective function(s) */
 
     /**
-        * @name Pointers to several objects. Note that these are NOT const, since if we want to resolve with MAiNGO, the pointers have to change
-        */
+     * @name Pointers to several objects. Note that these are NOT const, since if we want to resolve with MAiNGO, the pointers have to change
+     */
     /**@{*/
     std::shared_ptr<DagObj> _DAGobj;                                /*!< object holding the DAG */
     std::shared_ptr<Settings> _maingoSettings;                      /*!< pointer to object holding the settings */
@@ -737,8 +772,8 @@ class LowerBoundingSolver {
     /**@}*/
 
     /**
-        * @name Internal variables for storing information on the problem
-        */
+     * @name Internal variables for storing information on the problem
+     */
     /**@{*/
     std::vector<unsigned> _nLinObj;                                /*!< vector holding the number of linearization points of the objective function(s) */
     std::vector<unsigned> _nLinIneq;                               /*!< vector holding the number of linearization points of each inequality constraint */
@@ -769,22 +804,22 @@ class LowerBoundingSolver {
 
   private:
     /**
-        * @brief Function called by do_dbbt_and_probing for solving the lower bounding problem for probing on the current node
-        *
-        * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved
-        * @param[out] dualInfo is a struct containing information from the LP solved during probing
-        * @param[in] iVar is the variable to be fixed to its bound
-        * @param[in] fixToLowerBound denotes whether the variable shall be fixed to its lower bound
-        * @return Return code, either RETCODE_FEASIBLE or RETCODE_INFEASIBLE
-        */
+     * @brief Function called by do_dbbt_and_probing for solving the lower bounding problem for probing on the current node
+     *
+     * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved
+     * @param[out] dualInfo is a struct containing information from the LP solved during probing
+     * @param[in] iVar is the variable to be fixed to its bound
+     * @param[in] fixToLowerBound denotes whether the variable shall be fixed to its lower bound
+     * @return Return code, either RETCODE_FEASIBLE or RETCODE_INFEASIBLE
+     */
     SUBSOLVER_RETCODE _solve_probing_LBP(babBase::BabNode &currentNode, LbpDualInfo &dualInfo,
                                          const unsigned int iVar, const bool fixToLowerBound);
 
     /**
-        * @brief Function for setting the correct number of linearization points depending on the LBP_linpoints setting
-        *
-        * @param[in] LBP_linPoints is the corresponding setting
-        */
+     * @brief Function for setting the correct number of linearization points depending on the LBP_linpoints setting
+     *
+     * @param[in] LBP_linPoints is the corresponding setting
+     */
     void _set_number_of_linpoints(const unsigned int LBP_linPoints);
 
 
@@ -794,30 +829,31 @@ class LowerBoundingSolver {
 };
 
 /**
-* @brief Factory function for initializing different lower bounding solver wrappers
-*
-* @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
-* @param[in] DAGvars are the variables corresponding to the DAG
-* @param[in] DAGfunctions are the functions corresponding to the DAG
-* @param[in] variables is a vector containing the optimization variables
-* @param[in] variableIsLinear is a vector containing information about which variables occur only linearly
-* @param[in] nineqIn is the number of inequality constraints
-* @param[in] neqIn is the number of equality
-* @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem
-* @param[in] neqRelaxationOnlyIn is the number of equality constraints for use only in the relaxed problem
-* @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
-* @param[in] settingsIn is a pointer to the MAiNGO settings
-* @param[in] loggerIn is a pointer to the MAiNGO logger object
-* @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
-*/
+ * @brief Factory function for initializing different lower bounding solver wrappers
+ *
+ * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
+ * @param[in] DAGvars are the variables corresponding to the DAG
+ * @param[in] DAGfunctions are the functions corresponding to the DAG
+ * @param[in] variables is a vector containing the optimization variables
+ * @param[in] variableIsLinear is a vector containing information about which variables occur only linearly
+ * @param[in] nineqIn is the number of inequality constraints
+ * @param[in] neqIn is the number of equality
+ * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem
+ * @param[in] neqRelaxationOnlyIn is the number of equality constraints for use only in the relaxed problem
+ * @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
+ * @param[in] settingsIn is a pointer to the MAiNGO settings
+ * @param[in] loggerIn is a pointer to the MAiNGO logger object
+ * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
+ */
 std::shared_ptr<LowerBoundingSolver> make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions,
                                                      const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear,
                                                      const unsigned nineqIn, const unsigned neqIn,
                                                      const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn,
-                                                     std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn);
+                                                     std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn,
+                                                     bool printSolver = true);
 
 
 }    // end namespace lbp
 
 
-}    // end namespace maingo
\ No newline at end of file
+}    // end namespace maingo
diff --git a/inc/lbpClp.h b/inc/lbpClp.h
index 3655179..18bd55d 100644
--- a/inc/lbpClp.h
+++ b/inc/lbpClp.h
@@ -235,7 +235,7 @@ class LbpClp: public LowerBoundingSolver {
         *
         * @param[in] currentNode is the currentNode, needed for throwing exceptions or obtaining the lower and upper bounds of variables
         */
-    void _solve_LP(const babBase::BabNode &currentNode);
+	LP_RETCODE _solve_LP(const babBase::BabNode &currentNode);
 
     /**
         * @brief Function returning the current status of the lastly solved linear program.
diff --git a/inc/lbpCplex.h b/inc/lbpCplex.h
index 907870c..acb1e14 100644
--- a/inc/lbpCplex.h
+++ b/inc/lbpCplex.h
@@ -233,7 +233,7 @@ class LbpCplex: public LowerBoundingSolver {
         *
         * @param[in] currentNode is the currentNode, needed for throwing exceptions or obtaining the lower and upper bounds of variables
         */
-    void _solve_LP(const babBase::BabNode &currentNode);
+	LP_RETCODE _solve_LP(const babBase::BabNode &currentNode);
 
     /**
         * @brief Function returning the current status of the lastly solved linear program.
@@ -354,6 +354,11 @@ class LbpCplex: public LowerBoundingSolver {
     virtual void _write_LP_to_file(const std::string &fileName);
 #endif
 
+public:
+    void _print_LP() {
+        std::cout << cplxModel << "\n";
+    }
+
   private:
     /**
         * @brief Function for taking care of memory management by terminating Cplex (either called from destructor or when an exception is thrown)
diff --git a/inc/lbpDagObj.h b/inc/lbpDagObj.h
index c01a2b7..566e9ea 100644
--- a/inc/lbpDagObj.h
+++ b/inc/lbpDagObj.h
@@ -69,13 +69,23 @@ struct DagObj {
 
 #ifdef HAVE_GROWING_DATASETS
     //Additional variables for MAiNGO with growing datasets
-    unsigned int indexFirstData;                                    /*!< position of the first objective per data in MAiNGO::_DAGfunctions */
-    std::shared_ptr<std::vector<std::set<unsigned int>>> datasets;  /*!< pointer to a vector containing all available datasets */
-    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraph;    /*!< vector containing pointers to subgraph of all previously used datasets. Note: position in this vector = index of dataset */
-    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObj; /*!< vector containing pointers to subgraphObj of all previously used datasets. Note: position in this vector = index of dataset */
-    std::vector<std::vector<mc::FFVar>> storedFunctions;            /*!< vector containing functions of all previously used datasets. Note: position in this vector = index of dataset */
-    std::vector<std::vector<mc::FFVar>> storedFunctionsObj;         /*!< vector containing functionsObj of all previously used datasets. Note: position in this vector = index of dataset */
-#endif                                                              // HAVE_GROWING_DATASETS
+    bool useMse;                                                             /*!< whether to use mean squared error or summed squared error as the objective function */
+    unsigned int indexFirstData;                                             /*!< position of the first objective per data in MAiNGO::_DAGfunctions */
+    std::shared_ptr<std::vector<std::set<unsigned int>>> datasets;           /*!< pointer to a vector containing all available datasets */
+    std::shared_ptr<std::set<unsigned int>> datasetResampled;                /*!< pointer to resampled initial dataset */
+    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraph;             /*!< vector containing pointers to subgraph of all previously used datasets. Note: position in this vector = index of dataset */
+    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObj;          /*!< vector containing pointers to subgraphObj of all previously used datasets. Note: position in this vector = index of dataset */
+    std::vector<std::vector<mc::FFVar>> storedFunctions;                     /*!< vector containing functions of all previously used datasets. Note: position in this vector = index of dataset */
+    std::vector<std::vector<mc::FFVar>> storedFunctionsObj;                  /*!< vector containing functionsObj of all previously used datasets. Note: position in this vector = index of dataset */
+    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphCompl;        /*!< vector containing pointers to subgraph of all previously used complementary datasets. Note: position in this vector = index of corresponding reduced dataset - 1 */
+    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObjCompl;     /*!< vector containing pointers to subgraphObj of all previously used complementary datasets. Note: position in this vector = index of corresponding reduced dataset - 1 */
+    std::vector<std::vector<mc::FFVar>> storedFunctionsCompl;                /*!< vector containing functions of all previously used complementary datasets. Note: position in this vector = index of corresponding reduced dataset - 1 */
+    std::vector<std::vector<mc::FFVar>> storedFunctionsObjCompl;             /*!< vector containing functionsObj of all previously used complementary datasets. Note: position in this vector = index of corresponding reduced dataset - 1 */
+    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphResampled;    /*!< pointer to subgraph of resampled initial dataset. Note: used vector with one element such that MC++ iterators are not corrupted when leaving space of DagObj */
+    std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObjResampled; /*!< pointer to subgraphObj of resampled initial dataset. Note: used vector with one element such that MC++ iterators are not corrupted when leaving space of DagObj  */
+    std::vector<mc::FFVar> storedFunctionsResampled;                         /*!< functions of resampled initial dataset */
+    std::vector<mc::FFVar> storedFunctionsObjResampled;                      /*!< functionsObj of resampled initial dataset */
+#endif // HAVE_GROWING_DATASETS
 
     MC infinityMC;                  /*!< dummy MC object holding all zeros and infinity */
     double validIntervalLowerBound; /*!< variable holding a valid interval lower bound of the objective function */
@@ -117,12 +127,24 @@ struct DagObj {
     */
     void add_subgraph_for_new_dataset(const unsigned int indexDataset);
 
+    /**
+    * @brief Function for adding subgraph corresponding to complementary set of reduced dataset
+    *
+    * @param[in] indexDataset is the index number of the reduced dataset to be used
+    */
+    void add_subgraph_for_complementary_dataset(const unsigned int indexDataset);
+
     /**
 	* @brief Function for changing objective in dependence of a (reduced) dataset
 	*
 	* @param[in] indexDataset is the index number of the (reduced) dataset to be used
 	*/
-    void change_growing_objective(const unsigned int indexDataset);
+    void change_growing_objective(const int indexDataset);
+
+    /**
+    * @brief Function for changing objective to resampled initial dataset
+    */
+    void change_growing_objective_for_resampling();
 #endif    //HAVE_GROWING_DATASETS
 };
 
diff --git a/inc/lbpInterval.h b/inc/lbpInterval.h
index 4656972..6a1505b 100644
--- a/inc/lbpInterval.h
+++ b/inc/lbpInterval.h
@@ -159,7 +159,7 @@ class LbpInterval: public LowerBoundingSolver {
         *
         * @param[in] currentNode is the currentNode, needed for throwing exceptions or similar
         */
-    void _solve_LP(const babBase::BabNode &currentNode);
+	LP_RETCODE _solve_LP(const babBase::BabNode &currentNode);
 
     /**
         * @brief Function for checking if a specific option has to be turned off for a given lower bounding solver
diff --git a/inc/lbpTwoStage.h b/inc/lbpTwoStage.h
new file mode 100644
index 0000000..d17342a
--- /dev/null
+++ b/inc/lbpTwoStage.h
@@ -0,0 +1,2366 @@
+/**********************************************************************************
+ * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ **********************************************************************************/
+
+#pragma once
+
+#include <map>
+
+#include "instrumentor.h"
+
+#include "babTree.h"
+
+#include "logger.h"
+
+#include "lbp.h"
+#include "lbpClp.h"
+#include "lbpInterval.h"
+
+#include "siblingResults.h"
+
+#ifdef HAVE_CPLEX    // If Cmake has found CPLEX this pre-processor variable is set
+  #include "lbpCplex.h"
+#endif
+
+#ifdef _OPENMP   // If Cmake has found OpenMP this pre-processor variable is set
+  #include <omp.h>
+#endif
+
+#include "TwoStageModel.h"
+
+namespace maingo {
+
+namespace lbp {
+
+/**
+ * @class LbpTwoStage
+ * @brief Wrapper for handling the lower-bounding problems of two stage (stochastic) programming problems
+ *
+ * This class constructs one lower-bounding sub-problem solver per scenario and delegates solve calls to these subproblem solvers.
+ */
+template<class subsolver_class>
+class LbpTwoStage : public subsolver_class {
+
+  public:
+    /**
+     * @brief Constructor, stores information on the problem and initializes the sub-solver instances.
+     *
+     * @param[in] twoStageModel is the pointer to the TwoStageModel opbject
+     * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
+     * @param[in] DAGvars are the variables corresponding to the DAG
+     * @param[in] DAGfunctions are the functions corresponding to the DAG
+     * @param[in] variables is a vector containing the initial optimization variables defined in problem.h
+     * @param[in] nineqIn is the number of inequality constraints
+     * @param[in] neqIn is the number of equality
+     * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem
+     * @param[in] neqRelaxationOnlyIn is the number of equality constraints for use only in the relaxed problem
+     * @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
+     * @param[in] settingsIn is a pointer to the MAiNGO settings
+     * @param[in] loggerIn is a pointer to the MAiNGO logger object
+     * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
+     */
+    LbpTwoStage(const std::shared_ptr<maingo::TwoStageModel> twoStageModel,
+                mc::FFGraph &DAG,
+                const std::vector<mc::FFVar> &DAGvars,
+                const std::vector<mc::FFVar> &DAGfunctions,
+                const std::vector<babBase::OptimizationVariable> &variables,
+                const std::vector<bool> &variableIsLinear,
+                const unsigned nineqIn,
+                const unsigned neqIn,
+                const unsigned nineqRelaxationOnlyIn,
+                const unsigned neqRelaxationOnlyIn,
+                const unsigned nineqSquashIn,
+                std::shared_ptr<Settings> settingsIn,
+                std::shared_ptr<Logger> loggerIn,
+                std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn):
+        subsolver_class(DAG, DAGvars, DAGfunctions, variables, variableIsLinear,
+                        nineqIn, neqIn, nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn,
+                        settingsIn, loggerIn, constraintPropertiesIn),
+        _TwoStageModel(twoStageModel), _No(2 << (twoStageModel->Ns - 1))
+    {
+
+        PROFILE_FUNCTION()
+
+        _subproblemSolutions.resize(_TwoStageModel->Ns);
+        _subproblemBounds.reserve(  // enough space for storing serialized sibling results
+            1 * twoStageModel->Ns                                                    // parentSubproblemBounds
+            + 2 * twoStageModel->Ns                                                  // objectiveBounds for both siblings
+            + 2 * twoStageModel->Ns * (twoStageModel->Nx + twoStageModel->Ny)        // lower bounding solutions for both siblings
+            + 2 * 2 * twoStageModel->Ns * (twoStageModel->Nx + twoStageModel->Ny)    // lower and upper bounds for all scenario subproblems of both siblings
+        );
+        _subproblemBounds.resize(_TwoStageModel->Ns);
+        _subproblemDualInfo.resize(_TwoStageModel->Ns);
+
+        LowerBoundingSolver::_lowerVarBounds.resize(LowerBoundingSolver::_nvar);
+        LowerBoundingSolver::_upperVarBounds.resize(LowerBoundingSolver::_nvar);
+
+        // Creating empty containers for each scenario
+        SP_opt_variables.resize(_TwoStageModel->Ns);
+        SP_DAGs = std::vector<mc::FFGraph>(_TwoStageModel->Ns);
+        SP_DAG_variables.resize(_TwoStageModel->Ns);
+        SP_DAG_x.resize(_TwoStageModel->Ns);
+        SP_DAG_y.resize(_TwoStageModel->Ns);
+        SP_DAG_functions.resize(_TwoStageModel->Ns);
+        _subNodes.reserve(_TwoStageModel->Ns);
+
+      // Filling the containers above
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; ++s) {
+        PROFILE_SCOPE("LBS variable creation")
+
+        _subproblemSolutions[s].reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+        _subproblemDualInfo[s].multipliers.reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+
+        SP_opt_variables[s].reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+        SP_DAG_variables[s].reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+
+        for (unsigned ix = 0; ix < _TwoStageModel->Nx; ++ix) {
+          SP_opt_variables[s].push_back(variables[ix]);
+          SP_DAG_variables[s].emplace_back(&SP_DAGs[s]);
+          SP_DAG_x[s].push_back(SP_DAG_variables[s].back());
+        }
+      
+        for (unsigned iy = 0; iy < _TwoStageModel->Ny; ++iy) {
+          SP_opt_variables[s].push_back(variables[_TwoStageModel->Nx + s * _TwoStageModel->Ny + iy]);
+          SP_DAG_variables[s].emplace_back(&SP_DAGs[s]);
+          SP_DAG_y[s].push_back(SP_DAG_variables[s].back());
+        }
+      }
+      std::vector<std::vector<std::vector<NamedVar>>> g1, g2;
+      g1.reserve(_TwoStageModel->Ns);
+      g2.reserve(_TwoStageModel->Ns);
+      // This loop might need to happen sequentially, since _TwoStageModel may not be thread-safe (e.g. when defined via the Python API)
+      for (int s = 0; s < _TwoStageModel->Ns; ++s) {
+        PROFILE_SCOPE("LBS function evaluation")
+
+        SP_DAG_functions[s].emplace_back(
+          _TwoStageModel->f1_func(SP_DAG_x[s])
+          + _TwoStageModel->f2_func(SP_DAG_x[s], SP_DAG_y[s], _TwoStageModel->data[s])
+        );
+        g1.emplace_back(_TwoStageModel->g1_func(SP_DAG_x[s]));
+        g2.emplace_back(_TwoStageModel->g2_func(SP_DAG_x[s], SP_DAG_y[s], _TwoStageModel->data[s]));
+      }
+
+      SP_constraint_properties = std::vector<std::shared_ptr<std::vector<Constraint>>>(_TwoStageModel->Ns);
+      _subsolvers.resize(_TwoStageModel->Ns);
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; ++s) {
+        PROFILE_SCOPE("LBS constraint property creation")
+
+        SP_constraint_properties[s] = std::make_shared<std::vector<Constraint>>();
+        _prepare_constraints(std::to_string(s), SP_constraint_properties[s], SP_DAG_functions[s], g1[s], g2[s]);
+        _subsolvers[s] = lbp::make_lbp_solver(
+            SP_DAGs[s],
+            SP_DAG_variables[s],  // [x_1, ..., x_Nx, y_s_1, ..., y_s_Ny]
+            SP_DAG_functions[s],  // [f1+f2s, g1_1, ... g1_n, g2s_1, ..., g2s_m]
+            SP_opt_variables[s],
+            variableIsLinear,
+            _TwoStageModel->Nineq1 + _TwoStageModel->Nineq2,
+            _TwoStageModel->Neq1 + _TwoStageModel->Neq2,
+            _TwoStageModel->NineqRelOnly1 + _TwoStageModel->NineqRelOnly2,
+            _TwoStageModel->NeqRelOnly1 + _TwoStageModel->NeqRelOnly2,
+            _TwoStageModel->Nsquash1 + _TwoStageModel->Nsquash2,
+            settingsIn,
+            LowerBoundingSolver::_logger,  // might get noisy with verbose output when solving in parallel...
+            SP_constraint_properties[s],
+            false
+          );
+      }
+    }
+
+    std::vector<mc::FFGraph> SP_DAGs;  // DAGs corresponding to subgraphs of single-scenario SP subproblems
+    std::vector<std::vector<mc::FFVar>> SP_DAG_variables;  // copies of all variables
+    std::vector<std::vector<mc::FFVar>> SP_DAG_functions;  // copies of FFVar objects representing objective and constraints for subproblems
+    std::vector<std::vector<babBase::OptimizationVariable>> SP_opt_variables;
+    std::vector<std::shared_ptr<std::vector<Constraint>>> SP_constraint_properties;  // Revised constraint properties
+    // TODO: Do we really need these? --> Currently yes, due to the _TwoStageModel interface
+    std::vector<std::vector<mc::FFVar>> SP_DAG_x;  // copies of first-stage variables from SP_DAG_variables
+    std::vector<std::vector<mc::FFVar>> SP_DAG_y;  // copies of second-stage variables from SP_DAG_variables
+
+    /**
+     * @brief Function called by the B&B solver to heuristically activate more scaling in the LBS
+     */
+    void activate_more_scaling() override {
+      for (auto & ss : _subsolvers) {
+        ss->activate_more_scaling();
+      }
+    }
+
+    /**
+     * @brief Function called by B&B solver for optimality-based range reduction (cf., e.g., Gleixner et al., J. Glob. Optim. 67 (2017) 731)
+     *
+     * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved; if OBBT is successful in tightening bounds, currentNode will be modified accordingly
+     * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); It is used for the objective function cut if reductionType==OBBT_FEASOPT
+     * @param[in] reductionType determines whether OBBT should include only feasibility or also optimality (i.e., an objective function cut f_cv<=currentUBD)
+     * @return Return code, see enum TIGHTENING_RETCODE
+     */
+    TIGHTENING_RETCODE solve_OBBT(babBase::BabNode &currentNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars) override {
+      PROFILE_FUNCTION()
+
+      if (currentNode.get_parent_ID() > 0 && LowerBoundingSolver::_maingoSettings->TS_parallelOBBT) {
+        // use OBBT based on scenario subproblems
+        return solve_parallel_OBBT(currentNode, currentUBD, reductionType, includeLinearVars);
+      }
+
+      /** NOTE: We cannot just do the straightforward thing below, because
+       *        calls to the virtual functions from lbp will be redirected to
+       *        the overridden functions in this class instead of the
+       *        subsolver_class
+       */
+      // use OBBT based on deterministic equivalent
+      // return subsolver_class::solve_OBBT(currentNode, currentUBD, reductionType);
+
+      if ((reductionType == OBBT_FEAS) && LowerBoundingSolver::_onlyBoxConstraints)
+      {
+        return TIGHTENING_UNCHANGED;
+      }
+
+      std::vector<double> lowerVarBounds(currentNode.get_lower_bounds()), upperVarBounds(currentNode.get_upper_bounds());
+      std::vector<double> originalWidth(LowerBoundingSolver::_nvar);
+      for (size_t i = 0; i < LowerBoundingSolver::_nvar; ++i) {
+        originalWidth[i] = upperVarBounds[i] - lowerVarBounds[i];
+      }
+      bool nodeChanged = false;
+      // Update the LP for the current node (i.e., modify bounds and update coefficients and RHS)
+      LINEARIZATION_RETCODE linStatus;
+      try {
+        PROFILE_SCOPE("OBBT_update_LP")
+        linStatus = subsolver_class::_update_LP(currentNode);
+      }
+      catch (std::exception &e) { // GCOVR_EXCL_START
+        throw MAiNGOException("  Error while modifying the lower bounding LP for OBBT.", e, currentNode);
+      }
+      catch (...) {
+        throw MAiNGOException("  Unknown error while modifying the lower bounding LP for OBBT.", currentNode);
+      }
+      // GCOVR_EXCL_STOP
+
+      bool foundInfeasible = false;
+      if (linStatus == LINEARIZATION_INFEASIBLE) {
+        LowerBoundingSolver::_logger->print_message("  OBBT linearization status: Infeasible", VERB_ALL, LBP_VERBOSITY);
+
+#ifdef MAiNGO_DEBUG_MODE
+        if (_contains_incumbent(currentNode)) {
+          const  bool reallyInfeasible = subsolver_class::_check_if_LP_really_infeasible();
+          if (reallyInfeasible) {
+  #ifdef LP__WRITE_CHECK_FILES
+            _write_LP_to_file("solve_OBBT_infeas_at_linearization_with_incumbent_in_node");
+  #endif
+            if (currentNode.get_ID() == 0) {
+              return TIGHTENING_INFEASIBLE;    // For the root node, we immediately want to report this false infeasibility claim since we want to completeley disable OBBT based on this information.
+            }
+            std::ostringstream outstr;
+            outstr << "  Warning: Node with id " << currentNode.get_ID() << " declared infeasible by linearization technique in OBBT although it contains the incumbent. Skipping OBBT..." << std::endl;
+            LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+          }
+          else {
+            LowerBoundingSolver::_logger->print_message("  Found node to not actually be infeasible. Problem seems to be difficult numerically. Skipping OBBT...", VERB_ALL, LBP_VERBOSITY);
+          }
+          return TIGHTENING_UNCHANGED;
+        }
+#endif  // MAiNGO_DEBUG_MODE
+          foundInfeasible = true;
+  #ifdef LP__OPTIMALITY_CHECK
+          if (subsolver_class::_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) {
+            foundInfeasible = false;
+          }
+  #endif
+      }
+
+      // Only do OBBT if the LP was not found to be infeasible during the linearization
+      if (!foundInfeasible) {
+        // Prepare OBBT
+        std::list<unsigned> toTreatMax, toTreatMin;
+        std::vector<double> lastPoint(LowerBoundingSolver::_nvar);
+        for (unsigned ivar = 0; ivar < LowerBoundingSolver::_nvar; ivar++) {    // Note that we also treat auxiliaries (if any are added)
+          if (!LowerBoundingSolver::_variableIsLinear[ivar] || includeLinearVars) {
+            toTreatMax.push_back(ivar);
+            toTreatMin.push_back(ivar);
+          }
+          lastPoint[ivar] = 0.5 * (lowerVarBounds[ivar] + upperVarBounds[ivar]);
+        }
+        // Modify treatment of objective function
+        switch (reductionType) {
+          case OBBT_FEAS:    // Feasibility-based only
+          {
+            // Objective function is not needed in this case --> "deactivate" objective linearizations
+            subsolver_class::_deactivate_objective_function_for_OBBT();
+            break;
+          }
+          case OBBT_FEASOPT:    // including both feasibility and optimality
+          {
+            // Modify objective: remove eta and establish proper ubd
+            subsolver_class::_modify_LP_for_feasopt_OBBT(currentUBD, toTreatMax, toTreatMin);
+            break;
+          }
+          default:
+          {  // GCOVR_EXCL_START
+            std::ostringstream errmsg;
+            errmsg << "  Unknown OBBT range reduction type: " << reductionType;
+            throw MAiNGOException(errmsg.str(), currentNode);
+          }
+          // GCOVR_EXCL_STOP
+        }
+
+        // Loop over variable bounds until all variable bounds have either been treated by OBBT or filtered
+        unsigned OBBTcnt  = 0;
+        bool foundRelFeas = false;
+        while ((toTreatMax.size() + toTreatMin.size()) > 0) {
+          PROFILE_SCOPE("OBBT_loop")
+
+          OBBTcnt++;
+          // Select next candidate lower bound
+          std::list<unsigned>::iterator tmpIt = toTreatMin.begin(), nextMinIt = toTreatMin.begin();
+          double smallestDistanceMin = LowerBoundingSolver::_maingoSettings->infinity;
+          if (foundRelFeas == true) {
+            // Get minimum difference between last point and one lower variable bound, and use last point for trivial filtering (cf. Gleixner et al., J. Global Optim 67 (2017) 731)
+            while (tmpIt != toTreatMin.end()) {
+              double tmpDistance = (lastPoint[*tmpIt] - lowerVarBounds[*tmpIt]);
+              double diameter    = upperVarBounds[*tmpIt] - lowerVarBounds[*tmpIt];
+              if ((diameter < LowerBoundingSolver::_computationTol) || (tmpDistance <= diameter * LowerBoundingSolver::_maingoSettings->LBP_obbtMinImprovement)) {
+                tmpIt = toTreatMin.erase(tmpIt);
+              }
+              else {
+                if (tmpDistance < smallestDistanceMin) {
+                  smallestDistanceMin = tmpDistance;
+                  nextMinIt           = tmpIt;
+                }
+                tmpIt++;
+              }
+            }
+          }
+          else {
+            // If no feasible (in relaxation) point was found, no need to search for the closest bound (first lower bound will be considered, if one exists)
+            if (!toTreatMin.empty()) {
+              smallestDistanceMin = 0;    // If there are still lower bounds to be treated, these get priority
+            }
+          }
+
+          // Select next candidate upper bound
+          std::list<unsigned>::iterator nextMaxIt = toTreatMax.begin();
+          tmpIt                                   = toTreatMax.begin();
+          double smallestDistanceMax              = LowerBoundingSolver::_maingoSettings->infinity;
+          if (foundRelFeas == true) {
+            // Get minimum difference between last point and one upper variable bound and use last point for trivial filtering (cf. Gleixner et al., J. Global Optim 67 (2017) 731)
+            while (tmpIt != toTreatMax.end()) {
+              double tmpDistance = (upperVarBounds[*tmpIt] - lastPoint[*tmpIt]);
+              double diameter    = upperVarBounds[*tmpIt] - lowerVarBounds[*tmpIt];
+              if ((diameter < LowerBoundingSolver::_computationTol) || (tmpDistance <= diameter * LowerBoundingSolver::_maingoSettings->LBP_obbtMinImprovement)) {
+                tmpIt = toTreatMax.erase(tmpIt);
+              }
+              else {
+                if (tmpDistance < smallestDistanceMax) {
+                  smallestDistanceMax = tmpDistance;
+                  nextMaxIt           = tmpIt;
+                }
+                tmpIt++;
+              }
+            }
+          }
+          else {
+            // If no feasible (in relaxation) point was found, no need to search for the closest bound (first upper bound will be considered, if one exists)
+            if (!toTreatMax.empty()) {
+              smallestDistanceMax = 0.5;    // If there are still upper bounds to be treated, these should be considered (smallestDistanceMax<infinity), but lower bounds get priority (just to ensure reproducibility)
+            }
+          }
+
+          // If the last variables left just got erased, there is nothing left to do:
+          if ((smallestDistanceMax >= LowerBoundingSolver::_maingoSettings->infinity) && (smallestDistanceMin >= LowerBoundingSolver::_maingoSettings->infinity)) {
+            break;
+          }
+
+          // Depending on which one is better (max or min), prepare OBBT
+          unsigned iVar;                            // Index of the variable to be modified
+          std::vector<double> *boundVector;         // Pointer to the bound vector to be modified
+          std::vector<double> *otherBoundVector;    // Pointer to the bound vector that is not to be modified in the current run
+          int optimizationSense;                    // 1: minimize, -1: maximize
+          if (smallestDistanceMin <= smallestDistanceMax) {
+            iVar = *nextMinIt;
+            toTreatMin.erase(nextMinIt);
+            boundVector       = &lowerVarBounds;
+            otherBoundVector  = &upperVarBounds;
+            optimizationSense = 1;    // 1 = minimize
+          }
+          else {
+            iVar = *nextMaxIt;
+            toTreatMax.erase(nextMaxIt);
+            boundVector       = &upperVarBounds;
+            otherBoundVector  = &lowerVarBounds;
+            optimizationSense = -1;    // -1 = maximize
+          }
+
+          // Conduct OBBT: solve LP and update bound
+          subsolver_class::_set_optimization_sense_of_variable(iVar, optimizationSense);    // Depending on whether we want to change upper or lower bound, use +1 or -1 as coefficient
+          {
+            PROFILE_SCOPE("OBBT_solve_LP")
+            LowerBoundingSolver::_LPstatus = subsolver_class::_solve_LP(currentNode);
+          }
+          if (LowerBoundingSolver::_LPstatus == LP_INFEASIBLE) {
+
+#ifdef MAiNGO_DEBUG_MODE
+            if (_contains_incumbent(currentNode)) {
+                const bool reallyInfeasible = _check_if_LP_really_infeasible();
+                if (reallyInfeasible) {
+  #ifdef LP__WRITE_CHECK_FILES
+                _write_LP_to_file("solve_OBBT_infeas_with_incumbent_in_node");
+  #endif
+                if (currentNode.get_ID() == 0) {
+                  subsolver_class::_restore_LP_coefficients_after_OBBT();
+                  return TIGHTENING_INFEASIBLE;    // For the root node, we immediately want to report this false infeasibility claim since we want to completeley disable OBBT based on this information.
+                }
+                std::ostringstream outstr;
+                outstr << "  Warning: Node with id " << currentNode.get_ID() << " declared infeasible by OBBT although it contains the incumbent. Skipping OBBT..." << std::endl;
+                LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                break;
+              }
+              else {
+                std::ostringstream outstr;
+                outstr << "  Warning: Node with id " << currentNode.get_ID() << " is numerically sensitive in OBBT for bound " << iVar << " with sense " << optimizationSense << ". Skipping this bound..." << std::endl;
+                LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                subsolver_class::_set_optimization_sense_of_variable(iVar, 0);
+                continue;
+              }
+            }    // end of if (_contains_incumbent(currentNode))
+#endif  // MAiNGO_DEBUG_MODE
+            foundInfeasible = true;
+#ifdef LP__OPTIMALITY_CHECK
+            if (subsolver_class::_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) {
+              foundInfeasible = false;
+              break;
+            }
+#endif
+            LowerBoundingSolver::_logger->print_message("  OBBT status: " + std::to_string(LowerBoundingSolver::_LPstatus), VERB_ALL, LBP_VERBOSITY);
+
+            break;
+          }    // end of if(_LPstatus == LP_INFEASIBLE)
+          else if (LowerBoundingSolver::_LPstatus != LP_OPTIMAL) {  // LP_UNKNOWN
+            std::ostringstream outstr;
+            outstr << "  Warning: No optimal solution found in OBBT. Status: " << LowerBoundingSolver::_LPstatus << ". Skipping OBBT..." << std::endl;
+            LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+            break;
+          }
+          else {  // LP_OPTIMAL
+
+            // Process solution: solution point to be used as "last point" in the next round
+            std::vector<double> tmpPoint(LowerBoundingSolver::_nvar);
+            double dummy = 0;
+            try {
+              subsolver_class::_get_solution_point(tmpPoint, dummy);
+            }
+            catch (std::exception &e) {  // GCOVR_EXCL_START
+              std::ostringstream outstr;
+              outstr << "  Warning: Variables at solution of OBBT could be not obtained by LP solver: " << e.what() << std::endl;
+              LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+              subsolver_class::_set_optimization_sense_of_variable(iVar, 0);
+              continue;
+            }
+            // GCOVR_EXCL_STOP
+  #ifdef LP__OPTIMALITY_CHECK
+            if (subsolver_class::_check_feasibility(tmpPoint) == SUBSOLVER_INFEASIBLE) {
+              subsolver_class::_set_optimization_sense_of_variable(iVar, 0);
+              continue;
+            }
+  #endif
+            foundRelFeas = true;
+            lastPoint    = tmpPoint;
+
+            // Make sure the new bound makes sense and does not violate variable bounds
+            double objectiveValue = subsolver_class::_get_objective_value_solver();
+
+            if (!(objectiveValue >= (-LowerBoundingSolver::_maingoSettings->infinity))) {  // GCOVR_EXCL_START
+              std::ostringstream outstr;
+              outstr << "  Warning: Objective obtained from LP solver in OBBT is out of bounds (" << objectiveValue << ") although LP solution status is optimal. Skipping this bound." << std::endl;
+              LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+              subsolver_class::_set_optimization_sense_of_variable(iVar, 0);
+              continue;
+            }
+            // GCOVR_EXCL_STOP
+
+            double newBound = optimizationSense * objectiveValue;    // Again depending on whether we want to change upper or lower bound, need to account for sign
+
+#ifdef MAiNGO_DEBUG_MODE
+            if (_contains_incumbent(currentNode)) {
+              if (optimizationSense > 0) {    // Lower bound
+                if (iVar < LowerBoundingSolver::_incumbent.size()) {
+                  if (LowerBoundingSolver::_incumbent[iVar] < newBound) {
+                    // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                    if (!mc::isequal(LowerBoundingSolver::_incumbent[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+#ifdef LP__WRITE_CHECK_FILES
+                      subsolver_class::_write_LP_to_file("solve_OBBT_bound_infeas_with_incumbent_in_node");
+#endif
+                      std::ostringstream outstr;
+                      outstr << "  Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed a lower bound for variable " << iVar << " which cuts off the incumbent. " << std::endl
+                            << "           Correcting this bound and skipping OBBT... " << std::endl;
+                      LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                    }
+                    // We skip the bound, even if the bound is within computational tolerances
+                    break;
+                  }
+                }
+              }
+              else {    // Upper bound
+                if (iVar < LowerBoundingSolver::_incumbent.size()) {
+                  if (LowerBoundingSolver::_incumbent[iVar] > newBound) {
+                    // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                    if (!mc::isequal(LowerBoundingSolver::_incumbent[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                      std::ostringstream outstr;
+                      outstr << "  Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed an upper bound for variable " << iVar << " which cuts off the incumbent. " << std::endl
+                             << "           Correcting this bound and skipping OBBT... " << std::endl;
+                      LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                    }
+                    // We skip the bound, even if the bound is within computational tolerances
+                    break;
+                  }
+                }
+              }
+            }
+#endif  // MAiNGO_DEBUG_MODE
+
+            double remainingWidth = optimizationSense * (*otherBoundVector)[iVar] - optimizationSense * newBound;
+            if (remainingWidth < -LowerBoundingSolver::_computationTol) {
+              if (LowerBoundingSolver::_originalVariables[iVar].get_variable_type() >= babBase::enums::VT_BINARY /* this includes VT_INTEGER */) {
+                // The problem is found to be infeasible, since, e.g., lb of variable 1 was tightened to 3.2 and was then set to 4. Later on ub of variable 1 is tightened to 3.6 and thus set to 3,
+                // meaning that this node is infeasible
+                // This could be extended to saving the old lb value 3.2 and checking if it does not cross the new ub value 3.6
+                subsolver_class::_restore_LP_coefficients_after_OBBT();
+                return TIGHTENING_INFEASIBLE;
+              }
+              // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+              if (!mc::isequal(optimizationSense * newBound, optimizationSense * (*otherBoundVector)[iVar], LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                std::ostringstream outstr;
+                outstr << "  Warning: Bounds crossover for variable " << iVar << " during OBBT with optimizationSense " << optimizationSense << ":" << std::endl;
+                if (optimizationSense > 0) {
+                  outstr << std::setprecision(16) << "  Lower Bound = " << newBound << " > " << std::setprecision(16) << (*otherBoundVector)[iVar] << " = Upper Bound. Skipping this bound." << std::endl;
+                }
+                else {
+                  outstr << std::setprecision(16) << "  Upper Bound = " << newBound << " < " << std::setprecision(16) << (*otherBoundVector)[iVar] << " = Lower Bound. Skipping this bound." << std::endl;
+                }
+                LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+              }
+              subsolver_class::_set_optimization_sense_of_variable(iVar, 0);
+              continue;
+            }
+            else {
+
+              // Update bound
+              if (remainingWidth < LowerBoundingSolver::_computationTol) {
+                // Can't surely set bounds equal, there is the possibility that we make the LP infeasible although it isn't !
+                // (*boundVector)[iVar] = (*otherBoundVector)[iVar];
+              }
+              else {
+                switch (LowerBoundingSolver::_originalVariables[iVar].get_variable_type()) {
+                  case babBase::enums::VT_CONTINUOUS:
+                    // Only update bounds if difference is larger than tolerance
+                    if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                      nodeChanged          = true;
+                      (*boundVector)[iVar] = newBound;
+                    }
+                    break;
+                  case babBase::enums::VT_BINARY:
+                    // Round bounds to ensure binary values
+                    if (optimizationSense > 0) {    // Lower bound
+                      if (!mc::isequal(newBound, 0, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        nodeChanged          = true;
+                        (*boundVector)[iVar] = 1;
+                      }
+                      // Integer bounds crossing => node is infeasible
+                      if ((*boundVector)[iVar] > upperVarBounds[iVar]) {
+                        subsolver_class::_restore_LP_coefficients_after_OBBT();
+                        return TIGHTENING_INFEASIBLE;
+                      }
+                    }
+                    else {    // Upper bound
+                      if (!mc::isequal(newBound, 1, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        nodeChanged          = true;
+                        (*boundVector)[iVar] = 0;
+                      }
+                      // Integer bounds crossing => node is infeasible
+                      if ((*boundVector)[iVar] < lowerVarBounds[iVar]) {
+                        subsolver_class::_restore_LP_coefficients_after_OBBT();
+                        return TIGHTENING_INFEASIBLE;
+                      }
+                    }
+                    break;
+                  case babBase::enums::VT_INTEGER:
+                    // Round bounds to ensure integer values
+                    if (optimizationSense > 0) {    // Lower bound
+                      if (!mc::isequal(newBound, std::floor(newBound), LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        newBound = std::ceil(newBound);
+                      }
+                      else {
+                        newBound = std::floor(newBound);
+                      }
+                      if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        nodeChanged          = true;
+                        (*boundVector)[iVar] = newBound;
+                      }
+                      // Integer bounds crossing => node is infeasible
+                      if ((*boundVector)[iVar] > upperVarBounds[iVar]) {
+                        subsolver_class::_restore_LP_coefficients_after_OBBT();
+                        return TIGHTENING_INFEASIBLE;
+                      }
+                    }
+                    else {    // Upper bound
+                      if (!mc::isequal(newBound, std::ceil(newBound), LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        newBound = std::floor(newBound);
+                      }
+                      else {
+                        newBound = std::ceil(newBound);
+                      }
+                      if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        nodeChanged          = true;
+                        (*boundVector)[iVar] = newBound;
+                      }
+                      // Integer bounds crossing => node is infeasible
+                      if ((*boundVector)[iVar] < lowerVarBounds[iVar]) {
+                        subsolver_class::_restore_LP_coefficients_after_OBBT();
+                        return TIGHTENING_INFEASIBLE;
+                      }
+                    }
+                    break;
+                  default:
+                    throw MAiNGOException("  Error while solving OBBT: Unknown variable type.");  // GCOVR_EXCL_LINE
+                    break;
+                }  // end switch
+              }
+            }
+            // Restore objective coefficient
+            subsolver_class::_set_optimization_sense_of_variable(iVar, 0);
+          }
+        }    // End of OBBT while loop
+      }        // End of if(!foundInfeasible)
+
+      // Restore proper objective function and restore LP solver options
+      subsolver_class::_restore_LP_coefficients_after_OBBT();
+
+      // Return appropriate return code and possibly update currentNode with new bounds
+      if (foundInfeasible) {
+        return TIGHTENING_INFEASIBLE;
+      }
+      else {
+        if (!nodeChanged) {
+          return TIGHTENING_UNCHANGED;
+        }
+        else {
+          currentNode = babBase::BabNode(currentNode, lowerVarBounds, upperVarBounds);
+          return TIGHTENING_CHANGED;
+        }  // End of if (!nodeChanged)
+      }  // End of if (foundInfeasible)
+    }  // End of solveOBBT()
+
+
+    /**
+     * @brief Function called by B&B solver for parallel optimality-based range reduction (cf., e.g., Gleixner et al., J. Glob. Optim. 67 (2017) 731)
+     *
+     * @param[in,out] currentNode is the B&B node for which the lower bounding problem should be solved; if OBBT is successful in tightening bounds, currentNode will be modified accordingly
+     * @param[in] currentUBD is the current upper bounds (i.e., incumbent objective value); It is used for the objective function cut if reductionType==OBBT_FEASOPT
+     * @param[in] reductionType determines whether OBBT should include only feasibility or also optimality (i.e., an objective function cut f_cv<=currentUBD)
+     * @return Return code, see enum TIGHTENING_RETCODE
+     */
+    TIGHTENING_RETCODE solve_parallel_OBBT(babBase::BabNode &currentNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars) {
+      PROFILE_FUNCTION()
+
+      if ((reductionType == OBBT_FEAS) && LowerBoundingSolver::_onlyBoxConstraints) {
+        return TIGHTENING_UNCHANGED;
+      }
+
+      std::vector<double> lowerVarBounds(currentNode.get_lower_bounds()), upperVarBounds(currentNode.get_upper_bounds());
+      std::vector<double> originalWidth(LowerBoundingSolver::_nvar);
+      for (size_t i = 0; i < LowerBoundingSolver::_nvar; i++) {
+        originalWidth[i] = upperVarBounds[i] - lowerVarBounds[i];
+      }
+
+      // Update the LP for the current node (i.e., modify bounds and update coefficients and RHS)
+      LINEARIZATION_RETCODE linStatus;
+      try {
+        PROFILE_SCOPE("OBBT_update_LP")
+        linStatus = _update_LP(currentNode);
+      }
+      catch (std::exception &e) {  // GCOVR_EXCL_START
+        throw MAiNGOException("  Error while modifying the lower bounding LP for OBBT.", e, currentNode);
+      }
+      catch (...) {
+        throw MAiNGOException("  Unknown error while modifying the lower bounding LP for OBBT.", currentNode);
+      }
+      // GCOVR_EXCL_STOP
+
+      bool foundInfeasible = false;
+      if (linStatus == LINEARIZATION_INFEASIBLE) {
+          LowerBoundingSolver::_logger->print_message("  OBBT linearization status: Infeasible", VERB_ALL, LBP_VERBOSITY);
+
+#ifdef MAiNGO_DEBUG_MODE
+        if (_contains_incumbent(currentNode)) {
+            const bool reallyInfeasible = _check_if_LP_really_infeasible();
+          if (reallyInfeasible) {
+  #ifdef LP__WRITE_CHECK_FILES
+            _write_LP_to_file("solve_OBBT_infeas_at_linearization_with_incumbent_in_node");
+  #endif
+            if (currentNode.get_ID() == 0) {
+              return TIGHTENING_INFEASIBLE;    // For the root node, we immediately want to report this false infeasibility claim since we want to completeley disable OBBT based on this information.
+            }
+            std::ostringstream outstr;
+            outstr << "  Warning: Node with id " << currentNode.get_ID() << " declared infeasible by linearization technique in OBBT although it contains the incumbent. Skipping OBBT..." << std::endl;
+            LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+          }
+          else {
+            LowerBoundingSolver::_logger->print_message("  Found node to not actually be infeasible. Problem seems to be difficult numerically. Skipping OBBT...", VERB_NORMAL, LBP_VERBOSITY);
+          }
+          return TIGHTENING_UNCHANGED;
+        }
+#endif  // MAiNGO_DEBUG_MODE
+
+          foundInfeasible = true;
+  #ifdef LP__OPTIMALITY_CHECK
+          if (_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) {
+            foundInfeasible = false;
+          }
+  #endif
+      }
+
+      bool nodeChanged = false;
+      // Only do OBBT if the LP was not found to be infeasible during the linearization
+      if (!foundInfeasible) {
+        // Prepare OBBT
+        std::list<unsigned> toTreatMax, toTreatMin;
+        // minimum and maximum values encountered to be feasible in the relaxation, used for filtering
+        // initialized with the respective opposite bounds of the current node
+        std::vector<double> min_vals = upperVarBounds;
+        std::vector<double> max_vals = lowerVarBounds;
+        // pOBBT is done in the space of the subproblem variables, i.e., only for Nx + Ny variables
+        for (unsigned ivar = 0; ivar < (int)(_TwoStageModel->Nx + _TwoStageModel->Ny); ivar++) {    // Note that we also treat auxiliaries (if any are added)
+          if (!LowerBoundingSolver::_variableIsLinear[ivar] || includeLinearVars) {
+            toTreatMax.push_back(ivar);
+            toTreatMin.push_back(ivar);
+          }
+        }
+        // Modify treatment of objective function
+        switch (reductionType) {
+          case OBBT_FEAS:    // Feasibility-based only
+          {
+            // Objective function is not needed in this case --> "deactivate" objective linearizations
+            for (auto & ss : _subsolvers) {
+              ss->_deactivate_objective_function_for_OBBT();
+            }
+            break;
+          }
+          case OBBT_FEASOPT:    // including both feasibility and optimality
+          {
+            for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+              double scenarioUBD = _calculate_scenario_UBD(s, currentUBD);
+              // Modify objective: remove eta and establish proper ubd 
+              _subsolvers[s]->_modify_LP_for_feasopt_OBBT(scenarioUBD, toTreatMax, toTreatMin);
+            }
+            break;
+          }
+          default:
+          {  // GCOVR_EXCL_START
+            std::ostringstream errmsg;
+            errmsg << "  Unknown OBBT range reduction type: " << reductionType;
+            throw MAiNGOException(errmsg.str(), currentNode);
+          }
+          // GCOVR_EXCL_STOP
+        }
+        // Loop over variable bounds until all variable bounds have either been treated by OBBT or filtered
+        unsigned OBBTcnt  = 0;
+        bool foundRelFeas = false;
+        while ((toTreatMax.size() + toTreatMin.size()) > 0) {
+          PROFILE_SCOPE("OBBT_loop")
+
+          OBBTcnt++;
+          // Select next candidate lower bound
+          std::list<unsigned>::iterator tmpIt = toTreatMin.begin(), nextMinIt = toTreatMin.begin();
+          double smallestDistanceMin = LowerBoundingSolver::_maingoSettings->infinity;
+          if (foundRelFeas == true) {
+            // Get minimum difference between last point and one lower variable bound, and use last point for trivial filtering (cf. Gleixner et al., J. Global Optim 67 (2017) 731)
+            while (tmpIt != toTreatMin.end()) {
+              double tmpDistance = (min_vals[*tmpIt] - lowerVarBounds[*tmpIt]);
+              double diameter    = upperVarBounds[*tmpIt] - lowerVarBounds[*tmpIt];
+              if ((diameter < LowerBoundingSolver::_computationTol) || (tmpDistance <= diameter * LowerBoundingSolver::_maingoSettings->LBP_obbtMinImprovement)) {
+                tmpIt = toTreatMin.erase(tmpIt);
+              }
+              else {
+                if (tmpDistance < smallestDistanceMin) {
+                  smallestDistanceMin = tmpDistance;
+                  nextMinIt           = tmpIt;
+                }
+                tmpIt++;
+              }
+            }
+          }
+          else {
+            // If no feasible (in relaxation) point was found, no need to search for the closest bound (first lower bound will be considered, if one exists)
+            if (!toTreatMin.empty()) {
+              smallestDistanceMin = 0;    // If there are still lower bounds to be treated, these get priority
+            }
+          }
+
+          // Select next candidate upper bound
+          std::list<unsigned>::iterator nextMaxIt = toTreatMax.begin();
+          tmpIt                                   = toTreatMax.begin();
+          double smallestDistanceMax              = LowerBoundingSolver::_maingoSettings->infinity;
+          if (foundRelFeas == true) {
+            // Get minimum difference between last point and one upper variable bound and use last point for trivial filtering (cf. Gleixner et al., J. Global Optim 67 (2017) 731)
+            while (tmpIt != toTreatMax.end()) {
+              double tmpDistance = (upperVarBounds[*tmpIt] - max_vals[*tmpIt]);
+              double diameter    = upperVarBounds[*tmpIt] - lowerVarBounds[*tmpIt];
+              if ((diameter < LowerBoundingSolver::_computationTol) || (tmpDistance <= diameter * LowerBoundingSolver::_maingoSettings->LBP_obbtMinImprovement)) {
+                tmpIt = toTreatMax.erase(tmpIt);
+              }
+              else {
+                if (tmpDistance < smallestDistanceMax) {
+                  smallestDistanceMax = tmpDistance;
+                  nextMaxIt           = tmpIt;
+                }
+                tmpIt++;
+              }
+            }
+          }
+          else {
+            // If no feasible (in relaxation) point was found, no need to search for the closest bound (first upper bound will be considered, if one exists)
+            if (!toTreatMax.empty()) {
+              smallestDistanceMax = 0.5;    // If there are still upper bounds to be treated, these should be considered (smallestDistanceMax<infinity), but lower bounds get priority (just to ensure reproducibility)
+            }
+          }
+
+          // If the last variables left just got erased, there is nothing left to do:
+          if ((smallestDistanceMax >= LowerBoundingSolver::_maingoSettings->infinity) && (smallestDistanceMin >= LowerBoundingSolver::_maingoSettings->infinity)) {
+            break;
+          }
+
+          // Depending on which one is better (max or min), prepare OBBT
+          unsigned iVar;                            // Index of the variable to be modified
+          std::vector<double> *boundVector;         // Pointer to the bound vector to be modified
+          std::vector<double> *otherBoundVector;    // Pointer to the bound vector that is not to be modified in the current run
+          int optimizationSense;                    // 1: minimize, -1: maximize
+          if (smallestDistanceMin <= smallestDistanceMax) {
+            iVar = *nextMinIt;
+            toTreatMin.erase(nextMinIt);
+            boundVector       = &lowerVarBounds;
+            otherBoundVector  = &upperVarBounds;
+            optimizationSense = 1;    // 1 = minimize
+          }
+          else {
+            iVar = *nextMaxIt;
+            toTreatMax.erase(nextMaxIt);
+            boundVector       = &upperVarBounds;
+            otherBoundVector  = &lowerVarBounds;
+            optimizationSense = -1;    // -1 = maximize
+          }
+
+          // Conduct OBBT: solve LP and update bound
+          int iyVar = iVar - _TwoStageModel->Nx;  // Index offset by number of variables in first stage
+          for (int s = 0; s < _TwoStageModel->Ns; s++) {
+            _subsolvers[s]->_set_optimization_sense_of_variable(iVar, optimizationSense);
+          }
+          _solve_subproblem_LPs(currentNode, "OBBT"); // updates LowerBoundingSolver::_LPstatus
+
+          if (LowerBoundingSolver::_LPstatus == LP_INFEASIBLE) {  /** NOTE: This infeasibility can also occur due to the objective cut (i.e., the node is dominated), not only because the node is infeasible! */
+            std::ostringstream outstr;
+            outstr << "  LP Obbt status: Infeasible" << std::endl;
+            LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_ALL, LBP_VERBOSITY);
+
+#ifdef MAiNGO_DEBUG_MODE
+            if (_contains_incumbent(currentNode)) {
+              const bool reallyInfeasible = _check_if_LP_really_infeasible();
+              if (reallyInfeasible) {
+  #ifdef LP__WRITE_CHECK_FILES
+                _write_LP_to_file("solve_OBBT_infeas_with_incumbent_in_node");
+  #endif
+                // For the root node, we immediately want to report this false infeasibility claim since we want to completeley disable OBBT based on this information.
+                if (currentNode.get_ID() == 0) {
+                  _restore_LP_coefficients_after_OBBT();
+                  return TIGHTENING_INFEASIBLE;
+                }
+                std::ostringstream outstr;
+                outstr << "  Warning: Node with id " << currentNode.get_ID() << " declared infeasible by OBBT although it contains the incumbent. Skipping OBBT..." << std::endl;
+                LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                break;
+              }
+              else {
+                std::ostringstream outstr;
+                outstr << "  Warning: Node with id " << currentNode.get_ID() << " is numerically sensitive in OBBT for bound " << iVar << " with sense " << optimizationSense << ". Skipping this bound..." << std::endl;
+                LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                
+                for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                  _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+                };
+                continue;
+              }
+            }    // end of if (_contains_incumbent(currentNode))
+#endif  // MAiNGO_DEBUG_MODE
+            foundInfeasible = true;
+#ifdef LP__OPTIMALITY_CHECK
+            if (_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) {
+              foundInfeasible = false;
+              break;
+            }
+#endif
+            outstr.str("");
+            outstr.clear();
+            outstr << "  OBBT status: " << LowerBoundingSolver::_LPstatus << std::endl;
+            LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_ALL, LBP_VERBOSITY);
+
+            break;
+          }    // end of if(_LPstatus == LP_INFEASIBLE)
+          else if (LowerBoundingSolver::_LPstatus != LP_OPTIMAL) {  // LP_UNKNOWN // GCOVR_EXCL_START
+            std::ostringstream outstr;
+            outstr << "  Warning: No optimal solution found in OBBT. Status: " << LowerBoundingSolver::_LPstatus << ". Skipping OBBT..." << std::endl;
+            LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+            break;
+          }
+          // GCOVR_EXCL_STOP
+          else {  // LP_OPTIMAL
+
+            // New point to be used for filtering
+            try {
+              for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                // write directly to private members _subproblemSolutions and _subproblemDualInfo
+                auto & solution_s = _subproblemSolutions[s];
+                auto & obj_s = _subproblemDualInfo[s].lpLowerBound;
+                _subsolvers[s]->_get_solution_point(solution_s, obj_s);
+
+                for (int i = 0; i < (int)(_TwoStageModel->Nx + _TwoStageModel->Ny); i++) {
+                  if (solution_s[i] < min_vals[i]) {
+                    min_vals[i] = solution_s[i];
+                  }
+                  if (solution_s[i] > max_vals[i]) {
+                    max_vals[i] = solution_s[i];
+                  }
+                }
+              }
+            }
+            catch (std::exception &e) {  // GCOVR_EXCL_START
+              std::ostringstream outstr;
+              outstr << "  Warning: Variables at solution of OBBT could be not obtained by LP solver: " << e.what() << std::endl;
+              LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+              for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+              };
+              continue;
+            }
+            // GCOVR_EXCL_STOP
+  #ifdef LP__OPTIMALITY_CHECK
+            if (_check_feasibility({} /* UNUSED */) == SUBSOLVER_INFEASIBLE) {
+              for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+              };
+              continue;
+            }
+  #endif
+            foundRelFeas = true;  // This activates filtering
+
+            // Make sure the new bound makes sense and does not violate variable bounds
+            std::vector<double> objectiveValues(_TwoStageModel->Ns);
+            for (int s = 0; s < _TwoStageModel->Ns; s++) {
+              objectiveValues[s] = _subsolvers[s]->_get_objective_value_solver();
+            }
+            if (iyVar < 0) { // x variable, aggregate objective value
+              double objectiveValue;
+              if (optimizationSense > 0) { // Lower bound, take the maximum
+                objectiveValue = *std::max_element(objectiveValues.begin(), objectiveValues.end());
+              }
+              else { // Upper bound, take the minimum
+                objectiveValue = *std::min_element(objectiveValues.begin(), objectiveValues.end());
+              }
+
+              if (!(objectiveValue >= (-LowerBoundingSolver::_maingoSettings->infinity))) { // GCOVR_EXCL_START
+                std::ostringstream outstr;
+                outstr << "  Warning: Objective obtained from LP solver in OBBT is out of bounds (" << objectiveValue << ") although LP solution status is optimal. Skipping this bound." << std::endl;
+                LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                  _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+                }
+                
+                continue;
+              }
+              // GCOVR_EXCL_STOP
+
+              double newBound = optimizationSense * objectiveValue;    // Again depending on whether we want to change upper or lower bound, need to account for sign
+#ifdef MAiNGO_DEBUG_MODE
+              if (_contains_incumbent(currentNode)) {
+                if (optimizationSense > 0) {    // Lower bound
+                  if (iVar < LowerBoundingSolver::_incumbent.size()) {
+                    if (LowerBoundingSolver::_incumbent[iVar] < newBound) {
+                      // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                      if (!mc::isequal(LowerBoundingSolver::_incumbent[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+  #ifdef LP__WRITE_CHECK_FILES
+                        // x variable, write all subproblems
+                        for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                            _subsolvers[s]->_write_LP_to_file("solve_OBBT_bound_infeas_with_incumbent_in_node_" + std::to_string(s));
+                        }
+  #endif
+                        std::ostringstream outstr;
+                        outstr << "  Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed a lower bound for variable " << iVar << " which cuts off the incumbent. " << std::endl
+                              << "           Correcting this bound and skipping OBBT... " << std::endl;
+                        LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                      }
+                      // We skip the bound, even if the bound is within computational tolerances
+                      break;
+                    }
+                  }
+                }
+                else {    // Upper bound
+                  if (iVar < LowerBoundingSolver::_incumbent.size()) {
+                    if (LowerBoundingSolver::_incumbent[iVar] > newBound) {
+                      // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                      if (!mc::isequal(LowerBoundingSolver::_incumbent[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        std::ostringstream outstr;
+                        outstr << "  Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed an upper bound for variable " << iVar << " which cuts off the incumbent. " << std::endl
+                                << "           Correcting this bound and skipping OBBT... " << std::endl;
+                        LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                      }
+                      // We skip the bound, even if the bound is within computational tolerances
+                      break;
+                    }
+                  }
+                }
+              }
+#endif  // MAiNGO_DEBUG_MODE
+
+              double remainingWidth = optimizationSense * (*otherBoundVector)[iVar] - optimizationSense * newBound;
+              if (remainingWidth < -LowerBoundingSolver::_computationTol) {
+                // unlike in standard OBBT even for continuous variables this is not an indication of an error but of infeasibility, since we aggregate first-stage variable domains over all scenarios
+                _restore_LP_coefficients_after_OBBT();
+                return TIGHTENING_INFEASIBLE;
+              }
+              else {
+
+                // Update bound
+                if (remainingWidth < LowerBoundingSolver::_computationTol) {
+                  // Can't safely set bounds equal, there is the possibility that we make the LP infeasible although it isn't !
+                  // (*boundVector)[iVar] = (*otherBoundVector)[iVar];
+                }
+                else {
+                  switch (LowerBoundingSolver::_originalVariables[iVar].get_variable_type()) {
+                    case babBase::enums::VT_CONTINUOUS:
+                      // Only update bounds if difference is larger than tolerance
+                      if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                        nodeChanged          = true;
+                        (*boundVector)[iVar] = newBound;
+                      }
+                      break;
+                    case babBase::enums::VT_BINARY:
+                      // Round bounds to ensure binary values
+                      if (optimizationSense > 0) {    // Lower bound
+                        if (!mc::isequal(newBound, 0, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          nodeChanged          = true;
+                          (*boundVector)[iVar] = 1;
+                        }
+                        // Integer bounds crossing => node is infeasible
+                        if ((*boundVector)[iVar] > upperVarBounds[iVar]) {
+                          _restore_LP_coefficients_after_OBBT();
+                          return TIGHTENING_INFEASIBLE;
+                        }
+                      }
+                      else {    // Upper bound
+                        if (!mc::isequal(newBound, 1, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          nodeChanged          = true;
+                          (*boundVector)[iVar] = 0;
+                        }
+                        // Integer bounds crossing => node is infeasible
+                        if ((*boundVector)[iVar] < lowerVarBounds[iVar]) {
+                          _restore_LP_coefficients_after_OBBT();
+                          return TIGHTENING_INFEASIBLE;
+                        }
+                      }
+                      break;
+                    case babBase::enums::VT_INTEGER:
+                      // Round bounds to ensure integer values
+                      if (optimizationSense > 0) {    // Lower bound
+                        if (!mc::isequal(newBound, std::floor(newBound), LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          newBound = std::ceil(newBound);
+                        }
+                        else {
+                          newBound = std::floor(newBound);
+                        }
+                        if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          nodeChanged          = true;
+                          (*boundVector)[iVar] = newBound;
+                        }
+                        // Integer bounds crossing => node is infeasible
+                        if ((*boundVector)[iVar] > upperVarBounds[iVar]) {
+                          _restore_LP_coefficients_after_OBBT();
+                          return TIGHTENING_INFEASIBLE;
+                        }
+                      }
+                      else {    // Upper bound
+                        if (!mc::isequal(newBound, std::ceil(newBound), LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          newBound = std::floor(newBound);
+                        }
+                        else {
+                          newBound = std::ceil(newBound);
+                        }
+                        if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          nodeChanged          = true;
+                          (*boundVector)[iVar] = newBound;
+                        }
+                        // Integer bounds crossing => node is infeasible
+                        if ((*boundVector)[iVar] < lowerVarBounds[iVar]) {
+                          _restore_LP_coefficients_after_OBBT();
+                          return TIGHTENING_INFEASIBLE;
+                        }
+                      }
+                      break;
+                    default:
+                      throw MAiNGOException("  Error while solving OBBT: Unknown variable type.");  // GCOVR_EXCL_LINE
+                      break;
+                  }  // end switch
+                }
+              }
+              // Restore objective coefficient for x variable, restore coefficient in all subproblems
+              for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+              }
+            } // end if (x variable)
+            else { // y variable, use all objective values
+              bool obj_out_of_bounds = false;
+              bool return_infeasible = false;
+
+              #ifdef _OPENMP
+                #pragma omp parallel for
+              #endif
+              for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                if (!(objectiveValues[s] >= (-LowerBoundingSolver::_maingoSettings->infinity))) {  // GCOVR_EXCL_START
+                  std::ostringstream outstr;
+                  outstr << "  Warning: Objective obtained from LP solver in OBBT is out of bounds (" << objectiveValues[s] << ") although LP solution status is optimal. Skipping this bound." << std::endl;
+                  LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                  obj_out_of_bounds = true;
+                  
+                  // break;
+                  continue;
+                }  // GCOVR_EXCL_STOP
+
+                double newBound = optimizationSense * objectiveValues[s];    // Again depending on whether we want to change upper or lower bound, need to account for sign
+
+#ifdef MAiNGO_DEBUG_MODE
+                if (_contains_incumbent(currentNode)) {
+                  if (optimizationSense > 0) {    // Lower bound
+                    if (iVar < _subsolvers[s]->_incumbent.size()) {
+                      if (_subsolvers[s]->_incumbent[iVar] < newBound) {
+                        // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                        if (!mc::isequal(_subsolvers[s]->_incumbent[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+    #ifdef LP__WRITE_CHECK_FILES
+                          // y variable, write only the corresponding subproblem
+                          _subsolvers[s]->_write_LP_to_file("solve_OBBT_bound_infeas_with_incumbent_in_node" + std::to_string(s));
+    #endif
+                          std::ostringstream outstr;
+                          outstr << "  Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed a lower bound for variable " << iVar << " which cuts off the incumbent. " << std::endl
+                                << "           Correcting this bound and skipping OBBT... " << std::endl;
+                          LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                        }
+                        // We skip the bound, even if the bound is within computational tolerances
+                        // break;
+                        continue;
+                      }
+                    }
+                  }
+                  else {    // Upper bound
+                    if (iVar < _subsolvers[s]->_incumbent.size()) {
+                      if (_subsolvers[s]->_incumbent[iVar] > newBound) {
+                        // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                        if (!mc::isequal(_subsolvers[s]->_incumbent[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          std::ostringstream outstr;
+                          outstr << "  Warning: Node #" << currentNode.get_ID() << " contains the incumbent and OBBT computed an upper bound for variable " << iVar << " which cuts off the incumbent. " << std::endl
+                                  << "           Correcting this bound and skipping OBBT... " << std::endl;
+                          LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                        }
+                        // We skip the bound, even if the bound is within computational tolerances
+                        //break;
+                        continue;
+                      }
+                    }
+                  }
+                }
+#endif  // MAiNGO_DEBUG_MODE
+
+                int iTotalVar = _TwoStageModel->Nx + _TwoStageModel->Ny * s + iyVar;
+
+                double remainingWidth = optimizationSense * (*otherBoundVector)[iTotalVar] - optimizationSense * newBound;
+                if (remainingWidth < -LowerBoundingSolver::_computationTol) {
+                  if (LowerBoundingSolver::_originalVariables[iTotalVar].get_variable_type() >= babBase::enums::VT_BINARY /* this includes VT_INTEGER */) {
+                    // The problem is found to be infeasible, since, e.g., lb of variable 1 was tightened to 3.2 and was then set to 4. Later on ub of variable 1 is tightened to 3.6 and thus set to 3,
+                    // meaning that this node is infeasible
+                    // This could be extended to saving the old lb value 3.2 and checking if it does not cross the new ub value 3.6
+                    _subsolvers[s]->_restore_LP_coefficients_after_OBBT();
+                   
+                    // return TIGHTENING_INFEASIBLE;
+                    return_infeasible = true;
+                    continue;
+                  }
+                  // We only need to tell the user something if we are not within computational tolerances, meaning that something really went wrong
+                  if (!mc::isequal(optimizationSense * newBound, optimizationSense * (*otherBoundVector)[iTotalVar], LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                    std::ostringstream outstr;
+                    outstr << "  Warning: Bounds crossover for variable " << iTotalVar << " during OBBT with optimizationSense " << optimizationSense << ":" << std::endl;
+                    if (optimizationSense > 0) {
+                      outstr << std::setprecision(16) << "  Lower Bound = " << newBound << " > " << std::setprecision(16) << (*otherBoundVector)[iTotalVar] << " = Upper Bound. Skipping this bound." << std::endl;
+                    }
+                    else {
+                      outstr << std::setprecision(16) << "  Upper Bound = " << newBound << " < " << std::setprecision(16) << (*otherBoundVector)[iTotalVar] << " = Lower Bound. Skipping this bound." << std::endl;
+                    }
+                    LowerBoundingSolver::_logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+                  }
+                  _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+                  continue;
+                }
+                else {
+
+                  // Update bound
+                  if (remainingWidth < LowerBoundingSolver::_computationTol) {
+                    // Can't safely set bounds equal, there is the possibility that we make the LP infeasible although it isn't !
+                    // (*boundVector)[iVar] = (*otherBoundVector)[iVar];
+                  }
+                  else {
+                    switch (LowerBoundingSolver::_originalVariables[iTotalVar].get_variable_type()) {
+                      case babBase::enums::VT_CONTINUOUS:
+                        // Only update bounds if difference is larger than tolerance
+                        if (!mc::isequal((*boundVector)[iTotalVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                          nodeChanged          = true;
+                          (*boundVector)[iTotalVar] = newBound;
+                        }
+                        break;
+                      case babBase::enums::VT_BINARY:
+                        // Round bounds to ensure binary values
+                        if (optimizationSense > 0) {    // Lower bound
+                          if (!mc::isequal(newBound, 0, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                            nodeChanged          = true;
+                            (*boundVector)[iTotalVar] = 1;
+                          }
+                          // Integer bounds crossing => node is infeasible
+                          if ((*boundVector)[iTotalVar] > upperVarBounds[iTotalVar]) {
+                            _subsolvers[s]->_restore_LP_coefficients_after_OBBT();
+                            // return TIGHTENING_INFEASIBLE;
+                            return_infeasible = true;
+                            continue;
+                          }
+                        }
+                        else {    // Upper bound
+                          if (!mc::isequal(newBound, 1, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                            nodeChanged          = true;
+                            (*boundVector)[iTotalVar] = 0;
+                          }
+                          // Integer bounds crossing => node is infeasible
+                          if ((*boundVector)[iTotalVar] < lowerVarBounds[iTotalVar]) {
+                            _subsolvers[s]->_restore_LP_coefficients_after_OBBT();
+                            // return TIGHTENING_INFEASIBLE;
+                            return_infeasible = true;
+                            continue;
+                          }
+                        }
+                        break;
+                      case babBase::enums::VT_INTEGER:
+                        // Round bounds to ensure integer values
+                        if (optimizationSense > 0) {    // Lower bound
+                          if (!mc::isequal(newBound, std::floor(newBound), LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                            newBound = std::ceil(newBound);
+                          }
+                          else {
+                            newBound = std::floor(newBound);
+                          }
+                          if (!mc::isequal((*boundVector)[iTotalVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                            nodeChanged          = true;
+                            (*boundVector)[iTotalVar] = newBound;
+                          }
+                          // Integer bounds crossing => node is infeasible
+                          if ((*boundVector)[iTotalVar] > upperVarBounds[iTotalVar]) {
+                            _subsolvers[s]->_restore_LP_coefficients_after_OBBT();
+                            // return TIGHTENING_INFEASIBLE;
+                            return_infeasible = true;
+                            continue;
+                          }
+                        }
+                        else {    // Upper bound
+                          if (!mc::isequal(newBound, std::ceil(newBound), LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                            newBound = std::floor(newBound);
+                          }
+                          else {
+                            newBound = std::ceil(newBound);
+                          }
+                          if (!mc::isequal((*boundVector)[iVar], newBound, LowerBoundingSolver::_computationTol, LowerBoundingSolver::_computationTol)) {
+                            nodeChanged          = true;
+                            (*boundVector)[iVar] = newBound;
+                          }
+                          // Integer bounds crossing => node is infeasible
+                          if ((*boundVector)[iTotalVar] < lowerVarBounds[iTotalVar]) {
+                            _subsolvers[s]->_restore_LP_coefficients_after_OBBT();
+                            // return TIGHTENING_INFEASIBLE;
+                            return_infeasible = true;
+                            continue;
+                          }
+                        }
+                        break;
+                      default:
+                        throw MAiNGOException("  Error while solving OBBT: Unknown variable type.");
+                        break;
+                    }  // end switch
+                  }
+                }
+
+                // Restore objective coefficient
+                _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+              }
+              
+              if (return_infeasible) {
+                return TIGHTENING_INFEASIBLE;
+              }
+              if (obj_out_of_bounds) {  // GCOVR_EXCL_START
+                for (int s = 0; s < _TwoStageModel->Ns; s++) {
+                  _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+                };
+                
+                continue;  // with next iteration of OBBT loop
+              } // GCOVR_EXCL_STOP
+            }
+          }
+        }  // End of OBBT while loop
+      }  // End of if(!foundInfeasible)
+
+      // Restore proper objective function and restore LP solver options
+      _restore_LP_coefficients_after_OBBT();
+
+      // Return appropriate return code and possibly update currentNode with new bounds
+      if (foundInfeasible) {
+        return TIGHTENING_INFEASIBLE;
+      }
+      else {
+        if (!nodeChanged) {
+          return TIGHTENING_UNCHANGED;
+        }
+        else {
+          currentNode = babBase::BabNode(currentNode, lowerVarBounds, upperVarBounds);
+          return TIGHTENING_CHANGED;
+        }  // End of if (!nodeChanged)
+      }  // End of if (foundInfeasible)
+    } // End of solveOBBT()
+
+    /**
+     * @brief Check the need for options for the preprocessor.
+     * 
+     * @param[in] rootNode is the root node of the B&B tree
+    */
+    void preprocessor_check_options(const babBase::BabNode &rootNode) override {
+      PROFILE_FUNCTION()
+
+      // NOTE: bounds for root node are currently the same for all second stage variables.
+      //       if that remains like this, we can reuse the linearization point if necessary.
+      std::vector<std::vector<double>> lowerVarBounds_s, upperVarBounds_s, linearizationPoint_s(_TwoStageModel->Ns, std::vector<double>(_TwoStageModel->Nx + _TwoStageModel->Ny));
+      std::vector<double> lowerVarBounds(rootNode.get_lower_bounds());
+      std::vector<double> upperVarBounds(rootNode.get_upper_bounds());
+      std::tie(lowerVarBounds_s, upperVarBounds_s) = _split_bounds(lowerVarBounds, upperVarBounds);
+
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        for (unsigned i = 0; i < _TwoStageModel->Nx + _TwoStageModel->Ny; i++) {
+          linearizationPoint_s[s][i] = 0.5 * (lowerVarBounds_s[s][i] + upperVarBounds_s[s][i]);
+        }
+      }
+
+      LowerBoundingSolver::preprocessor_check_options(rootNode);
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+
+        auto & ss = _subsolvers[s];
+
+        // Set relNodeTol
+        ss->_maingoSettings->relNodeTol = std::min(ss->_maingoSettings->deltaIneq, std::min(ss->_maingoSettings->deltaEq, ss->_maingoSettings->relNodeTol));
+
+        // Currently the evaluation is used only to possibly detect interval and McCormick exceptions,
+        // since this is already checked in LowerBoundingSolver::preprocessor_check_options(rootNode),
+        // we don't need to repeat this here!
+
+        int _nvar = _TwoStageModel->Nx + _TwoStageModel->Ny;
+        for (unsigned int i = 0; i < _nvar; i++) {
+          ss->_DAGobj->McPoint[i] = MC(I(lowerVarBounds_s[s][i], upperVarBounds_s[s][i]), linearizationPoint_s[s][i]);
+          ss->_DAGobj->McPoint[i].sub(_nvar, i);
+        }
+
+        if (ss->_maingoSettings->LBP_subgradientIntervals) {
+          // Compute improved relaxations at mid point
+          bool oldSetting                 = MC::options.SUB_INT_HEUR_USE;
+          MC::options.SUB_INT_HEUR_USE    = true;
+          MC::subHeur.originalLowerBounds = &lowerVarBounds_s[s];
+          MC::subHeur.originalUpperBounds = &upperVarBounds_s[s];
+          MC::subHeur.referencePoint      = &linearizationPoint_s[s];
+
+          ss->_DAGobj->DAG.eval(ss->_DAGobj->subgraph, ss->_DAGobj->MCarray, ss->_DAGobj->functions.size(), ss->_DAGobj->functions.data(), ss->_DAGobj->resultRelaxation.data(), ss->_nvar, ss->_DAGobj->vars.data(), ss->_DAGobj->McPoint.data());
+          MC::options.SUB_INT_HEUR_USE        = oldSetting;
+          MC::subHeur.usePrecomputedIntervals = false;
+          MC::subHeur.reset_iterator();
+        }
+        else {
+          ss->_DAGobj->DAG.eval(ss->_DAGobj->subgraph, ss->_DAGobj->MCarray, ss->_DAGobj->functions.size(), ss->_DAGobj->functions.data(), ss->_DAGobj->resultRelaxation.data(), ss->_nvar, ss->_DAGobj->vars.data(), ss->_DAGobj->McPoint.data());
+        }
+
+        if (ss->_maingoSettings->LBP_addAuxiliaryVars && !ss->_maingoSettings->BAB_constraintPropagation) {
+          ss->_logger->print_message("        The option BAB_constraintPropagation has to be 1 when using option LBP_addAuxiliaryVars. Setting it to 1.\n", VERB_NORMAL, LBP_VERBOSITY);
+          ss->_maingoSettings->BAB_constraintPropagation = true;
+        }
+
+        // Check whether the options can be applied with respect to the used solver
+        ss->_turn_off_specific_options();
+      }
+    }
+
+    /**
+     * @brief Solve the subproblems of the siblings.
+     * 
+     * @param[in,out] siblingResults The struct storing the results of the sibling iteration.
+     * @param[in] ubd The best known upper bound.
+     * @param[in] obbtType The type of OBBT.
+     */
+    void solve_sibling_subproblems(
+      lbp::SiblingResults &siblingResults,
+      double ubd,
+      int obbtType
+    ) {
+
+      // Create subnodes for each sibling
+      for (unsigned int j : {0, 1}) {
+        std::tie(siblingResults.lowerBounds[j], siblingResults.upperBounds[j]) = 
+          _split_bounds(siblingResults.siblings[j].get_lower_bounds(),
+                        siblingResults.siblings[j].get_upper_bounds());
+        for (int s = 0; s < _TwoStageModel->Ns; s++) {
+          siblingResults.siblingSubNodes[s][j].set_lower_bound(siblingResults.lowerBounds[j][s]);
+          siblingResults.siblingSubNodes[s][j].set_upper_bound(siblingResults.upperBounds[j][s]);
+        }
+      }
+
+      // Fake _subNodes for parent node with appropriate bounds
+      _subNodes.clear();
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        _subNodes.emplace_back(siblingResults.siblings[0],
+                               siblingResults.siblingSubNodes[s][0].get_lower_bounds(),
+                               siblingResults.siblingSubNodes[s][1].get_upper_bounds());
+      }
+
+      /** 
+       * Scenario upper bounds based on the parent's subproblem bounds, valid for both siblings.
+       * Note that scenarioUBDs we could calculate based on siblingResults.subproblemBounds[i])
+       * are only valid for the node represented by sibling i.
+       * Since we want to re-use the sibling domains resulting from range reduction,
+       * we may only fathom by dominance if this fathoming is valid for all possible  orthants,
+       * i.e., only when we can fathom based on the the parent's subproblem bounds.
+       */
+      std::vector<double> scenarioUBD(_TwoStageModel->Ns, 0.0);
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        scenarioUBD[s] = _calculate_scenario_UBD(s, ubd);
+      }
+
+      /**
+       * Each of the following range reduction loops works as follows:
+       * - For all s
+       *   - Tighten the bounds of the subnode for scenario s of each sibling.
+       *   - Create the union of domains for that scenario.
+       *     (Note that if both sibling domains can be tightened, the union is a valid tightening of the parent domain.)
+       * Since the x values must be feasible for all scenarios, the intersection of the x part of the scenario union of domains is a valid tightening of the parent domain for x.
+       * If these tightenings result in an empty domain the parent can be fathomed, updated lower bounds 
+       */
+      bool parent_fathomed = false;
+      /** Retcodes (< 1 means fathomed, > 0 means potentially feasible)
+       *  -1 == fathomed by value dominance
+       *  0 == fathomed by infeasibility
+       *  1 == potentially feasible, unchanged variable bounds
+       *  2 == potentially feasible, changed variable bounds
+       */
+      std::vector<std::array<int, 2>> retcodes({_TwoStageModel->Ns, {{1, 1}}});
+
+      // CP
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; s++) { if (!parent_fathomed) { // only execute if we're not fathomed
+        for (unsigned int j : {0, 1}) {
+          retcodes[s][j] = static_cast<int>(
+            _subsolvers[s]->do_constraint_propagation(
+              siblingResults.siblingSubNodes[s][j],
+              scenarioUBD[s]
+            )
+          );
+        }
+        if (siblingResults.infeasible_after_parent_tightening(s, _subNodes[s], retcodes[s])) {
+            parent_fathomed = true;
+        }
+      }}
+      if (parent_fathomed) {
+        return;
+      }
+
+      if (siblingResults.infeasible_after_sibling_tightening(_subNodes, retcodes)) {
+        return;
+      }
+
+      if (obbtType != -1) {
+        // OBBT
+        #ifdef _OPENMP
+          #pragma omp parallel for
+        #endif
+        for (int s = 0; s < _TwoStageModel->Ns; s++) { if (!parent_fathomed) { // only execute if we're not fathomed
+          for (unsigned int j : {0, 1}) {
+            if (retcodes[s][j] > 0) {
+              retcodes[s][j] = static_cast<int>(
+                _subsolvers[s]->solve_OBBT(
+                  siblingResults.siblingSubNodes[s][j],
+                  scenarioUBD[s],
+                  static_cast<OBBT>(obbtType)
+                )
+              );
+            }
+          }
+          if (siblingResults.infeasible_after_parent_tightening(s, _subNodes[s], retcodes[s])) {
+            parent_fathomed = true;
+          }
+        }}
+        if (parent_fathomed) {
+          return;
+        }
+
+        if (siblingResults.infeasible_after_sibling_tightening(_subNodes, retcodes)) {
+          return;
+        }
+      }
+
+      auto _fathom_by_domination = [&](int s, int j) {
+        if (scenarioUBD[s] < siblingResults.objectiveBounds[j][s]) {
+          /**
+           * This scenario is dominated, i.e., the sibling can be fathomed by value dominance.
+           * As with infeasibility of a subproblem, the parent can only be fathomed if the scenario subproblem of the other sibling is also fathomed.
+           * We handle this domination as an infeasibility for ease of implementation, as it has the same implications.
+           */
+          siblingResults.solutions[j][s].clear();
+          retcodes[s][j] = -1;
+          if ((retcodes[s][0] < 1) && (retcodes[s][1] < 1)) {
+            // siblingResults.converged = true;
+            return true;
+          }
+        }
+        return false;
+      };
+
+      // If we got here we will solve the sibling nodes; we count each sibling as one LB solve
+      siblingResults.nAddedLBSolves = 2;
+
+      // Lower bounding
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; s++) { if (!parent_fathomed) { // only execute if we're not fathomed
+        for (unsigned int j : {0, 1}) {
+          if (retcodes[s][j] > 0) {
+            retcodes[s][j] = static_cast<int>(
+              _subsolvers[s]->solve_LBP(
+                siblingResults.siblingSubNodes[s][j],
+                siblingResults.objectiveBounds[j][s],
+                siblingResults.solutions[j][s],
+                siblingResults.dualInfo[j][s]
+              )
+            );
+            if (_fathom_by_domination(s, j)) { // early exit from loop
+              parent_fathomed = true;
+            }
+          }
+        }
+        if ((retcodes[s][0] < 1) && (retcodes[s][1] < 1)) {
+          parent_fathomed = true;
+        }
+      }}
+      if (parent_fathomed) {
+        return;
+      }
+
+      // We can update the subproblem bounds for the parent, the parentPruningScore and the scenarioUBD using the tightened objectiveBounds...
+      siblingResults.update_parent_pruning_score(_subproblemBounds);
+      // ... and check for domination again
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        scenarioUBD[s] = _calculate_scenario_UBD(s, ubd);
+        for (int j = 0; j < 2; j++) {
+          if (_fathom_by_domination(s, j)) {
+            return;
+          }
+        }
+      }
+
+      /**
+       * NOTE: In contrast to first-stage iterations, upper bounding is performed after DBBT.
+       *       This allows to keep the necessary callbacks that need to be set up to a minimum.
+       */
+
+      // DBBT
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; s++) { if (!parent_fathomed) { // only execute if we're not fathomed
+        for (unsigned int j : {0, 1}) {
+          // disaggregated DBBT for subproblems
+          if (retcodes[s][j] > 0) {
+            retcodes[s][j] = static_cast<int>(
+              _subsolvers[s]->do_dbbt_and_probing(
+                siblingResults.siblingSubNodes[s][j],
+                siblingResults.solutions[j][s],
+                siblingResults.dualInfo[j][s],
+                scenarioUBD[s]
+              )
+            );
+          }
+        }
+        if (siblingResults.infeasible_after_parent_tightening(s, _subNodes[s], retcodes[s])) {
+          parent_fathomed = true;
+        }
+      }}
+      if (parent_fathomed) {
+        return;
+      }
+
+      if (!siblingResults.infeasible_after_sibling_tightening(_subNodes, retcodes)) {
+        // Extract results and store in siblingResults
+        siblingResults.tighten_parent_objective_and_variable_bounds(retcodes);
+        siblingResults.feasible = true;
+      }
+    }
+
+    /**
+     * @brief Prepares and executes disaggregated DBBT and probing.
+     * 
+     * @param[in] currentNode is the node for which DBBT and probing are executed.
+     * @param[in] UNUSED_LBPSOLUTION is unused, instead we use _subproblemSolutions
+     * @param[in] UNUSED_DUALINFO is unused, instead we use _subproblemDualInfo
+     * @param[in] currentUBD is the best known upper bound
+     */
+    TIGHTENING_RETCODE
+    do_dbbt_and_probing(
+      babBase::BabNode &currentNode,
+      const std::vector<double> &UNUSED_LBPSOLUTION,
+      const LbpDualInfo &UNUSED_DUALINFO,
+      const double currentUBD
+    ) override {
+      std::vector<TIGHTENING_RETCODE> retcodes(_TwoStageModel->Ns, TIGHTENING_UNCHANGED);
+      #ifdef _OPENMP
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        if (_subproblemDualInfo[s].multipliers.size() == 0) {
+          continue;
+        }
+        double scenarioUBD = _calculate_scenario_UBD(s, currentUBD);
+        retcodes[s] = _subsolvers[s]->do_dbbt_and_probing(_subNodes[s], _subproblemSolutions[s], _subproblemDualInfo[s], scenarioUBD);
+      }
+      // Node is infeasible if DBBT finds any subproblem to be infeasible
+
+      // Size Nx + Ny * Ns
+      auto &LB                   = currentNode.get_lower_bounds();
+      auto &UB                   = currentNode.get_upper_bounds();
+      TIGHTENING_RETCODE retcode = TIGHTENING_UNCHANGED;
+      for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+        if (retcodes[s] == TIGHTENING_INFEASIBLE) {
+          return TIGHTENING_INFEASIBLE;
+        }
+
+        if (retcodes[s] == TIGHTENING_CHANGED) {
+          if (retcode != TIGHTENING_INFEASIBLE) {
+            retcode = TIGHTENING_CHANGED;
+          }
+
+          // Size Nx + Ny
+          auto const &LBs = _subNodes[s].get_lower_bounds();
+          auto const &UBs = _subNodes[s].get_upper_bounds();
+          // Use best bounds for first-stage variables
+          for (unsigned int ix = 0; ix < _TwoStageModel->Nx; ix++) {
+            currentNode.set_lower_bound(ix, std::max(LBs[ix], LB[ix]));
+            currentNode.set_upper_bound(ix, std::min(UBs[ix], UB[ix]));
+            // If the subproblems require any first-stage variable to be in subdomains that do not intersect, the node is infeasible
+            if (LB[ix] > UB[ix]) {
+              return TIGHTENING_INFEASIBLE;
+            }
+          }
+          // Update the second-stage variables associated to the current scenario
+          for (unsigned int iy = 0; iy < _TwoStageModel->Ny; ++iy) {
+            auto isp = _TwoStageModel->Nx + iy;
+            auto i   = isp + s * _TwoStageModel->Ny;
+            currentNode.set_lower_bound(i, LBs[isp]);
+            currentNode.set_upper_bound(i, UBs[isp]);
+          }
+        }
+      }
+      return retcode;
+    }
+
+    void
+    update_incumbent_LBP(const std::vector<double> &incumbentBAB) override {
+      LowerBoundingSolver::_incumbent = incumbentBAB;
+      std::vector<double> incumbent_s(_TwoStageModel->Nx + _TwoStageModel->Ny);
+      for (unsigned int i = 0; i < _TwoStageModel->Nx; i++) {
+        incumbent_s[i] = incumbentBAB[i];
+      }
+      for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+        for (unsigned int i = 0; i < _TwoStageModel->Ny; i++) {
+          incumbent_s[_TwoStageModel->Nx + i] = incumbentBAB[_TwoStageModel->Nx + s * _TwoStageModel->Ny + i];
+        }
+        _subsolvers[s]->update_incumbent_LBP(incumbent_s);
+      }
+    }
+
+  protected:
+    /**
+     * @brief Delegates calls for computing linearization points and modifying the LP coefficients to the subproblem solvers
+     *
+     * @param[in] currentNode is current node of the branch-and-bound tree
+     * @return returns a LINEARIZATION_RETCODE defining whether the final problem was already solved/proven infeasible during linearization
+     */
+    LINEARIZATION_RETCODE _update_LP(const babBase::BabNode &currentNode) override {
+      _subNodes.clear();
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        _subNodes.push_back(currentNode);
+      }
+      _set_variable_bounds(currentNode.get_lower_bounds(), currentNode.get_upper_bounds());
+
+      return _update_subproblem_LPs();
+    }  // GCOVR_EXCL_LINE
+
+    /**
+      * @brief Function for setting the bounds of variables
+      *
+      * @param[in] lowerVarBounds is the vector holding the lower bounds of the variables
+      * @param[in] upperVarBounds is the vector holding the upper bounds of the variables
+      */
+    void _set_variable_bounds(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds) override {
+      // PROFILE_FUNCTION()
+
+      std::vector<std::vector<double>> LBs, UBs;
+      std::tie(LBs, UBs) = _split_bounds(lowerVarBounds, upperVarBounds);
+
+      // #ifdef _OPENMP
+      //   #pragma omp parallel for
+      // #endif
+      for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+        // PROFILE_SCOPE("_set_variable_bounds_s")
+
+        _subsolvers[s]->_set_variable_bounds(LBs[s], UBs[s]);
+
+        // NOTE: you'd expect this to be done in the subsolvers, but if they don't take care of it we get problems later!
+        _subsolvers[s]->_lowerVarBounds = LBs[s];
+        _subsolvers[s]->_upperVarBounds = UBs[s];
+      }
+      subsolver_class::_set_variable_bounds(lowerVarBounds, upperVarBounds);
+    }
+
+    /**
+     * @brief Function for solving the currently constructed linear program.
+     *
+     * @param[in] currentNode is the currentNode, needed for throwing exceptions or obtaining the lower and upper bounds of variables
+     */
+    LP_RETCODE _solve_LP(const babBase::BabNode &currentNode) override {
+      // NOTE: this function should only be called by LowerBoundingSolver::solve_LBP
+      PROFILE_SCOPE("LBP_solve_LP")
+
+      _solve_subproblem_LPs(currentNode, "LBP"); // updates LowerBoundingSolver::_LPstatus
+
+      if (LowerBoundingSolver::_LPstatus == LP_OPTIMAL) {
+        for (int s = 0; s < _TwoStageModel->Ns; s++) {
+          _subproblemBounds[s] = std::max(_subsolvers[s]->_get_objective_value(), _subsolvers[s]->_DAGobj->validIntervalLowerBound);
+        }
+      }
+
+      return LowerBoundingSolver::_LPstatus;
+    }
+
+    /**
+     * @brief Function for updating the linear relaxations of the subproblems.
+     *
+     * @param[in] prefix is a prefix for communicating the calling scope to profiling
+     */
+    LINEARIZATION_RETCODE _update_subproblem_LPs(const std::string prefix = "") {
+      // The overall retcode is:
+      // - infeasible if any subproblems are infeasible
+      // - unknown if any subproblem is unknown and none are infeasible
+      // - optimal if all subproblem retcodes are optimal
+      LINEARIZATION_RETCODE retcode = LINEARIZATION_OPTIMAL, overall_retcode = LINEARIZATION_OPTIMAL;
+      #ifdef _OPENMP
+        std::vector<LINEARIZATION_RETCODE> retcodes(_TwoStageModel->Ns, LINEARIZATION_OPTIMAL);
+        #pragma omp parallel for
+      #endif
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        PROFILE_SCOPE(prefix + "_update_LP_s")  // update the LP relaxation of the subproblem s
+        _subNodes[s].set_lower_bound(_subsolvers[s]->_lowerVarBounds);
+        _subNodes[s].set_upper_bound(_subsolvers[s]->_upperVarBounds);
+        #ifdef _OPENMP
+          retcodes[s] = _subsolvers[s]->_update_LP(_subNodes[s]);
+        #else
+          retcode = _subsolvers[s]->_update_LP(_subNodes[s]);
+          
+          if (retcode == LINEARIZATION_INFEASIBLE)
+            return LINEARIZATION_INFEASIBLE;
+          else if (retcode == LINEARIZATION_UNKNOWN)
+            overall_retcode = LINEARIZATION_UNKNOWN;
+        #endif
+      }
+
+      #ifdef _OPENMP
+        for (auto && ret : retcodes) {
+          if (ret == LINEARIZATION_INFEASIBLE) {
+            return LINEARIZATION_INFEASIBLE;
+          }
+          else if (ret == LINEARIZATION_UNKNOWN)
+            overall_retcode = LINEARIZATION_UNKNOWN;
+        }
+      #endif
+      
+      if (retcode == LINEARIZATION_INFEASIBLE)
+          return LINEARIZATION_INFEASIBLE;
+      else if ((retcode == LINEARIZATION_UNKNOWN) || (overall_retcode == LINEARIZATION_UNKNOWN))
+        return LINEARIZATION_UNKNOWN;
+
+      // This can only happen for Kelley linearization points, we need to update the subproblem bounds as this won't happen via _solve_LP
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+          _subproblemBounds[s] = std::max(_subsolvers[s]->_get_objective_value(), _subsolvers[s]->_DAGobj->validIntervalLowerBound);
+      }
+      return LINEARIZATION_OPTIMAL;
+    }
+
+    /**
+     * @brief Function for solving the currently constructed linear relaxations of the subproblems.
+     *
+     * @param[in] currentNode is the currentNode, needed for throwing exceptions or obtaining the lower and upper bounds of variables
+     * @param[in] prefix is a prefix for communicating the calling scope to profiling
+     */
+    void _solve_subproblem_LPs(const babBase::BabNode &currentNode, const std::string prefix = "") {
+      LowerBoundingSolver::_LPstatus = LP_OPTIMAL;
+      #ifdef _OPENMP
+        #pragma omp parallel for
+        for (int s = 0; s < _TwoStageModel->Ns; s++) {
+          PROFILE_SCOPE(prefix + "_solve_LP_s")
+          _subsolvers[s]->_LPstatus = _subsolvers[s]->_solve_LP(_subNodes[s]);
+
+          if (LowerBoundingSolver::_LPstatus == LP_OPTIMAL && _subsolvers[s]->_LPstatus != LP_OPTIMAL) {
+            LowerBoundingSolver::_LPstatus = _subsolvers[s]->_LPstatus;
+          }
+        }
+      #else
+        _early_return_solve_subproblem_LPs(currentNode, prefix);
+      #endif
+    }
+
+    /**
+     * @brief Function for solving the currently constructed linear relaxations of the subproblems allowing for an early return if one subproblem is found to be infeasible.
+     *
+     * @param[in] currentNode is the currentNode, needed for throwing exceptions or obtaining the lower and upper bounds of variables
+     * @param[in] prefix is a prefix for communicating the calling scope to profiling
+     */
+    void _early_return_solve_subproblem_LPs(const babBase::BabNode &currentNode, const std::string prefix = "") {
+      LowerBoundingSolver::_LPstatus = LP_UNKNOWN;
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        PROFILE_SCOPE(prefix + "_solve_LP_s")
+        LowerBoundingSolver::_LPstatus = _subsolvers[s]->_LPstatus = _subsolvers[s]->_solve_LP(_subNodes[s]);
+        if (LowerBoundingSolver::_LPstatus != LP_OPTIMAL) {
+          return;  // early return if any subproblem has infeasible or unknown return status
+        }
+      }
+    }
+
+    /**
+     * @brief Function returning the current status of the last solved set of linear programs.
+     *
+     * @return Returns the current status of the last solved set of linear programs.
+     */
+    LP_RETCODE _get_LP_status() override {
+      // The overall retcode is:
+      // - infeasible if any subproblems are infeasible
+      // - unknown if any subproblem is unknown and none are infeasible
+      // - optimal if all subproblem retcodes are optimal
+      LP_RETCODE retcode = LP_OPTIMAL, overall_retcode = LP_OPTIMAL;
+      for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+          retcode = _subsolvers[s]->_get_LP_status();
+          
+          if (retcode == LP_INFEASIBLE)
+            return LP_INFEASIBLE;
+          else if (retcode == LP_UNKNOWN)
+            overall_retcode = LP_UNKNOWN;
+      }
+
+      return overall_retcode;
+    };
+
+    /**
+     * @brief Function for setting the solution to the solution point of the lastly solved LP.
+     *
+     * @param[in] solution is modified to hold the solution point of the lastly solved LP
+     * @param[in] etaVal is modified to hold the value of eta variable of the lastly solved LP
+     */
+    void _get_solution_point(std::vector<double> &solution, double &obj) override {
+
+      solution.resize(_TwoStageModel->Nx + _TwoStageModel->Ns * _TwoStageModel->Ny);
+      for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+        auto & solution_s = _subproblemSolutions[s];
+        /** TODO: Can we drop one of these lines setting _subproblemDualInfo[s].lpLowerBound? */
+        _subsolvers[s]->_get_solution_point(solution_s, _subproblemDualInfo[s].lpLowerBound);
+        _subproblemDualInfo[s].lpLowerBound = _subsolvers[s]->_get_objective_value();
+
+        // average over first-stage variable values
+        for (unsigned int i = 0; i < _TwoStageModel->Nx; i++) {
+          solution[i] += _TwoStageModel->w[s] * solution_s[i];
+        }
+        // assign correct element in overall variable vector
+        for (unsigned int i = 0; i < _TwoStageModel->Ny; i++) {
+          solution[_TwoStageModel->Nx + s * _TwoStageModel->Ny + i] = solution_s[_TwoStageModel->Nx + i];
+        }
+        // objective is weighted average over subproblem objectives
+        /** NOTE: Obj corresponds to the LP bound, the better of LP and interval bound will be used as the node bound */
+        obj += _TwoStageModel->w[s] * _subproblemDualInfo[s].lpLowerBound;
+      }
+
+      // alternatively set subsolver_class::_lowerVarBounds and subsolver_class::_upperVarBounds in _set_variable_bounds
+      auto lb        = _subsolvers[0]->_lowerVarBounds;
+      auto ub        = _subsolvers[0]->_upperVarBounds;
+      // Find the s for which xs is closest to the mean over the xs in the l2 norm
+      int s_opt      = -1;
+      double minDist = subsolver_class::_maingoSettings->infinity;
+      for (unsigned int s = 0; s < _TwoStageModel->Ns; s++) {
+        double sumSquaredRelDists = 0., dist, initGap, relDist;
+        for (unsigned int i = 0; i < _TwoStageModel->Nx; i++) {
+          dist                = _subproblemSolutions[s][i] - solution[i];
+          initGap             = subsolver_class::_originalVariables[i].get_upper_bound() - subsolver_class::_originalVariables[i].get_lower_bound();
+          relDist             = (initGap > 0) ? (dist / initGap) : 0.;
+          sumSquaredRelDists += std::pow(relDist, 2);
+        }
+        if (sumSquaredRelDists < minDist) {
+          minDist = sumSquaredRelDists;
+          s_opt   = s;
+          if (sumSquaredRelDists == 0) {
+            break;
+          }
+        }
+      }
+      // Update the solution point
+      for (unsigned int i = 0; i < _TwoStageModel->Nx; i++) {
+        solution[i] = _subproblemSolutions[s_opt][i];
+      }
+    }
+
+    /**
+     * @brief Function returning the objective value of the lastly solved LP.
+     *
+     * @return Returns the objective value of the lastly solved LP.
+     */
+    double _get_objective_value_solver() override {
+      double res = 0;
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        res += _TwoStageModel->w[s] * _subsolvers[s]->_get_objective_value_solver();
+      }
+      return res;
+    }
+
+    /**
+     * @brief Function for updating the multipliers from the lastly solved subproblem LPs.
+     * 
+     * The multipliers passed by LowerBoundingSolver::solve_LBP are not used
+     * when solving subproblems, instead we use internally stored multipliers
+     * in subproblemDualInfo. However since BranchAndBound::_postprocess_node
+     * tests for the size of multipliers we need to use dummy values.
+     *
+     * @param[out] dummy_multipliers is sized appropriately but not used.
+     */
+    void _get_multipliers(std::vector<double> &dummy_multipliers) override {
+      dummy_multipliers.resize(_TwoStageModel->Nx + _TwoStageModel->Ns * _TwoStageModel->Ny);
+      for (unsigned s = 0; s < _TwoStageModel->Ns; s++) {
+        _subsolvers[s]->_get_multipliers(_subproblemDualInfo[s].multipliers);
+      }
+    }
+
+    /**
+     * @brief Function for restoring proper coefficients and options in the LP after OBBT.
+     */
+    void _restore_LP_coefficients_after_OBBT() override {
+      for (int s = 0; s < (int)_TwoStageModel->Ns; s++) {
+        _subsolvers[s]->_restore_LP_coefficients_after_OBBT();
+      }
+    }
+
+    /**
+     * @brief Virtual function for fixing a variable to one of its bounds.
+     *
+     * @param[in] iVar is the number of variable which will be fixed.
+     * @param[in] fixToLowerBound describes whether the variable shall be fixed to its lower or upper bound.
+     */
+    void _fix_variable(const unsigned &iVar, const bool fixToLowerBound) override {
+  
+      LowerBoundingSolver::_fix_variable(iVar, fixToLowerBound);
+
+      if (iVar < _TwoStageModel->Nx) {
+        for (unsigned s = 0; s < _TwoStageModel->Ns; s++)
+          _subsolvers[s]->_fix_variable(iVar, fixToLowerBound);
+        return;
+      }
+
+      // For second-stage variables indices wrap-around
+      int s = (iVar - _TwoStageModel->Nx) / _TwoStageModel->Ny;
+      int i_s = _TwoStageModel->Nx + (iVar - _TwoStageModel->Nx) % _TwoStageModel->Nx;
+      _subsolvers[s]->_fix_variable(i_s, fixToLowerBound);
+    };
+
+    /**
+     * @brief Function for checking if the current linear program is really infeasible by, e.g., resolving it with different algorithms.
+     * 
+     * @return Returns true if the linear program is indeed infeasible, false if and optimal solution was found
+     */
+    bool _check_if_LP_really_infeasible() override {
+      // if any subproblem is infeasible, the main one is, too!
+      for (auto & ss : _subsolvers) {
+        if (ss->_check_if_LP_really_infeasible())
+          return true;
+      }
+      return false;
+    }
+
+#ifdef LP__OPTIMALITY_CHECK
+    /**
+     * @brief Function for checking if the solution point returned is really infeasible
+     *
+     * @param[in] currentNode is holding the current node in the branch-and-bound tree
+     * @return Returns whether the problem was confirmed to be infeasible or not
+     */
+    SUBSOLVER_RETCODE _check_infeasibility(const babBase::BabNode &currentNode) override {
+      // The overall retcode is:
+      // - infeasible if any subproblems are infeasible
+      // - feasible if all subproblem retcodes are feasible
+      for (unsigned s = 0; s < _TwoStageModel->Ns; s++) {
+        // Infeasibility check may only be performed if the subproblem is not optimal
+        if (_subsolvers[s]->_get_LP_status() == LP_OPTIMAL) {
+          continue;
+        }
+        auto retcode = _subsolvers[s]->_check_infeasibility(_subNodes[s]);
+
+        if (retcode == SUBSOLVER_INFEASIBLE)
+          return SUBSOLVER_INFEASIBLE;
+      }
+      return SUBSOLVER_FEASIBLE;
+    }
+
+
+    // void _print_check_feasibility(const std::shared_ptr<Logger> logger, const VERB verbosity, const std::vector<double> &solution, const std::vector<std::vector<double>> rhs, const std::string name, const double value, const unsigned i, unsigned k, const unsigned nvar);
+
+    /**
+     * @brief Function for checking if the solution point returned by CPLEX solver is really feasible
+     *
+     * @param[in] UNUSED_SOLUTION for matching the signature only
+     * @return Returns whether the given solution was confirmed to be feasible or not
+     */
+    SUBSOLVER_RETCODE _check_feasibility(const std::vector<double> &UNUSED_SOLUTION) override
+    {
+      // NOTE: Since we need to check feasibility with the subsolvers used for obtaining the solution and the first stage variables might differ, we use the previously computed solution instead of the passed one!
+      // FIXME: Even though right now this is save as calls to _check_feasibility are always preceeded by _get_solution_point, this approach should get a sanity check.
+      // std::vector<double> solution_s;
+      // double dummy;
+
+      for (unsigned s = 0; s < _TwoStageModel->Ns; s++) {
+
+        // _subsolvers[s]->_get_solution_point(solution_s, dummy);
+        auto & solution_s = _subproblemSolutions[s];
+
+        auto retcode = _subsolvers[s]->_check_feasibility(solution_s);
+
+        if (retcode == SUBSOLVER_INFEASIBLE)
+          return SUBSOLVER_INFEASIBLE;
+      }
+      return SUBSOLVER_FEASIBLE;
+    }
+
+    /**
+     * @brief Function for checking if the solution point is really optimal. Uses stored data for subproblems instead of the passed data.
+     *
+     * @param[in] UNUSED_CURRENT_NODE is holding the current node in the branch-and-bound tree
+     * @param[in] UNUSED_LBD is the value of the solution point to check
+     * @param[in] UNUSED_SOLUTION is holding the solution point to check
+     * @param[in] UNUSED_ETAVAL is holding the value of eta at the solution point
+     * @param[in] UNUSED_MULTIPLIERS is holding the dual multipliers of the solution
+     * @return Returns whether the given solution was confirmed to be optimal or not
+     */
+    SUBSOLVER_RETCODE _check_optimality(const babBase::BabNode &UNUSED_CURRENT_NODE, const double UNUSED_LBD, const std::vector<double> &UNUSED_SOLUTION, const double UNUSED_ETAVAL, const std::vector<double> &UNUSED_MULTIPLIERS) override {
+      for (unsigned s = 0; s < _TwoStageModel->Ns; s++) {
+        auto &obj_s = _subproblemDualInfo[s].lpLowerBound;
+        auto &solution_s = _subproblemSolutions[s];
+        auto &multipliers_s = _subproblemDualInfo[s].multipliers;
+        if (_subsolvers[s]->_check_optimality(_subNodes[s], obj_s, solution_s, obj_s, multipliers_s) == SUBSOLVER_INFEASIBLE)
+          return SUBSOLVER_INFEASIBLE;
+      }
+      return SUBSOLVER_FEASIBLE;
+    }
+
+#endif
+
+  private:
+
+    std::shared_ptr<maingo::TwoStageModel> _TwoStageModel;
+    std::vector<std::shared_ptr<LowerBoundingSolver>> _subsolvers;
+    std::vector<std::vector<double>> _subproblemSolutions;  // handle for storing the last group of solution points obtained using subsolvers
+  public:
+    std::vector<double> _subproblemBounds;  // handle for storing the last group of bounds on SP subproblems (the better of LP objective and interval arithmetic bound) to generate upper bounds for OBBT
+  private:
+    std::vector<LbpDualInfo> _subproblemDualInfo; // handle for dualInfo associated to the lower bounding subproblems
+    std::vector<double> _last_incumbent;
+    bool _update_for_OBBT = false;  // if using default OBBT the update of the 'normal' lower bounding problem is required instead of the parallel subproblems
+
+    std::vector<babBase::BabNode> _subNodes;  // the current node representation of the subproblems
+    unsigned int _No;  // Number of orthant nodes created by second-stage branching.
+
+    /**
+     * @brief prepare constraints for the subproblems
+     */
+    inline void _prepare_constraints(
+      const std::string &s_string,
+      std::shared_ptr<std::vector<Constraint>> &SP_constraint_properties_s,
+      std::vector<mc::FFVar> &SP_DAG_functions_s,
+      std::vector<std::vector<NamedVar>> g1_s,
+      std::vector<std::vector<NamedVar>> g2_s
+    ) {
+      unsigned indexOriginal = 0, indexNonconstant = 0;
+
+      SP_constraint_properties_s->emplace_back(
+          CONSTRAINT_TYPE::OBJ,
+          indexOriginal++,
+          0,
+          indexNonconstant++,
+          0,
+          "f1 + f2_" + s_string);
+
+      maingo::CONSTRAINT_TYPE ct[5] = {
+        maingo::CONSTRAINT_TYPE::INEQ,
+        maingo::CONSTRAINT_TYPE::INEQ_SQUASH,
+        maingo::CONSTRAINT_TYPE::EQ,
+        maingo::CONSTRAINT_TYPE::INEQ_REL_ONLY,
+        maingo::CONSTRAINT_TYPE::EQ_REL_ONLY
+      };
+
+      unsigned indexType[5]            = {0, 0, 0, 0, 0};
+      unsigned indexTypeNonconstant[5] = {0, 0, 0, 0, 0};
+      std::string type[5]{"ineq", "ineg_squash", "eq", "ineqRO", "eqRO"};
+      for (unsigned i = 0; i < 5; i++) {
+        for (auto &func : g1_s[i]) {
+          SP_DAG_functions_s.push_back(func.first);
+          SP_constraint_properties_s->emplace_back(
+              ct[i],
+              indexOriginal++,
+              indexType[i],
+              indexNonconstant++,
+              indexTypeNonconstant[i]++,
+              type[i] + '_' + s_string + '_' + std::to_string(indexType[i]++));
+        }
+        for (auto &func : g2_s[i]) {
+          SP_DAG_functions_s.push_back(func.first);
+          SP_constraint_properties_s->emplace_back(
+              ct[i],
+              indexOriginal++,
+              indexType[i],
+              indexNonconstant++,
+              indexTypeNonconstant[i]++,
+              type[i] + '_' + s_string + '_' + std::to_string(indexType[i]++));
+        }
+      }
+
+      // Now that we assembled all functions for scenario s, we analyze their properties
+
+      // Get dependency sets of all functions
+      unsigned size = SP_DAG_functions_s.size();
+      std::vector<std::map<int, int>> func_dep(size);
+      for (unsigned int i = 0; i < size; i++) {
+        func_dep[i] = SP_DAG_functions_s[i].dep().dep();
+      }
+
+      // Loop over all functions
+      unsigned indexLinear = 0, indexNonlinear = 0;
+      for (unsigned int i = 0; i < size; i++) {
+        mc::FFDep::TYPE functionStructure = mc::FFDep::L;
+        std::vector<unsigned> participatingVars;
+        for (unsigned int j = 0; j < LowerBoundingSolver::_nvar; j++) { // TODO: Only X and ys
+          auto ito = func_dep[i].find(j);
+          // Count all participating variables
+          if (ito != func_dep[i].end()) {
+            participatingVars.push_back(j);
+            mc::FFDep::TYPE variableDep = (mc::FFDep::TYPE)(ito->second);
+            // Update function type
+            if (functionStructure < variableDep) {
+              functionStructure = variableDep;
+            }
+          }
+        }
+
+        Constraint &func         = (*SP_constraint_properties_s)[i];
+        func.indexNonconstantUBP = i;
+
+        // determine dependency
+        func.nparticipatingVariables = participatingVars.size();
+        func.participatingVariables  = participatingVars;
+        switch (functionStructure) {
+          case mc::FFDep::L:
+            func.dependency  = LINEAR;
+            func.indexLinear = indexLinear++;
+            break;
+          case mc::FFDep::B:
+            func.dependency     = BILINEAR;
+            func.indexNonlinear = indexNonlinear++;
+            break;
+          case mc::FFDep::Q:
+            func.dependency     = QUADRATIC;
+            func.indexNonlinear = indexNonlinear++;
+            break;
+          case mc::FFDep::P:
+            func.dependency     = POLYNOMIAL;
+            func.indexNonlinear = indexNonlinear++;
+            break;
+          case mc::FFDep::R:
+            func.dependency     = RATIONAL;
+            func.indexNonlinear = indexNonlinear++;
+            break;
+          case mc::FFDep::N:
+          case mc::FFDep::D:
+          default:
+            func.dependency     = NONLINEAR;
+            func.indexNonlinear = indexNonlinear++;
+            break;
+        }
+        func.convexity    = CONV_NONE;
+        func.monotonicity = MON_NONE;
+      }
+    }
+
+    /**
+     * @brief Function for calculating scenario-specific upper bounds to be used as cutoffs on OBBT and DBBT.
+     */
+    inline double _calculate_scenario_UBD(unsigned int s, double currentUBD) {
+      // Since we know that
+      //   sum_{s}(_TwoStageModel->w[s] * subproblem_bound[s]) = localLB ≤ currentUBD
+      // we can use the following cutoff for the subproblem s:
+      //   ( currentUBD - sum_{q ≠ s}( _TwoStageModel->w[q] * subproblem_bound[q]) ) / _TwoStageModel->w[s]
+      //   = ( currentUBD - (localLB - _TwoStageModel->w[s] * subproblem_bound[s]) ) / _TwoStageModel->w[s]
+      //   = ( currentUBD - localLB ) / _TwoStageModel->w[s] + subproblem_bound[s] )
+      // however, since we do OBBT before solving the LBPs, the only bounds we can use are those from the parent node
+
+      // The complement lower bound is the node's local lower bound minus the current scenario's weighted bound
+      double complement_LBD_s = 0.;
+      for (int s_prime = 0; s_prime < _TwoStageModel->Ns; s_prime++) {
+        if (s_prime != s)
+          complement_LBD_s += _TwoStageModel->w[s_prime] * _subproblemBounds[s_prime];
+      }
+      return (currentUBD - complement_LBD_s) / _TwoStageModel->w[s];
+    }
+
+    inline void _reset_optimization_sense(int iyVar, int iVar, int iy, int is) {
+      PROFILE_FUNCTION()
+
+      if (iyVar < 0) {    // x variable, reset all subproblems
+        // #pragma omp parallel for
+        for (int s = 0; s < _TwoStageModel->Ns; s++) {
+          _subsolvers[s]->_set_optimization_sense_of_variable(iVar, 0);
+        }
+      }
+      else {    // y variable, reset the corresponding subproblem
+        _subsolvers[is]->_set_optimization_sense_of_variable(_TwoStageModel->Nx + iy, 0);
+      }
+    }
+
+    /**
+     * @brief Split bound vectors into Ns subvectors for subproblem solvers.
+     *
+     * @param[in] vec is the vector to be split
+     */
+    inline std::pair<std::vector<std::vector<double>>, std::vector<std::vector<double>>> _split_bounds(const std::vector<double> LB, const std::vector<double> UB) {
+      std::vector<std::vector<double>> LBs(_TwoStageModel->Ns, std::vector<double>(_TwoStageModel->Nx + _TwoStageModel->Ny));
+      std::vector<std::vector<double>> UBs(_TwoStageModel->Ns, std::vector<double>(_TwoStageModel->Nx + _TwoStageModel->Ny));
+      for (int s = 0; s < _TwoStageModel->Ns; s++) {
+        for (unsigned i = 0; i < _TwoStageModel->Nx; i++) {
+          LBs[s][i] = LB[i];
+          UBs[s][i] = UB[i];
+        }
+        for (unsigned i = 0; i < _TwoStageModel->Ny; i++) {
+          LBs[s][_TwoStageModel->Nx + i] = LB[_TwoStageModel->Nx + s * _TwoStageModel->Ny + i];
+          UBs[s][_TwoStageModel->Nx + i] = UB[_TwoStageModel->Nx + s * _TwoStageModel->Ny + i];
+        }
+      }
+      return {LBs, UBs};
+    }
+};
+
+
+/**
+ * @brief Factory function for initializing different TwoStage lower bounding solver wrappers
+ *
+ * @param[in] twoStageModel is the pointer to the TwoStageModel opbject
+ * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
+ * @param[in] DAGvars are the variables corresponding to the DAG
+ * @param[in] DAGfunctions are the functions corresponding to the DAG
+ * @param[in] variables is a vector containing the initial optimization variables defined in problem.h
+ * @param[in] nineqIn is the number of inequality constraints
+ * @param[in] neqIn is the number of equality
+ * @param[in] nineqRelaxationOnlyIn is the number of inequality for use only in the relaxed problem
+ * @param[in] neqRelaxationOnlyIn is the number of equality constraints for use only in the relaxed problem
+ * @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
+ * @param[in] settingsIn is a pointer to the MAiNGO settings
+ * @param[in] loggerIn is a pointer to the MAiNGO logger object
+ * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
+ */
+std::shared_ptr<LowerBoundingSolver> make_two_stage_lbp_solver(
+  const std::shared_ptr<maingo::TwoStageModel> twoStageModel,
+  mc::FFGraph &DAG,
+  const std::vector<mc::FFVar> &DAGvars,
+  const std::vector<mc::FFVar> &DAGfunctions,
+  const std::vector<babBase::OptimizationVariable> &variables,
+  const std::vector<bool> &_variableIsLinear,
+  const unsigned nineqIn,
+  const unsigned neqIn,
+  const unsigned nineqRelaxationOnlyIn,
+  const unsigned neqRelaxationOnlyIn,
+  const unsigned nineqSquashIn,
+  std::shared_ptr<Settings> settingsIn,
+  std::shared_ptr<Logger> loggerIn,
+  std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn){
+  PROFILE_FUNCTION()
+
+  switch (settingsIn->LBP_solver) {
+    case LBP_SOLVER_MAiNGO: {
+      loggerIn->print_message("      Two-stage lower bounding: MAiNGO internal solver (McCormick relaxations for objective, intervals for constraints)\n",
+                              VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<LbpTwoStage<LowerBoundingSolver>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, _variableIsLinear, nineqIn, neqIn,
+                                                                nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
+    }
+    case LBP_SOLVER_INTERVAL: {
+      loggerIn->print_message("      Two-stage lower bounding: Interval extensions\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<LbpTwoStage<LbpInterval>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, _variableIsLinear, nineqIn, neqIn,
+                                                        nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
+    }
+    case LBP_SOLVER_CPLEX: {
+#ifdef HAVE_CPLEX
+      loggerIn->print_message("      Two-stage lower bounding: CPLEX\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<LbpTwoStage<LbpCplex>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, _variableIsLinear, nineqIn, neqIn,
+                                                     nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
+#else
+      throw MAiNGOException("  Error in LbpTwoStage: Cannot use lower bounding strategy LBP_SOLVER_CPLEX: Your MAiNGO build does not contain CPLEX.");
+#endif
+    }
+    case LBP_SOLVER_CLP: {
+      loggerIn->print_message("      Two-stage lower bounding: CLP\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<LbpTwoStage<LbpClp>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, _variableIsLinear, nineqIn, neqIn,
+                                                   nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
+    }
+    default:
+    {  // GCOVR_EXCL_START
+      std::ostringstream errmsg;
+      errmsg << "  Error in LbpTwoStage Factory: Unknown lower bounding solver: " << settingsIn->LBP_solver;
+      throw MAiNGOException(errmsg.str());
+    }
+    // GCOVR_EXCL_STOP
+  }
+}
+
+
+}    // end of namespace lbp
+
+
+}    // end of namespace maingo
\ No newline at end of file
diff --git a/inc/logger.h b/inc/logger.h
index 4cdc69e..2f904ab 100644
--- a/inc/logger.h
+++ b/inc/logger.h
@@ -231,4 +231,4 @@ class Logger {
 };
 
 
-}    // end namespace maingo
\ No newline at end of file
+}    // end namespace maingo
diff --git a/inc/mpiUtilities.h b/inc/mpiUtilities.h
index 9391a19..f64e9b2 100644
--- a/inc/mpiUtilities.h
+++ b/inc/mpiUtilities.h
@@ -72,28 +72,38 @@ enum COMMUNICATION_TAG {
     TAG_NEW_NODE_NEW_INCUMBENT,       /*!< Manager will send new node and a new incumbent */
     TAG_NEW_NODE_NO_DATASET,          /*!< Manager will send new node, but no new dataset */
     TAG_NEW_NODE_NEW_DATASET,         /*!< Manager will send new node and a new dataset */
+    TAG_NEW_SIBLINGS_NO_INCUMBENT,    /*!< Manager will send new siblings, but no new incumbent */
+    TAG_NEW_SIBLINGS_NEW_INCUMBENT,   /*!< Manager will send new siblings and a new incumbent */
     TAG_NEW_NODE_UBD,                 /*!< New global UBD value*/
+    TAG_NEW_SUBPROBLEM_BOUNDS,        /*!< New subproblem bounds */
     TAG_SOLVED_NODE_STATUS_NORMAL,    /*!< The solved node is normal */
+    TAG_SOLVED_SIBLING_STATUS_NORMAL, /*!< The siblings were solved normally */
     TAG_SOLVED_NODE_STATUS_CONVERGED, /*!< The solved node converged */
-    TAG_SOLVED_NODE_STATUS_INFEAS,    /*!< The solved node is infeasible */
-    TAG_SOLVED_NODE_LBD,              /*!< LBD of solved node */
-    TAG_SOLVED_NODE_SOLUTION_POINT,   /*!< Solution point of solved node */
-    TAG_SOLVED_NODE_STATISTICS,       /*!< Statistic from solving node (lbd and ubd counts) */
-    TAG_NODE_ID,                      /*!< ID of the node being sent */
-    TAG_NODE_PRUNING_SCORE,           /*!< Pruning score of the node being sent */
-    TAG_NODE_LOWER_BOUNDS,            /*!< Variable lower bounds of the node being sent */
-    TAG_NODE_UPPER_BOUNDS,            /*!< Variable upper bounds of the node being sent */
-    TAG_NODE_HAS_INCUMBENT,           /*!< Whether the node being sent holds the incumbent */
-    TAG_NODE_DEPTH,                   /*!< Depth of the node being sent */
-    TAG_NODE_IDXDATA,                 /*!< Index of dataset used in node being sent */
-    TAG_NODE_AUGMENT_DATA,            /*!< Whether dataset has been augmented to get the dataset of the node being sent */
-    TAG_NODE_DATAPOINT,               /*!< Data point of a new dataset being sent */
-    TAG_WORKER_FINISHED,              /*!< Worker finished BAB-loop */
-    TAG_MS_STOP_SOLVING,              /*!< Multistart: worker should stop searching for local solutions */
-    TAG_MS_NEW_POINT,                 /*!< Multistart: new initial point for local search */
-    TAG_MS_SOLUTION,                  /*!< Multistart: local solution */
-    TAG_MS_FEAS,                      /*!< Multistart: found feasible solution */
-    TAG_MS_INFEAS,                    /*!< Multistart: initial point was infeasible */
+    TAG_SOLVED_SIBLING_STATUS_CONVERGED, /*!< The solved siblings converged */
+    TAG_SOLVED_NODE_STATUS_INFEAS, /*!< The solved node is infeasible */
+    TAG_SOLVED_SIBLING_STATUS_INFEAS, /*!< The solved siblings are infeasible */
+    TAG_SOLVED_NODE_LBD,               /*!< LBD of solved node */
+    TAG_SOLVED_SUBPROBLEM_LBD,         /*!< LBD of subproblem */
+    TAG_SERIALIZED_SIBLING_RESULTS,    /*!< Serialized results of siblings */
+    TAG_FATHOMED_SUBPROBLEMS,          /*!< Fathomed subproblems */
+    TAG_SOLVED_NODE_SOLUTION_POINT,    /*!< Solution point of solved node */
+    TAG_SOLVED_NODE_STATISTICS,        /*!< Statistic from solving node (lbd and ubd counts) */
+    TAG_NODE_ID,                       /*!< ID of the node being sent */
+    TAG_PARENT_ID,                     /*!< ID of the parent of the node being sent */
+    TAG_NODE_PRUNING_SCORE,            /*!< Pruning score of the node being sent */
+    TAG_NODE_LOWER_BOUNDS,             /*!< Variable lower bounds of the node being sent */
+    TAG_NODE_UPPER_BOUNDS,             /*!< Variable upper bounds of the node being sent */
+    TAG_NODE_HAS_INCUMBENT,            /*!< Whether the node being sent holds the incumbent */
+    TAG_NODE_DEPTH,                    /*!< Depth of the node being sent */
+    TAG_NODE_IDXDATA,                  /*!< Index of dataset used in node being sent */
+    TAG_NODE_AUGMENT_DATA,             /*!< Whether dataset has been augmented to get the dataset of the node being sent */
+    TAG_NODE_DATAPOINT,                /*!< Data point of a new dataset being sent */
+    TAG_WORKER_FINISHED,               /*!< Worker finished BAB-loop */
+    TAG_MS_STOP_SOLVING,               /*!< Multistart: worker should stop searching for local solutions */
+    TAG_MS_NEW_POINT,                  /*!< Multistart: new initial point for local search */
+    TAG_MS_SOLUTION,                   /*!< Multistart: local solution */
+    TAG_MS_FEAS,                       /*!< Multistart: found feasible solution */
+    TAG_MS_INFEAS,                     /*!< Multistart: initial point was infeasible */
 };
 
 /**
@@ -107,6 +117,7 @@ send_babnode(const babBase::BabNode &node, const int dest)
 {
 
     int id                 = node.get_ID();
+    int parent_id          = node.get_parent_ID();
     std::vector<double> lb = node.get_lower_bounds();
     std::vector<double> ub = node.get_upper_bounds();
     double pruningScore    = node.get_pruning_score();
@@ -115,6 +126,7 @@ send_babnode(const babBase::BabNode &node, const int dest)
     int aD                 = node.get_augment_data() ? 1 : 0;
     // Send all information
     MPI_Ssend(&id, 1, MPI_INT, dest, TAG_NODE_ID, MPI_COMM_WORLD);
+    MPI_Ssend(&parent_id, 1, MPI_INT, dest, TAG_PARENT_ID, MPI_COMM_WORLD);
     MPI_Ssend(&pruningScore, 1, MPI_DOUBLE, dest, TAG_NODE_PRUNING_SCORE, MPI_COMM_WORLD);
     MPI_Ssend(lb.data(), lb.size(), MPI_DOUBLE, dest, TAG_NODE_LOWER_BOUNDS, MPI_COMM_WORLD);
     MPI_Ssend(ub.data(), lb.size(), MPI_DOUBLE, dest, TAG_NODE_UPPER_BOUNDS, MPI_COMM_WORLD);
@@ -134,7 +146,7 @@ inline void
 recv_babnode(babBase::BabNode &node, const int source, const unsigned nvar)
 {
 
-    int id;
+    int id, parent_id;
     std::vector<double> lb(nvar, 0);
     std::vector<double> ub(nvar, 0);
     double pruningScore;
@@ -143,6 +155,7 @@ recv_babnode(babBase::BabNode &node, const int source, const unsigned nvar)
     int aD;
 
     MPI_Recv(&id, 1, MPI_INT, source, TAG_NODE_ID, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+    MPI_Recv(&parent_id, 1, MPI_INT, source, TAG_PARENT_ID, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
     MPI_Recv(&pruningScore, 1, MPI_DOUBLE, source, TAG_NODE_PRUNING_SCORE, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
     MPI_Recv(lb.data(), nvar, MPI_DOUBLE, source, TAG_NODE_LOWER_BOUNDS, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
     MPI_Recv(ub.data(), nvar, MPI_DOUBLE, source, TAG_NODE_UPPER_BOUNDS, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
@@ -150,7 +163,7 @@ recv_babnode(babBase::BabNode &node, const int source, const unsigned nvar)
     MPI_Recv(&idData, 1, MPI_INT, source, TAG_NODE_IDXDATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
     MPI_Recv(&aD, 1, MPI_INT, source, TAG_NODE_AUGMENT_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
 
-    node = babBase::BabNode(pruningScore, lb, ub, idData, id, depth, aD == 1);
+    node = babBase::BabNode(pruningScore, lb, ub, idData, parent_id, id, depth, aD == 1);
 }
 
 /**
diff --git a/inc/settings.h b/inc/settings.h
index df2ed3e..77d9b0e 100644
--- a/inc/settings.h
+++ b/inc/settings.h
@@ -37,7 +37,8 @@ enum SETTING_NAMES {
     TARGETUPPERBOUND,            /*!< target value for UBD at which MAiNGO terminates */
     BAB_MAXNODES,                /*!< max number of nodes */
     BAB_MAXITERATIONS,           /*!< max number of iterations */
-    MAXTIME,                     /*!< max time */
+    MAXTIME,                     /*!< max CPU time */
+    MAXWTIME,                    /*!< max wall time */
     CONFIRMTERMINATION,          /*!< whether to confirm termination */
     TERMINATEONFEASIBLEPOINT,    /*!< whether to terminate once a feasible point was found */
     PRE_MAXLOCALSEARCHES,        /*!< max local searches in pre-processing */
@@ -81,13 +82,23 @@ enum SETTING_NAMES {
     WRITETOLOGSEC,               /*!< write to log/csv every x seconds */
     PRE_PRINTEVERYLOCALSEARCH,   /*!< whether to print every local search */
     WRITETOOTHERLANGUAGE,        /*!< write a file in a different modeling language */
+    GROWING_APPROACHCHOSEN,      /*!< which approach of the B&B algorithm with growing datasets to use */
+    GROWING_MAXTIMEPOST,         /*!< max time for heuristic B&B algorithm with growing datasets in post-processing */
     GROWING_DATASIZETOL,         /*!< relative tolerance for considering dataset as full dataset */
     GROWING_DATASIZEINIT,        /*!< relative size of initial dataset (= smallest reduced dataset) compared to full dataset */
+    GROWING_USERESAMPLING,       /*!< whether to use resampling for calculating the lower bound based on the initial dataset when using the B&B algorithm with growing datasets */
+    GROWING_AUGMENTPERCENTAGE,   /*!< percentage of full dataset to be added when augmenting */
     GROWING_AUGMENTRULE,         /*!< choose augmentation rule */
     GROWING_AUGMENTFREQ,         /*!< frequency of augmenting dataset (regarding depth of nodes) */
     GROWING_AUGMENTWEIGHT,       /*!< weighting factor of heuristic lower bound, see augmentation rule SCALING */
-    GROWING_AUGMENTPERCENTAGE,   /*!< percentage of full dataset to be augmented */
-    UNKNOWN_SETTING = 500        /*!< unknown setting */
+    GROWING_AUGMENTTOL,          /*!< absolute and relative tolerance of lower bound, see augmentation rule TOL */
+    TS_USELBS,                   /*!< use lower bounding solvers for two-stage problems */
+    TS_USEUBS,                   /*!< use upper bounding solvers for two-stage problems */
+    TS_POBBT,                    /*!< whether to use parallel OBBT for two-stage problems */
+    TS_STRONGBRANCHINGTHRESHOLD, /*!< percentage of the best strong branching score above which scenarios may be selected for branching in two-stage problems */
+    TS_MAXBRANCHINGPOWER,        /*!< maximum branching power for two-stage problems, i.e., at most 2 ^ TS_MAXBRANCHINGPOWER nodes are created */
+    TS_ORTHANTUBD,               /*!< whether to perform UBD for each orthant separately */
+    UNKNOWN_SETTING = 500 /*!< unknown setting */
 };
 
 /**
@@ -123,13 +134,33 @@ enum WRITING_LANGUAGE {
 
 
 /**
-* @enum AUGMENTATION_RULE
-* @brief Enum for controlling the augmentation rule used in MAiNGO with growing datasets
+* @enum AUGMENTATION_APPROACH
+* @brief Enum for controlling the augmentation rule used in the B&B algorithm with growing datasets
 */
 enum AUGMENTATION_RULE {
     AUG_RULE_CONST = 0,    //!< (=0): Augment every growing_augmentFreq by growing_augmentPercentage data points
     AUG_RULE_SCALING,      //!< (=1): Augment if linear scaling of reduced LB to full LB suggests pruning
-    AUG_RULE_SCALCST       //!< (=2): Augment if SCALING or CONST is triggered
+    AUG_RULE_VALID,        //!< (=2): Augment if UB is smaller than validated LB
+    AUG_RULE_COMBI,        //!< (=3): Augment if UB is smaller than approximated LB which is a combination of reduced and validated LB
+    AUG_RULE_TOL,          //!< (=4): Augment if UB is within tolerance of the lower bound used for pruning. Currently the same value is used for the absolute and relative tolerance
+    AUG_RULE_SCALCST,      //!< (=5): Augment if SCALING or CONST is triggered
+    AUG_RULE_VALCST,       //!< (=6): Augment if VALID or CONST is triggered
+    AUG_RULE_COMBICST,     //!< (=7): Augment if COMBI or CONST is triggered
+    AUG_RULE_TOLCST        //!< (=8): Augment if TOL or CONST is triggered
+};
+
+
+/**
+* @enum GROWING_APPROACH
+* @brief Enum for controlling the approach of the B&B algorithm with growing datasets to be used
+*
+* Important note: When adding new approaches, ensure correct usage of this enum throughout code. As is, we exploit that DETERMINISTIC and
+*                 SSEHEURISTIC use the same objective as well as that SSEHEURISTIC and MSEHEURISTIC use the same lower bound for pruning.
+*/
+enum GROWING_APPROACH {
+    GROW_APPR_DETERMINISTIC = 0, //!< (=0): Deterministic approach, i.e., minimize summed squared error and use reduced lower bound for pruning
+    GROW_APPR_SSEHEURISTIC,      //!< (=1): SSE heuristic, i.e., minimize summed squared error and use combined lower bound for pruning
+    GROW_APPR_MSEHEURISTIC       //!< (=2): MSE heuristic, i.e., minimize mean squared error and use combined lower bound for pruning
 };
 
 
@@ -199,7 +230,7 @@ enum UBP_SOLVER {
 * @struct Settings
 * @brief Struct for storing settings for MAiNGO
 *
-* Contains settings for MAiNGO. The default values an be found in settings.cpp.
+* Contains settings for MAiNGO.
 *
 */
 struct Settings {
@@ -222,6 +253,7 @@ struct Settings {
     unsigned BAB_maxNodes         = std::numeric_limits<unsigned>::max();    //!< Maximum number of nodes (i.e., solver terminates when more than BAB_maxnodes are held in memory; used to avoid excessive branching)
     unsigned BAB_maxIterations    = std::numeric_limits<unsigned>::max();    //!< Maximum number of iterations (i.e., maximum number of nodes visited in the Branch-and-Bound tree)
     unsigned maxTime              = 86400 /*=24h*/;                          //!< CPU time limit in seconds
+    unsigned maxwTime             = 86400 /*=24h*/;                          //!< Wall time limit in seconds
     bool confirmTermination       = false;                                   //!< Whether to ask the user before terminating when reaching time, node, or iteration limits
     bool terminateOnFeasiblePoint = false;                                   //!< Whether to terminate as soon as the first feasible point was found (no guarantee of global or local optimality!)
     double targetLowerBound       = std::numeric_limits<double>::max();      //!< Target value for the lower bound on the optimal objective. MAiNGO terminates once LBD>=targetLowerBound  (no guarantee of global or local optimality!)
@@ -315,6 +347,17 @@ struct Settings {
     bool UBP_ignoreNodeBounds               = false;                    //!< Flag indicating whether the UBP solvers should ignore the box constraints of the current node during the B&B (and consider only the ones of the root node instead).
     /**@}*/
 
+    /**
+    * @name Two-Stage settings
+    */
+    /**@{*/
+    bool TS_useLowerBoundingSubsolvers = true;    //!< Whether to use separate subsolvers for lower bounding of problems given in two-stage formulation
+    bool TS_useUpperBoundingSubsolvers = true;    //!< Whether to use separate subsolvers for upper bounding of problems given in two-stage formulation
+    bool TS_parallelOBBT = true;                  //!< Whether to use parallelizable subproblems also for OBBT (otherwise use 'standard' LP relaxation)
+    double TS_strongBranchingThreshold = 1;       //!< Percentage of the best strong branching score above which scenarios may be selected for branching in two-stage problems
+    unsigned int TS_maxBranchingPower  = 1;       //!< Maximum branching power for two-stage problems, i.e., at most 2 ^ TS_maxBranchinPower nodes are created
+    /**@}*/
+
     /**
     * @name Epsilon-constraint settings
     */
@@ -323,15 +366,19 @@ struct Settings {
     /**@}*/
 
     /**
-    * @name Settings of MAiNGO with growing datasets
+    * @name Settings of B&B algorithm with growing datasets
     */
     /**@{*/
-    double growing_dataSizeTol            = 0.9;                 //!< Dataset of this relative size to full dataset considered as full dataset.
-    double growing_dataSizeInit           = 0.1;                 //!< Relative size of initial dataset (= smallest reduced dataset) compared to full dataset.
-    AUGMENTATION_RULE growing_augmentRule = AUG_RULE_SCALCST;    //!< Rule when to augment the dataset
-    unsigned growing_augmentFreq          = 10;                  //!< Augment dataset of all nodes whose depths is multiple of this frequency.
-    double growing_augmentWeight          = 1.;                  //!< Weight of heuristic lower bound calculated in augmentation rule SCALING.
-    double growing_augmentPercentage      = 0.25;                //!< How many dat apoints to be added when augmenting (fraction of the size of the full data).
+    GROWING_APPROACH growing_approach     = GROW_APPR_DETERMINISTIC;    //!< Flag indicating which approach of the B&B algorithm with growing datasets to use.
+    double growing_maxTimePostprocessing  = 60.;                        //!< Maximum CPU time for post-processing the heuristic B&B algorithm with growing datasets.
+    double growing_dataSizeTol            = 0.9;                        //!< Dataset of this relative size to full dataset considered as full dataset.
+    double growing_dataSizeInit           = 0.1;                        //!< Relative size of initial dataset (= smallest reduced dataset) compared to full dataset.
+    bool growing_useResampling            = false;                      //!< Flag indicating whether to use resampling for the lower bound based on the initial dataset.
+    double growing_augmentPercentage      = 0.25;                       //!< How many dat apoints to be added when augmenting (fraction of the size of the full data).
+    AUGMENTATION_RULE growing_augmentRule = AUG_RULE_TOLCST;            //!< Rule when to augment the dataset (default must be valid for any objective mode).
+    unsigned growing_augmentFreq          = 10;                         //!< Augment dataset of all nodes whose depths is multiple of this frequency.
+    double growing_augmentWeight          = 1.;                         //!< Weight of heuristic lower bound calculated in augmentation rule SCALING.
+    double growing_augmentTol             = 1.e-1;                      //!< Absolute and relative tolerance of lower bound used in augmentation rule TOL.
     /**@}*/
 };
 
diff --git a/inc/siblingResults.h b/inc/siblingResults.h
new file mode 100644
index 0000000..ee8168f
--- /dev/null
+++ b/inc/siblingResults.h
@@ -0,0 +1,434 @@
+/**********************************************************************************
+ * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ **********************************************************************************/
+
+#pragma once
+
+#include "lbp.h"
+
+#include <vector>
+#include <array>
+
+namespace babBase {
+
+class BabNode;
+
+}  // namespace babBase
+
+
+using babBase::BabNode;
+
+namespace maingo {
+
+namespace lbp {
+
+/**
+ * @brief Struct for storing results of a sibling iteration
+ */
+struct SiblingResults {
+  const unsigned int Nx;
+  const unsigned int Ny;
+  const unsigned int Ns;
+  const std::vector<double> &w;
+  bool feasible;  // set to true only if each scenario is determined to be feasible in at least one sibling by lower bounding
+  bool converged;  // set to true only if parent is converged
+  bool foundNewFeasiblePoint;  // set to true only if point is found
+  unsigned int nAddedLBSolves = 0;
+  std::vector<double> ubpSolutionPoint;
+  double ubpObjectiveValue;
+  double parentPruningScore;  // updated based on minimum sibling pruning score
+  std::vector<double> parentObjectiveBounds;
+  std::array<BabNode, 2> siblings;
+  std::vector<std::array<babBase::BabNode, 2>> siblingSubNodes;
+  std::array<std::vector<double>, 2> objectiveBounds;
+  std::array<std::vector<LbpDualInfo>, 2> dualInfo;
+  std::array<std::vector<std::vector<double>>, 2> solutions;
+  std::array<std::vector<std::vector<double>>, 2> lowerBounds;
+  std::array<std::vector<std::vector<double>>, 2> upperBounds;
+
+  SiblingResults(std::vector<double> &w, unsigned int Nx, unsigned int Ny):
+      Nx(Nx), Ny(Ny), Ns(w.size()), w(w),
+      parentObjectiveBounds(Ns, std::numeric_limits<double>::infinity()),
+      objectiveBounds({{std::vector<double>(Ns, std::numeric_limits<double>::infinity()),
+                        std::vector<double>(Ns, std::numeric_limits<double>::infinity())}}),
+      solutions({{std::vector<std::vector<double>>(Ns),
+                  std::vector<std::vector<double>>(Ns)}}),
+      lowerBounds({{std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, std::numeric_limits<double>::infinity())),
+                    std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, std::numeric_limits<double>::infinity()))}}),
+      upperBounds({{std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, -std::numeric_limits<double>::infinity())),
+                    std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, -std::numeric_limits<double>::infinity()))}})
+      {};
+
+  void reset(BabNode &&lowerSibling, BabNode &&upperSibling, std::vector<double> &parentSubproblemBounds) {
+    feasible = false;
+    converged = false;
+    foundNewFeasiblePoint = false;
+    nAddedLBSolves = 0;
+    ubpObjectiveValue = std::numeric_limits<double>::infinity();
+    parentPruningScore = lowerSibling.get_pruning_score();
+
+    if (parentSubproblemBounds.size() == Ns) {
+        // We have one set of bounds
+        parentObjectiveBounds = parentSubproblemBounds;
+    }
+    else {
+        /** The parent was an orthant node.
+         *  We have two sets of bounds:
+         *  1) The first Ns values are subproblem bounds based on the original parent subproblem bounds and are used to compute strong branching scores.
+         *  2) The remaining values are subproblem bounds based on the tightened parent subproblem bounds and are used to compute scenario upper bounds and pruning scores.
+         */
+        parentObjectiveBounds.assign(parentSubproblemBounds.begin() + Ns, parentSubproblemBounds.end());
+        parentSubproblemBounds.resize(Ns);
+    }
+    objectiveBounds[0] = parentSubproblemBounds;
+    objectiveBounds[1] = parentSubproblemBounds;
+
+    siblings = {lowerSibling, upperSibling};
+    siblingSubNodes = {Ns, {{siblings[0], siblings[1]}}};
+    dualInfo = {{std::vector<LbpDualInfo>(Ns),
+                 std::vector<LbpDualInfo>(Ns)}};
+    solutions = {{std::vector<std::vector<double>>(Ns),
+                 std::vector<std::vector<double>>(Ns)}};
+    lowerBounds = {{std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, std::numeric_limits<double>::infinity())),
+                   std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, std::numeric_limits<double>::infinity()))}};
+    upperBounds = {{std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, -std::numeric_limits<double>::infinity())),
+                   std::vector<std::vector<double>>(Ns, std::vector<double>(Nx + Ny, -std::numeric_limits<double>::infinity()))}};
+    ubpSolutionPoint.reserve(Nx + Ns * Ny);
+    ubpSolutionPoint = {};
+  };
+
+  const std::vector<double> & get_parent_lower_bounds() const {
+      return siblings[0].get_lower_bounds();
+  }
+
+  const double & get_parent_lower_bound(unsigned int j) const {
+      return siblings[0].get_lower_bound(j);
+  }
+
+  void set_parent_lower_bound(unsigned int j, double value) {
+      siblings[0].set_lower_bound(j, value);
+  }
+
+  const std::vector<double> & get_parent_upper_bounds() const {
+      return siblings[1].get_upper_bounds();
+  }
+
+  const double & get_parent_upper_bound(unsigned int j) const {
+      return siblings[1].get_upper_bound(j);
+  }
+
+  void set_parent_upper_bound(unsigned int j, double value) {
+      siblings[1].set_upper_bound(j, value);
+  }
+
+  /**
+   * @brief Check if the parent is infeasible after updating the variable domains for given scenario subproblem with the unions of variable domains for the corresponding sibling subproblems.
+   * 
+   * @param s Scenario index
+   * @param parentSubNode Node representing the parent subproblem
+   * @param retcodes Return codes for the sibling subproblems
+   * 
+   * @returns true if the parent node is infeasible
+   */
+  bool infeasible_after_parent_tightening(int s, babBase::BabNode & parentSubNode, const std::array<int, 2> &retcodes) {
+    // shorthands
+    auto ssn0 = siblingSubNodes[s][0];
+    auto ssn1 = siblingSubNodes[s][1];
+    // First check whether the subproblem of one sibling is infeasible, if so only the other siblings data is relevant.
+    if (retcodes[0] < 1) {  // scenario subproblem for sibling 0 is infeasible
+      if (retcodes[1] < 1) {
+        // both siblings are infeasible, so is the parent
+        return true;
+      }
+      // only use data of sibling 1, independent of whether tightening changed
+      for (int i = 0; i < Nx + Ny; ++i) {
+        parentSubNode.set_lower_bound(i, ssn1.get_lower_bound(i));
+        parentSubNode.set_upper_bound(i, ssn1.get_upper_bound(i));
+      }
+    }
+    // We know scenario subproblem for sibling 0 is feasible
+    else if (retcodes[1] < 1) {  // scenario subproblem for sibling 1 is infeasible
+      // only use data of sibling 0, independent of whether tightening changed
+      for (int i = 0; i < Nx + Ny; ++i) {
+        parentSubNode.set_lower_bound(i, ssn0.get_lower_bound(i));
+        parentSubNode.set_upper_bound(i, ssn0.get_upper_bound(i));
+      }
+    }
+    // Both sibling subproblems are feasible, so we consider the union of the sibling domains
+    else if ((retcodes[0] == 2) || (retcodes[1] == 2)) {  // at least one sibling was tightened
+      for (int i = 0; i < Nx + Ny; ++i) {
+        // if both siblings were tightened, the union over both siblings is a valid tightening of the parent bounds
+        parentSubNode.set_lower_bound(i, std::min(ssn0.get_lower_bound(i), ssn1.get_lower_bound(i)));
+        parentSubNode.set_upper_bound(i, std::max(ssn0.get_upper_bound(i), ssn1.get_upper_bound(i)));
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @brief Check if the parent is infeasible after updating all variable domains with intersections of x-domains and unions of y-domains from the sibling subproblems.
+   * 
+   * @param parentSubNodes parent subproblem nodes, assumed to contain unions of sibling domains
+   * @param retcodes Return codes for the sibling subproblems
+   * 
+   * @returns true if the parent node is infeasible
+   */
+  bool infeasible_after_sibling_tightening(std::vector<babBase::BabNode> &parentSubNodes, std::vector <std::array<int, 2>> & retcodes) {
+    // Compute intersection over all scenarios of sibling domains for x
+    // Since the resulting x domain is also valid for the parent node, we store it directly
+    // in the parent bounds (which are just the lower/upper bound of the lower/upper sibling).
+    for (int s = 0; s < Ns; ++s) {
+      for (int i = 0; i < Nx; ++i) {
+        // Update x domain of parent with the intersecion of unions of x domains over all scenarios
+        set_parent_lower_bound(i, std::max(get_parent_lower_bound(i), parentSubNodes[s].get_lower_bound(i)));
+        set_parent_upper_bound(i, std::min(get_parent_upper_bound(i), parentSubNodes[s].get_upper_bound(i)));
+        if (get_parent_lower_bound(i) > get_parent_upper_bound(i)) // x domain of parent node is empty
+          return true; // parent (and both siblings) infeasible
+      }
+      for (int i = 0; i < Ny; ++i) {  // Update y domain of parent with the unions of y domains over all scenarios
+        set_parent_lower_bound(Nx + s * Ny + i, parentSubNodes[s].get_lower_bound(Nx + i));
+        set_parent_upper_bound(Nx + s * Ny + i, parentSubNodes[s].get_upper_bound(Nx + i));
+      }
+    }
+
+    // intersect the parent and sibling subproblem domains with the valid subdomain for x
+    for (int s = 0; s < Ns; s++) {
+      // intersect parent subproblem domains with the parent domains
+      for (int i = 0; i < Nx; ++i) {
+        parentSubNodes[s].set_lower_bound(i, 
+          std::max(get_parent_lower_bound(i), parentSubNodes[s].get_lower_bound(i))
+        );
+        parentSubNodes[s].set_upper_bound(i, 
+          std::min(get_parent_upper_bound(i), parentSubNodes[s].get_upper_bound(i))
+        );
+      }
+
+      // intersect sibling subproblem domains with the parent domains
+      for (int j = 0; j < 2; ++j) {
+        if (retcodes[s][j] > 0) {
+          for (int i = 0; i < Nx; ++i) {
+            siblingSubNodes[s][j].set_lower_bound(i,
+              std::max(get_parent_lower_bound(i), siblingSubNodes[s][j].get_lower_bound(i))
+            );
+            siblingSubNodes[s][j].set_upper_bound(i,
+              std::min(get_parent_upper_bound(i), siblingSubNodes[s][j].get_upper_bound(i))
+            );
+            if (siblingSubNodes[s][j].get_lower_bound(i) > siblingSubNodes[s][j].get_upper_bound(i)) {
+              retcodes[s][j] = 0;
+              // If other sibling is also infeasible, parent is infeasible
+              int j_other = (j == 0) ? 1 : 0;
+              if (retcodes[s][j_other] < 1) {
+                return true;
+              }
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @brief Tighten parent objective bounds and variable domains based on sibling results
+   * 
+   * This method assumes that the parent node is not dominated, i.e., for each scenario, at most one sibling subproblem is dominated. 
+   * 
+   * @param retcodes Return codes for the sibling subproblems
+   */
+  void tighten_parent_objective_and_variable_bounds(const std::vector <std::array<int, 2>> & retcodes) {
+    #ifdef _OPENMP
+      #pragma omp parallel for
+    #endif
+    for (int s = 0; s < Ns; ++s) {
+      // Objective: The lower bound from both siblings is a valid (and possibly tighter) lower bound for the parent
+      if (retcodes[s][0] < 1) {
+        this->parentObjectiveBounds[s] = objectiveBounds[1][s];
+      }
+      else if (retcodes[s][1] < 1) {
+        this->parentObjectiveBounds[s] = objectiveBounds[0][s];
+      }
+      /** NOTE: Eventhough it would be a valid tightening, the following is detrimental.
+       *        It would skew the strong branching score calculation and result in poor candidate selection.
+       *        This in turn will cause excessive branching on some second-stage variables and thus it
+       *        will result in frequent termination due to reaching minimum node size.
+       * else {
+       *   this->parentObjectiveBounds[s] = std::min(objectiveBounds[0][s], objectiveBounds[1][s]);
+       * }
+       */
+
+      // Variables: We expect the bounds in subNodes to be tightened variable domains with consistent x subdomains
+      for (unsigned int j : {0, 1}) {
+        if (retcodes[s][j] > 0) {
+          this->lowerBounds[j][s] = siblingSubNodes[s][j].get_lower_bounds();
+          this->upperBounds[j][s] = siblingSubNodes[s][j].get_upper_bounds();
+        }
+      }
+    }
+  }
+
+  /**
+   * @brief Function for updating the parent pruning score to the minimum of all orthant nodes
+   */
+  void update_parent_pruning_score(std::vector <double> &parentSubproblemBounds) {
+    parentPruningScore = 0;
+    for (int s = 0; s < Ns; s++) {
+      if (solutions[0][s].size() == 0) {
+        if (solutions[1][s].size() == 0)
+          return;
+        objectiveBounds[0][s] = std::numeric_limits<double>::infinity();
+        parentSubproblemBounds[s] = objectiveBounds[1][s];
+        parentPruningScore += w[s] * objectiveBounds[1][s];
+      }
+      else if (solutions[1][s].size() == 0) {
+        objectiveBounds[1][s] = std::numeric_limits<double>::infinity();
+        parentSubproblemBounds[s]  = objectiveBounds[0][s];
+        parentPruningScore += w[s] * objectiveBounds[0][s];
+      }
+      else {
+        parentSubproblemBounds[s] = std::min(objectiveBounds[0][s], objectiveBounds[1][s]);
+        parentPruningScore += w[s] * std::min(objectiveBounds[0][s], objectiveBounds[1][s]);
+      }
+    }
+  }
+
+  /**
+   * @brief Function to calculate size of serializedSiblingResults
+   * 
+   * The serialization consists of a concatenation of:
+   * - subproblem objective bounds for the parent
+   * - subproblem objective bounds for the lower sibling
+   * - subproblem objective bounds for the upper sibling
+   * - lower bounding solutions for the lower sibling
+   * - lower bounding solutions for the upper sibling
+   * - lower bounds for the lower sibling
+   * - lower bounds for the upper sibling
+   * - upper bounds for the lower sibling
+   * - upper bounds for the upper sibling
+   * 
+   * @returns serializedSiblingResultsSize
+   */
+  static size_t getSerializedSiblingResultsSize(int Nx, int Ny, int Ns) {
+      return (
+          1 * Ns                      // parentSubproblemBounds
+          + 2 * Ns                    // objectiveBounds for both siblings
+          + 2 * Ns * (Nx + Ny)        // lower bounding solutions for both siblings
+          + 2 * 2 * Ns * (Nx + Ny)    // lower and upper bounds for all scenario subproblems of both siblings
+      );
+  }
+
+  /**
+   * @brief Function for serializig SiblingResults data to a single double vector
+   * 
+   * The serialization consists of a concatenation of:
+   * - subproblem objective bounds for the parent
+   * - subproblem objective bounds for the lower sibling
+   * - subproblem objective bounds for the upper sibling
+   * - lower bounding solutions for the lower sibling
+   * - lower bounding solutions for the upper sibling
+   * - lower bounds for the lower sibling
+   * - lower bounds for the upper sibling
+   * - upper bounds for the lower sibling
+   * - upper bounds for the upper sibling
+   * 
+   * @param[out] serializedSiblingResults The serialized data
+   * @param[in] fathomed_subproblems indicating which subproblems have been fathomed
+   * 
+   * NOTE: fathomed_subproblems uses int8_t instead of bool because due to its special implementation std::vector<bool> cannot be sent directly via MPI
+   */
+  void serialize(std::vector<double> &serializedSiblingResults, std::vector<int8_t> &fathomed_subproblems) const {
+    fathomed_subproblems.resize(2 * Ns, false);
+    serializedSiblingResults.reserve(getSerializedSiblingResultsSize(Nx, Ny, Ns));
+    serializedSiblingResults.insert(serializedSiblingResults.end(), parentObjectiveBounds.begin(), parentObjectiveBounds.end());
+    serializedSiblingResults.insert(serializedSiblingResults.end(), objectiveBounds[0].begin(), objectiveBounds[0].end());
+    serializedSiblingResults.insert(serializedSiblingResults.end(), objectiveBounds[1].begin(), objectiveBounds[1].end());
+    for (int j = 0; j < 2; ++j) {
+      for (int s = 0; s < Ns; ++s) {
+        if (solutions[j][s].size() == 0) {
+          fathomed_subproblems[j * Ns + s] = true;
+        }
+        else {
+          serializedSiblingResults.insert(serializedSiblingResults.end(), solutions[j][s].begin(), solutions[j][s].end());
+        }
+      }
+    }
+    for (int j = 0; j < 2; ++j) {
+        for (int s = 0; s < Ns; ++s) {
+            serializedSiblingResults.insert(serializedSiblingResults.end(), lowerBounds[j][s].begin(), lowerBounds[j][s].end());
+        }
+    }
+    for (int j = 0; j < 2; ++j) {
+        for (int s = 0; s < Ns; ++s) {
+            serializedSiblingResults.insert(serializedSiblingResults.end(), upperBounds[j][s].begin(), upperBounds[j][s].end());
+        }
+    }
+  }
+
+  /**
+   * @brief Function for updating the state of SiblingResults with serializedSiblingResults
+   * 
+   * @param[in] lowerSibling The lower sibling node
+   * @param[in] upperSibling The upper sibling node
+   * @param[in] serializedSiblingResults The serialized data to be stored
+   * @param[in] fathomed_subproblems indicating which subproblems have been fathomed
+   */
+  void deserialize(const babBase::BabNode &lowerSibling, const babBase::BabNode &upperSibling,
+                   std::vector<double> &serializedSiblingResults, const std::vector<int8_t> &fathomed_subproblems){
+    siblings = {lowerSibling, upperSibling};
+    auto it = serializedSiblingResults.begin();
+    parentObjectiveBounds.assign(it, it + Ns);
+    it += Ns;
+    objectiveBounds[0].assign(it, it + Ns);
+    it += Ns;
+    objectiveBounds[1].assign(it, it + Ns);
+    it += Ns;
+    for (int j = 0; j < 2; ++j) {
+      for (int s = 0; s < Ns; ++s) {
+        if (fathomed_subproblems[j * Ns + s]) {
+          solutions[j][s] = {};
+        }
+        else {
+          solutions[j][s].assign(it, it + Nx + Ny);
+        }
+        it += Nx + Ny;
+      }
+    }
+    for (int j = 0; j < 2; ++j) {
+      for (int s = 0; s < Ns; ++s) {
+        lowerBounds[j][s].assign(it, it + Nx + Ny);
+        it += Nx + Ny;
+      }
+    }
+    for (int j = 0; j < 2; ++j) {
+      for (int s = 0; s < Ns; ++s) {
+        upperBounds[j][s].assign(it, it + Nx + Ny);
+        it += Nx + Ny;
+      }
+    }
+    // clip to subproblem objective bounds for the parent
+    serializedSiblingResults.resize(Ns);
+    // Recompute parent pruning score
+    parentPruningScore = 0;
+    for (int s = 0; s < Ns; s++) {
+        if (solutions[0][s].size() == 0) {
+            parentPruningScore += w[s] * objectiveBounds[1][s];
+        }
+        else if (solutions[1][s].size() == 0) {
+            parentPruningScore += w[s] * objectiveBounds[0][s];
+        }
+        else {
+            parentPruningScore += w[s] * std::min(objectiveBounds[0][s], objectiveBounds[1][s]);
+        }
+    }
+  }
+};
+
+}    // namespace lbp
+
+}    // namespace maingo
\ No newline at end of file
diff --git a/inc/ubp.h b/inc/ubp.h
index df61bde..c61dda2 100644
--- a/inc/ubp.h
+++ b/inc/ubp.h
@@ -43,6 +43,9 @@ struct DagObj;
 */
 class UpperBoundingSolver {
 
+	template<class subsolver_class>
+  friend class UbsTwoStage;
+
   public:
     /**
 		* @enum UBS_USE
@@ -123,6 +126,13 @@ class UpperBoundingSolver {
 	*/
     void pass_data_position_to_solver(const std::shared_ptr<std::vector<std::set<unsigned int>>> datasets, const unsigned int indexFirstData);
 
+	/**
+    * @brief Function for telling solver whether mean squared error or summed squared error is used as objective function
+    *
+    * @param[in] useMse is the boolean to be passed
+	*/
+    void pass_use_mse_to_solver(const bool useMse);
+
 #ifdef HAVE_MAiNGO_MPI
     /**
     * @brief Function for updating dataset vector and adding subgraph after adding new dataset, needed for parallel version
@@ -260,10 +270,10 @@ class UpperBoundingSolver {
 std::shared_ptr<UpperBoundingSolver> make_ubp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions,
                                                      const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn, const unsigned neqIn,
                                                      const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn,
-                                                     UpperBoundingSolver::UBS_USE useIn);
+                                                     UpperBoundingSolver::UBS_USE useIn, bool printSolver = true);
 
 
 }    // end namespace ubp
 
 
-}    // end namespace maingo
\ No newline at end of file
+}    // end namespace maingo
diff --git a/inc/ubpDagObj.h b/inc/ubpDagObj.h
index 15a6cd4..ba7a36c 100644
--- a/inc/ubpDagObj.h
+++ b/inc/ubpDagObj.h
@@ -84,13 +84,14 @@ struct DagObj {
     mc::FFSubgraph subgraphIneqEq;                  /*!< subgraph holding the list of operations of the (squash) inequalities & equalities */
 
 #ifdef HAVE_GROWING_DATASETS
+    bool useMse;                                                    /*!< whether to use mean squared error or summed squared error as the objective function */
     unsigned int indexFirstData;                                    /*!< position of the first objective per data in MAiNGO::_DAGfunctions */
     std::shared_ptr<std::vector<std::set<unsigned int>>> datasets;  /*!< pointer to a vector containing all available datasets */
     std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraph;    /*!< vector containing pointers to subgraph of all previously used datasets. Note: position in this vector = index of dataset */
     std::vector<std::shared_ptr<mc::FFSubgraph>> storedSubgraphObj; /*!< vector containing pointers to subgraphObj of all previously used datasets. Note: position in this vector = index of dataset */
     std::vector<std::vector<mc::FFVar>> storedFunctions;            /*!< vector containing functions of all previously used datasets. Note: position in this vector = index of dataset */
     std::vector<std::vector<mc::FFVar>> storedFunctionsObj;         /*!< vector containing functionsObj of all previously used datasets. Note: position in this vector = index of dataset */
-#endif                                                              // HAVE_GROWING_DATASETS
+#endif    // HAVE_GROWING_DATASETS
 
     std::shared_ptr<Settings> maingoSettings; /*!< pointer to settings file, used for exception handling */
     std::shared_ptr<Logger> logger;           /*!< pointer to logger, used for exception handling */
@@ -127,6 +128,7 @@ struct DagObj {
             this->vars[i].set(&this->DAG);    // Add the new DAG variable to the DAG
         }
         this->resultVars.resize(DAGfunctions.size());
+
         DAG.eval(DAGfunctions.size(), DAGfunctions.data(), this->resultVars.data(), nvar, DAGvars.data(), this->vars.data());    // Get functions and write them to resultVars
         this->functions.resize(1 + nineq + neq + nineqSquash);
         // Note that the constraints in constraintProperties are in a different order than in MAiNGO.cpp!
@@ -261,8 +263,14 @@ struct DagObj {
         for (auto idxDataPoint : (*datasets)[indexDataset]) {
             obj += resultVars[idxDataPoint + indexFirstData];
         }
-        functions[0]    = obj;
-        functionsObj[0] = obj;
+        if (useMse) {// Use mean of summed objective per data as objective
+            functions[0]    = obj / (*datasets)[indexDataset].size();
+            functionsObj[0] = obj / (*datasets)[indexDataset].size();
+        }
+        else {// Use sum of objective per data as objective
+            functions[0]    = obj;
+            functionsObj[0] = obj;
+        }
 
         // Build new subgraphs
         storedFunctions.push_back(functions);
diff --git a/inc/ubpLazyQuadExpr.h b/inc/ubpLazyQuadExpr.h
index 8b8ca89..d4b0cea 100644
--- a/inc/ubpLazyQuadExpr.h
+++ b/inc/ubpLazyQuadExpr.h
@@ -424,7 +424,7 @@ class SparseMatrix {
         unsigned nnz         = row.get_non_zero_ids().size();
         unsigned newRowIndex = rowNumber;
         if (newRowIndex < minNumber) { // GCOVR_EXCL_START
-            throw MAiNGOException("Tried to append a row to a sparse matrix, but given row index lead to an insertion. Requested row index to append: " + std::to_string(rowNumber) + " index of last row already in matrix: " + std::to_string(minNumber - 1)); // GCOVR_EXCL_LINE
+            throw MAiNGOException("Tried to append a row to a sparse matrix, but given row index lead to an insertion. Requested row index to append: " + std::to_string(rowNumber) + " index of last row already in matrix: " + std::to_string(minNumber - 1));
         }
         for (unsigned i = 0; i < nnz; i++) { // GCOVR_EXCL_STOP
             //efficently inserts at the end of the map!
@@ -790,7 +790,7 @@ class LazyQuadExprTreeNode {
             switch (_op) {
                 case (OperationType::IDENITY): {
                     if (_order == Order::QUADRATIC) { // GCOVR_EXCL_START
-                        throw MAiNGOException(std::string("It should be impossible to create a lazy quadratic expression without creating it from linear expressions") + std::string("but the lazy quadratic expression tree still has an element that claims to be quadratic and an original expression.")); //GCOVR_EXCL_LINE
+                        throw MAiNGOException(std::string("It should be impossible to create a lazy quadratic expression without creating it from linear expressions") + std::string("but the lazy quadratic expression tree still has an element that claims to be quadratic and an original expression."));
                     }
                     result.linearPart = *_linearContent; // GCOVR_EXCL_STOP
                     return result;
@@ -986,7 +986,7 @@ class LazyQuadExpr {
     LazyQuadExpr(unsigned numberVars, unsigned active_index)
     {
         if (active_index >= numberVars) { // GCOVR_EXCL_START
-            throw MAiNGOException("Tried to create an lazy quadratic expresion for a variable with an index that is inconsistent with the specified number of variables"); //GCOVR_EXCL_LINE
+            throw MAiNGOException("Tried to create an lazy quadratic expresion for a variable with an index that is inconsistent with the specified number of variables");
         }
         LinExpr lin(0.0); // GCOVR_EXCL_STOP
         lin.set_value(active_index, 1.0);
diff --git a/inc/ubpTwoStage.h b/inc/ubpTwoStage.h
new file mode 100644
index 0000000..7b5620e
--- /dev/null
+++ b/inc/ubpTwoStage.h
@@ -0,0 +1,485 @@
+/**********************************************************************************
+ * Copyright (c) 2019 Process Systems Engineering (AVT.SVT), RWTH Aachen University
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ **********************************************************************************/
+
+#pragma once
+
+#include "MAiNGOException.h"
+#include "logger.h"
+#include "ubp.h"
+#include "ubpClp.h"
+#include "ubpIpopt.h"
+#include "ubpNLopt.h"
+
+#ifdef HAVE_CPLEX    // If CMake has found CPLEX ,this pre-processor variable is defined
+  #include "ubpCplex.h"
+#endif
+
+#ifdef HAVE_KNITRO    // If CMake has found KNITRO, this pre-processor variable is defined
+  #include "ubpKnitro.h"
+#endif
+
+#include "ubpDagObj.h"
+
+#include "TwoStageModel.h"
+
+namespace maingo {
+
+
+namespace ubp {
+
+
+/**
+* @class UbpTwoStage
+* @brief Wrapper for handling the upper-bounding problems of two stage (stochastic) programming problems
+*
+* This class constructs one upper-bounding subproblem solver per scenario and delegates solve calls to these subproblem solvers.
+*/
+template<class subsolver_class>
+class UbsTwoStage: public subsolver_class {
+  std::shared_ptr<maingo::TwoStageModel> _TwoStageModel;
+  std::vector<std::shared_ptr<UpperBoundingSolver>> _subsolvers;
+
+  mc::FFSubgraph _g1_subgraph;
+  std::vector<Var> _firstStageIneqEq;
+
+public:
+  
+  std::vector<std::vector<babBase::OptimizationVariable>> RP_opt_variables;
+  std::vector<mc::FFGraph> RP_DAGs;
+  std::vector<std::vector<mc::FFVar>> RP_DAG_variables;
+  std::vector<std::vector<mc::FFVar>> RP_DAG_x;
+  std::vector<std::vector<mc::FFVar>> RP_DAG_y;
+  std::vector<std::vector<mc::FFVar>> RP_DAG_functions;
+  std::vector<std::shared_ptr<std::vector<Constraint>>> RP_constraint_properties;
+
+  /**
+  * @brief Constructor, stores information on the problem and initializes the local-subsolvers used
+  *
+  * @param[in] twoStageModel is the pointer to the TwoStageModel opbject
+  * @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
+  * @param[in] DAGvars are the variables corresponding to the DAG
+  * @param[in] DAGfunctions are the functions corresponding to the DAG
+  * @param[in] variables is a vector containing the initial optimization variables defined in problem.h
+  * @param[in] nineqIn is the number of inequality constraints
+  * @param[in] neqIn is the number of equality constraints
+  * @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
+  * @param[in] settingsIn is a pointer to the MAiNGO settings
+  * @param[in] loggerIn is a pointer to the MAiNGO logger object
+  * @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
+  * @param[in] useIn communicates what the solver is to be used for
+  */
+  UbsTwoStage(const std::shared_ptr<maingo::TwoStageModel> twoStageModel,
+              mc::FFGraph &DAG,
+              const std::vector<mc::FFVar> &DAGvars,
+              const std::vector<mc::FFVar> &DAGfunctions,
+              const std::vector<babBase::OptimizationVariable> &variables,
+              const unsigned nineqIn,
+              const unsigned neqIn,
+              const unsigned nineqSquashIn,
+              std::shared_ptr<Settings> settingsIn,
+              std::shared_ptr<Logger> loggerIn,
+              std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn,
+              UpperBoundingSolver::UBS_USE useIn) :
+  subsolver_class(DAG, DAGvars, DAGfunctions, variables,
+                  nineqIn, neqIn, nineqSquashIn,
+                  settingsIn, loggerIn, constraintPropertiesIn, useIn),
+  _TwoStageModel(twoStageModel) {
+    // Creating empty containers for each scenario
+    RP_opt_variables.resize(_TwoStageModel->Ns);
+    RP_DAGs = std::vector<mc::FFGraph>(_TwoStageModel->Ns);  // one new DAG per scenario for RP subproblems
+    RP_DAG_variables.resize(_TwoStageModel->Ns);
+    RP_DAG_x.resize(_TwoStageModel->Ns);
+    RP_DAG_y.resize(_TwoStageModel->Ns);
+    RP_DAG_functions.resize(_TwoStageModel->Ns);
+
+    // Filling the containers above
+    #ifdef _OPENMP
+      #pragma omp parallel for
+    #endif
+    for (int s = 0; s < _TwoStageModel->Ns; ++s) {
+      RP_opt_variables[s].reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+      RP_DAG_variables[s].reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+
+      for (unsigned ix = 0; ix < _TwoStageModel->Nx; ++ix) {
+        RP_opt_variables[s].push_back(variables[ix]);
+        RP_DAG_variables[s].emplace_back(&RP_DAGs[s]);
+        RP_DAG_x[s].push_back(RP_DAG_variables[s].back());
+      }
+    
+      for (unsigned iy = 0; iy < _TwoStageModel->Ny; ++iy) {
+        RP_opt_variables[s].push_back(variables[_TwoStageModel->Nx + s * _TwoStageModel->Ny + iy]);
+        RP_DAG_variables[s].emplace_back(&RP_DAGs[s]);
+        RP_DAG_y[s].push_back(RP_DAG_variables[s].back());
+      }
+    }
+    
+    std::vector<std::vector<std::vector<NamedVar>>> g2;
+    g2.reserve(_TwoStageModel->Ns);
+    // This loop might need to happen sequentially, since _TwoStageModel may not be thread-safe (e.g. when defined via the Python API)
+    for (int s = 0; s < _TwoStageModel->Ns; ++s) {
+      RP_DAG_functions[s].emplace_back(
+        _TwoStageModel->f2_func(RP_DAG_x[s], RP_DAG_y[s], _TwoStageModel->data[s])
+      );
+      g2.emplace_back(_TwoStageModel->g2_func(RP_DAG_x[s], RP_DAG_y[s], _TwoStageModel->data[s]));
+    }
+
+    RP_constraint_properties = std::vector<std::shared_ptr<std::vector<Constraint>>>(_TwoStageModel->Ns);
+    _subsolvers.resize(_TwoStageModel->Ns);
+    #ifdef _OPENMP
+      #pragma omp parallel for
+    #endif
+    for (int s = 0; s < _TwoStageModel->Ns; ++s) {
+
+      RP_constraint_properties[s] = std::make_shared<std::vector<Constraint>>();
+      _prepare_constraints(std::to_string(s), RP_constraint_properties[s], RP_DAG_functions[s], g2[s]);
+      _subsolvers[s] = ubp::make_ubp_solver(
+        RP_DAGs[s],                   // different since DAGs are different, but are only dummy objects
+        RP_DAG_variables[s],          // [x_1, ..., x_Nx, y_s_1, ..., y_s_Ny]
+        RP_DAG_functions[s],          // separated in advance
+        RP_opt_variables[s],
+        /*_TwoStageModel->Nineq1 + */_TwoStageModel->Nineq2, 
+        /*_TwoStageModel->Neq1 + */_TwoStageModel->Neq2,
+        /*_TwoStageModel->Nsquash1 + */_TwoStageModel->Nsquash2,
+        settingsIn,
+        loggerIn,                       // might get noisy with verbose output when solving in parallel...
+        RP_constraint_properties[s],  // here we need to think about how to handle x as constant
+        useIn,
+        false
+      );
+    }
+
+    // Create subgraph of g1 for efficient evauation
+    std::vector<std::vector<NamedVar>> g1 = _TwoStageModel->g1_func(this->_DAGobj->vars);
+    _firstStageIneqEq.reserve(_TwoStageModel->Nineq1 + _TwoStageModel->Nsquash1 + _TwoStageModel->Neq1);
+    for (auto j : {0, 1, 2})  // positions of ineq, squash, and eq constraints in g1
+      for (auto i = 0; i < g1[j].size(); i++)
+        _firstStageIneqEq.push_back(g1[j][i].first);
+    _g1_subgraph = this->_DAGobj->DAG.subgraph(_firstStageIneqEq.size(), _firstStageIneqEq.data());
+  }
+
+private:
+
+  /**
+   * @brief Function for preparing the constraints for the subproblem solver
+   */
+  inline void _prepare_constraints(
+    const std::string &s_string,
+    std::shared_ptr<std::vector<Constraint>> &RP_constraint_properties_s,
+    std::vector<mc::FFVar> &RP_DAG_functions_s,
+    std::vector<std::vector<NamedVar>> &g2_s
+  ) {
+    unsigned indexOriginal = 0, indexNonconstant = 0;
+
+    RP_constraint_properties_s->emplace_back(
+      CONSTRAINT_TYPE::OBJ,
+      indexOriginal++,
+      0,
+      indexNonconstant++,
+      0,
+      "f2_" + s_string);
+
+    maingo::CONSTRAINT_TYPE ct[3] = {
+      maingo::CONSTRAINT_TYPE::INEQ,
+      maingo::CONSTRAINT_TYPE::INEQ_SQUASH,
+      maingo::CONSTRAINT_TYPE::EQ
+    };
+
+    unsigned indexType[3]            = {0, 0, 0};
+    unsigned indexTypeNonconstant[3] = {0, 0, 0};
+    std::string type[3] {"ineq", "ineg_squash", "eq"};
+    for (unsigned i = 0; i < 3; ++i) { // g2RPs_ineq, g2RPs_squash, g2RPs_eq
+      for (auto & func : g2_s[i]) {
+        RP_DAG_functions_s.push_back(func.first);
+        RP_constraint_properties_s->emplace_back(
+          ct[i],
+          indexOriginal++,
+          indexType[i],
+          indexNonconstant++,
+          indexTypeNonconstant[i]++,
+          type[i] + '_' + s_string + '_' + std::to_string(indexType[i]++));
+      }
+    }
+
+    // Now that we assembled all functions for scenario s, we analyze their properties
+
+    // Get dependency sets of all functions
+    unsigned size = RP_DAG_functions_s.size();
+    std::vector<std::map<int, int>> func_dep(size);
+    for (unsigned int i = 0; i < size; i++) {
+        func_dep[i] = RP_DAG_functions_s[i].dep().dep();
+    }
+
+    // Loop over all functions
+    unsigned indexLinear = 0, indexNonlinear = 0;
+    for (unsigned int i = 0; i < size; i++) {
+      mc::FFDep::TYPE functionStructure = mc::FFDep::L;
+      std::vector<unsigned> participatingVars;
+      for (unsigned int j = 0; j < UpperBoundingSolver::_nvar; j++) {    // TODO: Only X and ys
+        auto ito = func_dep[i].find(j);
+        // Count all participating variables
+        if (ito != func_dep[i].end()) {
+          participatingVars.push_back(j);
+          mc::FFDep::TYPE variableDep = (mc::FFDep::TYPE)(ito->second);
+          // Update function type
+          if (functionStructure < variableDep) {
+            functionStructure = variableDep;
+          }
+        }
+      }
+
+      Constraint & func        = (*RP_constraint_properties_s)[i];
+      func.indexNonconstantUBP = i;
+
+      // determine dependency
+      func.nparticipatingVariables = participatingVars.size();
+      func.participatingVariables  = participatingVars;
+      switch (functionStructure) {
+        case mc::FFDep::L:
+          func.dependency     = LINEAR;
+          func.indexLinear    = indexLinear++;
+          break;
+        case mc::FFDep::B:
+          func.dependency        = BILINEAR;
+          func.indexNonlinear    = indexNonlinear++;
+          break;
+        case mc::FFDep::Q:
+          func.dependency        = QUADRATIC;
+          func.indexNonlinear    = indexNonlinear++;
+          break;
+        case mc::FFDep::P:
+          func.dependency        = POLYNOMIAL;
+          func.indexNonlinear    = indexNonlinear++;
+          break;
+        case mc::FFDep::R:
+          func.dependency        = RATIONAL;
+          func.indexNonlinear    = indexNonlinear++;
+          break;
+        case mc::FFDep::N:
+        case mc::FFDep::D:
+        default:
+          func.dependency        = NONLINEAR;
+          func.indexNonlinear    = indexNonlinear++;
+          break;
+      }
+      func.convexity       = CONV_NONE;
+      func.monotonicity    = MON_NONE;
+    }
+  }
+
+  /**
+  * @brief Function for actually solving the NLP sub-problem.
+  *
+  * @param[in] lowerVarBounds is the vector containing the lower bounds on the variables within the current node
+  * @param[in] upperVarBounds is the vector containing the upper bounds on the variables within the current node
+  * @param[out] objectiveValue is the objective value obtained for the solution point of the upper bounding problem (need not be a local optimum!)
+  * @param[in,out] solutionPoint is the point at which objectiveValue was achieved (can in principle be any point within the current node!); it is also used for communicating the initial point (usually the LBP solution point)
+  * @return Return code, either SUBSOLVER_FEASIBLE or SUBSOLVER_INFEASIBLE, indicating whether the returned solutionPoint (!!) is feasible or not
+  */
+  SUBSOLVER_RETCODE _solve_nlp(
+    const std::vector<double> &lowerVarBounds,
+    const std::vector<double> &upperVarBounds,
+    double &objectiveValue,
+    std::vector<double> &solutionPoint
+  ) {
+
+    /** NOTE: Shortcut indicating that no NLP is solved */
+    if (subsolver_class::_maingoSettings->UBP_solverBab == UBP_SOLVER_EVAL)
+      return SUBSOLVER_INFEASIBLE;
+
+    // fix first stage variables
+    std::vector<double> firstStageValues;
+    for (unsigned i = 0; i <  _TwoStageModel->Nx; i++) {
+      double x_i = solutionPoint[i];
+      firstStageValues.push_back(x_i);
+    }
+
+    // Test feasibility of first stage constraints for given point
+    std::vector<double> g1_result(_firstStageIneqEq.size());
+    this->_DAGobj->DAG.eval(_g1_subgraph, _firstStageIneqEq.size(), _firstStageIneqEq.data(), g1_result.data(),
+                            _TwoStageModel->Nx, this->_DAGobj->vars.data(), firstStageValues.data());
+
+    // inequalities
+    for (unsigned int i = 0; i < _TwoStageModel->Nineq1; i++) {
+      if (g1_result[i] > UpperBoundingSolver::_maingoSettings->deltaIneq) {
+        objectiveValue = INFINITY;
+        return SUBSOLVER_INFEASIBLE;
+      }
+    }
+    // squash Inequalities
+    for (unsigned int i = _TwoStageModel->Nineq1; i < _TwoStageModel->Nineq1 + _TwoStageModel->Nsquash1; i++) {
+      if (g1_result[i] > 0) {
+        objectiveValue = INFINITY;
+        return SUBSOLVER_INFEASIBLE;
+      }
+    }
+    // equalities
+    for (unsigned int i = _TwoStageModel->Nineq1 + _TwoStageModel->Nsquash1; i < _TwoStageModel->Nineq1 + _TwoStageModel->Nsquash1 + _TwoStageModel->Neq1; i++) {
+      if (std::fabs(g1_result[i]) > UpperBoundingSolver::_maingoSettings->deltaEq) {
+        objectiveValue = INFINITY;
+        return SUBSOLVER_INFEASIBLE;
+      }
+    }
+
+    bool infeasible = false;
+    #ifdef _OPENMP
+      #pragma omp parallel for
+    #endif
+    for (auto s = 0; s < _TwoStageModel->Ns; s++) {
+      // Fix values of first stage variables
+      std::vector<double> lowerVarBounds_s, upperVarBounds_s;
+      for (auto &vec : {&lowerVarBounds_s, &upperVarBounds_s}) {
+        vec->reserve(_TwoStageModel->Nx + _TwoStageModel->Ny);
+        vec->insert(vec->end(), firstStageValues.begin(), firstStageValues.end());
+      }
+      // Add remaining bounds
+      auto ys_begin = _TwoStageModel->Nx + s * _TwoStageModel->Ny;
+      auto ys_end = ys_begin + _TwoStageModel->Ny;
+      lowerVarBounds_s.insert(lowerVarBounds_s.end(),
+                              lowerVarBounds.begin() + ys_begin,
+                              lowerVarBounds.begin() + ys_end);
+      upperVarBounds_s.insert(upperVarBounds_s.end(),
+                              upperVarBounds.begin() + ys_begin,
+                              upperVarBounds.begin() + ys_end);
+
+      double objectiveValue_s = INFINITY;
+      std::vector<double> solutionPoint_s;
+      solutionPoint_s.insert(solutionPoint_s.end(), solutionPoint.begin(), solutionPoint.begin() + _TwoStageModel->Nx);
+      solutionPoint_s.insert(solutionPoint_s.end(), solutionPoint.begin() + ys_begin, solutionPoint.begin() + ys_end);
+
+      // Solve subproblems
+      auto SUBSOLVER_RETCODE_s = _subsolvers[s]->_solve_nlp(
+        lowerVarBounds_s,
+        upperVarBounds_s,
+        objectiveValue_s,
+        solutionPoint_s
+      );
+      
+      if (SUBSOLVER_RETCODE_s == SUBSOLVER_INFEASIBLE){
+        infeasible = true;
+      }
+      // Update solutionPoint 
+      std::copy(solutionPoint_s.begin() + _TwoStageModel->Nx, solutionPoint_s.end(), solutionPoint.begin() + ys_begin);
+    }
+
+    if (infeasible) {
+      return SUBSOLVER_INFEASIBLE;
+    }
+
+    // Check if point returned by local solver is actually feasible. If it is, the objective function value will be stored as well.
+    return subsolver_class::check_feasibility(solutionPoint, objectiveValue);
+  }
+};
+
+
+/**
+* @brief Factory function for initializing different TwoStage upper bounding solver wrappers
+*
+* @param[in] twoStageModel is the pointer to the TwoStageModel opbject
+* @param[in] DAG is the directed acyclic graph constructed in MAiNGO.cpp needed to construct an own DAG for the lower bounding solver
+* @param[in] DAGvars are the variables corresponding to the DAG
+* @param[in] DAGfunctions are the functions corresponding to the DAG
+* @param[in] variables is a vector containing the initial optimization variables defined in problem.h
+* @param[in] nineqIn is the number of inequality constraints
+* @param[in] neqIn is the number of equality
+* @param[in] nineqSquashIn is the number of squash inequality constraints which are to be used only if the squash node has been used
+* @param[in] settingsIn is a pointer to the MAiNGO settings
+* @param[in] loggerIn is a pointer to the MAiNGO logger object
+* @param[in] constraintPropertiesIn is a pointer to the constraint properties determined by MAiNGO
+* @param[in] useIn is an ennum specifying the use of the UpperBoundingSolver (preprocessing of B&B)
+*/
+std::shared_ptr<UpperBoundingSolver> make_ubpTwoStage_solver(
+  const std::shared_ptr<maingo::TwoStageModel> twoStageModel,
+  mc::FFGraph &DAG,
+  const std::vector<mc::FFVar> &DAGvars,
+  const std::vector<mc::FFVar> &DAGfunctions,
+  const std::vector<babBase::OptimizationVariable> &variables,
+  const unsigned nineqIn,
+  const unsigned neqIn,
+  const unsigned nineqSquashIn,
+  std::shared_ptr<Settings> settingsIn,
+  std::shared_ptr<Logger> loggerIn,
+  std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn,
+  UpperBoundingSolver::UBS_USE useIn){
+  UBP_SOLVER desiredSolver;
+  std::string useDescription;
+  switch (useIn) {
+    case ubp::UpperBoundingSolver::USE_PRE:
+      useDescription = "Two-stage multistart";
+      desiredSolver  = settingsIn->UBP_solverPreprocessing;
+      break;
+    case ubp::UpperBoundingSolver::USE_BAB:
+      useDescription = "Two-stage upper bounding";
+      desiredSolver  = settingsIn->UBP_solverBab;
+      break;
+    default:
+      throw MAiNGOException("  Error in UbsTwoStage Factory: unknown intended use for upper bounding solver.");  // GCOVR_EXCL_LINE
+  }
+
+  switch (desiredSolver) {
+    case UBP_SOLVER_EVAL: {
+      loggerIn->print_message("      " + useDescription + ": Function evaluation\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UpperBoundingSolver>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    case UBP_SOLVER_COBYLA: {
+      loggerIn->print_message("      " + useDescription + ": COBYLA\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpNLopt>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    case UBP_SOLVER_BOBYQA: {
+      loggerIn->print_message("      " + useDescription + ": BOBYQA\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpNLopt>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    case UBP_SOLVER_LBFGS: {
+      loggerIn->print_message("      " + useDescription + ": LBFGS\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpNLopt>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    case UBP_SOLVER_SLSQP: {
+      loggerIn->print_message("      " + useDescription + ": SLSQP\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpNLopt>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    case UBP_SOLVER_IPOPT: {
+      loggerIn->print_message("      " + useDescription + ": IPOPT\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpIpopt>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    case UBP_SOLVER_KNITRO: {
+#ifdef HAVE_KNITRO
+      loggerIn->print_message("      " + useDescription + ": KNITRO\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpKnitro>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+#else
+      throw MAiNGOException("  Error in UbsTwoStage Factory: Cannot use upper bounding strategy UBP_SOLVER_KNITRO: Your MAiNGO build does not contain KNITRO.");  // GCOVR_EXCL_LINE
+#endif
+    }
+    case UBP_SOLVER_CPLEX: {
+#ifdef HAVE_CPLEX
+      loggerIn->print_message("      " + useDescription + ": CPLEX\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpCplex>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+#else
+      throw MAiNGOException("  Error in UbsTwoStage Factory: Cannot use upper bounding strategy UBP_SOLVER_CPLEX: Your MAiNGO build does not contain CPLEX.");
+#endif
+    }
+    case UBP_SOLVER_CLP: {
+      loggerIn->print_message("      " + useDescription + ": CLP\n", VERB_NORMAL, BAB_VERBOSITY);
+      return std::make_shared<UbsTwoStage<UbpClp>>(twoStageModel, DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
+    }
+    default:
+    {  // GCOVR_EXCL_START
+      std::ostringstream errmsg;
+      errmsg << "  Error in UbsTwoStage Factory: Unknown upper bounding strategy: " << desiredSolver << std::endl;
+      throw MAiNGOException("  Error in UbsTwoStage Factory: Unknown upper bounding strategy: " + std::to_string(desiredSolver));
+    }
+    // GCOVR_EXCL_STOP
+  }
+}
+
+
+}    // end namespace ubp
+
+
+}    // end namespace maingo
\ No newline at end of file
diff --git a/inc/variableLister.h b/inc/variableLister.h
index e63b62d..2b031f7 100644
--- a/inc/variableLister.h
+++ b/inc/variableLister.h
@@ -129,7 +129,7 @@ class VariableLister {
     }
 
     template <unsigned IDim>
-    void operator()(function_symbol<index<IDim>>* sym)
+    void operator()(function_symbol<ale::index<IDim>>* sym)
     {
     }
 
diff --git a/maingopy/__init__maingopy_and_melonpy.py.in b/maingopy/__init__maingopy_and_melonpy.py.in
index 2ef6261..8e83b61 100644
--- a/maingopy/__init__maingopy_and_melonpy.py.in
+++ b/maingopy/__init__maingopy_and_melonpy.py.in
@@ -1,9 +1,128 @@
-'''A Python package for using MAiNGO - McCormick-based Algorithm for mixed-integer Nonlinear Global Optimization
-
-MAiNGO is a deterministic global optimization solver for mixed-integer nonlinear programming problems.
-For information about the capabilities and use of MAiNGO, please refer to the documentation at https://avt-svt.pages.rwth-aachen.de/public/maingo/.
-This version of maingopy also contains the extension module melonpy, which enables the use of MeLOn (https://git.rwth-aachen.de/avt.svt/public/melon),
-a toolbox containing machine learning models for use in optimization problems to be solved by MAiNGO.
-'''
-from ._maingopy import *
-from . import melonpy
\ No newline at end of file
+"""A Python package for using MAiNGO
+
+MAiNGO (McCormick-based Algorithm for mixed-integer Nonlinear Global
+Optimization) is a deterministic global optimization solver for mixed-integer
+nonlinear programming problems.
+For information about the capabilities and use of MAiNGO, please refer to the
+documentation at https://avt-svt.pages.rwth-aachen.de/public/maingo/.
+This version of maingopy also contains the extension module melonpy, which
+enables the use of MeLOn (https://git.rwth-aachen.de/avt.svt/public/melon),
+a toolbox containing machine learning models for use in optimization problems
+to be solved by MAiNGO.
+"""
+import os
+maingopy_dir = os.path.dirname(__file__)
+
+try:
+    from ._maingopy import *
+except ImportError:
+    # NOTE: since Python 3.8 %PATH% is no longer searched for DLLs on Windows
+    #       for security reasons. This can result in failure to find DLLs
+    #       required by certain modules like maingopy. Let's see if this is
+    #       simply because we can't find CPLEX...
+    print("\nI found maingopy but encountered an error when trying to import "
+          "it. Most likely this is because some shared libraries of "
+          "dependencies (e.g. cplex, knitro, ...) could not be found.")
+    import sys
+    if (sys.platform == 'win32'
+            and sys.version_info.major >= 3
+            and sys.version_info.minor >= 8):
+        import os
+        from packaging import version
+        print("\nI'll now try Looking for CPLEX versions based on environment "
+              "variables starting with 'CPLEX_STUDIO_DIR'...\n")
+        CPLEX_versions = {
+            version.parse(f'{k[16:18]}.{k[18:]}'):
+            v[1:-1] if v.startswith('"') else v
+            for k, v in os.environ.items()
+            if k.startswith('CPLEX_STUDIO_DIR')
+        }
+        if len(CPLEX_versions) == 0:
+            raise ImportError("CPLEX wasnt found, but maingopy might depend "
+                              "on it. Try determining the the dependencies of "
+                              f"_maingopy.<version>.pyd in {maingopy_dir} and "
+                              "add them to Python's dll search paths. The "
+                              "simplest way to achieve this is to copy them "
+                              "to this folder.")
+        if len(CPLEX_versions) == 1:
+            version, path = next(iter(CPLEX_versions.items()))
+        else:
+            print('Found multiple CPLEX versions:')
+            for version, path in CPLEX_versions.items():
+                print('  Version', version, 'at', path)
+            version = max(CPLEX_versions)
+            path = CPLEX_versions[version]
+
+        print(f'using CPLEX {version} from {path}')
+
+        dll_path = os.path.join(path, "cplex", "bin", "x64_win64")
+        os.add_dll_directory(dll_path)
+
+        import re
+        for file in os.listdir(dll_path):
+            if re.search(r"cplsex\d+.dll", file):
+                break
+        else:
+            # raise FileNotFoundError(f"No cplex<version>.dll in {dll_path}")
+            file = "cplex<version>.dll"
+
+        try:
+            from ._maingopy import *
+        except ImportError:
+            print("... That didn't help!")
+            raise
+        print("... That helped! If you don't want to redo this all the time "
+              f"consider copying '{file}' from {dll_path} to {maingopy_dir}!")
+    else:
+        raise
+try:
+    from . import melonpy
+except ImportError:
+    raise ImportError("I found melonpy but encountered an error when trying "
+                      "to import it. Most likely this is because some shared "
+                      "libraries of dependencies could not be found. Try "
+                      "determining the the dependencies of "
+                      f"_melonpy.<version>.pyd in {maingopy_dir} and add them "
+                      "to Python's dll search paths. The simplest way to "
+                      "achieve this is to copy them to this folder.")
+
+if HAVE_MAiNGO_MPI():
+    import sys
+    try:
+        from mpi4py import MPI
+    except ImportError:
+        print("maingopy was built with MPI support, but mpi4py was not found.")
+        sys.exit(1)
+
+    __comm = MPI.COMM_WORLD
+    __rank = __comm.Get_rank()
+
+    def _get_old_stdout():
+        return _get_old_stdout.stdout
+    
+    _get_old_stdout.stdout = None
+    
+    __cppMuteWorker = muteWorker
+
+    def muteWorker():
+        """Mute the worker process.
+
+        This function extends the existing muteWorker function to work for any output generated on the Python side.
+        """
+        if __rank != 0 and _get_old_stdout() is None:
+            _get_old_stdout.stdout = sys.stdout
+            sys.stdout = open(os.devnull, 'w')
+        return __cppMuteWorker()
+
+    muteWorker = muteWorker
+
+    __cppUnmuteWorker = unmuteWorker
+    def unmuteWorker(buffers):
+        """Unmute the worker process.
+
+        This function extends the existing unmuteWorker function to work for any output generated on the Python side.
+        """
+        if __rank != 0 and _get_old_stdout() is not None:
+            sys.stdout = _get_old_stdout()
+            _get_old_stdout.stdout = None
+        __cppUnmuteWorker(buffers)
\ No newline at end of file
diff --git a/maingopy/__init__only_maingopy.py.in b/maingopy/__init__only_maingopy.py.in
index bbef2d1..0ecc5f4 100644
--- a/maingopy/__init__only_maingopy.py.in
+++ b/maingopy/__init__only_maingopy.py.in
@@ -1,6 +1,114 @@
-'''A Python package for using MAiNGO - McCormick-based Algorithm for mixed-integer Nonlinear Global Optimization
+"""A Python package for using MAiNGO
 
-MAiNGO is a deterministic global optimization solver for mixed-integer nonlinear programming problems.
-For information about the capabilities and use of MAiNGO, please refer to the documentation at https://avt-svt.pages.rwth-aachen.de/public/maingo/.;
-'''
-from ._maingopy import *
\ No newline at end of file
+MAiNGO (McCormick-based Algorithm for mixed-integer Nonlinear Global
+Optimization) is a deterministic global optimization solver for mixed-integer
+nonlinear programming problems.
+For information about the capabilities and use of MAiNGO, please refer to the
+documentation at https://avt-svt.pages.rwth-aachen.de/public/maingo/.
+"""
+import os
+maingopy_dir = os.path.dirname(__file__)
+
+try:
+    from ._maingopy import *
+except ImportError:
+    # NOTE: since Python 3.8 %PATH% is no longer searched for DLLs on Windows
+    #       for security reasons. This can result in failure to find DLLs
+    #       required by certain modules like maingopy. Let's see if this is
+    #       simply because we can't find CPLEX...
+    print("\nI found maingopy but encountered an error when trying to import "
+          "it. Most likely this is because some shared libraries of "
+          "dependencies (e.g. cplex, knitro, ...) could not be found.")
+    import sys
+    if (sys.platform == 'win32'
+            and sys.version_info.major >= 3
+            and sys.version_info.minor >= 8):
+        import os
+        from packaging import version
+        print("\nI'll now try Looking for CPLEX versions based on environment "
+              "variables starting with 'CPLEX_STUDIO_DIR'...\n")
+        CPLEX_versions = {
+            version.parse(f'{k[16:18]}.{k[18:]}'):
+            v[1:-1] if v.startswith('"') else v
+            for k, v in os.environ.items()
+            if k.startswith('CPLEX_STUDIO_DIR')
+        }
+        if len(CPLEX_versions) == 0:
+            raise ImportError("CPLEX wasnt found, but maingopy might depend "
+                              "on it. Try determining the the dependencies of "
+                              f"_maingopy.<version>.pyd in {maingopy_dir} and "
+                              "add them to Python's dll search paths. The "
+                              "simplest way to achieve this is to copy them "
+                              "to this folder.")
+        if len(CPLEX_versions) == 1:
+            version, path = next(iter(CPLEX_versions.items()))
+        else:
+            print('Found multiple CPLEX versions:')
+            for version, path in CPLEX_versions.items():
+                print('  Version', version, 'at', path)
+            version = max(CPLEX_versions)
+            path = CPLEX_versions[version]
+
+        print(f'using CPLEX {version} from {path}')
+
+        dll_path = os.path.join(path, "cplex", "bin", "x64_win64")
+        os.add_dll_directory(dll_path)
+
+        import re
+        for file in os.listdir(dll_path):
+            if re.search(r"cplsex\d+.dll", file):
+                break
+        else:
+            # raise FileNotFoundError(f"No cplex<version>.dll in {dll_path}")
+            file = "cplex<version>.dll"
+
+        try:
+            from ._maingopy import *
+        except ImportError:
+            print("... That didn't help!")
+            raise
+        print("... That helped! If you don't want to redo this all the time "
+              f"consider copying '{file}' from {dll_path} to {maingopy_dir}!")
+    else:
+        raise
+
+if HAVE_MAiNGO_MPI():
+    import sys
+    try:
+        from mpi4py import MPI
+    except ImportError:
+        print("maingopy was built with MPI support, but mpi4py was not found.")
+        sys.exit(1)
+
+    __comm = MPI.COMM_WORLD
+    __rank = __comm.Get_rank()
+
+    def _get_old_stdout():
+        return _get_old_stdout.stdout
+    
+    _get_old_stdout.stdout = None
+    
+    __cppMuteWorker = muteWorker
+
+    def muteWorker():
+        """Mute the worker process.
+
+        This function extends the existing muteWorker function to work for any output generated on the Python side.
+        """
+        if __rank != 0 and _get_old_stdout() is None:
+            _get_old_stdout.stdout = sys.stdout
+            sys.stdout = open(os.devnull, 'w')
+        return __cppMuteWorker()
+
+    muteWorker = muteWorker
+
+    __cppUnmuteWorker = unmuteWorker
+    def unmuteWorker(buffers):
+        """Unmute the worker process.
+
+        This function extends the existing unmuteWorker function to work for any output generated on the Python side.
+        """
+        if __rank != 0 and _get_old_stdout() is not None:
+            sys.stdout = _get_old_stdout()
+            _get_old_stdout.stdout = None
+        __cppUnmuteWorker(buffers)
\ No newline at end of file
diff --git a/maingopy/_maingopy.cpp b/maingopy/_maingopy.cpp
index f614b0e..b624d84 100644
--- a/maingopy/_maingopy.cpp
+++ b/maingopy/_maingopy.cpp
@@ -14,6 +14,7 @@
 #include "MAiNGOmodelEpsCon.h"
 #include "MAiNGOException.h"
 #include "functionWrapper.h"
+#include "instrumentor.h"
 #include "mpiUtilities.h"
 
 #include "babOptVar.h"
@@ -22,12 +23,15 @@
 
 #include <pybind11/operators.h>
 #include <pybind11/pybind11.h>
+#include <pybind11/numpy.h>
 #include <pybind11/stl.h>
 
 namespace py = pybind11;
 
 // make pre-processor variable HAVE_MAiNGO_MPI available for Python API
-bool haveMPI() {
+bool
+haveMPI()
+{
 
 #ifdef HAVE_MAiNGO_MPI
     return true;
@@ -36,42 +40,42 @@ bool haveMPI() {
 #endif
 }
 
-struct BufferPair
-{
+struct BufferPair {
     std::streambuf* coutBuf;
     std::streambuf* cerrBuf;
 };
 
 
-BufferPair muteWorkerOutput(){
-    
+BufferPair
+muteWorkerOutput()
+{
+    BufferPair originalBuffer = {std::cout.rdbuf(), std::cerr.rdbuf()};
 #ifdef HAVE_MAiNGO_MPI
 
     int _rank;
     MPI_Comm_rank(MPI_COMM_WORLD, &_rank);
-	
+
     // Mute cout for workers to avoid multiple outputs
     std::ostringstream mutestream;
-    	
+
     MAiNGO_IF_BAB_WORKER
         std::cout.rdbuf(mutestream.rdbuf());
         std::cerr.rdbuf(mutestream.rdbuf());
     MAiNGO_END_IF
 #endif
-    BufferPair curBuffer;
-    curBuffer.coutBuf = std::cout.rdbuf();
-    curBuffer.cerrBuf = std::cerr.rdbuf();
-    return curBuffer; 
+    return originalBuffer;
 }
 
 
-void unmuteWorkerOutput(BufferPair originalBuffer) {
+void
+unmuteWorkerOutput(BufferPair originalBuffer)
+{
     std::cout.rdbuf(originalBuffer.coutBuf);
     std::cerr.rdbuf(originalBuffer.cerrBuf);
     return;
 }
 
-// First, create a dummy classes that is needed later to expose the MAiNGOmodel base class
+// First, create a dummy class that is needed later to expose the MAiNGOmodel base class
 // so that it can be used and inherited from in Python
 class PyMAiNGOmodel: public maingo::MAiNGOmodel {
   public:
@@ -109,12 +113,90 @@ class PyMAiNGOmodel: public maingo::MAiNGOmodel {
 };
 
 
+class PyTwoStageModel: public maingo::TwoStageModel {
+  public:
+    using maingo::TwoStageModel::TwoStageModel;  /* Inherit the constructors */
+    using NamedVar = std::pair<Var, std::string>;
+
+    std::vector<maingo::OptimizationVariable> get_variables() override
+    {
+        PYBIND11_OVERLOAD_PURE(
+            std::vector<maingo::OptimizationVariable>, /* Return type */
+            maingo::TwoStageModel,                     /* Parent class */
+            get_variables,                             /* Name of function in C++ (must match Python name) */
+                                                       /* Argument(s) - here none */
+        );
+    }
+
+
+    std::vector<double> get_initial_point() override
+    {
+        PYBIND11_OVERRIDE(
+            std::vector<double>,   /* Return type */
+            maingo::TwoStageModel, /* Parent class */
+            get_initial_point,     /* Name of function in C++ (must match Python name) */
+                                   /* Argument(s) - here none */
+        );
+    }
+
+	mc::FFVar f1_func(const std::vector<mc::FFVar> & x) override {
+		PYBIND11_OVERRIDE(
+			mc::FFVar,             /* Return type */
+			maingo::TwoStageModel, /* Parent class */
+			f1_func,               /* Name of function in C++ (must match Python name) */
+			x                      /* Argument(s) */
+		);
+	}
+
+	mc::FFVar f2_func(const std::vector<mc::FFVar> & x,
+		const std::vector<mc::FFVar> & ys,
+		const std::vector<double> &ps) override {
+		PYBIND11_OVERRIDE(
+			mc::FFVar,             /* Return type */
+			maingo::TwoStageModel, /* Parent class */
+			f2_func,               /* Name of function in C++ (must match Python name) */
+			x, ys, ps              /* Argument(s) */
+		);
+	}
+
+    std::vector<std::vector<NamedVar>> g1_func(const std::vector<mc::FFVar> & x) override {
+        PYBIND11_OVERRIDE(
+            std::vector<std::vector<NamedVar>>, /* Return type */
+            maingo::TwoStageModel,               /* Parent class */
+            g1_func,                             /* Name of function in C++ (must match Python name) */
+            x                                    /* Argument(s) */
+        );
+    }
+
+    std::vector<std::vector<NamedVar>> g2_func(const std::vector<mc::FFVar> & x,
+                                                const std::vector<mc::FFVar> & ys,
+                                                const std::vector<double> &ps) override {
+        PYBIND11_OVERRIDE(
+            std::vector<std::vector<NamedVar>>, /* Return type */
+            maingo::TwoStageModel,               /* Parent class */
+            g2_func,                             /* Name of function in C++ (must match Python name) */
+            x, ys, ps                            /* Argument(s) */
+        );
+    }
+
+	void _update(const std::vector<mc::FFVar> & vars) override {
+		PYBIND11_OVERRIDE(
+			void,				   /* Return type */
+			maingo::TwoStageModel, /* Parent class */
+			_update,               /* Name of function in C++ (must match Python name) */
+			vars                   /* Argument(s) */
+		);
+	}
+};
+
 // Definition of the actual Python module called _maingopy
 PYBIND11_MODULE(_maingopy, m)
 {
+    PROFILE_SESSION("MAiNGOpy")
+
     m.doc() = "An extension module containing MAiNGO and its Python bindings.";
 
-    // Frist create bindings for the actual MAiNGO object as well as the enums for the return code of the solve method
+    // First create bindings for the actual MAiNGO object as well as the enums for the return code of the solve method
     py::class_<maingo::MAiNGO>(m, "MAiNGO")
         .def(py::init<std::shared_ptr<maingo::MAiNGOmodel>>())
         .def("solve", &maingo::MAiNGO::solve)
@@ -128,6 +210,7 @@ PYBIND11_MODULE(_maingopy, m)
         .def("set_solution_and_statistics_csv_file_name", &maingo::MAiNGO::set_solution_and_statistics_csv_file_name)
         .def("set_iterations_csv_file_name", &maingo::MAiNGO::set_iterations_csv_file_name)
         .def("set_json_file_name", &maingo::MAiNGO::set_json_file_name)
+        .def("set_bab_file_name", &maingo::MAiNGO::set_bab_file_name)
         .def("write_model_to_file_in_other_language", &maingo::MAiNGO::write_model_to_file_in_other_language,
              py::arg("writingLanguage"), py::arg("fileName") = "MAiNGO_written_model", py::arg("solverName") = "SCIP",
              py::arg("useMinMax") = true, py::arg("useTrig") = true, py::arg("ignoreBoundingFuncs") = false, py::arg("writeRelaxationOnly") = true)
@@ -200,7 +283,18 @@ PYBIND11_MODULE(_maingopy, m)
     py::enum_<maingo::AUGMENTATION_RULE>(m, "AUGMENTATION_RULE")
         .value("AUG_RULE_CONST", maingo::AUGMENTATION_RULE::AUG_RULE_CONST)
         .value("AUG_RULE_SCALING", maingo::AUGMENTATION_RULE::AUG_RULE_SCALING)
+        .value("AUG_RULE_VALID", maingo::AUGMENTATION_RULE::AUG_RULE_VALID)
+        .value("AUG_RULE_COMBI", maingo::AUGMENTATION_RULE::AUG_RULE_COMBI)
+        .value("AUG_RULE_TOL", maingo::AUGMENTATION_RULE::AUG_RULE_TOL)
         .value("AUG_RULE_SCALCST", maingo::AUGMENTATION_RULE::AUG_RULE_SCALCST)
+        .value("AUG_RULE_VALCST", maingo::AUGMENTATION_RULE::AUG_RULE_VALCST)
+        .value("AUG_RULE_COMBICST", maingo::AUGMENTATION_RULE::AUG_RULE_COMBICST)
+        .value("AUG_RULE_TOLCST", maingo::AUGMENTATION_RULE::AUG_RULE_TOLCST)
+        .export_values();
+    py::enum_<maingo::GROWING_APPROACH>(m, "GROWING_APPROACH")
+        .value("GROW_APPR_DETERMINISTIC", maingo::GROWING_APPROACH::GROW_APPR_DETERMINISTIC)
+        .value("GROW_APPR_SSEHEURISTIC", maingo::GROWING_APPROACH::GROW_APPR_SSEHEURISTIC)
+        .value("GROW_APPR_MSEHEURISTIC", maingo::GROWING_APPROACH::GROW_APPR_MSEHEURISTIC)
         .export_values();
 
     // Expose the MAiNGOmodel class via the dummy class defined above
@@ -210,6 +304,40 @@ PYBIND11_MODULE(_maingopy, m)
         .def("get_initial_point", &maingo::MAiNGOmodel::get_initial_point)
         .def("evaluate", &maingo::MAiNGOmodel::evaluate);
 
+    // Expose the TwoStageModel class
+    py::class_<maingo::TwoStageModel, maingo::MAiNGOmodel, PyTwoStageModel, std::shared_ptr<maingo::TwoStageModel>>(m, "TwoStageModel")
+        .def(py::init<const unsigned int,                       // Nx
+                      const unsigned int,                       // Ny
+                      const std::vector<std::vector<double>> &  // data
+                      >())
+        .def(py::init<const unsigned int,                       // Nx
+                      const unsigned int,                       // Ny
+                      const std::vector<std::vector<double>> &, // data
+                      const std::vector<double> &               // w
+                      >())
+        .def("get_variables", &maingo::TwoStageModel::get_variables)
+        .def("get_initial_point", &maingo::TwoStageModel::get_initial_point)
+        .def_readonly("w", &maingo::TwoStageModel::w)
+        .def_readonly("data", &maingo::TwoStageModel::data)
+        .def_readonly("Nx", &maingo::TwoStageModel::Nx)
+        .def_readonly("Ny", &maingo::TwoStageModel::Ny)
+        .def_readonly("Ns", &maingo::TwoStageModel::Ns)
+        .def_readonly("Nineq1", &maingo::TwoStageModel::Nineq1)
+        .def_readonly("Nsquash1", &maingo::TwoStageModel::Nsquash1)
+        .def_readonly("Neq1", &maingo::TwoStageModel::Neq1)
+        .def_readonly("NineqRelOnly1", &maingo::TwoStageModel::NineqRelOnly1)
+        .def_readonly("NeqRelOnly1", &maingo::TwoStageModel::NeqRelOnly1)
+        .def_readonly("Nineq2", &maingo::TwoStageModel::Nineq2)
+        .def_readonly("Nsquash2", &maingo::TwoStageModel::Nsquash2)
+        .def_readonly("Neq2", &maingo::TwoStageModel::Neq2)
+        .def_readonly("NineqRelOnly2", &maingo::TwoStageModel::NineqRelOnly2)
+        .def_readonly("NeqRelOnly2", &maingo::TwoStageModel::NeqRelOnly2)
+        .def("f1_func", &maingo::TwoStageModel::f1_func)
+        .def("f2_func", &maingo::TwoStageModel::f2_func)
+        .def("g1_func", &maingo::TwoStageModel::g1_func)
+        .def("g2_func", &maingo::TwoStageModel::g2_func)
+        .def("_update", &maingo::TwoStageModel::_update);
+
     // Expose everything needed to create variables
     py::class_<babBase::Bounds>(m, "Bounds")
         .def(py::init<const double, const double>());
@@ -236,10 +364,27 @@ PYBIND11_MODULE(_maingopy, m)
         .def(py::init<const unsigned, const std::string>())
         .def(py::init<const std::string>())
         .def("get_lower_bound", &babBase::OptimizationVariable::get_lower_bound)
-        .def("get_upper_bound", &babBase::OptimizationVariable::get_upper_bound);
+        .def("get_upper_bound", &babBase::OptimizationVariable::get_upper_bound)
+        .def("get_name", &babBase::OptimizationVariable::get_name)
+        .def("get_branching_priority", &babBase::OptimizationVariable::get_branching_priority)
+        ;
 
     // Expose FFVar along with the overloaded operators - so we can write the evaluate function
     py::class_<mc::FFVar>(m, "FFVar")
+        .def("name", &mc::FFVar::name)
+        .def("__repr__", [](const mc::FFVar& v) {
+                    std::stringstream stream;
+                    stream << "<FFVar object";
+                    if (v.dag())
+                        stream << " of DAG " << v.dag();
+                    else
+                        stream << " without DAG ";
+                    stream << " with id " << v.name();
+                    if (v.num().val())
+                        stream << " and value " << v.num();
+                    stream << " at " << &v << ">";
+                    return stream.str(); 
+                })
         .def(py::init<>())
         .def(py::init<double>())
         .def(py::init<int>())
@@ -464,13 +609,16 @@ PYBIND11_MODULE(_maingopy, m)
     m.def("muteWorker", &muteWorkerOutput, "Mute Output for workers to avoid multiple outputs");
     m.def("unmuteWorker", &unmuteWorkerOutput, "Unmute Output for workers");
     //Expose MAiNGOException
-    static py::exception<maingo::MAiNGOException> exc(m, "MAiNGOException");
+    PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> exc_storage;
+    exc_storage.call_once_and_store_result(
+        [&]() { return py::exception<maingo::MAiNGOException>(m, "MAiNGOException"); });
     py::register_exception_translator([](std::exception_ptr p) {
         try {
-            if (p) std::rethrow_exception(p);
+            if (p)
+                std::rethrow_exception(p);
         }
         catch (const maingo::MAiNGOException& e) {
-            exc(e.what());
+            py::set_error(exc_storage.get_stored(), e.what());
         }
-        });
+    });
 }
\ No newline at end of file
diff --git a/maingopy/tests/individualPythonTests/testSolver.py b/maingopy/tests/individualPythonTests/testSolver.py
index befd17b..667e44a 100644
--- a/maingopy/tests/individualPythonTests/testSolver.py
+++ b/maingopy/tests/individualPythonTests/testSolver.py
@@ -284,10 +284,24 @@ class TestMAiNGOgrowingDatasets(unittest.TestCase):
         try:
             myRetCode = AUG_RULE_CONST
             myRetCode = AUG_RULE_SCALING
+            myRetCode = AUG_RULE_VALID
+            myRetCode = AUG_RULE_COMBI
+            myRetCode = AUG_RULE_TOL
             myRetCode = AUG_RULE_SCALCST
+            myRetCode = AUG_RULE_VALCST
+            myRetCode = AUG_RULE_COMBICST
+            myRetCode = AUG_RULE_TOLCST
         except:
             self.fail("Value of enum AUG_RULE not available")
 
+    def test_GROWING_APPROACH_enum(self):
+        try:
+            myRetCode = GROW_APPR_DETERMINISTIC
+            myRetCode = GROW_APPR_SSEHEURISTIC
+            myRetCode = GROW_APPR_MSEHEURISTIC
+        except:
+            self.fail("Value of enum GROWING_APPROACH not available")
+
     def test_initialize_maingo(self):
         try:
             myMAiNGO = MAiNGO(ModelSimpleGrowing())
diff --git a/src/MAiNGO.cpp b/src/MAiNGO.cpp
index 1fa710c..f22b771 100644
--- a/src/MAiNGO.cpp
+++ b/src/MAiNGO.cpp
@@ -16,8 +16,10 @@
 #include "getTime.h"
 #include "intervalLibrary.h"
 #include "lbp.h"
+#include "lbpTwoStage.h"
 #include "mpiUtilities.h"
 #include "ubp.h"
+#include "ubpTwoStage.h"
 
 #include <cassert>
 
@@ -66,7 +68,7 @@ MAiNGO::solve()
     MAiNGO_IF_BAB_MANAGER
         // Start timing and print header
         _preprocessTime        = get_cpu_time();
-        _solutionTimeWallClock = get_wall_time();
+        _preprocessTimeWallClock = get_wall_time();
         _logger->clear();
         _logger->create_log_file();
         _print_MAiNGO_header();
@@ -91,7 +93,7 @@ MAiNGO::solve()
             write_model_to_file_in_other_language(_maingoSettings->modelWritingLanguage);
             _inMAiNGOsolve     = false;
             double tmpTimeCPU  = get_cpu_time() - _preprocessTime;
-            double tmpTimeWall = get_wall_time() - _solutionTimeWallClock;
+            double tmpTimeWall = get_wall_time() - _preprocessTimeWallClock;
             std::string str;
             switch (_maingoSettings->modelWritingLanguage) {
                 case LANG_GAMS:
@@ -107,8 +109,8 @@ MAiNGO::solve()
             outstr << "  Wall-clock time:  " << std::fixed << std::setprecision(3) << tmpTimeWall << " seconds.\n";
             _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
             // Reset times, since we don't want to add the file writing to the final MAiNGO solution time
-            _preprocessTime        = get_cpu_time();
-            _solutionTimeWallClock = get_wall_time();
+            _preprocessTime          = get_cpu_time();
+            _preprocessTimeWallClock = get_wall_time();
         }
     MAiNGO_END_IF
     MAiNGO_MPI_BARRIER
@@ -155,12 +157,19 @@ MAiNGO::solve()
     MAiNGO_IF_BAB_MANAGER
         // Timing for output
         _outputTime = get_cpu_time();
+        _outputTimeWallClock = get_wall_time();
 
         // Print problem statistics, solution, additional output & CPU time.
         _print_statistics();
         _print_solution();
         _print_additional_output();
         _print_time();
+#ifdef HAVE_GROWING_DATASETS
+        if (_maingoSettings->growing_maxTimePostprocessing > 0) {
+            _print_statistics_postprocessing();
+        }
+#endif    // HAVE_GROWING_DATASETS
+
 
         // Write files
         _write_files();
@@ -276,8 +285,8 @@ MAiNGO::solve_epsilon_constraint()
         epsConModel->set_single_objective(true);
         MAiNGO_IF_BAB_MANAGER
             // Reset timing for this run and give intermediate output
-            _preprocessTime        = get_cpu_time();
-            _solutionTimeWallClock = get_wall_time();
+            _preprocessTime          = get_cpu_time();
+            _preprocessTimeWallClock = get_wall_time();
             _print_message(std::string("*** Solving single-objective problem for objective " + std::to_string(iObj) + ". ***"));
         MAiNGO_END_IF
         epsConModel->set_objective_index(iObj);
@@ -308,6 +317,7 @@ MAiNGO::solve_epsilon_constraint()
         MAiNGO_IF_BAB_MANAGER
             // Print problem statistics, solution, additional output & CPU time.
             _outputTime = get_cpu_time();
+            _outputTimeWallClock = get_wall_time();
             _print_statistics();
             _print_solution();
             _print_additional_output();
@@ -349,8 +359,8 @@ MAiNGO::solve_epsilon_constraint()
             // Next, minimize the other objective s.t. iObj stays the same
             MAiNGO_IF_BAB_MANAGER
                 // Reset timing for this run and give intermediate output
-                _preprocessTime        = get_cpu_time();
-                _solutionTimeWallClock = get_wall_time();
+                _preprocessTime          = get_cpu_time();
+                _preprocessTimeWallClock = get_wall_time();
                 _print_message(std::string("*** Solving complementary problem to single-objective problem for objective " + std::to_string(iObj) + ". ***"));
             MAiNGO_END_IF
             // Get correct epsilon
@@ -393,6 +403,7 @@ MAiNGO::solve_epsilon_constraint()
             MAiNGO_IF_BAB_MANAGER
                 // Print problem statistics, solution, additional output & CPU time.
                 _outputTime = get_cpu_time();
+                _outputTimeWallClock = get_wall_time();
                 _print_statistics();
                 _print_solution();
                 _print_additional_output();
@@ -451,8 +462,8 @@ MAiNGO::solve_epsilon_constraint()
             for (size_t iEps = 1; iEps < nPoints - 1; iEps++) {
                 MAiNGO_IF_BAB_MANAGER
                     // Reset timing for this run and give intermediate output
-                    _preprocessTime        = get_cpu_time();
-                    _solutionTimeWallClock = get_wall_time();
+                    _preprocessTime          = get_cpu_time();
+                    _preprocessTimeWallClock = get_wall_time();
                     _print_message(std::string("*** Solving epsilon-constraint problem number " + std::to_string(iEps) + ". ***"));
                 MAiNGO_END_IF
                 for (size_t iObj = 0; iObj < nObj; iObj++) {
@@ -488,6 +499,7 @@ MAiNGO::solve_epsilon_constraint()
                 MAiNGO_IF_BAB_MANAGER
                     // Print problem statistics, solution, additional output & CPU time.
                     _outputTime = get_cpu_time();
+                    _outputTimeWallClock = get_wall_time();
                     _print_statistics();
                     _print_solution();
                     _print_additional_output();
@@ -535,10 +547,12 @@ MAiNGO::solve_epsilon_constraint()
         _write_epsilon_constraint_result(optimalObjectives, solutionPoints);
         // Print time (slight abuse of regular time variables...)
         _logger->print_message("\n  Overall time for epsilon constraint method:", VERB_NORMAL, BAB_VERBOSITY);
-        _preprocessTime        = 0.;
-        _babTime               = cpuTimeEpsCon;
-        _outputTime            = get_cpu_time();
-        _solutionTimeWallClock = wallTimeEpsCon;
+        _preprocessTime          = 0.;
+        _preprocessTimeWallClock = 0.;
+        _babTime                 = cpuTimeEpsCon;
+        _babTimeWallClock        = wallTimeEpsCon;
+        _outputTime              = get_cpu_time();
+        _outputTimeWallClock     = get_wall_time();
         _print_time();
     MAiNGO_END_IF
 
@@ -559,6 +573,9 @@ MAiNGO::_analyze_and_solve_problem()
         // ----------------------------------------------------------------------------------------------------
         // 3: Structure recognition (LP, MIP, QP, NLP, MINLP) and constraint properties (Linear, convex, etc.)
         // ----------------------------------------------------------------------------------------------------
+        // A bit dirty, but a simple way to get a handle on the special features of TwoStageModel
+        // Needs to be here because it is needed in _set_constraint_and_variable_properties to avoid setting branching prios to zero for two-stage (MI)LPs
+        _myTwoStageFFVARmodel = std::dynamic_pointer_cast<maingo::TwoStageModel>(_myFFVARmodel);
 
         try {
             _recognize_structure();
@@ -571,25 +588,38 @@ MAiNGO::_analyze_and_solve_problem()
                 errmsg << e.what() << "\n  Encountered a fatal error during structure recognition.";
                 _write_files_error(errmsg.str());
                 throw MAiNGOException("  Encountered a fatal error during structure recognition.", e);
-                MAiNGO_ELSE
-                    throw;
-                MAiNGO_END_IF
+            MAiNGO_ELSE
+                throw;
+            MAiNGO_END_IF
         }
         catch (...) {
             MAiNGO_IF_BAB_MANAGER
                 _write_files_error("  Encountered an unknown fatal error during structure recognition.");
                 throw MAiNGOException("  Encountered an unknown fatal error during structure recognition.");
-                MAiNGO_ELSE
-                    throw;
-                MAiNGO_END_IF
+            MAiNGO_ELSE
+                throw;
+            MAiNGO_END_IF
         }
         // GCOVR_EXCL_STOP
 
         // ---------------------------------------------------------------------------------
         // 4: Solve the problem
         // ---------------------------------------------------------------------------------
+        if (_myTwoStageFFVARmodel){
+            if (_maingoSettings->TS_useLowerBoundingSubsolvers || _maingoSettings->TS_useUpperBoundingSubsolvers) {
+#ifdef _OPENMP
+                omp_set_dynamic(0);    // Explicitly disable dynamic teams
+                omp_set_num_threads(std::min((unsigned int)omp_get_max_threads(), _myTwoStageFFVARmodel->Ns));
+                MAiNGO_IF_BAB_MANAGER
+                    _logger->print_message("\nUsing OpenMP with up to " + std::to_string(omp_get_max_threads()) + " threads!\n", VERB_NORMAL, BAB_VERBOSITY);
+                MAiNGO_END_IF
+#endif
+                return _solve_MINLP();
+            }
+        }
+
         switch (_problemStructure) {
-#ifdef HAVE_CPLEX    // If we have CPLEX, we can use of it directly for problems of type LP, MIP, QP, or MIQP
+#ifdef HAVE_CPLEX    // If we have CPLEX, we can use it directly for problems of type LP, MIP, QP, or MIQP
             case LP:
                 MAiNGO_IF_BAB_MANAGER
                     _logger->print_message("\n  Recognized the problem to be a linear program.\n", VERB_NORMAL, BAB_VERBOSITY);
@@ -752,14 +782,17 @@ MAiNGO::_solve_MIQP()
 
             _initialize_solve();
             _logger->print_message(aboutSolver, VERB_NORMAL, BAB_VERBOSITY);
-            _preprocessTime = get_cpu_time() - _preprocessTime;
+            _preprocessTime          = get_cpu_time() - _preprocessTime;
+            _preprocessTimeWallClock = get_wall_time() - _preprocessTimeWallClock;
 
             // ---------------------------------------------------------------------------------
             // 2: Solve the problem
             // ---------------------------------------------------------------------------------
             _babTime    = get_cpu_time();
+            _babTimeWallClock = get_wall_time();
             _miqpStatus = _myUBSPre->solve(_rootNode, _solutionValue, _solutionPoint);
             _babTime    = get_cpu_time() - _babTime;
+            _babTimeWallClock = get_wall_time() - _babTimeWallClock;
 
             // ---------------------------------------------------------------------------------
             // 3: Determine return code
@@ -864,20 +897,29 @@ MAiNGO::_solve_MINLP()
         MAiNGO_END_IF
         _initialize_solve();
 
-        if (!_maingoSettings->PRE_pureMultistart) {
+#ifdef HAVE_GROWING_DATASETS
+        // Warn user when skipping bound tightening
+        if ((!_maingoSettings->PRE_pureMultistart) && (_maingoSettings->growing_approach == GROW_APPR_MSEHEURISTIC)) {
+            if ((_maingoSettings->BAB_constraintPropagation) || (_maingoSettings->PRE_obbtMaxRounds > 0)) {
+                _logger->print_message("  Warning: No bound tightening performed in pre-processing since it may be erroneous for MSE heuristic (cf. growing_approach).\n", VERB_NORMAL, BAB_VERBOSITY);
+            }
+        }
+#endif    // HAVE_GROWING_DATASETS
+
+        if ( (!_maingoSettings->PRE_pureMultistart) && (_maingoSettings->growing_approach != GROW_APPR_MSEHEURISTIC) ) {
 
             // 1b: Check set options (large values in LP and additional option checks based on chosen lower bounding strategy)
             _myLBS->preprocessor_check_options(_rootNode);
             MAiNGO_MPI_BARRIER
 
-                // 1c: Constraint propagation before a local search is executed
-                if (_maingoSettings->BAB_constraintPropagation)
+            // 1c: Constraint propagation before a local search is executed
+            if (_maingoSettings->BAB_constraintPropagation)
             {
                 _root_constraint_propagation();
             }
             MAiNGO_MPI_BARRIER
 
-                if (_rootConPropStatus != TIGHTENING_INFEASIBLE)
+            if (_rootConPropStatus != TIGHTENING_INFEASIBLE)
             {    // If we haven't proven infeasibility, continue
 
                 // 1d: Optimization-based bound tightening (OBBT) at the root node considering feasibility only
@@ -886,14 +928,14 @@ MAiNGO::_solve_MINLP()
                 }
                 MAiNGO_MPI_BARRIER
 
-                    if (_rootObbtStatus != TIGHTENING_INFEASIBLE)
+                if (_rootObbtStatus != TIGHTENING_INFEASIBLE)
                 {    // If we haven't proven infeasibility, continue
 
                     // 1e: Try to get a good feasible point using a multi-start heuristic
                     _root_multistart();
                     MAiNGO_MPI_BARRIER
 
-                        if (_rootMultistartStatus == SUBSOLVER_FEASIBLE && !_maingoSettings->terminateOnFeasiblePoint && _solutionValue > _maingoSettings->targetUpperBound)
+                    if (_rootMultistartStatus == SUBSOLVER_FEASIBLE && !_maingoSettings->terminateOnFeasiblePoint && _solutionValue > _maingoSettings->targetUpperBound)
                     {    // If we have found a feasible point, but it isn't good enough yet, continue
 
                         // 1f: Constraint propagation after a local search has been executed
@@ -902,8 +944,8 @@ MAiNGO::_solve_MINLP()
                         }
                         MAiNGO_MPI_BARRIER
 
-                            // 1g: OBBT at the root node considering both feasibility and optimality
-                            if (_maingoSettings->PRE_obbtMaxRounds > 0)
+                        // 1g: OBBT at the root node considering both feasibility and optimality
+                        if (_maingoSettings->PRE_obbtMaxRounds > 0)
                         {
                             _root_obbt_feasibility_optimality();
                         }
@@ -920,9 +962,12 @@ MAiNGO::_solve_MINLP()
 
         // 1h: Timing
         MAiNGO_IF_BAB_MANAGER
-            _preprocessTime = get_cpu_time() - _preprocessTime;
+            _preprocessTime          = get_cpu_time() - _preprocessTime;
+            _preprocessTimeWallClock = get_wall_time() - _preprocessTimeWallClock;
+
             std::ostringstream outstr;
-            outstr << "    CPU time: " << std::setprecision(6) << _preprocessTime << " s." << std::endl;
+            outstr << "    CPU time:        " << std::setprecision(6) << _preprocessTime << " s." << std::endl;
+            outstr << "    Wall clock time: " << std::setprecision(6) << _preprocessTimeWallClock << " s." << std::endl;
             outstr << "  Done." << std::endl;
             _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
         MAiNGO_END_IF
@@ -947,14 +992,32 @@ MAiNGO::_solve_MINLP()
 #ifdef HAVE_GROWING_DATASETS
             _myBaB->pass_datasets_to_bab(_datasets);
 
-            //Change full dataset (root for pre-processing) to smallest reduced dataset (root of BaB) if these are different
-            if (_ndata > 1) {
+            // Check whether there is at least one free optimization variable
+            bool isFree = false;
+            for (auto i = 0; i < _nvar; i++) {
+                if (_originalVariables[i].get_upper_bound() > _originalVariables[i].get_lower_bound()) {
+                    isFree = true;
+                    break;
+                }
+            }
+            // Change full dataset (root for pre-processing) to smallest reduced dataset (root of BaB) if these are different and problem is not fixed
+            if ((_ndata > 1) && isFree) {
                 _rootNode.set_index_dataset(1);
             }
+            else {
+                if (_ndata > 1) {
+                    _logger->print_message("  Warning: Growing datasets will not be used since all optimization variables are fixed anyway.\n", VERB_NORMAL, BAB_VERBOSITY);
+                }
+                else {
+                    _logger->print_message("  Warning: Growing datasets cannot be used since dataset contains only 1 data point.\n", VERB_NORMAL, BAB_VERBOSITY);
+                }
+            }
 #endif
             _logger->create_iterations_csv_file(_maingoSettings->writeCsv);
-            _babStatus = _myBaB->solve(_rootNode, _solutionValue, _solutionPoint, _preprocessTime, _babTime);
-            _babTime -= _preprocessTime;    // Get the B&B time only
+            _babStatus = _myBaB->solve(_rootNode, _solutionValue, _solutionPoint, _preprocessTime, _preprocessTimeWallClock, _babTime, _babTimeWallClock);
+            // Get the B&B time only
+            _babTime          -= _preprocessTime;
+            _babTimeWallClock -= _preprocessTimeWallClock;
         }
         // ------------------------- End 2: Branch & Bound ---------------------------------
 
@@ -994,6 +1057,24 @@ MAiNGO::_solve_MINLP()
 
         // ----------------------------- End 3: Output -------------------------------------
 
+
+#ifdef HAVE_GROWING_DATASETS
+        // ---------------------------------------------------------------------------------
+        // 4: Post-processing for heuristic mode with growing datasets
+        // ---------------------------------------------------------------------------------
+        if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) && (_maingoSettings->growing_maxTimePostprocessing > 0)) {
+            if (_solutionValue < _maingoSettings->infinity) {
+                _myBaB->postprocess(_solutionValue);
+            }
+            else {
+                MAiNGO_IF_BAB_MANAGER
+                _logger->print_message("  Warning: No feasible solution found during heuristic B&B algorithm with growing datasets. Skipping post-processing...\n", VERB_NORMAL, BAB_VERBOSITY);
+                MAiNGO_END_IF
+            }
+        }
+        // ------------------------ End 4: Post-processing ---------------------------------
+#endif // HAVE_GROWING_DATASETS
+
         return _maingoStatus;
     }
     // GCOVR_EXCL_START
@@ -1030,7 +1111,6 @@ MAiNGO::_solve_MINLP()
     // GCOVR_EXCL_STOP
 }
 
-
 /////////////////////////////////////////////////////////////////////////
 // initializes internal model representation
 void
@@ -1108,13 +1188,11 @@ MAiNGO::_construct_DAG()
     // Build temporary DAG from problem definition first (before getting rid of unused variables)
     mc::FFGraph tmpDAG;
     std::vector<mc::FFVar> tmpDAGVars;
+    tmpDAGVars.reserve(_nvarOriginal);  // reserve to avoid reallocating and creating unnecessary copies!
     std::vector<mc::FFVar> tmpFunctions;
     // Declare the correct amount of DAG variables
     for (unsigned int i = 0; i < _nvarOriginal; i++) {
-        mc::FFVar X;
-        tmpDAGVars.push_back(X);
-        // Set the variables as DAG variables
-        tmpDAGVars[i].set(&tmpDAG);
+        tmpDAGVars.emplace_back(&tmpDAG);
     }
 
     _modelOutput.clear();
@@ -1167,12 +1245,11 @@ MAiNGO::_construct_DAG()
     // Make actual DAG without these unnecessary variables
     _DAG.clear();
     _DAGvars.clear();
+    _DAGvars.reserve(_nvar);
     unsigned iNewVars = 0;
     for (unsigned int iOldVars = 0; iOldVars < _nvarOriginal; iOldVars++) {
         if (!_removedVariables[iOldVars]) {
-            mc::FFVar Y;                      // Create a new DAG variable
-            _DAGvars.push_back(Y);            // Add the new DAG variable to the vars vector
-            _DAGvars[iNewVars].set(&_DAG);    // Add the new DAG variable to the DAG
+            _DAGvars.emplace_back(&_DAG);
             iNewVars++;
         }
     }
@@ -1210,6 +1287,7 @@ MAiNGO::_construct_DAG()
 
     _DAGfunctions.clear();
     _DAGoutputFunctions.clear();
+
     // Just to make sure, check one last time
     _check_for_hidden_zero_constraints(_DAGvars, _DAGfunctions, _DAGoutputFunctions);
 
@@ -1223,15 +1301,14 @@ MAiNGO::_construct_DAG()
 
     _DAGlbd.clear();
     _DAGvarsLbd.clear();
+    _DAGvarsLbd.reserve(_DAGvars.size());
     _DAGfunctionsLbd.clear();
     _DAGoutputFunctionsLbd.clear();
     _nvarLbd              = _nvar;
     _nauxiliaryRelOnlyEqs = 0;
     if (_maingoSettings->LBP_addAuxiliaryVars) {
         for (size_t i = 0; i < _DAGvars.size(); i++) {
-            mc::FFVar Y;                     // Create a new DAG variable
-            _DAGvarsLbd.push_back(Y);        // Add the new DAG variable to the vars vector
-            _DAGvarsLbd[i].set(&_DAGlbd);    // Add the new DAG variable to the DAG
+            _DAGvarsLbd.emplace_back(&_DAGlbd);
         }
 
         _resultVars.clear();
@@ -1322,33 +1399,68 @@ MAiNGO::_initialize_solve()
 {
 
     // Initialize subsolvers (upper bounding is always needed, lower bounding and B&B are not)
+
     _myUBSPre = ubp::make_ubp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraintsUBP, ubp::UpperBoundingSolver::USE_PRE);
 #ifdef HAVE_GROWING_DATASETS
     //objective per data saved after obj and non-constant constraints
     _myUBSPre->pass_data_position_to_solver(_datasets, 1 + _nineq + _neq + _nineqSquash);
+    if (_maingoSettings->growing_approach == GROW_APPR_MSEHEURISTIC) {
+        _myUBSPre->pass_use_mse_to_solver(true);
+    }
+    else {
+        _myUBSPre->pass_use_mse_to_solver(false);
+    }
 #endif    // HAVE_GROWING_DATASETS
 
     _myUBSBab = nullptr;
     _myLBS    = nullptr;
     _myBaB    = nullptr;
-    if (_problemStructure >= NLP) {
+    if ((_problemStructure >= NLP) || (_myTwoStageFFVARmodel && _maingoSettings->TS_useLowerBoundingSubsolvers)) {
         if (!_maingoSettings->PRE_pureMultistart) {    // For a pure multistart, lower bounding solver and the B&B tree are not needed
-            _myUBSBab = ubp::make_ubp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqSquash, _maingoSettings, _logger, _nonconstantConstraintsUBP, ubp::UpperBoundingSolver::USE_BAB);
+            if (_myTwoStageFFVARmodel && _maingoSettings->TS_useUpperBoundingSubsolvers)
+                _myUBSBab = ubp::make_ubpTwoStage_solver(_myTwoStageFFVARmodel, _DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqSquash,
+                                                         _maingoSettings, _logger, _nonconstantConstraintsUBP, ubp::UpperBoundingSolver::USE_BAB);
+            else
+                _myUBSBab = ubp::make_ubp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _nineq, _neq, _nineqSquash,
+                                                 _maingoSettings, _logger, _nonconstantConstraintsUBP, ubp::UpperBoundingSolver::USE_BAB);
             if (_maingoSettings->LBP_addAuxiliaryVars) {
-                _myLBS = lbp::make_lbp_solver(_DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _variableIsLinear, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs,
-                                              _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints);
+                if (_myTwoStageFFVARmodel && _maingoSettings->TS_useLowerBoundingSubsolvers)
+                    _myLBS = lbp::make_two_stage_lbp_solver(_myTwoStageFFVARmodel, _DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _variableIsLinear, _nineq, _neq,
+                                                            _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs, _nineqSquash,
+                                                            _maingoSettings, _logger, _nonconstantConstraints);
+                else
+                    _myLBS = lbp::make_lbp_solver(_DAGlbd, _DAGvarsLbd, _DAGfunctionsLbd, _variablesLbd, _variableIsLinear, _nineq, _neq,
+                                                  _nineqRelaxationOnly, _neqRelaxationOnly + _nauxiliaryRelOnlyEqs, _nineqSquash,
+                                                  _maingoSettings, _logger, _nonconstantConstraints);
             }
             else {
-                _myLBS = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _variableIsLinear, _nineq, _neq, _nineqRelaxationOnly, _neqRelaxationOnly,
-                                              _nineqSquash, _maingoSettings, _logger, _nonconstantConstraints);
+                if (_myTwoStageFFVARmodel && _maingoSettings->TS_useLowerBoundingSubsolvers)
+                    _myLBS = lbp::make_two_stage_lbp_solver(_myTwoStageFFVARmodel, _DAG, _DAGvars, _DAGfunctions, _variables, _variableIsLinear, _nineq, _neq,
+                                                          _nineqRelaxationOnly, _neqRelaxationOnly, _nineqSquash,
+                                                          _maingoSettings, _logger, _nonconstantConstraints);
+                else
+                    _myLBS = lbp::make_lbp_solver(_DAG, _DAGvars, _DAGfunctions, _variables, _variableIsLinear, _nineq, _neq,
+                                                  _nineqRelaxationOnly, _neqRelaxationOnly, _nineqSquash,
+                                                  _maingoSettings, _logger, _nonconstantConstraints);
             }
 #ifdef HAVE_GROWING_DATASETS
             //objective per data saved after obj and non-constant constraints
             _myUBSBab->pass_data_position_to_solver(_datasets, 1 + _nineq + _neq + _nineqSquash);
             _myLBS->pass_data_position_to_solver(_datasets, 1 + _nineq + _neq + _nineqSquash + _nineqRelaxationOnly + _neqRelaxationOnly + _nauxiliaryRelOnlyEqs);
+            if (_maingoSettings->growing_approach == GROW_APPR_MSEHEURISTIC) {
+                _myUBSBab->pass_use_mse_to_solver(true);
+                _myLBS->pass_use_mse_to_solver(true);
+            }
+            else {
+                _myUBSBab->pass_use_mse_to_solver(false);
+                _myLBS->pass_use_mse_to_solver(false);
+            }
+            if (_maingoSettings->growing_useResampling) {
+                _myLBS->pass_resampled_dataset_to_solver(_datasetResampled);
+            }
 #endif    // HAVE_GROWING_DATASETS
 
-            _myBaB = std::make_shared<bab::BranchAndBound>(_variablesLbd, _myLBS, _myUBSBab, _maingoSettings, _logger, /*number of variables w/o auxiliaries*/ _nvar, _inputStream);
+            _myBaB = std::make_shared<bab::BranchAndBound>(_variablesLbd, _myLBS, _myUBSBab, _maingoSettings, _logger, /*number of variables w/o auxiliaries*/ _nvar, _inputStream, _babFileName);
 
 #if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS)
             _myLBSFull = nullptr;
@@ -1362,14 +1474,64 @@ MAiNGO::_initialize_solve()
             }
             _myBaB->pass_LBSFull_to_bab(_myLBSFull);
 #endif
-        }
-    }
+
+            if (_maingoSettings->TS_useLowerBoundingSubsolvers) {
+                // Preprocessor macro to avoid excessive code duplication
+                #define ASSIGN_CALLBACK(subsolver_class) {                                           \
+                  auto ptr = std::dynamic_pointer_cast<lbp::LbpTwoStage<subsolver_class>>(_myLBS);   \
+                  if (ptr != nullptr) {                                                              \
+                    _myBaB->setup_two_stage_branching(                                               \
+                      _myTwoStageFFVARmodel->Nx,                                                     \
+                      _myTwoStageFFVARmodel->Ny,                                                     \
+                      _myTwoStageFFVARmodel->w,                                                      \
+                      ptr->_subproblemBounds,                                                        \
+                      /** Callback for solve_sibling_subproblems */                                  \
+                      [=](                                                                           \
+                        lbp::SiblingResults& siblingResults,                                         \
+                        double ubd,                                                                  \
+                        int obbtType                                                                 \
+                      ) {                                                                            \
+                        return ptr->solve_sibling_subproblems(                                       \
+                          siblingResults,                                                            \
+                          ubd,                                                                       \
+                          obbtType                                                                   \
+                        );                                                                           \
+                      },                                                                             \
+                      _maingoSettings->TS_strongBranchingThreshold,                                  \
+                      _maingoSettings->TS_maxBranchingPower                                          \
+                    );                                                                               \
+                  };                                                                                 \
+                }
+                switch (_maingoSettings->LBP_solver) {
+                    case lbp::LBP_SOLVER::LBP_SOLVER_MAiNGO: {
+                        ASSIGN_CALLBACK(lbp::LowerBoundingSolver)
+                        break;
+                    }
+                    case lbp::LBP_SOLVER::LBP_SOLVER_INTERVAL: {
+                        ASSIGN_CALLBACK(lbp::LbpInterval)
+                        break;
+                    }
+#ifdef HAVE_CPLEX
+                    case lbp::LBP_SOLVER::LBP_SOLVER_CPLEX: {
+                        ASSIGN_CALLBACK(lbp::LbpCplex)
+                        break;
+                    }
+#endif
+                    case lbp::LBP_SOLVER::LBP_SOLVER_CLP: {
+                        ASSIGN_CALLBACK(lbp::LbpClp)
+                        break;
+                    }
+                }
+            } // End if Two Stage
+        } // End if multistart
+    } // End (>=NLP || Two Stage)
 
 
     // Initialize solution variables
     _solutionPoint.clear();
-    _solutionValue = _maingoSettings->infinity;
-    _babTime       = 0.;
+    _solutionValue    = _maingoSettings->infinity;
+    _babTime          = 0.;
+    _babTimeWallClock = 0.;
 
     // Initialize status
     MAiNGO_IF_BAB_MANAGER
@@ -1388,10 +1550,10 @@ MAiNGO::_initialize_solve()
         // Initialize root node
         // with full dataset (MAiNGO with growing datasets)
         if (_maingoSettings->LBP_addAuxiliaryVars) {
-            _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variablesLbd, 0 /*index dataset*/, 0 /*ID*/, 0 /* depth */, false /*augment data*/);
+            _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variablesLbd);
         }
         else {
-            _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variables, 0 /*index dataset*/, 0 /*ID*/, 0 /* depth */, false /*augment data*/);
+            _rootNode = babBase::BabNode(-_maingoSettings->infinity, _variables);
         }
 
         // Clear logging (except for settings)
@@ -2235,32 +2397,35 @@ MAiNGO::_check_for_hidden_zero_constraints(const std::vector<mc::FFVar>& tmpDAGV
     if (updateIndices) {
         unsigned indexObj = 0, indexIneq = 0, indexEq = 0, indexIneqRelOnly = 0, indexEqRelOnly = 0, indexIneqSquash = 0;
         for (size_t i = 0; i < _nonconstantConstraints->size(); i++) {
-            (*_nonconstantConstraints)[i].indexNonconstant                                        = i;
-            (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexNonconstant = i;
-            switch ((*_nonconstantConstraints)[i].type) {
+            Constraint & nonconstCon = (*_nonconstantConstraints)[i];
+            Constraint & origCon     = (*_originalConstraints)[nonconstCon.indexOriginal];
+
+            nonconstCon.indexNonconstant = i;
+            origCon.indexNonconstant     = i;
+            switch (nonconstCon.type) {
                 case OBJ:
-                    (*_nonconstantConstraints)[i].indexTypeNonconstant                                        = indexObj;
-                    (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexTypeNonconstant = indexObj++;
+                    nonconstCon.indexTypeNonconstant = indexObj;
+                    origCon.indexTypeNonconstant     = indexObj++;
                     break;
                 case INEQ:
-                    (*_nonconstantConstraints)[i].indexTypeNonconstant                                        = indexIneq;
-                    (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexTypeNonconstant = indexIneq++;
+                    nonconstCon.indexTypeNonconstant = indexIneq;
+                    origCon.indexTypeNonconstant     = indexIneq++;
                     break;
                 case EQ:
-                    (*_nonconstantConstraints)[i].indexTypeNonconstant                                        = indexEq;
-                    (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexTypeNonconstant = indexEq++;
+                    nonconstCon.indexTypeNonconstant = indexEq;
+                    origCon.indexTypeNonconstant     = indexEq++;
                     break;
                 case INEQ_REL_ONLY:
-                    (*_nonconstantConstraints)[i].indexTypeNonconstant                                        = indexIneqRelOnly;
-                    (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexTypeNonconstant = indexIneqRelOnly++;
+                    nonconstCon.indexTypeNonconstant = indexIneqRelOnly;
+                    origCon.indexTypeNonconstant     = indexIneqRelOnly++;
                     break;
                 case EQ_REL_ONLY:
-                    (*_nonconstantConstraints)[i].indexTypeNonconstant                                        = indexEqRelOnly;
-                    (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexTypeNonconstant = indexEqRelOnly++;
+                    nonconstCon.indexTypeNonconstant = indexEqRelOnly;
+                    origCon.indexTypeNonconstant     = indexEqRelOnly++;
                     break;
                 case INEQ_SQUASH:
-                    (*_nonconstantConstraints)[i].indexTypeNonconstant                                        = indexIneqSquash;
-                    (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].indexTypeNonconstant = indexIneqSquash++;
+                    nonconstCon.indexTypeNonconstant = indexIneqSquash;
+                    origCon.indexTypeNonconstant     = indexIneqSquash++;
                     break;
                 default:    // GCOVR_EXCL_LINE
                     throw MAiNGOException("Error recognizing hidden zeros in problem: Unknown constraint type " + std::to_string((*_nonconstantConstraints)[i].type));    // GCOVR_EXCL_LIN
@@ -2273,32 +2438,35 @@ MAiNGO::_check_for_hidden_zero_constraints(const std::vector<mc::FFVar>& tmpDAGV
         indexEqRelOnly   = 0;
         indexIneqSquash  = 0;
         for (size_t i = 0; i < _constantConstraints->size(); i++) {
-            (*_constantConstraints)[i].indexConstant                                        = i;
-            (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexConstant = i;
-            switch ((*_constantConstraints)[i].type) {
+            Constraint & constCon = (*_constantConstraints)[i];
+            Constraint & origCon  = (*_originalConstraints)[constCon.indexOriginal];
+
+            constCon.indexConstant = i;
+            origCon.indexConstant  = i;
+            switch (constCon.type) {
                 case OBJ:
-                    (*_constantConstraints)[i].indexTypeConstant                                        = indexObj;
-                    (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexTypeConstant = indexObj++;
+                    constCon.indexTypeConstant = indexObj;
+                    origCon.indexTypeConstant  = indexObj++;
                     break;
                 case INEQ:
-                    (*_constantConstraints)[i].indexTypeConstant                                        = indexIneq;
-                    (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexTypeConstant = indexIneq++;
+                    constCon.indexTypeConstant = indexIneq;
+                    origCon.indexTypeConstant  = indexIneq++;
                     break;
                 case EQ:
-                    (*_constantConstraints)[i].indexTypeConstant                                        = indexEq;
-                    (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexTypeConstant = indexEq++;
+                    constCon.indexTypeConstant = indexEq;
+                    origCon.indexTypeConstant  = indexEq++;
                     break;
                 case INEQ_REL_ONLY:
-                    (*_constantConstraints)[i].indexTypeConstant                                        = indexIneqRelOnly;
-                    (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexTypeConstant = indexIneqRelOnly++;
+                    constCon.indexTypeConstant = indexIneqRelOnly;
+                    origCon.indexTypeConstant  = indexIneqRelOnly++;
                     break;
                 case EQ_REL_ONLY:
-                    (*_constantConstraints)[i].indexTypeConstant                                        = indexEqRelOnly;
-                    (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexTypeConstant = indexEqRelOnly++;
+                    constCon.indexTypeConstant = indexEqRelOnly;
+                    origCon.indexTypeConstant  = indexEqRelOnly++;
                     break;
                 case INEQ_SQUASH:
-                    (*_constantConstraints)[i].indexTypeConstant                                        = indexIneqSquash;
-                    (*_originalConstraints)[(*_constantConstraints)[i].indexOriginal].indexTypeConstant = indexIneqSquash++;
+                    constCon.indexTypeConstant = indexIneqSquash;
+                    origCon.indexTypeConstant  = indexIneqSquash++;
                     break;
                 default:    // GCOVR_EXCL_LINE
                     throw MAiNGOException("Error recognizing hidden zeros in problem: Unknown constraint type " + std::to_string((*_nonconstantConstraints)[i].type));    // GCOVR_EXCL_LINE
@@ -2385,47 +2553,69 @@ MAiNGO::_initialize_objective_from_objective_per_data()
         for (auto i = 0; i < _modelOutput.objective_per_data.size(); i++) {
             obj += _modelOutput.objective_per_data[i];
         }
-        _modelOutput.objective = obj;
+        if (_maingoSettings->growing_approach == GROW_APPR_MSEHEURISTIC) {// Use mean of summed objective per data as objective
+            _modelOutput.objective = obj / _modelOutput.objective_per_data.size();
+        }
+        else {// Use sum of objective per data as objective
+            _modelOutput.objective = obj;
+        }
     }
 }
 
 
 ////////////////////////////////////////////////////////////////////////
-// initializes full dataset (= largest set, index 0) and initial reduced dataset (= smallest set, index 1)
+// randomly samples reduced datasets with the initial size given by user setting
 void
-MAiNGO::_initialize_dataset()
+MAiNGO::_sample_initial_dataset(const unsigned seed, std::set<unsigned int> &dataset)
 {
-    _datasets = std::make_shared<std::vector<std::set<unsigned int>>>();
-
-    // Full dataset is range 0,...,_ndata-1
-    std::set<unsigned int> tmpFullSet;
-    for (unsigned int i = 0; i < _ndata; i++) {
-        tmpFullSet.insert(tmpFullSet.end(), i);
-    }
-    _datasets->push_back(tmpFullSet);
-
-    // Smallest reduced dataset
-    // Random set with size given by settings
     if (_ndata > 1) {    // Only if smaller than full dataset
-        std::set<unsigned int> tmpReducedSet;
         int noAddedData = std::round((double)_maingoSettings->growing_dataSizeInit * (double)_ndata);
         noAddedData     = std::max(1, noAddedData);
 
-        std::srand(5);    // For reproducible results
+        std::srand(seed);     // For reproducible results
+        std::rand();     // First call seems to give similar (small) numbers for all random seeds, resulting for small _ndata in a much higher probability to pick the first datapoint
         std::pair<std::set<unsigned int>::iterator, bool> ans;
         int count = 0;
 
         while (count < noAddedData) {
             double tmpRand = std::rand() / ((double)RAND_MAX + 1);    // Random value between 0. and 1.
 
-            // Choose from unused data points (set difference)
+            // Choose from full dataset
             size_t idxRand = tmpRand * _ndata;
-            ans            = tmpReducedSet.insert(*(std::next(tmpFullSet.begin(), idxRand)));
+            ans = dataset.insert(*(std::next((* _datasets)[0].begin(), idxRand)));
             if (ans.second == true) {
                 count++;
             }
         }
-        _datasets->push_back(tmpReducedSet);
+    }
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// initializes full dataset (= largest set, index 0) and initial reduced dataset (= smallest set, index 1)
+// if applicable, initializes also resampled initial dataset
+void
+MAiNGO::_initialize_dataset()
+{
+    _datasets = std::make_shared<std::vector<std::set<unsigned int>>>();
+
+    // Full dataset is range 0,...,_ndata-1
+    std::set<unsigned int> tmpFullSet;
+    for (unsigned int i = 0; i < _ndata; i++) {
+        tmpFullSet.insert(tmpFullSet.end(), i);
+    }
+    _datasets->push_back(tmpFullSet);
+
+    // Smallest reduced dataset
+    std::set<unsigned int> tmpReducedSet;
+    _sample_initial_dataset(5,tmpReducedSet);
+    _datasets->push_back(tmpReducedSet);
+
+    // Resampled initial dataset
+    if (_maingoSettings->growing_useResampling) {
+        tmpReducedSet.clear();
+        _sample_initial_dataset(4, tmpReducedSet);
+        _datasetResampled = std::make_shared<std::set<unsigned int>>(tmpReducedSet);
     }
 }
 #endif    //HAVE_GROWING_DATASETS
@@ -2628,9 +2818,8 @@ MAiNGO::_set_constraint_and_variable_properties()
 {
 
     // Get dependency sets of all functions
-    std::vector<std::map<int, int>> func_dep;
     unsigned size = 1 + _nineq + _neq + _nineqRelaxationOnly + _neqRelaxationOnly + _nineqSquash + _nauxiliaryRelOnlyEqs;
-    func_dep.resize(size);
+    std::vector<std::map<int, int>> func_dep(size);
     for (unsigned int i = 0; i < size; i++) {
         if (_maingoSettings->LBP_addAuxiliaryVars) {
             func_dep[i] = _DAGfunctionsLbd[i].dep().dep();
@@ -2663,55 +2852,58 @@ MAiNGO::_set_constraint_and_variable_properties()
                 }
             }
         }
-        (*_nonconstantConstraints)[i].nparticipatingVariables                                        = participatingVars.size();
-        (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].nparticipatingVariables = participatingVars.size();
-        (*_nonconstantConstraints)[i].participatingVariables                                         = participatingVars;
-        (*_originalConstraints)[(*_nonconstantConstraints)[i].indexOriginal].participatingVariables  = participatingVars;
-        unsigned indexOriginal                                                                       = (*_nonconstantConstraints)[i].indexOriginal;
+
+        Constraint & nonconstCon = (*_nonconstantConstraints)[i];
+        Constraint & origCon     = (*_originalConstraints)[nonconstCon.indexOriginal];
+
+        nonconstCon.nparticipatingVariables = participatingVars.size();
+        origCon.nparticipatingVariables     = participatingVars.size();
+        nonconstCon.participatingVariables  = participatingVars;
+        origCon.participatingVariables      = participatingVars;
         switch (functionStructure) {
             case mc::FFDep::L:
-                (*_nonconstantConstraints)[i].dependency           = LINEAR;
-                (*_originalConstraints)[indexOriginal].dependency  = LINEAR;
-                (*_nonconstantConstraints)[i].indexLinear          = indexLinear;
-                (*_originalConstraints)[indexOriginal].indexLinear = indexLinear++;
+                nonconstCon.dependency  = LINEAR;
+                origCon.dependency      = LINEAR;
+                nonconstCon.indexLinear = indexLinear;
+                origCon.indexLinear     = indexLinear++;
                 break;
             case mc::FFDep::B:
-                (*_nonconstantConstraints)[i].dependency              = BILINEAR;
-                (*_originalConstraints)[indexOriginal].dependency     = BILINEAR;
-                (*_nonconstantConstraints)[i].indexNonlinear          = indexNonlinear;
-                (*_originalConstraints)[indexOriginal].indexNonlinear = indexNonlinear++;
+                nonconstCon.dependency     = BILINEAR;
+                origCon.dependency         = BILINEAR;
+                nonconstCon.indexNonlinear = indexNonlinear;
+                origCon.indexNonlinear     = indexNonlinear++;
                 break;
             case mc::FFDep::Q:
-                (*_nonconstantConstraints)[i].dependency              = QUADRATIC;
-                (*_originalConstraints)[indexOriginal].dependency     = QUADRATIC;
-                (*_nonconstantConstraints)[i].indexNonlinear          = indexNonlinear;
-                (*_originalConstraints)[indexOriginal].indexNonlinear = indexNonlinear++;
+                nonconstCon.dependency     = QUADRATIC;
+                origCon.dependency         = QUADRATIC;
+                nonconstCon.indexNonlinear = indexNonlinear;
+                origCon.indexNonlinear     = indexNonlinear++;
                 break;
             case mc::FFDep::P:
-                (*_nonconstantConstraints)[i].dependency              = POLYNOMIAL;
-                (*_originalConstraints)[indexOriginal].dependency     = POLYNOMIAL;
-                (*_nonconstantConstraints)[i].indexNonlinear          = indexNonlinear;
-                (*_originalConstraints)[indexOriginal].indexNonlinear = indexNonlinear++;
+                nonconstCon.dependency     = POLYNOMIAL;
+                origCon.dependency         = POLYNOMIAL;
+                nonconstCon.indexNonlinear = indexNonlinear;
+                origCon.indexNonlinear     = indexNonlinear++;
                 break;
             case mc::FFDep::R:
-                (*_nonconstantConstraints)[i].dependency              = RATIONAL;
-                (*_originalConstraints)[indexOriginal].dependency     = RATIONAL;
-                (*_nonconstantConstraints)[i].indexNonlinear          = indexNonlinear;
-                (*_originalConstraints)[indexOriginal].indexNonlinear = indexNonlinear++;
+                nonconstCon.dependency     = RATIONAL;
+                origCon.dependency         = RATIONAL;
+                nonconstCon.indexNonlinear = indexNonlinear;
+                origCon.indexNonlinear     = indexNonlinear++;
                 break;
             case mc::FFDep::N:
             case mc::FFDep::D:
             default:
-                (*_nonconstantConstraints)[i].dependency              = NONLINEAR;
-                (*_originalConstraints)[indexOriginal].dependency     = NONLINEAR;
-                (*_nonconstantConstraints)[i].indexNonlinear          = indexNonlinear;
-                (*_originalConstraints)[indexOriginal].indexNonlinear = indexNonlinear++;
+                nonconstCon.dependency     = NONLINEAR;
+                origCon.dependency         = NONLINEAR;
+                nonconstCon.indexNonlinear = indexNonlinear;
+                origCon.indexNonlinear     = indexNonlinear++;
                 break;
         }
-        (*_nonconstantConstraints)[i].convexity             = CONV_NONE;
-        (*_originalConstraints)[indexOriginal].convexity    = CONV_NONE;
-        (*_nonconstantConstraints)[i].monotonicity          = MON_NONE;
-        (*_originalConstraints)[indexOriginal].monotonicity = MON_NONE;
+        nonconstCon.convexity    = CONV_NONE;
+        origCon.convexity        = CONV_NONE;
+        nonconstCon.monotonicity = MON_NONE;
+        origCon.monotonicity     = MON_NONE;
     }
 
     // Fill the vector with nonconstant constraints for the UBS properly
@@ -2720,27 +2912,30 @@ MAiNGO::_set_constraint_and_variable_properties()
     _nonconstantConstraintsUBP = std::make_shared<std::vector<Constraint>>();
     _nonconstantConstraintsUBP->resize(1 + _nineq + _nineqSquash + _neq);
     for (unsigned int i = 0; i < size; i++) {
-        unsigned indexOriginal = (*_nonconstantConstraints)[i].indexOriginal;
-        switch ((*_nonconstantConstraints)[i].type) {
+
+        Constraint & nonconstCon = (*_nonconstantConstraints)[i];
+        Constraint & origCon     = (*_originalConstraints)[nonconstCon.indexOriginal];
+
+        switch (nonconstCon.type) {
             case OBJ:
             case INEQ:    // Objective and inequalities keep their order
-                (*_nonconstantConstraintsUBP)[i]                           = (*_nonconstantConstraints)[i];
-                (*_nonconstantConstraintsUBP)[i].indexNonconstantUBP       = indexUBP;
-                (*_nonconstantConstraints)[i].indexNonconstantUBP          = indexUBP;
-                (*_originalConstraints)[indexOriginal].indexNonconstantUBP = indexUBP++;
+                (*_nonconstantConstraintsUBP)[i]                     = nonconstCon;
+                (*_nonconstantConstraintsUBP)[i].indexNonconstantUBP = indexUBP;
+                nonconstCon.indexNonconstantUBP                      = indexUBP;
+                origCon.indexNonconstantUBP                          = indexUBP++;
                 break;
             case EQ:
-                (*_nonconstantConstraintsUBP)[i + _nineqSquash]                     = (*_nonconstantConstraints)[i];
+                (*_nonconstantConstraintsUBP)[i + _nineqSquash]                     = nonconstCon;
                 (*_nonconstantConstraintsUBP)[i + _nineqSquash].indexNonconstantUBP = indexUBP + _nineqSquash;
-                (*_nonconstantConstraints)[i].indexNonconstantUBP                   = indexUBP + _nineqSquash;
-                (*_originalConstraints)[indexOriginal].indexNonconstantUBP          = indexUBP + _nineqSquash;
+                nonconstCon.indexNonconstantUBP                                     = indexUBP + _nineqSquash;
+                origCon.indexNonconstantUBP                                         = indexUBP + _nineqSquash;
                 indexUBP++;
                 break;
             case INEQ_SQUASH:
-                (*_nonconstantConstraintsUBP)[i - offset]                     = (*_nonconstantConstraints)[i];
+                (*_nonconstantConstraintsUBP)[i - offset]                     = nonconstCon;
                 (*_nonconstantConstraintsUBP)[i - offset].indexNonconstantUBP = indexUBP - _neq;
-                (*_nonconstantConstraints)[i].indexNonconstantUBP             = indexUBP - _neq;
-                (*_originalConstraints)[indexOriginal].indexNonconstantUBP    = indexUBP - _neq;
+                nonconstCon.indexNonconstantUBP                               = indexUBP - _neq;
+                origCon.indexNonconstantUBP                                   = indexUBP - _neq;
                 indexUBP++;
                 break;
             case INEQ_REL_ONLY:
@@ -2751,6 +2946,9 @@ MAiNGO::_set_constraint_and_variable_properties()
         }
     }
 
+    // If the model should be solved as a two-stage problem, were done
+    if (_myTwoStageFFVARmodel && (_maingoSettings->TS_useLowerBoundingSubsolvers || _maingoSettings->TS_useUpperBoundingSubsolvers))
+        return;
     // Set branching priorities on linear variables to zero to avoid branching on them
     for (size_t i = 0; i < _nvarLbd; i++) {
         if (_variableIsLinear[i]) {
diff --git a/src/MAiNGOgetOption.cpp b/src/MAiNGOgetOption.cpp
index 660249e..fc117a9 100644
--- a/src/MAiNGOgetOption.cpp
+++ b/src/MAiNGOgetOption.cpp
@@ -180,12 +180,24 @@ MAiNGO::get_option(const std::string& option) const
     else if (option == "modelWritingLanguage") {
         return _maingoSettings->modelWritingLanguage;
     }
+    else if (option == "growing_approach") {
+        return _maingoSettings->growing_approach;
+    }
+    else if (option == "growing_maxTimePostprocessing") {
+        return _maingoSettings->growing_maxTimePostprocessing;
+    }
     else if (option == "growing_dataSizeTol") {
         return _maingoSettings->growing_dataSizeTol;
     }
     else if (option == "growing_dataSizeInit") {
         return _maingoSettings->growing_dataSizeInit;
     }
+    else if (option == "growing_useResampling") {
+        return _maingoSettings->growing_useResampling;
+    }
+    else if (option == "growing_augmentPercentage") {
+        return _maingoSettings->growing_augmentPercentage;
+    }
     else if (option == "growing_augmentRule") {
         return _maingoSettings->growing_augmentRule;
     }
@@ -195,8 +207,8 @@ MAiNGO::get_option(const std::string& option) const
     else if (option == "growing_augmentWeight") {
         return _maingoSettings->growing_augmentWeight;
     }
-    else if (option == "growing_augmentPercentage") {
-        return _maingoSettings->growing_augmentPercentage;
+    else if (option == "growing_augmentTol") {
+        return _maingoSettings->growing_augmentTol;
     }
     throw MAiNGOException("Error getting option: unknown option" + option + ".");
 }
\ No newline at end of file
diff --git a/src/MAiNGOprintingFunctions.cpp b/src/MAiNGOprintingFunctions.cpp
index 1c09da4..1939447 100644
--- a/src/MAiNGOprintingFunctions.cpp
+++ b/src/MAiNGOprintingFunctions.cpp
@@ -248,7 +248,7 @@ MAiNGO::_print_solution()
             }
         }
     }
-    else if (_problemStructure >= NLP) {
+    else if (_problemStructure >= NLP || (_myTwoStageFFVARmodel && (_maingoSettings->TS_useLowerBoundingSubsolvers || _maingoSettings->TS_useUpperBoundingSubsolvers))) {
         if (!_solutionPoint.empty()) {
             outstream << std::endl;
             if (!_feasibilityProblem) {
@@ -415,8 +415,10 @@ MAiNGO::_print_time()
 
     // Wall-clock time
     outstr.str("");
-    outstr.clear();    // Properly clear the ostringstream
-    _solutionTimeWallClock = get_wall_time() - _solutionTimeWallClock;
+    outstr.clear();
+    _outputTimeWallClock   = get_wall_time() - _outputTimeWallClock;
+    _solutionTimeWallClock = _preprocessTimeWallClock + _babTimeWallClock + _outputTimeWallClock;
+
     outstr << "  Wall-clock time: " << std::fixed << std::setprecision(3) << (_solutionTimeWallClock) << " seconds (Preprocessing + B&B).\n";
     if (_solutionTimeWallClock > 60) {
         outstr << "                   This equals ";
@@ -469,6 +471,39 @@ MAiNGO::_print_additional_output()
 }
 
 
+#ifdef HAVE_GROWING_DATASETS
+////////////////////////////////////////////////////////////////////////
+// prints statistics of post-processing for heuristic B&B algorithm with growing datasets on screen
+void
+MAiNGO::_print_statistics_postprocessing()
+{
+
+    std::ostringstream outstream;
+
+    outstream << "===================================================================" << std::endl;
+
+    outstream << std::endl
+              << "  Post-processing statistics: " << std::endl;
+    outstream << "    Total number of nodes tracked "   << std::setw(4) << "= " << _myBaB->get_nodes_tracked_for_postprocessing() << std::endl;
+    outstream << "    Total number of nodes processed " << std::setw(1) << "= " << _myBaB->get_nodes_processed_in_postprocessing() << std::endl;
+    outstream << "    Final LBD "                      << std::setw(24) << "= " << std::setprecision(6) <<  _myBaB->get_lower_bound_after_postprocessing();
+    if (_myBaB->get_lower_bound_after_postprocessing() < _myBaB->get_final_LBD()) {
+        outstream << " (changed)" << std::endl;
+    }
+    else {
+        outstream << " (unchanged)" << std::endl;
+    }
+    outstream << "    CPU time " << std::setw(25) << "= " << _myBaB->get_time_for_postprocessing() << " seconds";
+#ifdef HAVE_MAiNGO_MPI
+    outstream << " (manager process) ";
+#endif // HAVE_MAiNGO_MPI
+    outstream << std::endl;
+
+    _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY);
+}
+#endif // HAVE_GROWING_DATASETS
+
+
 /////////////////////////////////////////////////////////////////////////
 // print an ASCII MAiNGO and copyright
 void
diff --git a/src/MAiNGOsetOption.cpp b/src/MAiNGOsetOption.cpp
index 6179f4d..0af40f4 100644
--- a/src/MAiNGOsetOption.cpp
+++ b/src/MAiNGOsetOption.cpp
@@ -129,6 +129,22 @@ MAiNGO::set_option(const std::string& option, const double value)
             }
         }
     }
+    else if (option == "maxwTime") {
+        if (value < 10 && value != -1) {
+            _logger->save_setting(MAXWTIME, "maxwTime has to be >= 10 or -1 (=inf), setting it to 10");
+            _maingoSettings->maxwTime = 10;
+        }
+        else {
+            if (value == -1) {
+                _maingoSettings->maxwTime = std::numeric_limits<unsigned>::max();
+                _logger->save_setting(MAXWTIME, option + " " + oss.str());
+            }
+            else {
+                _maingoSettings->maxwTime = (int)value;
+                _logger->save_setting(MAXWTIME, option + " " + oss.str());
+            }
+        }
+    }
     else if (option == "confirmTermination") {
         if (value != 0 && value != 1) {
             _logger->save_setting(CONFIRMTERMINATION, "confirmTermination has to be 0 or 1, setting to 0");
@@ -799,6 +815,47 @@ MAiNGO::set_option(const std::string& option, const double value)
             _logger->save_setting(WRITETOOTHERLANGUAGE, option + " " + oss.str());
         }
     }
+    else if (option == "growing_approach") {
+#ifdef HAVE_GROWING_DATASETS
+    if (value != 0 && value != 1 && value != 2) {
+        _logger->save_setting(GROWING_APPROACHCHOSEN, "growing_approach has to be 0, 1, or 2, setting it to default (0)");
+        _maingoSettings->growing_approach = GROW_APPR_DETERMINISTIC;
+    }
+    else {
+        if ((int)value == 0) {
+            _maingoSettings->growing_approach = GROW_APPR_DETERMINISTIC;
+        }
+        else if ((int)value == 1) {
+            _maingoSettings->growing_approach = GROW_APPR_SSEHEURISTIC;
+        }
+        else if ((int)value == 2) {
+            _maingoSettings->growing_approach = GROW_APPR_MSEHEURISTIC;
+        }
+        _logger->save_setting(GROWING_APPROACHCHOSEN, option + " " + oss.str());
+    }
+#else
+    _logger->save_setting(GROWING_APPROACHCHOSEN, "MAiNGO is used without growing datasets: changes of growing_approach will not have an effect");
+#endif    // HAVE_GROWING_DATASETS
+    }
+    else if (option == "growing_maxTimePostprocessing") {
+#ifdef HAVE_GROWING_DATASETS
+        if (_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) {
+            if (value < 0.0) {
+                _logger->save_setting(GROWING_MAXTIMEPOST, "growing_maxTimePostprocessing has to be at least 0, setting it to default (60)");
+                _maingoSettings->growing_maxTimePostprocessing = 60.;
+            }
+            else {
+                _maingoSettings->growing_maxTimePostprocessing = value;
+                _logger->save_setting(GROWING_MAXTIMEPOST, option + " " + oss.str());
+            }
+        } else if (value > 0.0) {
+            _logger->save_setting(GROWING_MAXTIMEPOST, "growing_maxTimePostprocessing set to 0 since GROW_APPR_DETERMINISTIC is chosen");
+            _maingoSettings->growing_maxTimePostprocessing = 0.;
+        }
+#else
+    _logger->save_setting(GROWING_MAXTIMEPOST, "MAiNGO is used without growing datasets: changes of growing_maxTimePostprocessing will not have an effect");
+#endif    // HAVE_GROWING_DATASETS
+    }
     else if (option == "growing_dataSizeTol") {
 #ifdef HAVE_GROWING_DATASETS
         if (value < 0. || value > 1.) {
@@ -810,28 +867,68 @@ MAiNGO::set_option(const std::string& option, const double value)
             _logger->save_setting(GROWING_DATASIZETOL, option + " " + oss.str());
         }
 #else
-        _logger->save_setting(GROWING_AUGMENTFREQ, "MAiNGO is used without growing datasets: changes of growing_dataSizeTol will not have an effect");
+    _logger->save_setting(GROWING_DATASIZETOL, "MAiNGO is used without growing datasets: changes of growing_dataSizeTol will not have an effect");
 #endif    // HAVE_GROWING_DATASETS
-    }
+        }
     else if (option == "growing_dataSizeInit") {
 #ifdef HAVE_GROWING_DATASETS
-        if (value < 0. || value > 1.) {
-            _logger->save_setting(GROWING_DATASIZEINIT, "growing_dataSizeInit has to be between 0 and 1, setting it to default (0.1)");
-            _maingoSettings->growing_dataSizeInit = 0.1;
+    if (value < 0. || value > 1.) {
+        _logger->save_setting(GROWING_DATASIZEINIT, "growing_dataSizeInit has to be between 0 and 1, setting it to default (0.1)");
+        _maingoSettings->growing_dataSizeInit = 0.1;
+    }
+    else {
+        _maingoSettings->growing_dataSizeInit = value;
+        _logger->save_setting(GROWING_DATASIZEINIT, option + " " + oss.str());
+    }
+#else
+    _logger->save_setting(GROWING_DATASIZEINIT, "MAiNGO is used without growing datasets: changes of growing_dataSizeTol will not have an effect");
+#endif    // HAVE_GROWING_DATASETS
+    }
+    else if (option == "growing_useResampling") {
+#ifdef HAVE_GROWING_DATASETS
+    if (value != 0 && value != 1) {
+        _logger->save_setting(GROWING_USERESAMPLING, "growing_useResampling has to be 0 or 1, setting it to default (0)");
+        _maingoSettings->growing_useResampling = false;
+    }
+    else {
+        if (value == 0) {
+            _maingoSettings->growing_useResampling = false;
+            _logger->save_setting(GROWING_USERESAMPLING, option + " 0");
         }
         else {
-            _maingoSettings->growing_dataSizeInit = value;
-            _logger->save_setting(GROWING_DATASIZEINIT, option + " " + oss.str());
+            _maingoSettings->growing_useResampling = true;
+            _logger->save_setting(GROWING_USERESAMPLING, option + " 1");
         }
+    }
 #else
-        _logger->save_setting(GROWING_AUGMENTFREQ, "MAiNGO is used without growing datasets: changes of growing_dataSizeTol will not have an effect");
+    _logger->save_setting(GROWING_USERESAMPLING, "MAiNGO is used without growing datasets: changes of growing_useResampling will not have an effect");
+#endif    // HAVE_GROWING_DATASETS
+    }
+    else if (option == "growing_augmentPercentage") {
+#ifdef HAVE_GROWING_DATASETS
+    if (value < 0. && value > 1.) {
+        _logger->save_setting(GROWING_AUGMENTPERCENTAGE, "growing_augmentPercentage has to be between 0 and 1, setting it to default (0.25)");
+        _maingoSettings->growing_augmentPercentage = 0.25;
+    }
+    else {
+        _maingoSettings->growing_augmentPercentage = value;
+        _logger->save_setting(GROWING_AUGMENTPERCENTAGE, option + " " + oss.str());
+    }
+#else
+    _logger->save_setting(GROWING_AUGMENTPERCENTAGE, "MAiNGO is used without growing datasets: changes of growing_augmentPercentage will not have an effect");
 #endif    // HAVE_GROWING_DATASETS
     }
     else if (option == "growing_augmentRule") {
 #ifdef HAVE_GROWING_DATASETS
-        if (value != 0 && value != 1 && value != 2) {
-            _logger->save_setting(GROWING_AUGMENTRULE, "growing_augmentRule has to be 0, 1, 2, setting it to 2");
-            _maingoSettings->growing_augmentRule = AUG_RULE_SCALCST;
+    AUGMENTATION_RULE defaultRule = AUG_RULE_TOLCST;
+    if ((_maingoSettings->growing_approach == GROW_APPR_MSEHEURISTIC) && (value == 1 || value == 5)) {
+        _logger->save_setting(GROWING_AUGMENTRULE, "growing_augmentRule cannot use SCAL(ING) when using GROW_APPR_MSEHEURISTIC, setting it to default (8)");
+        _maingoSettings->growing_augmentRule = defaultRule;
+    }
+    else {
+        if (value != 0 && value != 1 && value != 2 && value != 3 && value != 4 && value != 5 && value != 6 && value != 7 && value != 8) {
+            _logger->save_setting(GROWING_AUGMENTRULE, "growing_augmentRule has to be 0, 1, 2, 3, 4, 5, 6, 7, or 8, setting it to default (8)");
+            _maingoSettings->growing_augmentRule = defaultRule;
         }
         else {
             if ((int)value == 0) {
@@ -841,17 +938,38 @@ MAiNGO::set_option(const std::string& option, const double value)
                 _maingoSettings->growing_augmentRule = AUG_RULE_SCALING;
             }
             else if ((int)value == 2) {
+                _maingoSettings->growing_augmentRule = AUG_RULE_VALID;
+            }
+            else if ((int)value == 3) {
+                _maingoSettings->growing_augmentRule = AUG_RULE_COMBI;
+            }
+            else if ((int)value == 4) {
+                _maingoSettings->growing_augmentRule = AUG_RULE_TOL;
+            }
+            else if ((int)value == 5) {
                 _maingoSettings->growing_augmentRule = AUG_RULE_SCALCST;
             }
+            else if ((int)value == 6) {
+                _maingoSettings->growing_augmentRule = AUG_RULE_VALCST;
+            }
+            else if ((int)value == 7) {
+                _maingoSettings->growing_augmentRule = AUG_RULE_COMBICST;
+            }
+            else if ((int)value == 8) {
+                _maingoSettings->growing_augmentRule = AUG_RULE_TOLCST;
+            }
             _logger->save_setting(GROWING_AUGMENTRULE, option + " " + oss.str());
         }
+    }
 #else
         _logger->save_setting(GROWING_AUGMENTRULE, "MAiNGO is used without growing datasets: changes of growing_augmentRule will not have an effect");
 #endif    // HAVE_GROWING_DATASETS
     }
     else if (option == "growing_augmentFreq") {
 #ifdef HAVE_GROWING_DATASETS
-        if (_maingoSettings->growing_augmentRule == AUG_RULE_CONST || _maingoSettings->growing_augmentRule == AUG_RULE_SCALCST) {
+        if (_maingoSettings->growing_augmentRule == AUG_RULE_CONST
+            || _maingoSettings->growing_augmentRule == AUG_RULE_SCALCST  || _maingoSettings->growing_augmentRule == AUG_RULE_VALCST
+            || _maingoSettings->growing_augmentRule == AUG_RULE_COMBICST || _maingoSettings->growing_augmentRule == AUG_RULE_TOLCST) {
             if (value < 1.) {
                 _logger->save_setting(GROWING_AUGMENTFREQ, "growing_augmentFreq has to be at least 1, setting it to default (10)");
                 _maingoSettings->growing_augmentFreq = 10;
@@ -862,7 +980,7 @@ MAiNGO::set_option(const std::string& option, const double value)
             }
         }
         else {
-            _logger->save_setting(GROWING_AUGMENTFREQ, "Neither AUG_RULE_CONST nor AUG_RULE_SCALCST is used: changes of growing_augmentFreq will not have an effect");
+            _logger->save_setting(GROWING_AUGMENTFREQ, "CONST/CST is not used within augmentation rule: changes of growing_augmentFreq will not have an effect");
         }
 #else
         _logger->save_setting(GROWING_AUGMENTFREQ, "MAiNGO is used without growing datasets: changes of growing_augmentFreq will not have an effect");
@@ -881,26 +999,104 @@ MAiNGO::set_option(const std::string& option, const double value)
             }
         }
         else {
-            _logger->save_setting(GROWING_AUGMENTWEIGHT, "Neither AUG_RULE_SCALING nor AUG_RULE_SCALCST is used: changes of growing_augmentWeight will not have an effect");
+            _logger->save_setting(GROWING_AUGMENTWEIGHT, "SCAL(ING) is not used within augmentation rule: changes of growing_augmentWeight will not have an effect");
         }
 #else
         _logger->save_setting(GROWING_AUGMENTWEIGHT, "MAiNGO is used without growing datasets: changes of growing_augmentWeight will not have an effect");
 #endif    // HAVE_GROWING_DATASETS
     }
-    else if (option == "growing_augmentPercentage") {
+    else if (option == "growing_augmentTol") {
 #ifdef HAVE_GROWING_DATASETS
-        if (value < 0. && value > 1.) {
-            _logger->save_setting(GROWING_AUGMENTPERCENTAGE, "growing_augmentPercentage has to be between 0 and 1, setting it to default (0.25)");
-            _maingoSettings->growing_augmentPercentage = 0.25;
+        if (_maingoSettings->growing_augmentRule == AUG_RULE_TOL || _maingoSettings->growing_augmentRule == AUG_RULE_TOLCST) {
+            double minValue = std::min(_maingoSettings->epsilonA, _maingoSettings->epsilonR);
+            if ( value < minValue) {
+                _logger->save_setting(GROWING_AUGMENTTOL, "growing_augmentTol has to be > min(epsilonA,epsilonR), setting it to 10*min(epsilonA,epsilonR) = " + std::to_string(10 * minValue));
+                _maingoSettings->growing_augmentTol = 10*minValue;
+            }
+            else {
+                _maingoSettings->growing_augmentTol = value;
+                _logger->save_setting(GROWING_AUGMENTTOL, option + " " + oss.str());
+            }
         }
         else {
-            _maingoSettings->growing_augmentPercentage = value;
-            _logger->save_setting(GROWING_AUGMENTPERCENTAGE, option + " " + oss.str());
+            _logger->save_setting(GROWING_AUGMENTTOL, "TOL is not used within augmentation rule: changes of growing_augmentTol will not have an effect");
         }
 #else
-        _logger->save_setting(GROWING_AUGMENTPERCENTAGE, "MAiNGO is used without growing datasets: changes of growing_augmentPercentage will not have an effect");
+        _logger->save_setting(GROWING_AUGMENTTOL, "MAiNGO is used without growing datasets: changes of growing_augmentTol will not have an effect");
 #endif    // HAVE_GROWING_DATASETS
     }
+    else if (option == "TS_useLowerBoundingSubsolvers") {
+        if (value != 0 && value != 1) {
+            _logger->save_setting(TS_USELBS, "TS_useLowerBoundingSubsolvers has to be 0 or 1, setting to 1");
+            _maingoSettings->TS_useLowerBoundingSubsolvers = true;
+        }
+        else {
+            if (value == 0) {
+                _maingoSettings->TS_useLowerBoundingSubsolvers = false;
+                _logger->save_setting(TS_USELBS, option + " 0");
+            }
+            else {
+                _maingoSettings->TS_useLowerBoundingSubsolvers = true;
+                _logger->save_setting(TS_USELBS, option + " 1");
+            }
+        }
+    }
+    else if (option == "TS_useUpperBoundingSubsolvers") {
+        if (value != 0 && value != 1) {
+            _logger->save_setting(TS_USEUBS, "TS_useUpperBoundingSubsolvers has to be 0 or 1, setting to 0");
+            _maingoSettings->TS_useUpperBoundingSubsolvers = false;
+        }
+        else {
+            if (value == 0) {
+                _maingoSettings->TS_useUpperBoundingSubsolvers = false;
+                _logger->save_setting(TS_USEUBS, option + " 0");
+            }
+            else {
+                _maingoSettings->TS_useUpperBoundingSubsolvers = true;
+                _logger->save_setting(TS_USEUBS, option + " 1");
+            }
+        }
+    }
+    else if (option == "TS_parallelOBBT") {
+        if (value != 0 && value != 1) {
+            _logger->save_setting(TS_POBBT, "TS_parallelOBBT has to be 0 or 1, setting to 0");
+            _maingoSettings->TS_parallelOBBT = false;
+        }
+        else {
+            if (value == 0) {
+                _maingoSettings->TS_parallelOBBT = false;
+                _logger->save_setting(TS_POBBT, option + " 0");
+            }
+            else {
+                _maingoSettings->TS_parallelOBBT = true;
+                _logger->save_setting(TS_POBBT, option + " 1");
+            }
+        }
+    }
+    else if (option == "TS_strongBranchingThreshold") {
+        if (value < 0 || value > 1) {
+            _logger->save_setting(TS_STRONGBRANCHINGTHRESHOLD, "TS_strongBranchingThreshold has to be between 0. and 1., setting to 1.");
+            _maingoSettings->TS_strongBranchingThreshold = 1;
+        }
+        else {
+            _maingoSettings->TS_strongBranchingThreshold = value;
+            _logger->save_setting(TS_STRONGBRANCHINGTHRESHOLD, option + " " + oss.str());
+        }
+    }
+    else if (option == "TS_maxBranchingPower") {
+        unsigned int k_max(value);
+        if (double(k_max) != value) {
+            _logger->save_setting(TS_MAXBRANCHINGPOWER, "TS_maxBranchingPower has to be a positive integer, setting to 1");
+            _maingoSettings->TS_maxBranchingPower = 4;
+        }
+        else {
+            if (k_max > 14) {
+                _logger->print_message("Warning, setting TS_maxBranchingPower higher than 14, more than 10000 nodes may be created for every second stage branching!", VERB_NONE, TS_MAXBRANCHINGPOWER);
+            }
+            _maingoSettings->TS_maxBranchingPower = k_max;
+            _logger->save_setting(TS_MAXBRANCHINGPOWER, option + " " + oss.str());
+        }
+    }
     else {
         _logger->save_setting(UNKNOWN_SETTING, "Could not find setting " + option + ". Proceeding.");
         return false;
diff --git a/src/MAiNGOwritingFunctions.cpp b/src/MAiNGOwritingFunctions.cpp
index 1babae9..32ca63a 100644
--- a/src/MAiNGOwritingFunctions.cpp
+++ b/src/MAiNGOwritingFunctions.cpp
@@ -259,10 +259,12 @@ MAiNGO::_write_solution_and_statistics_csv()
         solutionStatisticsFile << "No of nodes left," << _myBaB->get_nodes_left() << std::endl;
     }
 
-    solutionStatisticsFile << "Time pre-processing (s)," << _preprocessTime << std::endl;
-    solutionStatisticsFile << "Time branch-and-bound (s)," << _solutionTime - _preprocessTime << std::endl;
+    solutionStatisticsFile << "CPU time pre-processing (s)," << _preprocessTime << std::endl;
+    solutionStatisticsFile << "Wall-clock time pre-processing (s)," << _preprocessTimeWallClock << std::endl;
+    solutionStatisticsFile << "CPU time branch-and-bound (s)," << _solutionTime - _preprocessTime << std::endl;
+    solutionStatisticsFile << "Wall-clock time branch-and-bound (s)," << _solutionTimeWallClock - _preprocessTimeWallClock << std::endl;
     solutionStatisticsFile << "Total CPU solution time (s)," << _solutionTime << std::endl;
-    solutionStatisticsFile << "Total wall solution time (s)," << _solutionTimeWallClock << std::endl;
+    solutionStatisticsFile << "Total wall-clock solution time (s)," << _solutionTimeWallClock << std::endl;
     solutionStatisticsFile << "Found feasible solution," << (!_solutionPoint.empty()) << std::endl;
 
 
@@ -339,7 +341,9 @@ MAiNGO::_write_json_file()
     }
     jsonFile << "  \"Timing\" : {" << std::endl;
     jsonFile << "    \"PreProcessingCPU\" : " << _preprocessTime << "," << std::endl;
+    jsonFile << "    \"PreProcessingWall\" : " << _preprocessTimeWallClock << "," << std::endl;
     jsonFile << "    \"BranchAndBoundCPU\" : " << _solutionTime - _preprocessTime << "," << std::endl;
+    jsonFile << "    \"BranchAndBoundWall\" : " << _solutionTimeWallClock - _preprocessTimeWallClock << "," << std::endl;
     jsonFile << "    \"TotalCPU\" : " << _solutionTime << "," << std::endl;
     jsonFile << "    \"TotalWall\" : " << _solutionTimeWallClock << std::endl;
     jsonFile << "  }," << std::endl;
diff --git a/src/bab.cpp b/src/bab.cpp
index 8f533d3..407cef2 100644
--- a/src/bab.cpp
+++ b/src/bab.cpp
@@ -14,6 +14,9 @@
 #include "decayingProbability.h"
 #include "getTime.h"
 #include "lbp.h"
+#include "siblingResults.h"
+#include "instrumentor.h"
+#include "MAiNGOException.h"
 #include "mpiUtilities.h"
 #include "ubp.h"
 
@@ -27,7 +30,8 @@ using namespace bab;
 ////////////////////////////////////////////////////////////////////////////////////////
 // constructor for the branch and bound object
 BranchAndBound::BranchAndBound(const std::vector<babBase::OptimizationVariable> &variables, std::shared_ptr<lbp::LowerBoundingSolver> LBSIn, std::shared_ptr<ubp::UpperBoundingSolver> UBSIn,
-                               std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, const unsigned nvarWOauxIn, std::istream *const inputStream):
+                               std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, const unsigned nvarWOauxIn, std::istream *const inputStream, const std::string &babFileName):
+    babBase::FathomObserver(), _parentSubproblemBounds(),
     _originalVariables(variables),
     _LBS(LBSIn), _UBS(UBSIn), _nvar(variables.size()), _maingoSettings(settingsIn), _logger(loggerIn), _nvarWOaux(nvarWOauxIn), _inputStream(inputStream)
 {
@@ -54,27 +58,30 @@ BranchAndBound::BranchAndBound(const std::vector<babBase::OptimizationVariable>
 #endif                                                                         // HAVE_GROWING_DATASETS
 
         _nodesGivenToWorkers = std::vector<std::pair<bool, double>>(_nProcs - 1, std::make_pair(false, _maingoSettings->infinity));
-        MAiNGO_ELSE
+    MAiNGO_ELSE
             _brancher = nullptr;
-        MAiNGO_END_IF
+    MAiNGO_END_IF
 
 #endif
 
-        MAiNGO_IF_BAB_MANAGER
+    MAiNGO_IF_BAB_MANAGER
             //Create brancher
-            _brancher = std::unique_ptr<babBase::Brancher>(new babBase::Brancher(variables));
+            _brancher = std::unique_ptr<babBase::Brancher>(new babBase::Brancher(variables, babFileName));
             // Store settings:
             _brancher->set_branching_dimension_selection_strategy(_maingoSettings->BAB_branchVariable);
             _brancher->set_node_selection_strategy(_maingoSettings->BAB_nodeSelection);
             _brancher->decrease_pruning_score_threshold_to(_maingoSettings->infinity);
             _brancher->enable_pruning_with_rel_and_abs_tolerance(_maingoSettings->epsilonR, _maingoSettings->epsilonA);
-        MAiNGO_END_IF
+    MAiNGO_END_IF
 
         // Initialize internals
         _foundFeas       = false;
         _firstFound      = 0;
         _incumbentNodeId = 0;
         _lbd             = -_maingoSettings->infinity;
+#ifdef HAVE_GROWING_DATASETS
+        _lbdPostpro      = -_maingoSettings->infinity;
+#endif // HAVE_GROWING_DATASETS
         _lbdOld          = -_maingoSettings->infinity;
         _bestLbdFathomed = _maingoSettings->infinity;
         _ubd             = _maingoSettings->infinity;
@@ -85,11 +92,21 @@ BranchAndBound::BranchAndBound(const std::vector<babBase::OptimizationVariable>
         _nNodesDeleted        = 0;
         _nNodesFathomed       = 0;
         _nNodesMaxInMemory    = 0;
+#ifdef HAVE_GROWING_DATASETS
+        _nNodesTrackedPost    = 0;
+        _nNodesProcessedPost  = 0;
+#endif    // HAVE_GROWING_DATASETS
         _lbdcnt               = 0;
         _ubdcnt               = 0;
         _timePassed           = 0.0;
+        _wtimePassed          = 0.0;
         _timePreprocess       = 0.0;
+        _wtimePreprocess      = 0.0;
         _daysPassed           = 0;
+        _wdaysPassed          = 0;
+#ifdef HAVE_GROWING_DATASETS
+        _timePostpro          = 0.0;
+#endif    // HAVE_GROWING_DATASETS
         _iterations           = 0;
         _linesprinted         = 0;
         _printNewIncumbent    = false;
@@ -111,286 +128,398 @@ BranchAndBound::BranchAndBound(const std::vector<babBase::OptimizationVariable>
 ////////////////////////////////////////////////////////////////////////////////////////
 // main branch and bound algorithm
 babBase::enums::BAB_RETCODE
-BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std::vector<double> &solutionPoint, const double preprocessTime, double &timePassed)
+BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std::vector<double> &solutionPoint, const double preprocessTime, const double preprocessWallTime, double &timePassed, double &wallTimePassed)
 {
     try {
         // ---------------------------------------------------------------------------------
         // 0: Process Inputs
         // ---------------------------------------------------------------------------------
+#ifdef HAVE_GROWING_DATASETS
+        // Warn user when skipping bound tightening
+        MAiNGO_IF_BAB_MANAGER
+        if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) && (!_maingoSettings->PRE_pureMultistart)) {
+            if ((_maingoSettings->BAB_alwaysSolveObbt) || (_maingoSettings->BAB_dbbt)
+                || (_maingoSettings->BAB_probing) || (_maingoSettings->BAB_constraintPropagation)) {
+                _logger->print_message("\n  Warning: No bound tightening performed in B&B nodes with a reduced dataset since it may be erroneuous for heuristic approaches.\n", VERB_NORMAL, BAB_VERBOSITY);
+            }
+        }
+        MAiNGO_END_IF
+#endif    // HAVE_GROWING_DATASETS
 
         // Auxiliary variables for timing (have to be declared outside of MAiNGO_IF_BAB_MANAGER scope to be available in whole loop)
-        double startTime, currentTime, lastTime;
+        double startTime, startwTime, currentTime, currentwTime, lastTime, lastwTime;
         double currentLBD;    // This is declared here to be available for the Manager and Workers in the parallel version
 
 #ifdef HAVE_GROWING_DATASETS
         std::shared_ptr<babBase::BabNode> incumbentNode;    // Auxiliary variable for storing node containing the best incumbent (may already have been fathomed)
 #endif                                                      // HAVE_GROWING_DATASETS
 
-        MAiNGO_IF_BAB_MANAGER
-            // Store initial feasible point if given
-            if (!solutionPoint.empty()) {
-                _update_incumbent_and_fathom(solutionValue, solutionPoint, 0);
-                _incumbentNodeId = 1;    // To make sure we track correctly that the incumbent is now in the root node (which will have ID 1, although the solution was found at node 0 (i.e., pre-processing))
-                // Since _update_incumbent_and_fathom triggers an asterisk in the output whenever a new incumbent was found,
-                // we need to suppress this here (otherwise it would look like this incumbent was found during B&B in the root node, although it comes from pre-processing)
-                _printNewIncumbent = false;
-            }
+    MAiNGO_IF_BAB_MANAGER
+        // Store initial feasible point if given
+        if (!solutionPoint.empty()) {
+            _update_incumbent_and_fathom(solutionValue, solutionPoint, 0);
+            _incumbentNodeId = 1;    // To make sure we track correctly that the incumbent is now in the root node (which will have ID 1, although the solution was found at node 0 (i.e., pre-processing))
+            // Since _update_incumbent_and_fathom triggers an asterisk in the output whenever a new incumbent was found,
+            // we need to suppress this here (otherwise it would look like this incumbent was found during B&B in the root node, although it comes from pre-processing)
+            _printNewIncumbent = false;
+        }
 
-            // Hand root node to _brancher
-            // Change id from 0 to 1 since we start B&B
-            rootNodeIn = babBase::BabNode(rootNodeIn.get_pruning_score(), rootNodeIn.get_lower_bounds(), rootNodeIn.get_upper_bounds(), rootNodeIn.get_index_dataset(), 1 /*ID*/, 0 /*depth*/, rootNodeIn.get_augment_data());
-            _brancher->insert_root_node(rootNodeIn);
-            _nNodesLeft = _brancher->get_nodes_in_tree();
+        // Hand root node to _brancher
+        // Change id from 0 to 1 since we start B&B
+        rootNodeIn = babBase::BabNode(rootNodeIn.get_pruning_score(), rootNodeIn.get_lower_bounds(), rootNodeIn.get_upper_bounds(), rootNodeIn.get_index_dataset(), 0 /*parent ID*/, 1 /*ID*/, 0 /*depth*/, rootNodeIn.get_augment_data());
+        _brancher->insert_root_node(rootNodeIn);
+        _nNodesLeft = _brancher->get_nodes_in_tree();
 
 #ifdef HAVE_GROWING_DATASETS
-            // Initialize node containing solution with root node
-            incumbentNode = std::make_shared<babBase::BabNode>(rootNodeIn);
+        // Initialize node containing solution with root node
+        incumbentNode = std::make_shared<babBase::BabNode>(rootNodeIn);
 #endif    // HAVE_GROWING_DATASETS
 
-            // Initialize stuff for timing and logging
-            _timePreprocess = preprocessTime;
-            _timePassed     = preprocessTime;
-            startTime       = get_cpu_time();
-            lastTime        = -1e10;    // This is needed since the time is reset after 24h --> if we detect this, we can store it as days passed
+        // Initialize stuff for timing and logging
+        _timePreprocess  = preprocessTime;
+        _wtimePreprocess = preprocessWallTime;
+        _timePassed      = preprocessTime;
+        _wtimePassed     = preprocessWallTime;
+        startTime        = get_cpu_time();
+        startwTime       = get_wall_time();
+        lastTime         = -1e10;    // This is needed since the time is reset after 24h --> if we detect this, we can store it as days passed
+        lastwTime        = -1e10;    // This is needed since the time is reset after 24h --> if we detect this, we can store it as days passed
 #ifdef HAVE_MAiNGO_MPI
-            MAiNGO_ELSE    // Worker
-                startTime = get_cpu_time();
-                // Opening for broadcasts from master
-                MPI_Ibcast(&_bcastTag, 1, MPI_INT, 0, MPI_COMM_WORLD, &_bcastReq);
-#endif
-            MAiNGO_END_IF
+    MAiNGO_ELSE // WORKER
+        startTime = get_cpu_time();
+        // Opening for broadcasts from master
+        MPI_Ibcast(&_bcastTag, 1, MPI_INT, 0, MPI_COMM_WORLD, &_bcastReq);
+#endif  // HAVE_MAiNGO_MPI
+    MAiNGO_END_IF
 
-            // ------------------------- End 0: Process Inputs ---------------------------------
+        // ------------------------- End 0: Process Inputs ---------------------------------
 
 
-            // ---------------------------------------------------------------------------------
-            // 1-10: Main Branch-and-Bound loop
-            // ---------------------------------------------------------------------------------
-            MAiNGO_IF_BAB_MANAGER
-                _logger->print_message("\n  Entering branch-and-bound loop:\n", VERB_NORMAL, BAB_VERBOSITY);
-            MAiNGO_END_IF
+    // ---------------------------------------------------------------------------------
+    // 1-10: Main Branch-and-Bound loop
+    // ---------------------------------------------------------------------------------
+    MAiNGO_IF_BAB_MANAGER
+        _logger->print_message("\n  Entering branch-and-bound loop:\n", VERB_NORMAL, BAB_VERBOSITY);
+    MAiNGO_END_IF
 
-            // Check termination criteria: when terminating regularly, no nodes should be left; otherwise, termination occurs when hitting CPU time or node limit
-            _TERMINATION_TYPE termination = _check_termination();    // _workers == 0
-            while (termination != _TERMINATED) {
+        // Check termination criteria: when terminating regularly, no nodes should be left; otherwise, termination occurs when hitting CPU time or node limit
+        _TERMINATION_TYPE termination = _check_termination();    // _workers == 0
+        while (termination != _TERMINATED) {
 
-                babBase::BabNode currentNode;
+            babBase::BabNode currentNode;
+            babBase::BabNode sibling;
+            lbp::SiblingResults siblingResults(_w, _Nx, _Ny);
 
 #ifdef HAVE_MAiNGO_MPI
-                MPI_Status status;
-                MAiNGO_IF_BAB_MANAGER
-                    // -----------------------------------
-                    // Check incoming messages from workers
-                    // -----------------------------------
-                    // Don't accept node requests if babTree is empty or terminated
-                    if (_nNodesLeft == 0 || termination == _TERMINATED_WORKERS_ACTIVE) {
-                        const int tagCount = 5;
-                        // Allowed are exceptions and solved nodes
-                        COMMUNICATION_TAG allowedTags[tagCount] = {TAG_EXCEPTION, TAG_FOUND_INCUMBENT, TAG_SOLVED_NODE_STATUS_NORMAL,
-                                                                   TAG_SOLVED_NODE_STATUS_CONVERGED, TAG_SOLVED_NODE_STATUS_INFEAS};
-                        int flag                                = 0;
-                        // Probe for messages with allowed tags
-                        while (!flag) {
-                            for (int i = 0; i < tagCount; i++) {
-                                MPI_Iprobe(MPI_ANY_SOURCE, allowedTags[i], MPI_COMM_WORLD, &flag, &status);
-                                if (flag)
-                                    break;
-                            }
-                        }
-                    }
-                    else {
-                        // Otherwise just check for any incoming tags
-                        MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
+            MPI_Status status;
+    MAiNGO_IF_BAB_MANAGER
+            // -----------------------------------
+            // Check incoming messages from workers
+            // -----------------------------------
+            // Don't accept node requests if babTree is empty or terminated
+            if (_nNodesLeft == 0 || termination == _TERMINATED_WORKERS_ACTIVE) {
+                const int tagCount = 8;
+                // Allowed are exceptions and solved nodes
+                COMMUNICATION_TAG allowedTags[tagCount] = {TAG_EXCEPTION, TAG_FOUND_INCUMBENT, TAG_SOLVED_NODE_STATUS_NORMAL,
+                                                           TAG_SOLVED_NODE_STATUS_CONVERGED, TAG_SOLVED_NODE_STATUS_INFEAS,
+                                                           TAG_SOLVED_SIBLING_STATUS_NORMAL, TAG_SOLVED_SIBLING_STATUS_CONVERGED,
+                                                           TAG_SOLVED_SIBLING_STATUS_INFEAS};
+                int flag                                = 0;
+                // Probe for messages with allowed tags
+                while (!flag) {
+                    for (int i = 0; i < tagCount; i++) {
+                        MPI_Iprobe(MPI_ANY_SOURCE, allowedTags[i], MPI_COMM_WORLD, &flag, &status);
+                        if (flag)
+                            break;
                     }
+                }
+            }
+            else {
+                // Otherwise just check for any incoming tags
+                MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
+            }
 
-                    if (status.MPI_TAG == TAG_EXCEPTION) {
-                        // -----------------------------------
-                        // Worker ran into an exception
-                        // -----------------------------------
-                        MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-                        std::string str = "  Worker ran into exception!";
-                        throw MAiNGOMpiException(str, MAiNGOMpiException::ORIGIN_OTHER);
-                    }
-                    else if (status.MPI_TAG == TAG_FOUND_INCUMBENT) {
-                        // -----------------------------------
-                        // Worker has found new incumbent
-                        // -----------------------------------
-                        std::vector<double> incumbentBuf(_nvarWOaux + 2);
-                        MPI_Recv(incumbentBuf.data(), _nvarWOaux + 2, MPI_DOUBLE, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+            if (status.MPI_TAG == TAG_EXCEPTION) {
+                // -----------------------------------
+                // Worker ran into an exception
+                // -----------------------------------
+                MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+                std::string str = "  Worker ran into exception!";
+                throw MAiNGOMpiException(str, MAiNGOMpiException::ORIGIN_OTHER);
+            }
+            else if (status.MPI_TAG == TAG_FOUND_INCUMBENT) {
+                // -----------------------------------
+                // Worker has found new incumbent
+                // -----------------------------------
+                std::vector<double> incumbentBuf(_nvarWOaux + 2);
+                MPI_Recv(incumbentBuf.data(), _nvarWOaux + 2, MPI_DOUBLE, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
 
-                        // Store incumbent
-                        std::vector<double> newIncumbent(incumbentBuf.begin(), incumbentBuf.begin() + _nvarWOaux);
-                        double newUBD        = incumbentBuf[_nvarWOaux];
-                        unsigned incumbentID = incumbentBuf[_nvarWOaux + 1];
+                // Store incumbent
+                std::vector<double> newIncumbent(incumbentBuf.begin(), incumbentBuf.begin() + _nvarWOaux);
+                double newUBD        = incumbentBuf[_nvarWOaux];
+                unsigned incumbentID = incumbentBuf[_nvarWOaux + 1];
 
-                        _update_incumbent_and_fathom(newUBD, newIncumbent, incumbentID);
+                _update_incumbent_and_fathom(newUBD, newIncumbent, incumbentID);
 
-                        continue;
-                    }
-                    else if (status.MPI_TAG == TAG_NODE_REQUEST) {
+                continue;
+            }
+            else if (status.MPI_TAG == TAG_NODE_REQUEST) {
 
-                        // -----------------------------------
-                        // Worker requests a new node
-                        // -----------------------------------
-                        MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-                        _workCount++;
-#endif
+                // -----------------------------------
+                // Worker requests a new node
+                // -----------------------------------
+                MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+                _workCount++;
+#endif   // HAVE_MAiNGO_MPI (MANAGER IS ACTIVE)
 
-                        // -----------------------------------
-                        // 1. Node selection: Determine which node to treat next
-                        // -----------------------------------
-                        currentNode = _brancher->get_next_node();
-                        if (_maingoSettings->BAB_verbosity > VERB_NORMAL) {
-                            _print_one_node(currentNode.get_pruning_score(), currentNode);
-                        }
+                // -----------------------------------
+                // 1. Node selection: Determine which node to treat next
+                // -----------------------------------
+                currentNode = _brancher->get_next_node();
+                if (_maingoSettings->BAB_verbosity > VERB_NORMAL) {
+                    _print_one_node(currentNode.get_pruning_score(), currentNode);
+                }
+                bool sibling_iteration = false;
+                if (_maingoSettings->TS_useLowerBoundingSubsolvers && _subproblemBounds != nullptr) {
+                    // See if this node has a sibling
+                    sibling_iteration = _brancher->find_sibling(currentNode, sibling);
+                    _retreive_parent_subproblem_bounds(currentNode, sibling_iteration);
+                }
 
-#ifdef HAVE_MAiNGO_MPI
+#ifdef HAVE_MAiNGO_MPI  // (MANAGER IS ACTIVE)
+                if (sibling_iteration) {
+                    // -----------------------------------
+                    // Send siblings to worker
+                    // -----------------------------------
+                    _send_new_sibling_problem(currentNode, sibling, status.MPI_SOURCE);
+                }
+                else {
+                    // -----------------------------------
+                    // Send node to worker
+                    // -----------------------------------
+                    _send_new_problem(currentNode, status.MPI_SOURCE);
+                }   
+                #ifdef HAVE_GROWING_DATASETS
+                _send_new_dataset((*_datasets).back(), status.MPI_SOURCE);
+                #endif    // HAVE_GROWING_DATASETS
+                _nNodesLeft                                        = _brancher->get_nodes_in_tree();
+                _nodesGivenToWorkers[status.MPI_SOURCE - 1].first  = true;
+                _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = currentNode.get_pruning_score();
+
+                continue;
+
+            } // end else if (status.MPI_TAG == TAG_NODE_REQUEST)
+    MAiNGO_ELSE // switch from MANAGER TO WORKER
+#endif // HAVE_MAiNGO_MPI (WORKER ACTIVE)
+
+            //-------------------------------------
+            // Process Node:
+            // 2. Node pre-processing:
+            //      a) Constraint propagation
+            //      b) Optimization-based bound tightening (OBBT)
+            // 3. Lower bounding: solve lower bounding problem (LBP) to derive a better lower bound
+            // 4. Upper bounding: try to find a good feasible point within the current node
+            // 5. Node post-processing:
+            //      a) Duality-based bounds tightening (DBBT)
+            //      b) Probing
+            //-------------------------------------
+            bool nodeProvenInfeasible = false;
+            bool nodeConverged        = false;
+            unsigned nAddedLBSolves   = 0;
+            unsigned nAddedUBSolves   = 0;
+            std::vector<double> lbpSolutionPoint;
+            bool foundNewFeasiblePoint = false;
+            double ubpObjectiveValue   = _maingoSettings->infinity;
+            std::vector<double> ubpSolutionPoint;
+
+#ifdef HAVE_MAiNGO_MPI  // (WORKER ACTIVE)
+            // -----------------------------------
+            // Receive new node from Manager
+            // -----------------------------------
 
-                        // -----------------------------------
-                        // Send node to worker
-                        // -----------------------------------
-                        _send_new_problem(currentNode, status.MPI_SOURCE);
-#ifdef HAVE_GROWING_DATASETS
-                        _send_new_dataset((*_datasets).back(), status.MPI_SOURCE);
-#endif    // HAVE_GROWING_DATASETS
-                        _nNodesLeft                                        = _brancher->get_nodes_in_tree();
-                        _nodesGivenToWorkers[status.MPI_SOURCE - 1].first  = true;
-                        _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = currentNode.get_pruning_score();
+            // Send node request to manager
+            MPI_Request node_request;
+            MPI_Issend(NULL, 0, MPI_INT, 0, TAG_NODE_REQUEST, MPI_COMM_WORLD, &node_request);
 
-                        continue;
-                    }
-                MAiNGO_END_IF    // End of MAiNGO_IF_BAB_MANAGER
-#endif
+            bool terminate;
+            _sync_with_master(node_request, terminate);
+            if (terminate)
+                break;
 
-#ifdef HAVE_MAiNGO_MPI
-                MAiNGO_IF_BAB_WORKER
-                    // -----------------------------------
-                    // Receive new node from Manager
-                    // -----------------------------------
+            // Receive new problem and update _incumbent if necessary
+            bool sibling_iteration = (_recv_new_problem(currentNode, sibling) == babBase::enums::SIBLING_ITERATION);
+#endif // HAVE_MAiNGO_MPI (WORKER ACTIVE)
+
+            if (sibling_iteration) {
+                siblingResults.reset(std::move(currentNode), std::move(sibling), *_subproblemBounds);
+                _process_siblings(siblingResults);
+                nodeConverged        = siblingResults.converged;
+                nodeProvenInfeasible = !siblingResults.feasible;
+                nAddedLBSolves       = siblingResults.nAddedLBSolves;
+                nAddedUBSolves       = siblingResults.feasible; // If feasible we did one 
+                currentLBD           = siblingResults.parentPruningScore;
+                lbpSolutionPoint     = {};
+                foundNewFeasiblePoint = siblingResults.foundNewFeasiblePoint;
+                ubpObjectiveValue     = siblingResults.ubpObjectiveValue;
+                ubpSolutionPoint      = siblingResults.ubpSolutionPoint;
+
+#ifdef HAVE_MAiNGO_MPI // (WORKER ACTIVE)
+                // -----------------------------------
+                // Send sibling results to Manager
+                // -----------------------------------
 
-                    // Send node request to manager
-                    MPI_Request node_request;
-                    MPI_Issend(NULL, 0, MPI_INT, 0, TAG_NODE_REQUEST, MPI_COMM_WORLD, &node_request);
+                // Wait until manager accepted pending incumbent updates
+                if (_pendingIncumbentUpdate) {
+                _sync_with_master(_incumbentReq, terminate);
+                _pendingIncumbentUpdate = false;
+                if (terminate)
+                    break;
+                }
 
-                    bool terminate;
-                    _sync_with_master(node_request, terminate);
-                    if (terminate)
-                        break;
+                MPI_Request solutionRequest;
+                // Choose communication tag according to node status
+                COMMUNICATION_TAG nodeStatus = TAG_SOLVED_SIBLING_STATUS_NORMAL;
+                if (nodeConverged)
+                    nodeStatus = TAG_SOLVED_SIBLING_STATUS_CONVERGED;  // or just TAG_SOLVED_NODE_STATUS_CONVERGED?
+                if (nodeProvenInfeasible)
+                    nodeStatus = TAG_SOLVED_SIBLING_STATUS_INFEAS; // or just TAG_SOLVED_NODE_STATUS_INFEAS?
+
+                // Send communication request to manager
+                MPI_Issend(NULL, 0, MPI_INT, 0, nodeStatus, MPI_COMM_WORLD, &solutionRequest);
+                _sync_with_master(solutionRequest, terminate);
+                if (terminate) {
+                    break;
+                }
 
-                    // Receive new problem and update _incumbent if necessary
-                    _recv_new_problem(currentNode);
-#ifdef HAVE_GROWING_DATASETS
-                    _recv_new_dataset();
-#endif    // HAVE_GROWING_DATASETS
-#endif
-                    //-------------------------------------
-                    // Process Node:
-                    // 2. Node pre-processing:
-                    //      a) Constraint propagation
-                    //      b) Optimization-based bound tightening (OBBT)
-                    // 3. Lower bounding: solve lower bounding problem (LBP) to derive a better lower bound
-                    // 4. Upper bounding: try to find a good feasible point within the current node
-                    // 5. Node post-processing:
-                    //      a) Duality-based bounds tightening (DBBT)
-                    //      b) Probing
-                    //-------------------------------------
-                    bool nodeProvenInfeasible = false;
-                    bool nodeConverged        = false;
-                    unsigned nAddedLBSolves   = 0;
-                    unsigned nAddedUBSolves   = 0;
-                    currentLBD                = currentNode.get_pruning_score();
-                    std::vector<double> lbpSolutionPoint;
-                    bool foundNewFeasiblePoint = false;
-                    double ubpObjectiveValue   = _maingoSettings->infinity;
-                    std::vector<double> ubpSolutionPoint;
-
-                    std::tie(nodeProvenInfeasible, nodeConverged, nAddedLBSolves, nAddedUBSolves, currentLBD, lbpSolutionPoint, foundNewFeasiblePoint, ubpObjectiveValue, ubpSolutionPoint) = _process_node(currentNode);
+                _send_solved_sibling_problem(siblingResults, nAddedLBSolves, nAddedUBSolves, nodeStatus);
+#else // SERIAL
+                if (nodeConverged || nodeProvenInfeasible) {
+                    // set current node to dummy parent to register fathoming
+                    currentNode = babBase::BabNode(siblingResults.parentPruningScore, {}, {}, 0, 0, siblingResults.siblings[0].get_parent_ID(), 0, false);
+                }
+#endif // end HAVE_MAiNGO_MPI (WORKER ACTIVE)
 
-#ifdef HAVE_MAiNGO_MPI
-                    // -----------------------------------
-                    // Send solved node to Manager
-                    // -----------------------------------
+            } else { // end sibling iteration, begin normal iteration
+                std::tie(nodeProvenInfeasible, nodeConverged, nAddedLBSolves, nAddedUBSolves, currentLBD, lbpSolutionPoint, foundNewFeasiblePoint, ubpObjectiveValue, ubpSolutionPoint) = _process_node(currentNode);
 
-                    // Wait until manager accepted pending incumbent updates
-                    if (_pendingIncumbentUpdate) {
-                        _sync_with_master(_incumbentReq, terminate);
-                        _pendingIncumbentUpdate = false;
-                        if (terminate)
-                            break;
+#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) && ! defined(HAVE_MAiNGO_MPI)
+                    if (currentNode.get_index_dataset() > 0) {
+                        _currentLBDValid = _evaluate_lower_bound_for_validation_set(currentNode, lbpSolutionPoint, currentLBD);
+                    }
+                    else {
+                        _currentLBDValid = -_maingoSettings->infinity; // Not defined for full dataset
                     }
+#endif
+
+#ifdef HAVE_MAiNGO_MPI // (WORKER ACTIVE)
+                // -----------------------------------
+                // Send solved node to Manager
+                // -----------------------------------
 
-                    MPI_Request solutionRequest;
-                    // Choose communication tag according to node status
-                    COMMUNICATION_TAG nodeStatus = TAG_SOLVED_NODE_STATUS_NORMAL;
-                    if (nodeConverged)
-                        nodeStatus = TAG_SOLVED_NODE_STATUS_CONVERGED;
-                    if (nodeProvenInfeasible)
-                        nodeStatus = TAG_SOLVED_NODE_STATUS_INFEAS;
-
-                    // Send communication request to manager
-                    MPI_Issend(NULL, 0, MPI_INT, 0, nodeStatus, MPI_COMM_WORLD, &solutionRequest);
-                    _sync_with_master(solutionRequest, terminate);
-                    if (terminate) {
+                // Wait until manager accepted pending incumbent updates
+                if (_pendingIncumbentUpdate) {
+                    _sync_with_master(_incumbentReq, terminate);
+                    _pendingIncumbentUpdate = false;
+                    if (terminate)
                         break;
-                    }
-                    _send_solved_problem(currentNode, currentLBD, lbpSolutionPoint, nAddedLBSolves, nAddedUBSolves, nodeStatus);
+                }
 
-                MAiNGO_END_IF    // End of MAiNGO_IF_BAB_WORKER
-#endif
+                MPI_Request solutionRequest;
+                // Choose communication tag according to node status
+                COMMUNICATION_TAG nodeStatus = TAG_SOLVED_NODE_STATUS_NORMAL;
+                if (nodeConverged)
+                    nodeStatus = TAG_SOLVED_NODE_STATUS_CONVERGED;
+                if (nodeProvenInfeasible)
+                    nodeStatus = TAG_SOLVED_NODE_STATUS_INFEAS;
+
+                // Send communication request to manager
+                MPI_Issend(NULL, 0, MPI_INT, 0, nodeStatus, MPI_COMM_WORLD, &solutionRequest);
+                _sync_with_master(solutionRequest, terminate);
+                if (terminate) {
+                    break;
+                }
+                _send_solved_problem(currentNode, currentLBD, lbpSolutionPoint, nAddedLBSolves, nAddedUBSolves, nodeStatus);
+    MAiNGO_END_IF // end of MAiNGO_IF_BAB_WORKER
+#endif // HAVE_MAiNGO_MPI (WORKER ACTIVE)
+
+            } // end normal iteration
 
 #ifdef HAVE_MAiNGO_MPI
-                MAiNGO_IF_BAB_MANAGER
-                    // -----------------------------------
-                    // Receive solved node from Worker
-                    // -----------------------------------
+    MAiNGO_IF_BAB_MANAGER
+            // -----------------------------------
+            // Receive solved node from Worker
+            // -----------------------------------
 
-                    MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-                    _workCount--;
-                    _nodesGivenToWorkers[status.MPI_SOURCE - 1].first  = false;
-                    _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = _maingoSettings->infinity;
-
-                    // Receive problem
-                    COMMUNICATION_TAG nodeStatus = (COMMUNICATION_TAG)status.MPI_TAG;
-                    std::vector<double> lbpSolutionPoint;
-                    unsigned nAddedLBSolves;
-                    unsigned nAddedUBSolves;
-                    bool nodeConverged        = false;
-                    bool nodeProvenInfeasible = false;
-                    // Get node status
-                    switch (nodeStatus) {
-                        case TAG_SOLVED_NODE_STATUS_CONVERGED:
-                            nodeConverged = true;
-                            break;
-                        case TAG_SOLVED_NODE_STATUS_INFEAS:
-                            nodeProvenInfeasible = true;
-                            break;
-                        default:
-                            break;
-                    }
+            MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+            _workCount--;
+            _nodesGivenToWorkers[status.MPI_SOURCE - 1].first  = false;
+            _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = _maingoSettings->infinity;
+
+            // Receive problem
+            COMMUNICATION_TAG nodeStatus = (COMMUNICATION_TAG)status.MPI_TAG;
+            std::vector<double> lbpSolutionPoint;
+            unsigned nAddedLBSolves;
+            unsigned nAddedUBSolves;
+            bool nodeConverged        = false;
+            bool nodeProvenInfeasible = false;
+            bool sibling_iteration    = false;
+            // Get node status
+            switch (nodeStatus) {
+                case TAG_SOLVED_SIBLING_STATUS_NORMAL:
+                    sibling_iteration = true;
+                    break;
+                case TAG_SOLVED_SIBLING_STATUS_CONVERGED:
+                    nodeConverged = true;
+                    sibling_iteration = true;
+                    break;
+                case TAG_SOLVED_SIBLING_STATUS_INFEAS:
+                    nodeProvenInfeasible = true;
+                    sibling_iteration = true;
+                    break;
+                case TAG_SOLVED_NODE_STATUS_CONVERGED:
+                    nodeConverged = true;
+                    break;
+                case TAG_SOLVED_NODE_STATUS_INFEAS:
+                    nodeProvenInfeasible = true;
+                    break;
+                default:
+                    break;
+            }
 
-                    _recv_solved_problem(currentNode, currentLBD, lbpSolutionPoint, nAddedLBSolves, nAddedUBSolves, nodeStatus, status.MPI_SOURCE);
-#endif
+            _recv_solved_problem(currentNode, sibling, siblingResults, currentLBD, lbpSolutionPoint, nAddedLBSolves, nAddedUBSolves, nodeStatus, status.MPI_SOURCE);
+#else // (SERIAL) In the parallel version the worker already told the manager about the incumbent
 
-                    //-------------------------------------
-                    // 6. Process the incumbent status of the processed node
-                    //-------------------------------------
-#ifndef HAVE_MAiNGO_MPI    // In the parallel version the worker already told the manager about the incumbent
-                    if (foundNewFeasiblePoint) {
-                        // Check if this feasible point is better than the incumbent & fathom by value dominance (first remaining tree and then current node):
-                        _update_incumbent_and_fathom(ubpObjectiveValue, ubpSolutionPoint, currentNode.get_ID());
-                    }
-#endif
+            //-------------------------------------
+            // 6. Process the incumbent status of the processed node
+            //-------------------------------------
+            if (foundNewFeasiblePoint) {
+                // Check if this feasible point is better than the incumbent & fathom by value dominance (first remaining tree and then current node):
+                _update_incumbent_and_fathom(ubpObjectiveValue, ubpSolutionPoint, currentNode.get_ID());
+            }
+#endif // end SERIAL (MANAGER ACTIVE)
 
 #ifdef HAVE_GROWING_DATASETS
-                    if (currentNode.get_ID() == _incumbentNodeId) {
-                        incumbentNode = std::make_shared<babBase::BabNode>(currentNode);
+            if (currentNode.get_ID() == _incumbentNodeId) {
+                incumbentNode = std::make_shared<babBase::BabNode>(currentNode);
+            }
+
+                    // -----------------------------------
+                    // B&B algorithm with growing datasets: Evaluating validation set if applicable
+                    // -----------------------------------
+                    // After getting the currentLBD from workers
+                    // Before augmenting and pruning
+                    double validatedLBD = -_maingoSettings->infinity;
+
+                    if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) && (currentNode.get_index_dataset() > 0)) {
+                        // Validation set is only defined for reduced dataset
+                        validatedLBD = _evaluate_lower_bound_for_validation_set(currentNode, lbpSolutionPoint, currentLBD);
                     }
 
                     // -----------------------------------
-                    // MAiNGO with growing datasets: Augmenting
+                    // B&B algorithm with growing datasets: Augmenting
                     // -----------------------------------
                     if (nodeProvenInfeasible == false) {
-                        currentNode.set_augment_data(_check_whether_to_augment(currentNode, lbpSolutionPoint, currentLBD));
+                        currentNode.set_augment_data(_check_whether_to_augment(currentNode, lbpSolutionPoint, currentLBD, validatedLBD));
                         if (currentNode.get_augment_data()) {
                             unsigned int newDataIndex = _augment_dataset(currentNode);
                             if ((nodeConverged == false) || (currentNode.get_index_dataset() > 0)) {
@@ -405,195 +534,622 @@ BranchAndBound::solve(babBase::BabNode &rootNodeIn, double &solutionValue, std::
                     }
 #endif    // HAVE_GROWING_DATASETS
 
-                    // -----------------------------------
-                    // 7. Branching
-                    // -----------------------------------
-                    bool nodeReachedMinRelNodeSize = false;    // nodeReachedMinRelNodeSize indicates whether the brancher chose not to branch on the node because it is too small
+            // -----------------------------------
+            // 7. Branching
+            // -----------------------------------
+            bool nodeReachedMinRelNodeSize = false;    // nodeReachedMinRelNodeSize indicates whether the brancher chose not to branch on the node because it is too small
 
-                    // Either branch or augment
-                    // Without growing datasets: always branch (_augmentData == false by initialization and never changed)
-                    if (!currentNode.get_augment_data()) {
-                        // Inform brancher about changes in node
-                        _brancher->register_node_change(currentNode.get_ID(), currentNode);
-                        // If node is feasible but it did not converge yet branch it
-                        bool nodeReachedMinRelNodeSize = false;    // nodeReachedMinRelNodeSize indicates whether the brancher chose not to branch on the node because it is too small
-                        if ((nodeProvenInfeasible == false) && (nodeConverged == false)) {
-                            currentNode.set_pruning_score(currentLBD);
-                            bool dummy;
-                            std::tie(dummy, nodeReachedMinRelNodeSize) = _brancher->branch_on_node(currentNode, lbpSolutionPoint, currentLBD, _maingoSettings->relNodeTol);
+            // Either branch or augment
+            // Without growing datasets: always branch (_augmentData == false by initialization and never changed)
+            if (!currentNode.get_augment_data()) {
+
+                if (sibling_iteration) {
+                    // If the sibling results indicate convergence or infeasibility, the parent node (whose id is now stored in the dummy currentNode) is fathomed
+                    if (nodeConverged) {
+                        _brancher->register_node_status(currentNode, babBase::enums::NodeStatus::DOMINATED);
+                    }
+                    else if (nodeProvenInfeasible) {
+                        _brancher->register_node_status(currentNode, babBase::enums::NodeStatus::INFEASIBLE);
+                    }
+                    else {  // neither converged, nor infeasible, we will multisect
+                        // Callback to test for domination of orthant nodes resulting from multisection
+                        auto dominance_test = [&](unsigned int id, double pruningScore) {
+                            bool dominated = babBase::larger_or_equal_within_rel_and_abs_tolerance(
+                                pruningScore, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA);
+                            if (dominated) {
+                                std::ostringstream outstr;
+                                outstr << "  Node #" << id << " converged with LBD "
+                                        << pruningScore << " to UBD " << _ubd << std::endl;
+                                _logger->print_message(outstr.str(), VERB_ALL, BAB_VERBOSITY);
+                            }
+                            return dominated;
+                        };
+
+                        double previousUbd = _ubd;
+
+                        // Callback for storing orthant subproblem bounds
+                        auto postprocess_orthant = [&](
+                            babBase::BabNode &orthantNode,
+                            const std::vector<unsigned int> &bits,
+                            const std::array<std::vector<double>, 2> &subproblemBounds)
+                        {
+                            /** NOTE: If the second-stage variables corresponding to some scenario are not branched, the assiciated subproblem bounds for a given orthant node are derived from the orthant node's parent.
+                             *        Since the subproblem bounds for both siblings are at least as tight as the subproblem bounds of the parent, the former constitute a valid tightening, which we can use to compute better scenario upper bounds and pruning scores.
+                             *        However, since this tightening is not based on the orthant node domain, we cannot use it to compute strong branching scores.
+                             *        To accurately measure subproblem bound improvement resulting from a possible multisection branching of an orthant node, we therefore additionally store subproblem bounds based on the original parent node. 
+                             */
+                            // orthant subproblem bounds for scenario upper bounds and pruning [0] and for calculation of strong-branching scores [1]
+                            std::vector<std::vector<double>> OSPBs = {std::vector<double>(siblingResults.parentObjectiveBounds.size()), siblingResults.parentObjectiveBounds};
+                            #ifdef _OPENMP
+                            #pragma omp parallel for
+                            #endif
+                            for (int s = 0; s < _Ns; s++) {
+                              auto bit = bits[s];
+                              if (bit <= 1) { // siblings
+                                OSPBs[0][s] = subproblemBounds[bit][s];
+                                OSPBs[1][s] = subproblemBounds[bit][s];
+                              }
+                              else { // parent
+                                OSPBs[0][s] = std::min(subproblemBounds[0][s], subproblemBounds[1][s]);
+                              }
+                            }
+                            _parentSubproblemBounds[orthantNode.get_ID()] = {OSPBs, true};
+                        };
+
+                        _brancher->multisect_parent_node(
+                            siblingResults.parentPruningScore,
+                            siblingResults.parentObjectiveBounds,
+                            siblingResults.siblings,
+                            siblingResults.objectiveBounds,
+                            siblingResults.solutions,
+                            siblingResults.lowerBounds,
+                            siblingResults.upperBounds,
+                            _nNodesFathomed,
+                            _bestLbdFathomed,
+                            dominance_test,
+                            postprocess_orthant,
+                            _maingoSettings->relNodeTol
+                        );
+                    } // end if feasible
+                } else { // return normal node
+                    // Inform brancher about changes in node
+                    _brancher->register_node_change(currentNode.get_ID(), currentNode);
+                    if (nodeConverged) {
+                        _brancher->register_node_status(currentNode, babBase::enums::NodeStatus::DOMINATED);
+                    }
+                    else if (nodeProvenInfeasible) {
+                        _brancher->register_node_status(currentNode, babBase::enums::NodeStatus::INFEASIBLE);
+                    }
+                    // If node is feasible but it did not converge yet branch it
+                    bool nodeReachedMinRelNodeSize = false;    // nodeReachedMinRelNodeSize indicates whether the brancher chose not to branch on the node because it is too small
+                    if ((nodeProvenInfeasible == false) && (nodeConverged == false)) {
+                        if (_maingoSettings->TS_useLowerBoundingSubsolvers && _subproblemBounds != nullptr) {
+                            _parentSubproblemBounds[currentNode.get_ID()] = {{*_subproblemBounds}, true};
                         }
+#ifdef HAVE_GROWING_DATASETS
+                            if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) && (currentNode.get_index_dataset() > 0)) {
+                                // Pruning based on combined lower bound
+                                // For full dataset, we have combinedLBD = fullLBD = currentLBD and, thus, nothing to do
+                                currentLBD = _calculate_combined_lower_bound(currentLBD, validatedLBD, currentNode.get_index_dataset());
+                            }
+#endif    // HAVE_GROWING_DATASETS
+                        currentNode.set_pruning_score(currentLBD);
+                        bool dummy;
+                        std::tie(dummy, nodeReachedMinRelNodeSize) = _brancher->branch_on_node(currentNode, lbpSolutionPoint, currentLBD, _maingoSettings->relNodeTol);
                     }
+                } // end node or sibling handling
+            } // end if (!currentNode.get_augment_data())
 
 #ifdef HAVE_GROWING_DATASETS
-                    _nNodesLeft = _brancher->get_nodes_in_tree();
-                    if (_nNodesLeft == 0 && currentNode.get_index_dataset() > 0) {
-                        // If the optimality gap can't be closed based on the reduced problem
-                        // and rules like SCALING are not able to trigger augmenting,
-                        // use node containing incumbent to get correct LB
-                        currentNode.set_augment_data(true);
-                        _brancher->add_node_with_new_data_index(*incumbentNode, 0);
+            _nNodesLeft = _brancher->get_nodes_in_tree();
+            if (_nNodesLeft == 0 && currentNode.get_index_dataset() > 0) {
+                // If the optimality gap can't be closed based on the reduced problem
+                // and rules like SCALING are not able to trigger augmenting,
+                // use node containing incumbent to get correct LB
+                currentNode.set_augment_data(true);
+                _brancher->add_node_with_new_data_index(*incumbentNode, 0);
+            }
+#endif // !HAVE_GROWING_DATASETS
+
+
+            // -----------------------------------
+            // 8. Heuristic checks
+            // -----------------------------------
+            if (!_moreScalingActivated) {
+                _check_if_more_scaling_needed();
+            }
+
+            // -----------------------------------
+            // 9. Updating statistics
+            // -----------------------------------
+            // Update best fathomed lower bound properly. This ensures that the final overall lower bound is correct.
+            // In particular, nodeReachedMinRelNodeSize is needed to ensure the overall LBD does not get better than that of nodes which were thrown out because they were too small.
+            if (nodeConverged || nodeReachedMinRelNodeSize) {
+                _bestLbdFathomed = std::min(_bestLbdFathomed, currentLBD);
+            }
+            // Update overall lbd
+            _update_lowest_lbd();
+            // Update counters
+            _iterations++;
+            _ubdcnt += nAddedUBSolves;
+            _lbdcnt += nAddedLBSolves;
+
+            if (nodeConverged || nodeProvenInfeasible) {
+                _nNodesFathomed++;
+            }
+            _nNodesLeft        = _brancher->get_nodes_in_tree();
+            _nNodesMaxInMemory = std::max(_nNodesMaxInMemory, _nNodesLeft);
+
+#ifdef HAVE_MAiNGO_MPI // (MANAGER IS ACTIVE)
+    MAiNGO_END_IF // end of MAiNGO_IF_BAB_MANAGER
+#endif // end HAVE_MAiNGO_MPI
+
+            // -----------------------------------
+            // 10. Timing & output
+            // -----------------------------------
+            // Get time
+            currentTime = get_cpu_time();
+            if ((currentTime - startTime) < lastTime) { // GCOVR_EXCL_START
+                _daysPassed += 1; // NOTE: Assumes each iteration takes at most one day
+    MAiNGO_IF_BAB_MANAGER
+                std::ostringstream outstr;
+                outstr << "    Days spent: " << _daysPassed << std::endl
+                        << std::endl;
+                _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
+    MAiNGO_END_IF
+            } // GCOVR_EXCL_STOP
+            lastTime    = currentTime - startTime;
+            _timePassed = _timePreprocess + _daysPassed * 86400 + lastTime;
+
+            currentwTime = get_wall_time();
+            if ((currentwTime - startwTime) < lastwTime) { // GCOVR_EXCL_START
+                _wdaysPassed += 1; // NOTE: Assumes each iteration takes at most one day
+            } // GCOVR_EXCL_STOP
+            lastwTime    = currentwTime - startwTime;
+            _wtimePassed = _wtimePreprocess + _wdaysPassed * 86400 + lastwTime;
+    MAiNGO_IF_BAB_MANAGER
+            // Print output
+            _display_and_log_progress(currentLBD, currentNode);
+#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) && !defined(HAVE_MAiNGO_MPI)
+                    _log_nodes(currentNode, currentLBD, ubpObjectiveValue, lbpSolutionPoint);
+#endif
+
+            // -----------------------------------
+            // 11. Check termination
+            // -----------------------------------
+            termination = _check_termination();
+    MAiNGO_END_IF
+
+        }    // main while loop
+
+        // ----------------- End 1-10: Main branch and bound loop ----------------------------
+
+#ifdef HAVE_MAiNGO_MPI
+    MAiNGO_IF_BAB_MANAGER
+        // ---------------------------------------------------------------------------------
+        // Inform workers about termination
+        // ---------------------------------------------------------------------------------
+
+        _inform_worker_about_event(BCAST_TERMINATE, /* blocking broadcast */ true);
+        // Wait for all workers to get out of the while(true) loop
+        MAiNGO_MPI_BARRIER
+
+        // Clean up all pending requests -- it is not the best way, but MPI_Cancel seems to have problems on different operating systems so we do this just to make sure everything is fine
+        bool pendingRequests = true;
+        while (pendingRequests) {
+            int flag = 0;
+            MPI_Status status;
+            MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, &status);
+            if (flag) {
+                MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+            }
+            else {
+                pendingRequests = false;
+            }
+        }
+        MAiNGO_MPI_BARRIER
+
+        // Get CPU times of workers
+        std::vector<double> processTimes(_nProcs);
+        MPI_Gather(&_timePassed, 1, MPI_DOUBLE, processTimes.data(), 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+
+        // Calculate sum of compute times
+        _timePassed = 0;
+        for (int i = 0; i < _nProcs; i++) {
+            _timePassed += processTimes[i];
+        }
+    MAiNGO_ELSE
+        // ---------------------------------------------------------------------------------
+        // Inform manager about required solution time
+        // ---------------------------------------------------------------------------------
+        MAiNGO_MPI_BARRIER // Wait for all workers to get out of the while(true) loop
+        MAiNGO_MPI_BARRIER // Wait for manager to clean up all pending requests
+        // Send needed CPU time to master
+        MPI_Gather(&_timePassed, 1, MPI_DOUBLE, NULL, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+    MAiNGO_END_IF
+#endif
+
+        // ---------------------------------------------------------------------------------
+        // 11: Prepare output
+        // ---------------------------------------------------------------------------------
+    MAiNGO_IF_BAB_MANAGER
+        // Store solution for returning to MAiNGO
+        if (_foundFeas == true) {
+            solutionPoint = _incumbent;
+            solutionValue = _ubd;
+        }
+
+        // Save time passed (this is especially needed when the passed CPU time is >24h)
+        timePassed = _timePassed;
+        wallTimePassed = _wtimePassed;
+    MAiNGO_END_IF
+        // --------------------- End: Prepare output -------------------------------
+    }
+#ifdef HAVE_MAiNGO_MPI
+    catch (std::exception &e) {
+        MAiNGOMpiException e_mpi("  Error during branch-and-bound.", e, MAiNGOMpiException::ORIGIN_ME);
+        _communicate_exception_and_throw(e_mpi);
+    }
+    catch (...) {
+        std::string str;
+    MAiNGO_IF_BAB_MANAGER
+            str = "  Unknown error during branch-and-bound (Manager).";
+    MAiNGO_ELSE
+            str = "  Unknown error during branch-and-bound (Worker).";
+    MAiNGO_END_IF
+        MAiNGOMpiException e(str, MAiNGOMpiException::ORIGIN_ME);
+        _communicate_exception_and_throw(e);
+    }
+#else
+    catch (std::exception &e) // GCOVR_EXCL_START
+    {
+        throw MAiNGOException("  Error during branch-and-bound.", e);
+    }
+    catch (...)
+    {
+        throw MAiNGOException("  Unknown error during branch-and-bound.");
+    }
+// GCOVR_EXCL_STOP
+#endif
+    return _status;    // Return code is determined in _check_termination()
+}
+
+void
+BranchAndBound::setup_two_stage_branching(
+    unsigned Nx, unsigned Ny, const std::vector<double> &w,
+    std::vector<double>& subproblemBounds,
+    std::function<
+        void(
+            lbp::SiblingResults &siblingResults,
+            double ubd,
+            int obbtType
+        )
+    > solve_sibling_subproblems,
+    double alpha,
+    unsigned int k_max) {
+        _w  = w;
+        _Ns = w.size();
+        _Nx = Nx;
+        _Ny = Ny;
+#ifdef HAVE_MAINGO_MPI
+    MAiNGO_IF_BAB_MANAGER
+        // The passed subproblemBounds are from the MANAGER's LBS which is not used
+        // We create a new vector and communicate with the workers to get the correct subproblem bounds
+        _subproblemBounds = std::make_shared<std::vector<double>>(2 * _Ns, _maingoSettings->infinity);
+#endif
+    if (_brancher != nullptr)
+        _brancher->setup_two_stage_branching(
+            Nx, Ny, w,
+            std::shared_ptr<babBase::FathomObserver>(this, [](babBase::FathomObserver *) {}),
+            alpha,
+            k_max);
+#ifdef HAVE_MAINGO_MPI
+    MAiNGO_ELSE // switch to WORKER
+#endif
+        // using a noop deleter to prevent deletion of the original vector
+        _subproblemBounds = std::shared_ptr<std::vector<double>>(&subproblemBounds, [](std::vector<double> *) {});
+        _solve_sibling_subproblems = solve_sibling_subproblems;
+#ifdef HAVE_MAINGO_MPI
+    MAiNGO_END_IF
+#endif
+}
+
+#ifdef HAVE_GROWING_DATASETS
+////////////////////////////////////////////////////////////////////////////////////////
+// Function for post-processing nodes in heuristic approach with growing datasets
+void
+BranchAndBound::postprocess(const double& finalUBD)
+{
+    try {
+        // -----------------------------------
+        // 0. Initialization
+        // -----------------------------------
+
+        // Initialize variables and timing for both Manager and Workers
+        double fullLBD;
+        SUBSOLVER_RETCODE lbpStatus;
+
+        double startTime, currentTime;
+        _timePostpro = 0;
+
+        MAiNGO_IF_BAB_MANAGER
+            startTime = get_cpu_time();
+#ifdef HAVE_MAiNGO_MPI
+        MAiNGO_ELSE    // Worker
+            startTime = get_cpu_time();
+            // Opening for broadcasts from master
+            MPI_Ibcast(&_bcastTag, 1, MPI_INT, 0, MPI_COMM_WORLD, &_bcastReq);
+#endif
+        MAiNGO_END_IF
+
+        // Change to full dataset (for both Manager and Workers)
+        _LBS->change_growing_objective(0);
+
+        MAiNGO_IF_BAB_MANAGER
+            // Initialize variables for statistics
+            _nNodesProcessedPost = 0;
+            _lbdPostpro          = _lbd;
+            _nNodesTrackedPost   = _brancher->get_nodes_tracked_for_postprocessing();
+
+            // Sort nodes w.r.t. LBD such that nodes with tightest decisions are processed first if hitting CPU time limit
+            _brancher->sort_nodes_for_postprocessing();
+
+            // Inform user about start of post-processing
+			_logger->print_message("\n  Entering post-processing with \n", VERB_NORMAL, BAB_VERBOSITY);
+            std::ostringstream outstream;
+            outstream << "  Best upper bound  = " << std::setprecision(6) << finalUBD << std::endl
+                      << "  Final lower bound = " << std::setprecision(6) << _lbd << std::endl
+                      << std::endl;
+            _logger->print_message(outstream.str(), VERB_NORMAL, BAB_VERBOSITY);
+        MAiNGO_END_IF
+
+        // ------------------------- End 0: Initialization ---------------------------------
+
+
+        // ---------------------------------------------------------------------------------
+        // 1-4: Main post-processing loop
+        // ---------------------------------------------------------------------------------
+        _TERMINATION_TYPE termination = _check_termination_postprocessing(); // For workers always false
+        while (termination != _TERMINATED) {
+
+            babBase::BabNode currentNode;
+
+#ifdef HAVE_MAiNGO_MPI
+            MPI_Status status;
+            MAiNGO_IF_BAB_MANAGER
+                // -----------------------------------
+                // Check incoming messages from workers
+                // -----------------------------------
+                // Don't accept node requests if terminated
+                if (termination == _TERMINATED_WORKERS_ACTIVE) {
+                    const int tagCount = 3;
+                    // Allowed are exceptions and solved nodes
+                    COMMUNICATION_TAG allowedTags[tagCount] = { TAG_EXCEPTION, TAG_SOLVED_NODE_STATUS_NORMAL, TAG_SOLVED_NODE_STATUS_INFEAS };
+                    int flag = 0;
+                    // Probe for messages with allowed tags
+                    while (!flag) {
+                        for (int i = 0; i < tagCount; i++) {
+                            MPI_Iprobe(MPI_ANY_SOURCE, allowedTags[i], MPI_COMM_WORLD, &flag, &status);
+                            if (flag)
+                                break;
+                        }
                     }
-#endif    // !HAVE_GROWING_DATASETS
+                }
+                else {
+                    // Otherwise just check for any incoming tags
+                    MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
+                }
+
+                if (status.MPI_TAG == TAG_EXCEPTION) {
+                    // -----------------------------------
+                    // Worker ran into an exception
+                    // -----------------------------------
+                    MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+                    std::string str = "  Worker ran into exception!";
+                    throw MAiNGOMpiException(str, MAiNGOMpiException::ORIGIN_OTHER);
+                }
+                else if (status.MPI_TAG == TAG_NODE_REQUEST) {
+                    // -----------------------------------
+                    // Worker requests a new node
+                    // -----------------------------------
+                    MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+                    _workCount++;
+#endif
 
+                // -----------------------------------
+                // 1. Node selection:Next entry of vector _nodesPostprocessing
+                // -----------------------------------
+                currentNode = _brancher->get_next_node_for_postprocessing(_nNodesProcessedPost);
+                _nNodesProcessedPost++;
 
+#ifdef HAVE_MAiNGO_MPI
                     // -----------------------------------
-                    // 8. Heuristic checks
+                    // Send node to worker
                     // -----------------------------------
-                    if (!_moreScalingActivated) {
-                        _check_if_more_scaling_needed();
-                    }
+                    // Send Request Answer
+                    MPI_Ssend(NULL, 0, MPI_INT, status.MPI_SOURCE, TAG_NEW_NODE_NO_INCUMBENT, MPI_COMM_WORLD);
+                    // Send new node
+                    send_babnode(currentNode, status.MPI_SOURCE);
 
-                    // -----------------------------------
-                    // 9. Updating statistics
-                    // -----------------------------------
-                    // Update best fathomed lower bound properly. This ensures that the final overall lower bound is correct.
-                    // In particular, nodeReachedMinRelNodeSize is needed to ensure the overall LBD does not get better than that of nodes which were thrown out because they were too small.
-                    if (nodeConverged || nodeReachedMinRelNodeSize) {
-                        _bestLbdFathomed = std::min(_bestLbdFathomed, currentLBD);
-                    }
-                    // Update overall lbd
-                    _update_lowest_lbd();
-                    // Update counters
-                    _iterations++;
-                    _ubdcnt += nAddedUBSolves;
-                    _lbdcnt += nAddedLBSolves;
-                    if (nodeConverged || nodeProvenInfeasible) {
-                        _nNodesFathomed++;
-                    }
-                    _nNodesLeft        = _brancher->get_nodes_in_tree();
-                    _nNodesMaxInMemory = std::max(_nNodesMaxInMemory, _nNodesLeft);
-                MAiNGO_END_IF    // End of MAiNGO_IF_BAB_MANAGER in line 325
+                    _nodesGivenToWorkers[status.MPI_SOURCE - 1].first = true;
+                    _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = currentNode.get_pruning_score();
+
+                    continue;
+                }
+            MAiNGO_END_IF    // End of MAiNGO_IF_BAB_MANAGER
+#endif
 
+#ifdef HAVE_MAiNGO_MPI
+            MAiNGO_IF_BAB_WORKER
                 // -----------------------------------
-                // 10. Timing & output
+                // Receive new node from Manager
                 // -----------------------------------
-                // Get time
-                currentTime = get_cpu_time();
-                if ((currentTime - startTime) < lastTime) { // GCOVR_EXCL_START
-                    _daysPassed += 1;
-                    MAiNGO_IF_BAB_MANAGER
-                        std::ostringstream outstr;
-                        outstr << "    Days spent: " << _daysPassed << std::endl
-                               << std::endl;
-                        _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
-                    MAiNGO_END_IF
-                }
-                // GCOVR_EXCL_STOP
-                _timePassed = _timePreprocess + _daysPassed * 86400 + (currentTime - startTime);
-                lastTime    = currentTime - startTime;
-                MAiNGO_IF_BAB_MANAGER
-                    // Print output
-                    _display_and_log_progress(currentLBD, currentNode);
-#if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS) && !defined(HAVE_MAiNGO_MPI)
-                    _log_nodes(currentNode, currentLBD, _currentLBDFull, ubpObjectiveValue, lbpSolutionPoint, _lbpSolutionPointFull);
+
+                // Send node request to manager
+                MPI_Request node_request;
+                MPI_Issend(NULL, 0, MPI_INT, 0, TAG_NODE_REQUEST, MPI_COMM_WORLD, &node_request);
+
+                bool terminate;
+                _sync_with_master(node_request, terminate);
+                if (terminate)
+                    break;
+
+                // Receive request answer
+                MPI_Status status;
+                MPI_Recv(NULL, 0, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
+                // Receive new node
+                recv_babnode(currentNode, 0, _nvar);
 #endif
+            // -----------------------------------
+            // 2. Lower bounding: solve lower bounding problem (LBP) with full dataset to derive a valid lower bound
+            // -----------------------------------
+            std::vector<double> lbpSolutionPoint;
+            lbp::LbpDualInfo dualInfo;
 
-                    // -----------------------------------
-                    // 11. Check termination
-                    // -----------------------------------
-                    termination = _check_termination();
-                MAiNGO_END_IF
+            lbpStatus = _LBS->solve_LBP(currentNode, fullLBD, lbpSolutionPoint, dualInfo);
+
+#ifdef HAVE_MAiNGO_MPI
+                // -----------------------------------
+                // Send solved node to Manager
+                // -----------------------------------
 
-            }    // main while loop
+                MPI_Request solutionRequest;
+                // Choose communication tag according to node status
+                COMMUNICATION_TAG nodeStatus = TAG_SOLVED_NODE_STATUS_NORMAL;
+                if (lbpStatus == SUBSOLVER_INFEASIBLE) {
+                    nodeStatus = TAG_SOLVED_NODE_STATUS_INFEAS;
+                }
 
-            // ----------------- End 1-10: Main branch and bound loop ----------------------------
+                // Send communication request to manager
+                MPI_Issend(NULL, 0, MPI_INT, 0, nodeStatus, MPI_COMM_WORLD, &solutionRequest);
+                _sync_with_master(solutionRequest, terminate);
+                if (terminate) {
+                    break;
+                }
+                if (nodeStatus == TAG_SOLVED_NODE_STATUS_NORMAL) {
+                    // Send fullLBD and corresponding node
+                    MPI_Ssend(&fullLBD, 1, MPI_DOUBLE, 0, TAG_SOLVED_NODE_LBD, MPI_COMM_WORLD);
+                    send_babnode(currentNode, 0);
+                }
+            MAiNGO_END_IF    // End of MAiNGO_IF_BAB_WORKER
+#endif
 
 #ifdef HAVE_MAiNGO_MPI
             MAiNGO_IF_BAB_MANAGER
-                // ---------------------------------------------------------------------------------
-                // Inform workers about termination
-                // ---------------------------------------------------------------------------------
-
-                _inform_worker_about_event(BCAST_TERMINATE, /* blocking broadcast */ true);
-                // Wait for all workers to get out of the while(true) loop
-                MAiNGO_MPI_BARRIER
-                    // Clean up all pending requests -- it is not the best way, but MPI_Cancel seems to have problems on different operating systems so we do this just to make sure everything is fine
-                    bool pendingRequests = true;
-                while (pendingRequests) {
-                    int flag = 0;
-                    MPI_Status status;
-                    MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, &status);
-                    if (flag) {
-                        MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
-                    }
-                    else {
-                        pendingRequests = false;
-                    }
+                // -----------------------------------
+                // Receive solved node from Worker
+                // -----------------------------------
+                // Receive communication request from worker
+                MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+                _workCount--;
+                _nodesGivenToWorkers[status.MPI_SOURCE - 1].first = false;
+                _nodesGivenToWorkers[status.MPI_SOURCE - 1].second = _maingoSettings->infinity;
+
+                // Catch lbpStatus
+                COMMUNICATION_TAG nodeStatus = (COMMUNICATION_TAG)status.MPI_TAG;
+                if (nodeStatus == TAG_SOLVED_NODE_STATUS_NORMAL) {
+                    lbpStatus = SUBSOLVER_FEASIBLE;
+                    // Receive fullLBD and corresponding node
+                    MPI_Recv(&fullLBD, 1, MPI_DOUBLE, status.MPI_SOURCE, TAG_SOLVED_NODE_LBD, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+                    recv_babnode(currentNode, status.MPI_SOURCE, _nvar);
+                }
+                else if (nodeStatus == TAG_SOLVED_NODE_STATUS_INFEAS) {
+                    lbpStatus = SUBSOLVER_INFEASIBLE;
                 }
-                MAiNGO_MPI_BARRIER
+#endif
+
+            // -----------------------------------
+            // 3. Update best lower bound
+            // -----------------------------------
+            // Infeasible nodes and nodes with reducedLBD > finalUBD where pruned correctly
+            // Update final LBD for all other nodes and print information about change
+            if (lbpStatus == SUBSOLVER_FEASIBLE
+                && !(babBase::larger_or_equal_within_rel_and_abs_tolerance(fullLBD, finalUBD, _maingoSettings->epsilonR, _maingoSettings->epsilonA)))
+            {
+                _print_postprocessed_node(currentNode.get_ID(), currentNode.get_pruning_score(), fullLBD);
+                _lbdPostpro = std::min(_lbdPostpro, fullLBD);
+            }
+
+            // -----------------------------------
+            // 4. Check termination
+            // -----------------------------------
+            currentTime  = get_cpu_time();
+            _timePostpro = currentTime - startTime;
+
+            termination  = _check_termination_postprocessing();
+
+            MAiNGO_END_IF    // End of MAiNGO_IF_BAB_MANAGER
 
-                    // Get CPU times of workers
-                    std::vector<double>
-                        processTimes(_nProcs);
-                MPI_Gather(&_timePassed, 1, MPI_DOUBLE, processTimes.data(), 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+        }    // main while loop
 
-                // Calculate sum of compute times
-                _timePassed = 0;
-                for (int i = 0; i < _nProcs; i++) {
-                    _timePassed += processTimes[i];
+        // ----------------- End 1-4: Main post-processing loop ----------------------------
+
+#ifdef HAVE_MAiNGO_MPI
+            // ---------------------------------------------------------------------------------
+            // Inform workers about termination
+            // ---------------------------------------------------------------------------------
+        MAiNGO_IF_BAB_MANAGER
+
+            _inform_worker_about_event(BCAST_TERMINATE, /* blocking broadcast */ true);
+            // Wait for all workers to get out of the while(true) loop
+            MAiNGO_MPI_BARRIER
+            // Clean up all pending requests -- it is not the best way, but MPI_Cancel seems to have problems on different operating systems so we do this just to make sure everything is fine
+            bool pendingRequests = true;
+            while (pendingRequests) {
+                int flag = 0;
+                MPI_Status status;
+                MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, &status);
+                if (flag) {
+                    MPI_Recv(NULL, 0, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                 }
-                MAiNGO_ELSE
-                    // ---------------------------------------------------------------------------------
-                    // Inform manager about required solution time
-                    // ---------------------------------------------------------------------------------
-                    MAiNGO_MPI_BARRIER        // Wait for all workers to get out of the while(true) loop
-                        MAiNGO_MPI_BARRIER    // Wait for manager to clean up all pending requests
-                            // Send needed CPU time to master
-                            MPI_Gather(&_timePassed, 1, MPI_DOUBLE, NULL, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
-                MAiNGO_END_IF
-#endif
+                else {
+                    pendingRequests = false;
+                }
+            }
+            MAiNGO_MPI_BARRIER
 
-                // ---------------------------------------------------------------------------------
-                // 11: Prepare output
-                // ---------------------------------------------------------------------------------
-                MAiNGO_IF_BAB_MANAGER
-                    // Store solution for returning to MAiNGO
-                    if (_foundFeas == true) {
-                        solutionPoint = _incumbent;
-                        solutionValue = _ubd;
-                    }
+        MAiNGO_ELSE
 
-                    // Save time passed (this is especially needed when the passed CPU time is >24h)
-                    timePassed = _timePassed;
-                MAiNGO_END_IF
-        // --------------------- End: Prepare output -------------------------------
+            MAiNGO_MPI_BARRIER    // Wait for all workers to get out of the while(true) loop
+            MAiNGO_MPI_BARRIER    // Wait for manager to clean up all pending requests
+
+        MAiNGO_END_IF
+#endif
     }
 #ifdef HAVE_MAiNGO_MPI
-    catch (std::exception &e) {
-        MAiNGOMpiException e_mpi("  Error during branch-and-bound.", e, MAiNGOMpiException::ORIGIN_ME);
+    catch (std::exception& e) {
+        MAiNGOMpiException e_mpi("  Error during post-processing.", e, MAiNGOMpiException::ORIGIN_ME);
         _communicate_exception_and_throw(e_mpi);
     }
     catch (...) {
         std::string str;
         MAiNGO_IF_BAB_MANAGER
-            str = "  Unknown error during branch-and-bound (Manager).";
-            MAiNGO_ELSE
-                str = "  Unknown error during branch-and-bound (Worker).";
-            MAiNGO_END_IF
+            str = "  Unknown error during post-processing (Manager).";
+        MAiNGO_ELSE
+            str = "  Unknown error during post-processing (Worker).";
+        MAiNGO_END_IF
             MAiNGOMpiException e(str, MAiNGOMpiException::ORIGIN_ME);
-            _communicate_exception_and_throw(e);
+        _communicate_exception_and_throw(e);
     }
 #else
-catch (std::exception &e) // GCOVR_EXCL_START
-{
-    throw MAiNGOException("  Error during branch-and-bound.", e);
-}
-catch (...)
-{
-    throw MAiNGOException("  Unknown error during branch-and-bound.");
-}
-// GCOVR_EXCL_STOP
+    catch (std::exception& e)
+    {
+        throw MAiNGOException("  Error during post-processing.", e);
+    }
+    catch (...)
+    {
+        throw MAiNGOException("  Unknown error during post-processing.");
+    }
 #endif
-    return _status;    // Return code is determined in _check_termination()
 }
+#endif    // HAVE_GROWING_DATASETS
 
-
-////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////
 // function processing the current node (pre-processing, LBP, UBP, post-processing)
 std::tuple<bool, bool, int, int, double, std::vector<double>, bool, double, std::vector<double>>
 BranchAndBound::_process_node(babBase::BabNode &currentNode)
 {
-
+    PROFILE_FUNCTION()
 
     // Prepare output variables
     bool nodeProvenInfeasible = false;
@@ -616,8 +1172,13 @@ BranchAndBound::_process_node(babBase::BabNode &currentNode)
     // -----------------------------------
     // 2. Node pre-processing: Constraint propagation and Optimization-based bound tightening (OBBT)
     // -----------------------------------
-    nodeProvenInfeasible = _preprocess_node(currentNode);    // Tightens bounds and possibly proves infeasibilty
+    if ( (_maingoSettings->growing_approach == GROW_APPR_DETERMINISTIC) || (currentNode.get_index_dataset() == 0) ) {
+        // Without growing datasets: growing_approach = GROW_APPR_DETERMINISTIC (default) and index dataset == 0 (no augmentation)
+        // With growing datasets: Only in this case bound tightening techniques are guaranteed to be correct
+        nodeProvenInfeasible = _preprocess_node(currentNode);    // Tightens bounds and possibly proves infeasibilty
+    }
 
+    // Infeasible nodes get infinity as currentLBD
     if (nodeProvenInfeasible == false) {    // Only continue with 3-5 if node has not been proven to be infeasible
 
         // -----------------------------------
@@ -626,6 +1187,9 @@ BranchAndBound::_process_node(babBase::BabNode &currentNode)
         lbp::LbpDualInfo dualInfo;
         std::tie(nodeProvenInfeasible, nodeConverged, currentLBD, lbpSolutionPoint, dualInfo) = _solve_LBP(currentNode);
         lbdcnt++;
+        if (_maingoSettings->growing_useResampling && (currentNode.get_index_dataset()==1)) {
+            lbdcnt++; // Within _solve_LBP a second dataset, namely the resampled initial dataset, has been solved
+        }
 
         if ((nodeProvenInfeasible == false) && (nodeConverged == false)) {
 
@@ -643,13 +1207,14 @@ BranchAndBound::_process_node(babBase::BabNode &currentNode)
                 // -----------------------------------
                 // 5. Node post-processing: Duality-based bounds tightening (DBBT) & probing
                 // -----------------------------------
-                nodeProvenInfeasible = _postprocess_node(currentNode, lbpSolutionPoint, dualInfo);
+                if ((_maingoSettings->growing_approach == GROW_APPR_DETERMINISTIC) || (currentNode.get_index_dataset() == 0)) {
+                    // Without growing datasets: growing_approach = GROW_APPR_DETERMINISTIC (default) and index dataset == 0 (no augmentation)
+                    // With growing datasets: Only in this case bound tightening techniques are guaranteed to be correct
+                    nodeProvenInfeasible = _postprocess_node(currentNode, lbpSolutionPoint, dualInfo);
+                }
 
             }    // end if(nodeConverged==false)
-
         }    // end if(((nodeProvenInfeasible==false)&&(nodeConverged==false))
-
-
     }    // end if(nodeProvenInfeasible==false)
 
     // Infeasible nodes get infinity as currentLBD
@@ -663,15 +1228,128 @@ BranchAndBound::_process_node(babBase::BabNode &currentNode)
         _lbpSolutionPointFull = lbpSolutionPoint;
     }
 #endif
+
+    if (nodeConverged) {
+        currentNode.set_pruning_score(currentLBD);
+    }
+
     return std::make_tuple(nodeProvenInfeasible, nodeConverged, lbdcnt, ubdcnt, currentLBD, lbpSolutionPoint, foundNewFeasiblePoint, ubpObjectiveValue, ubpSolutionPoint);
 }
 
+void BranchAndBound::_process_siblings(lbp::SiblingResults & siblingResults) {
+    int obbtType = -1;  // don't do OBBT
+    if (_maingoSettings->BAB_alwaysSolveObbt) {
+        if (_foundFeas == true) {
+            obbtType = lbp::OBBT_FEASOPT;
+        }
+        else {
+            obbtType = lbp::OBBT_FEAS;
+        }
+    }
+    _solve_sibling_subproblems(siblingResults, _ubd, obbtType);
+
+    if (!siblingResults.feasible) {
+        return;
+    }
+    /**
+     * Generate a consistent starting point for upper bounding from the solutions of both sibling subproblems
+     * In contrast to nodes obtained from first-stage branching, where we only need to aggregate the x part of subproblem solutions, here we have split second-stage domains.
+     * Thus we need to decide on a consistent way to aggregate the solutions of both the x and the y part of solutions from subproblems of both siblings.
+     * 
+     * There are several possibilities how to do this:
+     * 1: solve a single upper bounding problem using the x part of the scenario that is closest to the mean of all x parts from all feasible subproblems of both siblings.
+     *    1.1 Either the scenario for which this is the case determines the sibling from which the y part is used (consistency over scenarios with chosen x part, but somewhat biased towards the orthant corresponding to the selected sibling),
+     *    1.2 or the scenario average over y parts over both siblings is used (unbiased starting point in parent domain, but likely inconsistent with the selected x part).
+     * 2: solve two upper bounding problems, one for each sibling using the above aggregation separately for each sibling.
+     *    This is analogous to nodes obtained from first-stage branching, however, it biases search towards the orthant nodes corresponding to the siblings, and is thus somewhat arbitrary.
+     * 3: solve 2 Ns upper bounding problems, one for each scenario of each sibling; use the x-parts withouth aggregation and aggregate the y-parts per scenario over both siblings.
+     *    While this is costly, the cost seems reasonable, given that 2^k nodes are produced.
+     *    However it is likely that the x parts repeat for large Ns, so we might want to impose a minimum distance to ensure that we don't solve the same problem over and over again.
+     *    This could be done via clustering algorithms.
+     * 
+     * In the following we implement 1.1, as it is straightforward and the bias towards the orthant corresponding to the selected sibling is relativized by the fact that the selected x part is somewhat representative of all x parts.
+     */
+
+    // initialize x part of solution with mean of xs
+    siblingResults.ubpSolutionPoint = std::vector<double>(siblingResults.Nx + siblingResults.Ns * siblingResults.Ny, 0.0);
+    for (unsigned int j = 0; j < 2; ++j) {
+      for (unsigned int s = 0; s < siblingResults.Ns; s++) {
+        auto & solution_s = siblingResults.solutions[j][s];
+        if (solution_s.size() == 0) {
+          // If the scenario subproblem for one sibling is infeasible, we use the solution of the other sibling, effectively increasing the weight of the feasible sibling.
+          solution_s = siblingResults.solutions[(j + 1) % 2][s];
+        }
+        // average over first-stage variable values of both siblings
+        for (unsigned i = 0; i < siblingResults.Nx; i++) {
+          siblingResults.ubpSolutionPoint[i] += 0.5 * siblingResults.w[s] * solution_s[i];
+        }
+      }
+    }
+
+    // Find the s for which xs is closest to the mean over the xs in the l2 norm
+    auto & lb = siblingResults.get_parent_lower_bounds();
+    auto & ub = siblingResults.get_parent_upper_bounds();
+    int s_opt         = -1;
+    int j_opt         = -1;
+    double minRelDist = _maingoSettings->infinity;
+    for (unsigned int j = 0; j < 2; ++j) {
+      for (unsigned int s = 0; s < siblingResults.Ns; s++) {
+        double sumSquaredRelDists = 0.0, dist = 0.0, initGap=0.0, relDist = 0.0;
+        auto & solution_s = siblingResults.solutions[j][s];
+        if (solution_s.size() == 0) {
+          // An infeasible scenario subproblem cannot be selected
+          continue;
+        }
+        for (unsigned int i = 0; i < siblingResults.Nx; i++) {
+          dist                = solution_s[i] - siblingResults.ubpSolutionPoint[i];
+          initGap             = _upperVarBoundsOrig[i] - _lowerVarBoundsOrig[i];
+          relDist             = (initGap > 0) ? (dist / initGap) : 0.;
+          sumSquaredRelDists += std::pow(relDist, 2);
+        }
+        if (sumSquaredRelDists < minRelDist) {
+          minRelDist = sumSquaredRelDists;
+          s_opt      = s;
+          j_opt      = j;
+          if (sumSquaredRelDists == 0) {
+            break;
+          }
+        }
+      }
+    }
+    
+    // Update the solution point
+    for (unsigned int i = 0; i < siblingResults.Nx; i++) {
+      siblingResults.ubpSolutionPoint[i] = siblingResults.solutions[j_opt][s_opt][i];
+    }
+    for (unsigned int s = 0; s < siblingResults.Ns; s++) {
+      for (unsigned int i = 0; i < siblingResults.Ny; i++) {
+        siblingResults.ubpSolutionPoint[siblingResults.Nx + s * siblingResults.Ny + i] = siblingResults.solutions[j_opt][s][siblingResults.Nx + i];
+      }
+    }
+
+    // only used for bounds and id
+    auto fakeParent = babBase::BabNode(siblingResults.parentPruningScore, lb, ub, 0, -1, siblingResults.siblings[0].get_parent_ID(), siblingResults.siblings[0].get_depth(), false);
+    std::tie(siblingResults.foundNewFeasiblePoint, siblingResults.converged, siblingResults.ubpObjectiveValue) = _solve_UBP(fakeParent, siblingResults.ubpSolutionPoint, siblingResults.parentPruningScore);
+
+    if (siblingResults.foundNewFeasiblePoint) {
+        #ifdef HAVE_MAiNGO_MPI
+            // Check if this feasible point is better than the incumbent & inform manager about it
+            if (siblingResults.ubpObjectiveValue < _ubd) {
+                _send_incumbent(siblingResults.ubpObjectiveValue, siblingResults.ubpSolutionPoint, fakeParent.get_ID());
+            }
+        #else
+            // Check if this feasible point is better than the incumbent & fathom by value dominance (first remaining tree and then current node):
+            _update_incumbent_and_fathom(siblingResults.ubpObjectiveValue, siblingResults.ubpSolutionPoint, fakeParent.get_ID());
+        #endif
+    }
+}
 
 ////////////////////////////////////////////////////////////////////////////////////////
 // function for pre-processing the current node (constraint propagation + OBBT)
 bool
 BranchAndBound::_preprocess_node(babBase::BabNode &currentNode)
 {
+    PROFILE_FUNCTION()
 
     bool nodeProvenInfeasible = false;
 
@@ -693,7 +1371,7 @@ BranchAndBound::_preprocess_node(babBase::BabNode &currentNode)
 
     // Check if constraint propagation proved node to be infeasible
     if (conPropStatus == TIGHTENING_INFEASIBLE) {
-
+        
         nodeProvenInfeasible = true;
     }
     // If constraint propagation did not prove node to be infeasible,
@@ -740,6 +1418,27 @@ BranchAndBound::_solve_LBP(const babBase::BabNode &currentNode)
 
     SUBSOLVER_RETCODE lbpStatus = _LBS->solve_LBP(currentNode, currentLBD, lbpSolutionPoint, dualInfo);
 
+#ifdef HAVE_GROWING_DATASETS
+    if (_maingoSettings->growing_useResampling && (currentNode.get_index_dataset() == 1)) {
+        double resampledLBD = parentLBD;
+
+        // Using input and output dummies to not overwrite information obtained with initial dataset
+        lbp::LbpDualInfo dualInfo2;
+        std::vector<double> lbpSolutionPoint2;
+
+        _LBS->change_growing_objective_for_resampling();
+        SUBSOLVER_RETCODE lbpStatus2 = _LBS->solve_LBP(currentNode, resampledLBD, lbpSolutionPoint2, dualInfo2);
+        if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) || (resampledLBD > currentLBD) ) {
+            // Do not decrease (weaken) currentLBD when using deterministic approach
+            // But only correct currentLBD when using heuristic approaches
+            currentLBD = (currentLBD + resampledLBD) / 2;
+        }
+
+        // Reset to former dataset
+        _LBS->change_growing_objective(currentNode.get_index_dataset());
+    }
+#endif    // HAVE_GROWING_DATASETS
+
     if (currentLBD < parentLBD) {   // GCOVR_EXCL_START
         std::ostringstream outstr;
         outstr << "  LBD obtained for node " << currentNode.get_ID() << " is lower than LBD of its parent node. Using parent LBD." << std::endl;
@@ -783,6 +1482,7 @@ BranchAndBound::_solve_LBP(const babBase::BabNode &currentNode)
 std::tuple<bool /*foundNewFeasiblePoint*/, bool /*nodeConverged*/, double /*ubpObjectiveValue*/>
 BranchAndBound::_solve_UBP(const babBase::BabNode &currentNode, std::vector<double> &ubpSolutionPoint, const double currentLBD)
 {
+    PROFILE_FUNCTION()
 
     bool foundNewFeasiblePoint  = false;
     bool nodeConverged          = false;
@@ -795,16 +1495,35 @@ BranchAndBound::_solve_UBP(const babBase::BabNode &currentNode, std::vector<doub
         // Sanity check to detect inconsistent bounding operations
         if ((ubpObjectiveValue < currentLBD - _maingoSettings->epsilonA) && (ubpObjectiveValue < currentLBD - std::fabs(ubpObjectiveValue) * _maingoSettings->epsilonR)) {   // GCOVR_EXCL_START
             if (ubpObjectiveValue > -_maingoSettings->infinity) {
-                std::ostringstream errmsg;
-                errmsg << std::endl
-                       << "  Error while checking objective returned by upper bounding solver: Upper bound < lower bound:" << std::endl;
-                errmsg << "  LBD = " << std::setprecision(16) << currentLBD << std::endl
-                       << "UBD = " << ubpObjectiveValue << std::endl;
+#ifdef HAVE_GROWING_DATASETS
+                if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC)
+                    && ((currentNode.get_index_dataset() > 0) || currentNode.get_augment_data()) ) {
+                    // May also happen for full dataset if LBD is inherited from parent node with reduced dataset
+                    babBase::BabNode newNode = currentNode;
+                    newNode.set_pruning_score(-_maingoSettings->infinity);
+                    unsigned int newDataIndex = _augment_dataset(newNode);
+                    _brancher->add_node_with_new_data_index(newNode, newDataIndex);
+
+                    std::ostringstream outstr;
+                    outstr << "  Warning: Upper bound < heuristic lower bound in node " << currentNode.get_ID() << " . Augmenting dataset and resetting lower bound to -infinity..." << std::endl;
+                    _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
+                }
+                else {
+#endif    // HAVE_GROWING_DATASETS
+                    std::ostringstream errmsg;
+                    errmsg << std::endl
+                           << "  Error while checking objective returned by upper bounding solver: Upper bound < lower bound:" << std::endl;
+                    errmsg << "  LBD = " << std::setprecision(16) << currentLBD << std::endl
+                           << "UBD = " << ubpObjectiveValue << std::endl;
 #ifdef HAVE_MAiNGO_MPI
-                throw MAiNGOMpiException(errmsg.str(), currentNode, MAiNGOMpiException::ORIGIN_ME);
+                    throw MAiNGOMpiException(errmsg.str(), currentNode, MAiNGOMpiException::ORIGIN_ME);
 #else
-                throw MAiNGOException(errmsg.str());
+                    throw MAiNGOException(errmsg.str());
 #endif
+#ifdef HAVE_GROWING_DATASETS
+                } // else
+#endif // HAVE_GROWING_DATASETS
+
             }
             else {
                 ubpObjectiveValue = _maingoSettings->infinity;
@@ -843,6 +1562,7 @@ BranchAndBound::_solve_UBP(const babBase::BabNode &currentNode, std::vector<doub
 bool
 BranchAndBound::_postprocess_node(babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const lbp::LbpDualInfo &dualInfo)
 {
+    PROFILE_FUNCTION()
 
     bool nodeProvenInfeasible = false;
     if ((dualInfo.multipliers.size() != _nvar) || (lbpSolutionPoint.size() != _nvar)) {    // We can only do DBBT or probing if we have multipliers and a solution point from LBP
@@ -898,6 +1618,14 @@ BranchAndBound::_update_incumbent_and_fathom(const double solval, const std::vec
         _informedWorkerAboutIncumbent = std::vector<bool>(_nProcs - 1, false);
 #endif
 
+#ifdef HAVE_GROWING_DATASETS
+        // Before pruning, update list of nodes for post-processing (if post-processing turned on)
+        if ((_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) && (_maingoSettings->growing_maxTimePostprocessing > 0))
+        {
+            _brancher->update_nodes_for_postprocessing(solval);
+        }
+#endif    // HAVE_GROWING_DATASETS
+
         // Inform _brancher about new incumbent
         size_t nodesBefore         = _brancher->get_nodes_in_tree();
         double smallestFathomedLBD = _brancher->decrease_pruning_score_threshold_to(_ubd);    // Here, nodes with lbd exceeding the new _ubd are fathomed
@@ -942,10 +1670,119 @@ BranchAndBound::_update_lowest_lbd()
 
 
 #ifdef HAVE_GROWING_DATASETS
+////////////////////////////////////////////////////////////////////////////////////////
+// auxiliary function calling augmentation rule CONST
+bool
+BranchAndBound::_call_augmentation_rule_const(const int depth)
+{
+    // Augment nodes in depth which is a multiple of freq
+    return !((int)fmod(depth, (double)_maingoSettings->growing_augmentFreq));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// auxiliary function calling augmentation rule SCALING
+bool
+BranchAndBound::_call_augmentation_rule_scaling(const int indexDataset, const double currentLBD)
+{
+    // Augment if linear scaling of reduced LB to full LB suggests pruning
+    double scaledLBD = _maingoSettings->growing_augmentWeight * currentLBD * (double)(*_datasets)[0].size() / (double)(*_datasets)[indexDataset].size();
+    return babBase::larger_or_equal_within_rel_and_abs_tolerance(scaledLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// function for evaluating the lower bounding solution point for the validation set
+double
+BranchAndBound::_evaluate_lower_bound_for_validation_set(const babBase::BabNode& currentNode, const std::vector<double>& lbpSolutionPoint, const double currentLBD)
+{
+    // Sanity check
+    if (currentNode.get_index_dataset() == 0) {
+        std::ostringstream errmsg;
+        errmsg << "  Error in BaB - evaluating lower bound for validation set: validation set for full dataset is empty by definition." << std::endl;
+        throw MAiNGOException(errmsg.str());
+    }
+
+    double validatedLBD = _maingoSettings->infinity;
+
+    // Evaluate validation set
+    _LBS->change_growing_objective(-currentNode.get_index_dataset());
+    _LBS->evaluate_LBP(currentNode, lbpSolutionPoint, validatedLBD);
+
+    // Reset current dataset to former training set
+    _LBS->change_growing_objective(currentNode.get_index_dataset());
+
+    return validatedLBD;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// function for calculating approximated lower bound as combination of reduced and validated lower bound
+double
+BranchAndBound::_calculate_combined_lower_bound(const double currentLBD, const double validatedLBD, const unsigned int indexDataset)
+{
+    double combiLBD;
+
+    // approximate lower bound by convex combination of currentLBD and validated LBD
+    if (_maingoSettings->growing_approach == GROW_APPR_MSEHEURISTIC) {
+        // objective of LBP is mean squared error
+        // Thus, use dataRatio * reduced LBD + (1-dataRatio) * validated LBD
+        double dataRatio = (double)(*_datasets)[indexDataset].size() / (double)(*_datasets)[0].size();
+        combiLBD = dataRatio * currentLBD + (1 - dataRatio) * validatedLBD;
+    }
+    else {
+        // objective of LBP is summed squared error
+        // Thus, use reduced LBD + validated LBD
+        combiLBD = currentLBD + validatedLBD;
+    }
+
+    return combiLBD;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// auxiliary function calling augmentation rule VALID
+bool
+BranchAndBound::_call_augmentation_rule_valid(const double validatedLBD)
+{
+    // Augment if validated LB suggests pruning
+    return babBase::larger_or_equal_within_rel_and_abs_tolerance(validatedLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// auxiliary function calling augmentation rule COMBI
+bool
+BranchAndBound::_call_augmentation_rule_combi(const double currentLBD, const double validatedLBD, const unsigned int indexDataset)
+{
+    // Augment if combined LB suggests pruning
+    double combiLBD = _calculate_combined_lower_bound(currentLBD, validatedLBD, indexDataset);
+    return babBase::larger_or_equal_within_rel_and_abs_tolerance(combiLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// auxiliary function calling augmentation rule TOL
+bool
+BranchAndBound::_call_augmentation_rule_tol(const double currentLBD, const double validatedLBD, const unsigned int indexDataset)
+{
+    // Augment if upper bound is smaller than the lower bound used for pruning plus an additional absolute or relative tolerance
+    if (_maingoSettings->growing_approach > GROW_APPR_DETERMINISTIC) {
+        // Combined lower bound used for pruning
+        double combiLBD = _calculate_combined_lower_bound(currentLBD, validatedLBD, indexDataset);
+        return babBase::larger_or_equal_within_rel_and_abs_tolerance(combiLBD, _ubd, _maingoSettings->growing_augmentTol, _maingoSettings->growing_augmentTol);
+    }
+    else {
+        // Reduced lower bound used for pruning
+        return babBase::larger_or_equal_within_rel_and_abs_tolerance(currentLBD, _ubd, _maingoSettings->growing_augmentTol, _maingoSettings->growing_augmentTol);
+    }
+}
+
+
 ////////////////////////////////////////////////////////////////////////////////////////
 // function which checks whether to augment the dataset
 bool
-BranchAndBound::_check_whether_to_augment(const babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const double currentLBD)
+BranchAndBound::_check_whether_to_augment(const babBase::BabNode &currentNode, const std::vector<double> &lbpSolutionPoint, const double currentLBD, const double validatedLBD)
 {
     bool augment = false;
 
@@ -954,21 +1791,43 @@ BranchAndBound::_check_whether_to_augment(const babBase::BabNode &currentNode, c
         // Otherwise follow augmentation rule
         switch (_maingoSettings->growing_augmentRule) {
             case AUG_RULE_CONST: {
-                // Augment nodes in depth which is a multiple of freq
-                augment = !((int)fmod(currentNode.get_depth(), (double)_maingoSettings->growing_augmentFreq));
+                augment = _call_augmentation_rule_const(currentNode.get_depth());
                 break;
             }
             case AUG_RULE_SCALING: {
-                double scaledLBD = _maingoSettings->growing_augmentWeight * currentLBD * (double)(*_datasets)[0].size() / (double)(*_datasets)[currentNode.get_index_dataset()].size();
-                augment          = babBase::larger_or_equal_within_rel_and_abs_tolerance(scaledLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA);
+                augment = _call_augmentation_rule_scaling(currentNode.get_index_dataset(), currentLBD);
+                break;
+            }
+            case AUG_RULE_VALID: {
+                augment = _call_augmentation_rule_valid(validatedLBD);
+                break;
+            }
+            case AUG_RULE_COMBI: {
+                augment = _call_augmentation_rule_combi(currentLBD, validatedLBD, currentNode.get_index_dataset());
+                break;
+            }
+            case AUG_RULE_TOL: {
+                augment = _call_augmentation_rule_tol(currentLBD, validatedLBD, currentNode.get_index_dataset());
                 break;
             }
             case AUG_RULE_SCALCST: {
-                // Augment if SCALING condition is met
-                double scaledLBD = _maingoSettings->growing_augmentWeight * currentLBD * (double)(*_datasets)[0].size() / (double)(*_datasets)[currentNode.get_index_dataset()].size();
-                augment          = babBase::larger_or_equal_within_rel_and_abs_tolerance(scaledLBD, _ubd, _maingoSettings->epsilonR, _maingoSettings->epsilonA);
-                // Or if depth of node is a multiple of freq
-                augment = augment || (!((int)fmod(currentNode.get_depth(), (double)_maingoSettings->growing_augmentFreq)));
+                augment = _call_augmentation_rule_const(currentNode.get_depth())
+                          || _call_augmentation_rule_scaling(currentNode.get_index_dataset(), currentLBD);
+                break;
+            }
+            case AUG_RULE_VALCST: {// Augment if VALID or CONST is triggered
+                augment = _call_augmentation_rule_const(currentNode.get_depth())
+                          || _call_augmentation_rule_valid(validatedLBD);
+                break;
+            }
+            case AUG_RULE_COMBICST: {
+                augment = _call_augmentation_rule_const(currentNode.get_depth())
+                    || _call_augmentation_rule_combi(currentLBD, validatedLBD, currentNode.get_index_dataset());
+                break;
+            }
+            case AUG_RULE_TOLCST: {// Augment if VALID or CONST is triggered
+                augment = _call_augmentation_rule_const(validatedLBD)
+                    || _call_augmentation_rule_tol(currentLBD, validatedLBD, currentNode.get_index_dataset());
                 break;
             }
             default: {
@@ -1082,8 +1941,9 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab
     bool solved               = (_nNodesLeft == 0 && _workCount == 0);
     bool maxNodesReached      = (_nNodesLeft >= _maingoSettings->BAB_maxNodes && _workCount == 0);
     bool maxTimeReached       = (_timePassed >= _maingoSettings->maxTime && _workCount == 0);
+    bool maxwTimeReached       = (_wtimePassed >= _maingoSettings->maxwTime && _workCount == 0);
     bool maxIterationsReached = (_iterations >= _maingoSettings->BAB_maxIterations && _workCount == 0);
-    bool specialOccasion      = _printNewIncumbent || solved || maxNodesReached || maxTimeReached || maxIterationsReached || _iterations == 1;
+    bool specialOccasion      = _printNewIncumbent || solved || maxNodesReached || maxTimeReached || maxwTimeReached || maxIterationsReached || _iterations == 1;
 #if defined(MAiNGO_DEBUG_MODE) && defined(HAVE_GROWING_DATASETS)
     specialOccasion = specialOccasion || currentNode.get_augment_data();
 #endif
@@ -1119,6 +1979,8 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab
                    << "  "
                    << std::setw(15) << "CPU     "
                    << "  "
+                   << std::setw(15) << "Wall     "
+                   << "  "
                    << std::endl;
             _linesprinted = 1;
         }
@@ -1158,7 +2020,8 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab
             else {
                 strout << std::setw(15) << (_ubd - _lbd) / std::fabs(_ubd) << "  ";
             }
-            strout << std::setw(15) << _timePassed << "  " << std::endl;
+            strout << std::setw(15) << _timePassed << "  " 
+                   << std::setw(15) << _wtimePassed << "  " << std::endl;
             strout.unsetf(std::ios_base::scientific);
             if (_maingoSettings->writeCsv) {
                 stroutCsv << std::setw(8) << _iterations << ","
@@ -1188,7 +2051,8 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab
                 else {
                     stroutCsv << std::setw(15) << (_ubd - _lbd) / std::fabs(_ubd) << ",";
                 }
-                stroutCsv << std::setw(15) << _timePassed << "," << std::endl;
+                stroutCsv << std::setw(15) << _timePassed << ","
+                          << std::setw(15) << _wtimePassed << std::endl;
                 stroutCsv.unsetf(std::ios_base::scientific);
             }
         }
@@ -1225,7 +2089,7 @@ BranchAndBound::_display_and_log_progress(const double currentNodeLBD, const bab
 ////////////////////////////////////////////////////////////////////////////////////////
 // function for printing the current node to a separate log file
 void
-BranchAndBound::_log_nodes(const babBase::BabNode &currentNode, const double currentNodeLbd, const double currentNodeLbdFull, const double currentNodeUbdFull, const std::vector<double> lbpSolutionPoint, const std::vector<double> lbpSolutionPointFull)
+BranchAndBound::_log_nodes(const babBase::BabNode &currentNode, const double currentNodeLbd, const double currentNodeUbdFull, const std::vector<double> lbpSolutionPoint)
 {
 
 #ifndef HAVE_MAiNGO_MPI
@@ -1262,6 +2126,8 @@ BranchAndBound::_log_nodes(const babBase::BabNode &currentNode, const double cur
                << "  "
                << std::setw(15) << "NodeLB_full "
                << "  "
+               << std::setw(15) << "NodeLB_valid "
+               << "  "
                << std::setw(15) << "LB      "
                << "  "
                << std::setw(15) << "NodeUB   "
@@ -1309,7 +2175,8 @@ BranchAndBound::_log_nodes(const babBase::BabNode &currentNode, const double cur
         }
         strout << "  "
                << std::setw(15) << currentNodeLbd << "  "
-               << std::setw(15) << currentNodeLbdFull << "  "
+               << std::setw(15) << _currentLBDFull << "  "
+               << std::setw(15) << _currentLBDValid << "  "
                << std::setw(15) << _lbd << "  "
                << std::setw(15) << currentNodeUbdFull << "  "
                << std::setw(15) << _ubd << "  ";
@@ -1330,9 +2197,9 @@ BranchAndBound::_log_nodes(const babBase::BabNode &currentNode, const double cur
                        << "  ";
             }
         }
-        if (lbpSolutionPointFull.size() > 0) {    // Feasible point found
+        if (_lbpSolutionPointFull.size() > 0) {    // Feasible point found
             for (size_t i = 0; i < _incumbent.size(); i++) {
-                strout << std::setw(15) << lbpSolutionPointFull[i] << "  ";
+                strout << std::setw(15) << _lbpSolutionPointFull[i] << "  ";
             }
         }
         else {    // No feasible point found
@@ -1352,6 +2219,19 @@ BranchAndBound::_log_nodes(const babBase::BabNode &currentNode, const double cur
 }
 #endif
 
+#ifdef HAVE_GROWING_DATASETS
+////////////////////////////////////////////////////////////////////////////////////////
+// function for printing post-processing information of one node
+void
+BranchAndBound::_print_postprocessed_node(const int ID, const double oldLBD, const double newLBD)
+{
+    std::ostringstream outstr;
+    outstr << "  Lower bound of NODE " << std::setw(9) << ID << " changed from " << std::setw(10) << std::setprecision(6) << oldLBD
+           << " to " << std::setw(10) << std::setprecision(6) << newLBD << std::endl;
+    _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
+}
+#endif // HAVE_GROWING_DATASETS
+
 
 ////////////////////////////////////////////////////////////////////////////////////////
 void
@@ -1385,6 +2265,7 @@ BranchAndBound::_check_termination()
     bool solved                 = (_nNodesLeft == 0 && _workCount == 0);
     bool maxNodesReached        = (_nNodesLeft >= _maingoSettings->BAB_maxNodes);
     bool maxTimeReached         = (_timePassed >= _maingoSettings->maxTime);
+    bool maxwTimeReached         = (_wtimePassed >= _maingoSettings->maxwTime);
     bool maxIterationsReached   = (_iterations >= _maingoSettings->BAB_maxIterations);
     bool reachedTargetUbd       = (_ubd <= _maingoSettings->targetUpperBound);
     bool reachedTargetLbd       = (_lbd >= _maingoSettings->targetLowerBound);
@@ -1447,26 +2328,32 @@ BranchAndBound::_check_termination()
         _logger->print_message("  Done.\n", VERB_NORMAL, BAB_VERBOSITY);
         _print_termination("*                                            *** Found feasible point. ***                                             *");
     }
-    else if (maxNodesReached || maxTimeReached || maxIterationsReached) {    // Reached some other termination criterion.
+    else if (maxNodesReached || maxTimeReached || maxwTimeReached || maxIterationsReached) {    // Reached some other termination criterion.
 
         if (_workCount > 0) {
             return _TERMINATED_WORKERS_ACTIVE;
         }
         babBase::enums::BAB_RETCODE potentialStatus;
         if (maxNodesReached) {
-            message         = "*                                   *** Reached maximum number of nodes in memory! ***                                 *";
+            message         = "*                                  *** Reached maximum number of nodes in memory! ***                                  *";
             criterion       = "number of nodes in memory";
             limit           = &_maingoSettings->BAB_maxNodes;
             potentialStatus = babBase::enums::MAX_NODES;
         }
         else if (maxTimeReached) {
-            message         = "*                                              *** Reached CPU time limit! ***                                             *";
+            message         = "*                                            *** Reached CPU time limit! ***                                           *";
             criterion       = "CPU time";
             limit           = &_maingoSettings->maxTime;
             potentialStatus = babBase::enums::MAX_TIME;
         }
+        else if (maxwTimeReached) {
+            message         = "*                                           *** Reached Wall time limit! ***                                           *";
+            criterion       = "Wall time";
+            limit           = &_maingoSettings->maxwTime;
+            potentialStatus = babBase::enums::MAX_TIME;
+        }
         else {
-            message         = "*                                      *** Reached maximum number of B&B iterations! ***                                   *";
+            message         = "*                                   *** Reached maximum number of B&B iterations! ***                                  *";
             criterion       = "B&B iterations";
             limit           = &_maingoSettings->BAB_maxIterations;
             potentialStatus = babBase::enums::MAX_ITERATIONS;
@@ -1551,6 +2438,43 @@ BranchAndBound::_check_termination()
 }
 
 
+#ifdef HAVE_GROWING_DATASETS
+////////////////////////////////////////////////////////////////////////////////////////
+// function for checking termination of post-processing of heuristic B&B algorithm with growing datasets
+BranchAndBound::_TERMINATION_TYPE
+BranchAndBound::_check_termination_postprocessing()
+{
+
+#ifdef HAVE_MAiNGO_MPI
+    MAiNGO_IF_BAB_WORKER
+        // Workers get their termination signal from the manager, which causes the post-processingp to break
+        return _NOT_TERMINATED;
+    MAiNGO_END_IF
+#else
+    unsigned _workCount = 0;    // Dummy for convenience
+#endif
+
+    if (_nNodesProcessedPost == _nNodesTrackedPost) {    // All tracked nodes processed - regular termination.
+        // Print on screen and write to log
+        _logger->print_message("  Done.\n", VERB_NORMAL, BAB_VERBOSITY);
+        _print_termination("*                                             *** Regular termination. ***                                             *");
+
+        return _TERMINATED;
+    }
+    else if (_timePostpro >= _maingoSettings->growing_maxTimePostprocessing) {
+        if (_workCount > 0) {
+            return _TERMINATED_WORKERS_ACTIVE;
+        }
+
+        _print_termination("*                                           *** Reached CPU time limit! ***                                            *");
+        return _TERMINATED;
+    }
+
+    return _NOT_TERMINATED;
+}
+#endif    // HAVE_GROWING_DATASETS
+
+
 ////////////////////////////////////////////////////////////////////////////////////////
 // function for printing stars in the termination
 void
@@ -1569,3 +2493,73 @@ BranchAndBound::_print_termination(std::string message)
 
     _logger->print_message(outstr.str(), VERB_NORMAL, BAB_VERBOSITY);
 }
+
+//////////////////////////////////////////////////////
+// Utility functions for solving Two Stage problems //
+//////////////////////////////////////////////////////
+inline bool 
+BranchAndBound::_update_psb_entry(std::map<int, std::pair<std::vector<std::vector<double>>, bool>>::iterator &it) {
+    if (it->second.second) {
+        // first lookup for child of node with this id, one lookup remaining
+        it->second.second = false;
+        return false;
+    }
+    else {
+        // second lookup for child of node with this id, removing
+        this->_parentSubproblemBounds.erase(it);
+        return true;
+    }
+}
+
+void 
+BranchAndBound::_retreive_parent_subproblem_bounds(const babBase::BabNode &n, const bool sibling_iteration) {
+    if (n.get_ID() == 1) return; // We don't store bounds for the root node
+    auto it = this->_parentSubproblemBounds.find(n.get_parent_ID());
+    if (it == this->_parentSubproblemBounds.end()) {
+        throw MAiNGOException("  Error: Lookup of parent subproblem bounds"
+                                " for child node with id " +
+                                std::to_string(n.get_ID()) + " and parent id " +
+                                std::to_string(n.get_parent_ID()) + " failed.");
+    }
+    else {
+        /** NOTE: Entries in _parentSubproblemBounds may contain one or two sets of bounds.
+         *        The latter case occurs if the parent of the current siblings was
+         *        an orthant node, that was itself obtained by second stage branching.
+         *        In that case, the two sets of bounds only differ in the scenarios
+         *        corresponding to unbranched variables.
+         *        For those scenarios, the first set of bounds is based on the original
+         *        SPBs of the orthant node's parent (except for tightening by
+         *        infeasibility), while the second set of bounds is based on tightened
+         *        SPBs (using the minima over the two associated sibling SPBs).
+         */
+        *_subproblemBounds = it->second.first[0];
+
+        if (sibling_iteration) {
+            if (it->second.first.size() > 1) {
+                _subproblemBounds->insert(_subproblemBounds->end(), it->second.first[1].begin(), it->second.first[1].end());
+            }
+            _parentSubproblemBounds.erase(it);
+        }
+        else {
+            if (_update_psb_entry(it)) {
+            }
+        }
+    }
+}
+
+void
+BranchAndBound::observe_fathoming(const babBase::BabNode &n) {
+    auto it = _parentSubproblemBounds.find(n.get_parent_ID());
+    if (it != _parentSubproblemBounds.end()) {
+        if (_update_psb_entry(it)) {
+            // possible debug output
+        }
+    }
+    else {
+        throw MAiNGOException("  Error: When fathoming child node with id "
+                              + std::to_string(n.get_ID())
+                              + " no subproblem bounds entry could be found"
+                              " for the parent node with id "
+                              + std::to_string(n.get_parent_ID()));
+    }
+}
\ No newline at end of file
diff --git a/src/babMpi.cpp b/src/babMpi.cpp
index 28f7df5..dcbe2e4 100644
--- a/src/babMpi.cpp
+++ b/src/babMpi.cpp
@@ -30,11 +30,13 @@ using namespace bab;
 ////////////////////////////////////////////////////////////////////////////////////////
 // function for recieving a solved problem from worker src
 void
-BranchAndBound::_recv_solved_problem(babBase::BabNode &node, double &lbd, std::vector<double> &lbdSolutionPoint, unsigned &lbdcnt,
+BranchAndBound::_recv_solved_problem(babBase::BabNode &node,
+                                     babBase::BabNode &sibling, lbp::SiblingResults &siblingResults,
+                                     double &lbd, std::vector<double> &lbdSolutionPoint, unsigned &lbdcnt,
                                      unsigned &ubdcnt, const COMMUNICATION_TAG status, const int src)
 {
 
-    const int statCount = (status == TAG_SOLVED_NODE_STATUS_NORMAL) ? 2 : 3;
+    const int statCount = ((status == TAG_SOLVED_NODE_STATUS_NORMAL) || (status == TAG_SOLVED_SIBLING_STATUS_NORMAL)) ? 2 : 3;
     std::vector<unsigned> statistics(statCount);
 
     if (status == TAG_SOLVED_NODE_STATUS_NORMAL) {
@@ -47,16 +49,34 @@ BranchAndBound::_recv_solved_problem(babBase::BabNode &node, double &lbd, std::v
         MPI_Recv(&lbd, 1, MPI_DOUBLE, src, TAG_SOLVED_NODE_LBD, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
         // Receive lbpSolutionPoint
         MPI_Recv(lbdSolutionPoint.data(), lbdSolutionPoint.size(), MPI_DOUBLE, src, TAG_SOLVED_NODE_SOLUTION_POINT, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+        if (_maingoSettings->TS_useLowerBoundingSubsolvers && _subproblemBounds != nullptr) {
+            // Make space for up to two sets of subproblem bounds
+            _subproblemBounds->reserve(2 * _Ns);
+            MPI_Recv(_subproblemBounds->data(), 2 * _Ns, MPI_DOUBLE, src, TAG_SOLVED_SUBPROBLEM_LBD, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+        }
     }
-    else if (status == TAG_SOLVED_NODE_STATUS_CONVERGED) {
+    else if ((status == TAG_SOLVED_NODE_STATUS_CONVERGED) || (status == TAG_SOLVED_SIBLING_STATUS_CONVERGED)) {
         // Node converged
-        // => Send lbd
+        // => receive lbd
         MPI_Recv(&lbd, 1, MPI_DOUBLE, src, TAG_SOLVED_NODE_LBD, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
     }
-    else if (status == TAG_SOLVED_NODE_STATUS_INFEAS) {
+    else if ((status == TAG_SOLVED_NODE_STATUS_INFEAS) || (status == TAG_SOLVED_SIBLING_STATUS_INFEAS)) {
         // Node infeasible
         lbd = _maingoSettings->infinity;
     }
+    else if (status == TAG_SOLVED_SIBLING_STATUS_NORMAL) {
+        // Receive lower and upper sibling
+        recv_babnode(node, src, _nvar);
+        recv_babnode(sibling, src, _nvar);
+        // Receive serialized sibling results data and fathoming info needed for deserialization
+        auto size = lbp::SiblingResults::getSerializedSiblingResultsSize(_Nx, _Ny, _Ns);
+        _subproblemBounds->clear();
+        _subproblemBounds->reserve(size);
+        MPI_Recv(_subproblemBounds->data(), size, MPI_DOUBLE, src, TAG_SERIALIZED_SIBLING_RESULTS, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+        std::vector<int8_t> fathomed_subpoblems(2 * _Ns, false);
+        MPI_Recv(fathomed_subpoblems.data(), 2 * _Ns, MPI_INT8_T, src, TAG_FATHOMED_SUBPROBLEMS, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+        siblingResults.deserialize(node, sibling, *_subproblemBounds, fathomed_subpoblems);
+    }
 
     // Statistics and node id have to be sent in every case
     MPI_Recv(statistics.data(), statCount, MPI_UNSIGNED, src, TAG_SOLVED_NODE_STATISTICS, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
@@ -65,9 +85,9 @@ BranchAndBound::_recv_solved_problem(babBase::BabNode &node, double &lbd, std::v
     ubdcnt = statistics[1];
 
     // In case the node converged or was infeasible and therefore not sent back, create a dummy node
-    if (status != TAG_SOLVED_NODE_STATUS_NORMAL) {
-        unsigned nodeID = statistics[2];
-        node            = babBase::BabNode(node.get_pruning_score(), node.get_lower_bounds(), node.get_upper_bounds(), node.get_index_dataset(), nodeID, node.get_depth(), node.get_augment_data());
+    if (statCount == 3) {
+        unsigned int nodeID = statistics[2];
+        node                = babBase::BabNode(lbd, {}, {}, 0, -1, nodeID, 0, false);
     }
 }
 
@@ -78,11 +98,8 @@ void
 BranchAndBound::_send_new_problem(const babBase::BabNode &node, const int dest)
 {
 
-    // Check if worker knows current incumbent
-    bool workerKnowsIncumbent = _informedWorkerAboutIncumbent[dest - 1];
-
     COMMUNICATION_TAG answerTag;
-    if (!workerKnowsIncumbent) {
+    if (!_informedWorkerAboutIncumbent[dest - 1]) {
         answerTag                               = TAG_NEW_NODE_NEW_INCUMBENT;
         _informedWorkerAboutIncumbent[dest - 1] = true;
     }
@@ -96,19 +113,50 @@ BranchAndBound::_send_new_problem(const babBase::BabNode &node, const int dest)
     // Send BabNode
     send_babnode(node, dest);
 
-    // Only send incumbent if worker does not know it yet
-    if (!workerKnowsIncumbent) {
-        // Send bounds of objective function
-        MPI_Ssend(&_ubd, 1, MPI_DOUBLE, dest, TAG_NEW_NODE_UBD, MPI_COMM_WORLD);
+    if (_maingoSettings->TS_useLowerBoundingSubsolvers && _subproblemBounds != nullptr && node.get_ID() > 1) {
+        // for all but the root node we send subproblem bounds of the parent node
+        MPI_Ssend(_subproblemBounds->data(), _subproblemBounds->size(), MPI_DOUBLE, dest, TAG_NEW_SUBPROBLEM_BOUNDS, MPI_COMM_WORLD);
+    }
+    if (answerTag == TAG_NEW_NODE_NEW_INCUMBENT)
+        send_incumbent(dest);
+}
 
-        // Send new incumbent
-        MPI_Ssend(_incumbent.data(), _incumbent.size(), MPI_DOUBLE, dest, TAG_NEW_INCUMBENT, MPI_COMM_WORLD);
+void
+BranchAndBound::_send_new_sibling_problem(const babBase::BabNode &lower_sibling, const babBase::BabNode &upper_sibling, const int dest)
+{
 
-        // Send incumbent ID
-        MPI_Ssend(&_incumbentNodeId, 1, MPI_UNSIGNED, dest, TAG_NEW_INCUMBENT_ID, MPI_COMM_WORLD);
+    COMMUNICATION_TAG answerTag;
+    if (!_informedWorkerAboutIncumbent[dest - 1]) { // worker does not know current incumbent
+        answerTag                               = TAG_NEW_SIBLINGS_NEW_INCUMBENT;
+        _informedWorkerAboutIncumbent[dest - 1] = true;
+    }
+    else { // worker knows current incumbent
+        answerTag = TAG_NEW_SIBLINGS_NO_INCUMBENT;
     }
+
+    // Send Request Answer
+    MPI_Ssend(NULL, 0, MPI_INT, dest, answerTag, MPI_COMM_WORLD);
+
+    // Send siblings and subproblem data
+    send_babnode(lower_sibling, dest);
+    send_babnode(upper_sibling, dest);
+    MPI_Ssend(_subproblemBounds->data(), _subproblemBounds->size(), MPI_DOUBLE, dest, TAG_NEW_SUBPROBLEM_BOUNDS, MPI_COMM_WORLD);
+    if (answerTag == TAG_NEW_SIBLINGS_NEW_INCUMBENT)
+        send_incumbent(dest);
 }
 
+inline void
+BranchAndBound::send_incumbent(const int dest)
+{
+    // Send bounds of objective function
+    MPI_Ssend(&_ubd, 1, MPI_DOUBLE, dest, TAG_NEW_NODE_UBD, MPI_COMM_WORLD);
+
+    // Send new incumbent
+    MPI_Ssend(_incumbent.data(), _incumbent.size(), MPI_DOUBLE, dest, TAG_NEW_INCUMBENT, MPI_COMM_WORLD);
+
+    // Send incumbent ID
+    MPI_Ssend(&_incumbentNodeId, 1, MPI_UNSIGNED, dest, TAG_NEW_INCUMBENT_ID, MPI_COMM_WORLD);
+};
 
 #ifdef HAVE_GROWING_DATASETS
 ////////////////////////////////////////////////////////////////////////////////////////
@@ -206,6 +254,10 @@ BranchAndBound::_send_solved_problem(const babBase::BabNode node, const double l
         MPI_Ssend(&lbd, 1, MPI_DOUBLE, 0, TAG_SOLVED_NODE_LBD, MPI_COMM_WORLD);
         // Send lbdSolutionPoint
         MPI_Ssend(lbdSolutionPoint.data(), lbdSolutionPoint.size(), MPI_DOUBLE, 0, TAG_SOLVED_NODE_SOLUTION_POINT, MPI_COMM_WORLD);
+        if (_maingoSettings->TS_useLowerBoundingSubsolvers && _subproblemBounds != nullptr) {
+            // send subproblem bounds of this node to be stored by the master
+            MPI_Ssend(_subproblemBounds->data(), _subproblemBounds->size(), MPI_DOUBLE, 0, TAG_SOLVED_SUBPROBLEM_LBD, MPI_COMM_WORLD);
+        }
     }
     else if (status == TAG_SOLVED_NODE_STATUS_CONVERGED) {
         // Node converged
@@ -217,19 +269,65 @@ BranchAndBound::_send_solved_problem(const babBase::BabNode node, const double l
     MPI_Ssend(counts, statCount, MPI_UNSIGNED, 0, TAG_SOLVED_NODE_STATISTICS, MPI_COMM_WORLD);
 }
 
+void
+BranchAndBound::_send_solved_sibling_problem(const lbp::SiblingResults &siblingResults,
+                                             const unsigned lbdcnt, const unsigned ubdcnt, const COMMUNICATION_TAG status)
+{
+    const int statCount = (status == TAG_SOLVED_SIBLING_STATUS_NORMAL) ? 2 : 3;
+    unsigned counts[3]  = {lbdcnt, ubdcnt, (unsigned int)siblingResults.siblings[0].get_parent_ID()};
+
+    if (status == TAG_SOLVED_SIBLING_STATUS_NORMAL) {
+
+        // Send lower and upper sibling
+        send_babnode(siblingResults.siblings[0], 0);
+        send_babnode(siblingResults.siblings[1], 0);
+
+        // Serialize and send sibling results data and fathoming info needed for deserialization
+        std::vector<double> serializedSiblingResults;
+        std::vector<int8_t> fathomed_subpoblems;
+        siblingResults.serialize(serializedSiblingResults, fathomed_subpoblems);
+        MPI_Ssend(serializedSiblingResults.data(), serializedSiblingResults.size(), MPI_DOUBLE, 0, TAG_SERIALIZED_SIBLING_RESULTS, MPI_COMM_WORLD);
+        MPI_Ssend(fathomed_subpoblems.data(), fathomed_subpoblems.size(), MPI_INT8_T, 0, TAG_FATHOMED_SUBPROBLEMS, MPI_COMM_WORLD);
+    }
+    else if (status == TAG_SOLVED_SIBLING_STATUS_CONVERGED) {
+        // Parent converged
+        // => Send lbd
+        MPI_Ssend(&(siblingResults.parentPruningScore), 1, MPI_DOUBLE, 0, TAG_SOLVED_NODE_LBD, MPI_COMM_WORLD);
+    }
+ 
+    // Statistics have to be sent in every case
+    MPI_Ssend(counts, statCount, MPI_UNSIGNED, 0, TAG_SOLVED_NODE_STATISTICS, MPI_COMM_WORLD);
+}
+
 
 ////////////////////////////////////////////////////////////////////////////////////////
 // function for recieving a new problem from the master with a new incumbent
-void
-BranchAndBound::_recv_new_problem(babBase::BabNode &node)
+babBase::enums::ITERATION_TYPE
+BranchAndBound::_recv_new_problem(babBase::BabNode &node, babBase::BabNode &sibling)
 {
     MPI_Status status;
     MPI_Recv(NULL, 0, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
-
     // Receive BabNode
     recv_babnode(node, 0, _nvar);
 
-    if (status.MPI_TAG == TAG_NEW_NODE_NEW_INCUMBENT) {
+    babBase::enums::ITERATION_TYPE iterationType;
+    if (status.MPI_TAG == TAG_NEW_SIBLINGS_NEW_INCUMBENT || status.MPI_TAG == TAG_NEW_SIBLINGS_NO_INCUMBENT) {
+        // Receive (upper) sibling of previous node (lower sibling)
+        recv_babnode(sibling, 0, _nvar);
+        iterationType = babBase::enums::SIBLING_ITERATION;
+    }
+    else {
+        iterationType = babBase::enums::NORMAL_ITERATION;
+    }
+
+    if (_maingoSettings->TS_useLowerBoundingSubsolvers && _subproblemBounds != nullptr && node.get_ID() > 1) {
+        _subproblemBounds->resize(2 * _Ns, std::numeric_limits<double>::infinity());
+        MPI_Recv(_subproblemBounds->data(), 2 * _Ns, MPI_DOUBLE, 0, TAG_NEW_SUBPROBLEM_BOUNDS, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
+        if (_subproblemBounds->at(_Ns) == std::numeric_limits<double>::infinity())
+            _subproblemBounds->resize(_Ns);
+    }
+
+    if (status.MPI_TAG == TAG_NEW_NODE_NEW_INCUMBENT || status.MPI_TAG == TAG_NEW_SIBLINGS_NEW_INCUMBENT) {
         _incumbent.resize(_nvarWOaux);
 
         // Receive bounds of objective function
@@ -243,9 +341,9 @@ BranchAndBound::_recv_new_problem(babBase::BabNode &node)
 
         _LBS->update_incumbent_LBP(_incumbent);
     }
+    return iterationType;
 }
 
-
 #ifdef HAVE_GROWING_DATASETS
 ////////////////////////////////////////////////////////////////////////////////////////
 // function for recieving a new dataset from the master
@@ -307,7 +405,9 @@ BranchAndBound::_sync_with_master(MPI_Request &req, bool &terminate)
 
             MPI_Request_get_status(req, &flag, &status);
             if (_bcastTag == BCAST_EXCEPTION) {    // Other worker ran into an exception
-                _logger->print_message("  Received exception flag from master.\n", VERB_NORMAL, BAB_VERBOSITY);
+                MAiNGO_IF_BAB_MANAGER
+                    _logger->print_message("  Received exception flag from master.\n", VERB_NORMAL, BAB_VERBOSITY);
+                MAiNGO_END_IF
                 // Cancel pending node request
                 MPI_Cancel(&req);
                 MPI_Request_free(&req);
@@ -345,7 +445,6 @@ BranchAndBound::_communicate_exception_and_throw(const maingo::MAiNGOMpiExceptio
         _inform_worker_about_event(BCAST_EXCEPTION, true);
         MAiNGO_ELSE
             if (e.origin() == MAiNGOMpiException::ORIGIN_ME) {
-                std::cout << e.what() << std::endl;
                 MPI_Request req;
                 MPI_Issend(NULL, 0, MPI_INT, 0, TAG_EXCEPTION, MPI_COMM_WORLD, &req);
 
diff --git a/src/lbp.cpp b/src/lbp.cpp
index bf3f772..a91851f 100644
--- a/src/lbp.cpp
+++ b/src/lbp.cpp
@@ -13,6 +13,7 @@
 #include "MAiNGOException.h"
 #include "lbpDagObj.h"
 #include "pointIsWithinNodeBounds.h"
+#include "instrumentor.h"
 #include "version.h"
 
 #include <fstream>
@@ -386,10 +387,12 @@ LowerBoundingSolver::_set_number_of_linpoints(const unsigned int LBP_linPoints)
 SUBSOLVER_RETCODE
 LowerBoundingSolver::solve_LBP(const babBase::BabNode &currentNode, double &lowerBound, std::vector<double> &solution, LbpDualInfo &dualInfo)
 {
+    PROFILE_FUNCTION()
 
     // Update the LP for the current node (i.e., modify bounds and update coefficients and RHS)
     LINEARIZATION_RETCODE linStatus;
     try {
+        PROFILE_SCOPE("LBP_update_LP")
         linStatus = _update_LP(currentNode);
     }
     catch (std::exception &e) { // GCOVR_EXCL_START
@@ -402,10 +405,12 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode &currentNode, double &lowe
     // Solve problem and check return status
     if (linStatus == LINEARIZATION_UNKNOWN) {
         // Only need to solve the problem if it was not solved during linearization
-        _solve_LP(currentNode);
+        PROFILE_SCOPE("LBP_solve_LP")
+		_LPstatus = _solve_LP(currentNode);
+    }
+    else {
+        _LPstatus = _get_LP_status();
     }
-
-    _LPstatus = _get_LP_status();
 
     if (_LPstatus == LP_INFEASIBLE) {
 
@@ -433,8 +438,8 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode &currentNode, double &lowe
         if (_maingoSettings->LBP_solver == lbp::LBP_SOLVER_CLP) {    // For CLP, we need this additional check to avoid weird behavior for some problems
             bool reallyInfeasible = _check_if_LP_really_infeasible();
             if (!reallyInfeasible)
-                _logger->print_message("  Found node to not actually be infeasible. Problem seems to be difficult numerically. Proceeding with parent LBD...", VERB_ALL, LBP_VERBOSITY);
-            return SUBSOLVER_FEASIBLE;
+                _logger->print_message("  Found node to not actually be infeasible. Problem seems to be numerically difficult. Using interval bounds instead.", VERB_ALL, LBP_VERBOSITY);
+            return _fallback_to_intervals(lowerBound);
         }
         return _check_infeasibility(currentNode);
 #endif
@@ -457,7 +462,7 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode &currentNode, double &lowe
     }
     catch (std::exception &e) {
         std::ostringstream outstr;
-        outstr << "  Warning: Variables at solution of LBP could be not obtained by LP solver: " << e.what() << std::endl;
+        outstr << "  Warning: Variables at solution of LBP could not be obtained by LP solver: " << e.what() << std::endl;
         _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
         // Return empty solution instead
         solution.clear();
@@ -525,11 +530,75 @@ LowerBoundingSolver::solve_LBP(const babBase::BabNode &currentNode, double &lowe
 }
 
 
+#ifdef HAVE_GROWING_DATASETS
+/////////////////////////////////////////////////////////////////////////////////////////////
+// evaluate lower bounding problem based on affine relaxations at a given point
+void
+LowerBoundingSolver::evaluate_LBP(const babBase::BabNode& currentNode, const std::vector<double>& evaluationPoint, double& resultValue)
+{
+    // Update the LP for the current node (i.e., modify bounds and update coefficients and RHS)
+    // Simplest approach: always use midpoint linearization
+    lbp::LINP tempLinPoints = _maingoSettings->LBP_linPoints;
+    _maingoSettings->LBP_linPoints = LINP_MID;
+
+    LINEARIZATION_RETCODE linStatus;
+    try {
+        linStatus = _update_LP(currentNode);
+    }
+    catch (std::exception& e) {
+        throw MAiNGOException("  Error while modifying the lower bounding LP for LBP.", e, currentNode);
+    }
+    catch (...) {
+        throw MAiNGOException("  Unknown error while modifying the lower bounding LP for LBP.", currentNode);
+    }
+
+    if (linStatus == LINEARIZATION_INFEASIBLE) {
+        std::ostringstream errmsg;
+        errmsg << "  Error in LowerBoundingSolver - evaluation of LBP: LBP based on LINP_MID is reported to be infeasible. " << std::endl;
+        throw MAiNGOException(errmsg.str());
+    }
+    // Reset linearization point strategy to previous setting
+    _maingoSettings->LBP_linPoints = tempLinPoints;
+
+    // Evaluate objective of LBP
+    if (!evaluationPoint.empty()) {
+        // LBP with 1 linearization point and single objective (cf. _print_LP(...)):
+        // min_x  eta
+        // s.t.   sum_j _matrixObj[0][0][j]* x_j - _objectiveScalingFactors[0][0]*eta <= _rhsObj[0][0]
+        //        other constraints
+        resultValue = -_rhsObj[0][0];
+        for (unsigned j = 0; j < _nvar; j++) {
+            resultValue = resultValue + _matrixObj[0][0][j] * evaluationPoint[j];
+        }
+        resultValue = resultValue / _objectiveScalingFactors[0][0];
+
+        // Check for NaN
+        if (resultValue != resultValue) {
+            resultValue = -_maingoSettings->infinity;
+        }
+
+        resultValue = std::max(resultValue, _DAGobj->validIntervalLowerBound);    // As for solve_LBP: In case the interval bound is better, use that one
+
+        // Check if evaluationPoint is feasible
+        if (_check_feasibility(evaluationPoint) == SUBSOLVER_INFEASIBLE) {
+            std::ostringstream outstr;
+            outstr << "  Warning: Evaluated LBP in node with id " << currentNode.get_ID() << " is infeasible at the given point." << std::endl;
+            _logger->print_message(outstr.str(), VERB_NORMAL, LBP_VERBOSITY);
+        }
+    }
+    else {
+        _fallback_to_intervals(resultValue);
+    }
+}
+#endif    // HAVE_GROWING_DATASETS
+
+
 /////////////////////////////////////////////////////////////////////////////////////////////
 // solve LP for optimization-based range reduction
 TIGHTENING_RETCODE
 LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double currentUBD, const OBBT reductionType, const bool includeLinearVars)
 {
+    PROFILE_FUNCTION()
 
     if ((reductionType == OBBT_FEAS) && _onlyBoxConstraints) {
         return TIGHTENING_UNCHANGED;
@@ -544,6 +613,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
     // Update the LP for the current node (i.e., modify bounds and update coefficients and RHS)
     LINEARIZATION_RETCODE linStatus;
     try {
+        PROFILE_SCOPE("OBBT_update_LP")
         linStatus = _update_LP(currentNode);
     }
     catch (std::exception &e) { // GCOVR_EXCL_START
@@ -576,7 +646,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
             }
             return TIGHTENING_UNCHANGED;
         }
-#endif
+#endif  // MAiNGO_DEBUG_MODE
 
         foundInfeasible = true;
 #ifdef LP__OPTIMALITY_CHECK
@@ -623,6 +693,8 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
         unsigned OBBTcnt  = 0;
         bool foundRelFeas = false;
         while ((toTreatMax.size() + toTreatMin.size()) > 0) {
+			PROFILE_SCOPE("OBBT_loop")
+
             OBBTcnt++;
             // Select next candidate lower bound
             std::list<unsigned>::iterator tmpIt = toTreatMin.begin(), nextMinIt = toTreatMin.begin();
@@ -706,20 +778,20 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
 
             // Conduct OBBT: solve LP and update bound
             _set_optimization_sense_of_variable(iVar, optimizationSense);    // Depending on whether we want to change upper or lower bound, use +1 or -1 as coefficient
-            _solve_LP(currentNode);
-            _LPstatus = _get_LP_status();
-
+			{
+                PROFILE_SCOPE("OBBT_solve_LP")
+                _LPstatus = _solve_LP(currentNode);
+            }
             if (_LPstatus == LP_INFEASIBLE) {
                 _logger->print_message("  OBBT tightening LP status: Infeasible", VERB_ALL, LBP_VERBOSITY);
 
 #ifdef MAiNGO_DEBUG_MODE
                 if (_contains_incumbent(currentNode)) {
-
                     const bool reallyInfeasible = _check_if_LP_really_infeasible();
                     if (reallyInfeasible) {
-#ifdef LP__WRITE_CHECK_FILES
+  #ifdef LP__WRITE_CHECK_FILES
                         _write_LP_to_file("solve_OBBT_infeas_with_incumbent_in_node");
-#endif
+  #endif
                         if (currentNode.get_ID() == 0) {
                             _restore_LP_coefficients_after_OBBT();
                             return TIGHTENING_INFEASIBLE;    // For the root node, we immediately want to report this false infeasibility claim since we want to completeley disable OBBT based on this information.
@@ -737,8 +809,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
                         continue;
                     }
                 }
-#endif
-
+#endif  // MAiNGO_DEBUG_MODE
                 foundInfeasible = true;
 #ifdef LP__OPTIMALITY_CHECK
                 if (_check_infeasibility(currentNode) == SUBSOLVER_FEASIBLE) {
@@ -829,7 +900,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
                         }
                     }
                 }
-#endif
+#endif  // MAiNGO_DEBUG_MODE
 
                 double remainingWidth = optimizationSense * (*otherBoundVector)[iVar] - optimizationSense * newBound;
                 if (remainingWidth < -_computationTol) {
@@ -957,7 +1028,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
             return TIGHTENING_UNCHANGED;
         }
         else {
-            currentNode = babBase::BabNode(currentNode.get_pruning_score(), lowerVarBounds, upperVarBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data());
+            currentNode = babBase::BabNode(currentNode, lowerVarBounds, upperVarBounds);
             return TIGHTENING_CHANGED;
         }
     }
@@ -969,6 +1040,7 @@ LowerBoundingSolver::solve_OBBT(babBase::BabNode &currentNode, const double curr
 TIGHTENING_RETCODE
 LowerBoundingSolver::do_constraint_propagation(babBase::BabNode &currentNode, const double currentUBD, const unsigned pass)
 {
+    PROFILE_FUNCTION()
 
     if ((currentUBD >= _maingoSettings->infinity) && _onlyBoxConstraints) {
         return TIGHTENING_UNCHANGED;
@@ -1002,7 +1074,7 @@ LowerBoundingSolver::do_constraint_propagation(babBase::BabNode &currentNode, co
     }
 
     // Flag describing the status of constraint propagation <0 means that the problem is infeasible, 0 means that no bound tightening was possible, >0 means that tightening was possible
-    // 3 is the number of maximum rounds and 0.01 stands for a minimum improvement of 1%
+    // 0.001 stands for a minimum improvement of 0.1%
     unsigned int maxpass = pass;
     int flag             = _DAGobj->DAG.reval(_DAGobj->subgraph, _DAGobj->intervalArray, _DAGobj->functions.size(), _DAGobj->functions.data(),
                                               _DAGobj->constraintIntervals.data(), _nvar, _DAGobj->vars.data(), _DAGobj->currentIntervals.data(), maxpass, 0.001);
@@ -1130,7 +1202,7 @@ LowerBoundingSolver::do_constraint_propagation(babBase::BabNode &currentNode, co
         return TIGHTENING_UNCHANGED;
     }
     else {
-        currentNode = babBase::BabNode(currentNode.get_pruning_score(), lowerVarBounds, upperVarBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data());
+        currentNode = babBase::BabNode(currentNode, lowerVarBounds, upperVarBounds);
         return TIGHTENING_CHANGED;
     }
 }
@@ -1171,7 +1243,7 @@ LowerBoundingSolver::do_dbbt_and_probing(babBase::BabNode &currentNode, const st
                     // Sanity check:
                     if (newLowerBounds[iVar] > newUpperBounds[iVar]) {
                         std::ostringstream errmsg; // GCOVR_EXCL_START
-                        errmsg << "  Error in LowerBoundingSolver - DBBT - 1: while setting new lower bound during DBBT for variable " << iVar << ": upper bound became larger than lower bound: " << std::endl;
+                        errmsg << "  Error in LowerBoundingSolver - DBBT - 1: while setting new lower bound during DBBT for variable " << iVar << ": lower bound became larger than upper bound: " << std::endl;
                         errmsg << "  " << newLowerBounds[iVar] << " > " << newUpperBounds[iVar];
                         throw MAiNGOException(errmsg.str());
                     }
@@ -1198,7 +1270,7 @@ LowerBoundingSolver::do_dbbt_and_probing(babBase::BabNode &currentNode, const st
                     // Sanity check:
                     if (newLowerBounds[iVar] > newUpperBounds[iVar]) {
                         std::ostringstream errmsg; // GCOVR_EXCL_START
-                        errmsg << "  Error in LowerBoundingSolver - DBBT - 2: while setting new lower bound during DBBT for variable " << iVar << ": upper bound became larger than lower bound: " << std::endl;
+                        errmsg << "  Error in LowerBoundingSolver - DBBT - 2: while setting new upper bound during DBBT for variable " << iVar << ": lower bound became larger than upper bound: " << std::endl;
                         errmsg << "  " << newLowerBounds[iVar] << " > " << newUpperBounds[iVar];
                         throw MAiNGOException(errmsg.str());
                     }
@@ -1214,7 +1286,7 @@ LowerBoundingSolver::do_dbbt_and_probing(babBase::BabNode &currentNode, const st
                 // Lower bound:
                 LbpDualInfo probingDualInfo;
                 // Declare a tmp node to work on
-                babBase::BabNode tmpNode(currentNode.get_pruning_score(), newLowerBounds, newUpperBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data());
+                babBase::BabNode tmpNode(currentNode, newLowerBounds, newUpperBounds);
                 SUBSOLVER_RETCODE probingStatus = _solve_probing_LBP(tmpNode, probingDualInfo, iVar, true);
                 if ((probingStatus == SUBSOLVER_FEASIBLE) && (probingDualInfo.multipliers.size() == _nvar)) {
                     if (probingDualInfo.multipliers[iVar] < 0) {
@@ -1257,7 +1329,7 @@ LowerBoundingSolver::do_dbbt_and_probing(babBase::BabNode &currentNode, const st
     }    // end of for loop over variables
 
     if (changedBounds) {
-        currentNode = babBase::BabNode(currentNode.get_pruning_score(), newLowerBounds, newUpperBounds, currentNode.get_index_dataset(), currentNode.get_ID(), currentNode.get_depth(), currentNode.get_augment_data());
+        currentNode = babBase::BabNode(currentNode, newLowerBounds, newUpperBounds);
         return TIGHTENING_CHANGED;
     }
     else {
@@ -1294,6 +1366,7 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode &currentNode, LbpDualIn
 
     // Update the LP for the current node (i.e., modify bounds and update coefficients and RHS)
     try {
+        PROFILE_SCOPE("probing_update_LP")
         _update_LP(currentNode);
     }
     catch (std::exception &e) { // GCOVR_EXCL_START
@@ -1306,8 +1379,10 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode &currentNode, LbpDualIn
     // Fix variable
     _fix_variable(iVar, fixToLowerBound);
     // Solve problem and check return status
-    _solve_LP(currentNode);
-    _LPstatus = _get_LP_status();
+    {
+        PROFILE_SCOPE("probing_solve_LP")
+	    _LPstatus = _solve_LP(currentNode);
+    }
 
     // If LP solver says the problem is infeasible, it's ok, since probing is just a heuristic
     if (_LPstatus == LP_INFEASIBLE) {
@@ -1424,7 +1499,6 @@ LowerBoundingSolver::_solve_probing_LBP(babBase::BabNode &currentNode, LbpDualIn
 LINEARIZATION_RETCODE
 LowerBoundingSolver::_update_LP(const babBase::BabNode &currentNode)
 {
-
     // Set bounds for current node
     std::vector<double> lowerVarBounds(currentNode.get_lower_bounds());
     std::vector<double> upperVarBounds(currentNode.get_upper_bounds());
@@ -1627,6 +1701,8 @@ LowerBoundingSolver::_update_LP_ineq_squash(const MC &resultRelaxation, const st
 void
 LowerBoundingSolver::_update_whole_LP_at_linpoint(const std::vector<MC> &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds, unsigned const &iLin)
 {
+    PROFILE_FUNCTION()
+
     // The function simply calls the methods for updating specific functions
     // Note that all functions are linearized at the same point
     for (size_t i = 0; i < _constraintProperties->size(); i++) {
@@ -1809,10 +1885,9 @@ LowerBoundingSolver::_update_whole_LP_at_vector_linpoints(const std::vector<vMC>
 
 /////////////////////////////////////////////////////////////////////////////////////////////
 // function for solving the currently constructed linear program, also internally sets the _solutionPoint, _multipliers, and the _LPstatus
-void
+LP_RETCODE
 LowerBoundingSolver::_solve_LP(const babBase::BabNode &currentNode)
 {
-
     _solutionPoint.clear();
     _multipliers.clear();
     for (size_t i = 0; i < (*_constraintProperties).size(); i++) {
@@ -1823,7 +1898,7 @@ LowerBoundingSolver::_solve_LP(const babBase::BabNode &currentNode)
             case INEQ_REL_ONLY:
                 if (constraintValueL > _maingoSettings->deltaIneq) {
                     _LPstatus = LP_INFEASIBLE;
-                    return;
+                    return _LPstatus;
                 }
                 break;
             case EQ:
@@ -1831,13 +1906,13 @@ LowerBoundingSolver::_solve_LP(const babBase::BabNode &currentNode)
             case AUX_EQ_REL_ONLY:
                 if (constraintValueL > _maingoSettings->deltaEq || constraintValueU < -_maingoSettings->deltaEq) {
                     _LPstatus = LP_INFEASIBLE;
-                    return;
+                    return _LPstatus;
                 }
                 break;
             case INEQ_SQUASH:
                 if (constraintValueL > 0) {
                     _LPstatus = LP_INFEASIBLE;
-                    return;
+                    return _LPstatus;
                 }
                 break;
             case OBJ:
@@ -1870,6 +1945,7 @@ LowerBoundingSolver::_solve_LP(const babBase::BabNode &currentNode)
     _DAGobj->validIntervalLowerBound = _DAGobj->resultRelaxation[0].l();
 
     _LPstatus = LP_OPTIMAL;
+	return _LPstatus;
 }
 
 
@@ -1957,7 +2033,7 @@ LowerBoundingSolver::_set_optimization_sense_of_variable(const unsigned &iVar, c
 
 
 /////////////////////////////////////////////////////////////////////////////////////////////
-// function for setting the optimization sense of variable iVar
+// function for fixing the optimization variable iVar
 void
 LowerBoundingSolver::_fix_variable(const unsigned &iVar, const bool fixToLowerBound)
 {
@@ -2013,6 +2089,7 @@ LowerBoundingSolver::preprocessor_check_options(const babBase::BabNode &rootNode
             MC::subHeur.originalLowerBounds = &lowerVarBounds;
             MC::subHeur.originalUpperBounds = &upperVarBounds;
             MC::subHeur.referencePoint      = &linearizationPoint;
+
             _DAGobj->DAG.eval(_DAGobj->subgraph, _DAGobj->MCarray, _DAGobj->functions.size(), _DAGobj->functions.data(), _DAGobj->resultRelaxation.data(), _nvar, _DAGobj->vars.data(), _DAGobj->McPoint.data());
             MC::options.SUB_INT_HEUR_USE        = oldSetting;
             MC::subHeur.usePrecomputedIntervals = false;
@@ -2049,12 +2126,21 @@ LowerBoundingSolver::preprocessor_check_options(const babBase::BabNode &rootNode
 /////////////////////////////////////////////////////////////////////////
 // passes index of new dataset to DagObj routine
 void
-LowerBoundingSolver::change_growing_objective(const unsigned int indexDataset)
+LowerBoundingSolver::change_growing_objective(const int indexDataset)
 {
     _DAGobj->change_growing_objective(indexDataset);
 }
 
 
+/////////////////////////////////////////////////////////////////////////
+// calling respective DagObj routine
+void
+LowerBoundingSolver::change_growing_objective_for_resampling()
+{
+    _DAGobj->change_growing_objective_for_resampling();
+}
+
+
 /////////////////////////////////////////////////////////////////////////
 // passes dataset and position of first data point to DagObj routine
 void
@@ -2065,6 +2151,24 @@ LowerBoundingSolver::pass_data_position_to_solver(const std::shared_ptr<std::vec
 }
 
 
+/////////////////////////////////////////////////////////////////////////
+// passes resampled initial dataset to DagObj routine
+void
+LowerBoundingSolver::pass_resampled_dataset_to_solver(const std::shared_ptr<std::set<unsigned int>> datasetIn)
+{
+    _DAGobj->datasetResampled = datasetIn;
+}
+
+
+/////////////////////////////////////////////////////////////////////////
+// passes flag indicating whether to use mean squared error as the objective function to respective DagObj routine
+void
+LowerBoundingSolver::pass_use_mse_to_solver(const bool useMseIn)
+{
+    _DAGobj->useMse = useMseIn;
+}
+
+
 #ifdef HAVE_MAiNGO_MPI
 /////////////////////////////////////////////////////////////////////////////////////////////
 // update dataset vector and add respective subgraph after adding new dataset
diff --git a/src/lbpClp.cpp b/src/lbpClp.cpp
index 313f0e7..940c9b1 100644
--- a/src/lbpClp.cpp
+++ b/src/lbpClp.cpp
@@ -792,7 +792,7 @@ LbpClp::_update_LP_ineq_squash(const vMC &resultRelaxationVMC, const std::vector
 
 /////////////////////////////////////////////////////////////////////////////////////////////
 // solves the current linear program
-void
+LP_RETCODE
 LbpClp::_solve_LP(const babBase::BabNode &currentNode)
 {
 
@@ -907,6 +907,8 @@ LbpClp::_solve_LP(const babBase::BabNode &currentNode)
     catch (...) {
         throw MAiNGOException("  Unknown error while solving the LP with CLP.", currentNode);
     }
+
+	return LbpClp::_get_LP_status();  // ensure we don't use any overrides
 } // GCOVR_EXCL_STOP
 
 
diff --git a/src/lbpCplex.cpp b/src/lbpCplex.cpp
index 288d551..b47c5a5 100644
--- a/src/lbpCplex.cpp
+++ b/src/lbpCplex.cpp
@@ -14,6 +14,7 @@
 #include "lbpCplex.h"
 #include "MAiNGOException.h"
 #include "lbpDagObj.h"
+#include "instrumentor.h"
 
 
 using namespace maingo;
@@ -126,6 +127,10 @@ LbpCplex::LbpCplex(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, cons
 
         // Initialize CPLEX solver
         cplex = IloCplex(cplxModel);
+        #ifdef _OPENMP
+            // Ensure each CPLEX instance only starts one thread, since we're using one thread per logical core already
+            cplex.setParam(IloCplex::Threads, 1);
+        #endif
         // Do not warm-start from previous solution since problems are too dissimilar (warm-start can lead to spurious results!!):
         cplex.setParam(IloCplex::AdvInd, 0);
         cplex.setParam(IloCplex::RootAlg, IloCplex::Dual);
@@ -954,9 +959,11 @@ LbpCplex::_update_LP_ineq_squash(const vMC &resultRelaxationVMC, const std::vect
 
 /////////////////////////////////////////////////////////////////////////////////////////////
 // solves the current linear program
-void
+LP_RETCODE
 LbpCplex::_solve_LP(const babBase::BabNode &currentNode)
 {
+    PROFILE_FUNCTION()
+
     try {
         cplex.solve();
     }
@@ -966,6 +973,7 @@ LbpCplex::_solve_LP(const babBase::BabNode &currentNode)
     catch (...) {
         throw MAiNGOException("  Unknown error while solving the LP with CPLEX.", currentNode);
     }
+	return LbpCplex::_get_LP_status();  // ensure we don't use any overrides
 } // GCOVR_EXCL_STOP
 
 
@@ -1059,7 +1067,6 @@ LbpCplex::_get_multipliers(std::vector<double> &multipliers)
 void
 LbpCplex::_deactivate_objective_function_for_OBBT()
 {
-
     for (unsigned iLinObj = 0; iLinObj < _nLinObj[0]; iLinObj++) {
         for (unsigned iVar = 0; iVar < _nvar; iVar++) {
             linObj[0][iLinObj].setLinearCoef(cplxVars[iVar], 0);
@@ -1078,7 +1085,6 @@ LbpCplex::_deactivate_objective_function_for_OBBT()
 void
 LbpCplex::_modify_LP_for_feasopt_OBBT(const double &currentUBD, std::list<unsigned> &toTreatMax, std::list<unsigned> &toTreatMin)
 {
-
     for (unsigned iLinObj = 0; iLinObj < _nLinObj[0]; iLinObj++) {
         linObj[0][iLinObj].setLinearCoef(eta, 0);
         if (std::fabs(linObj[0][iLinObj].getUB() / _objectiveScalingFactors[0][iLinObj] + currentUBD) > 1e19) {
@@ -1140,7 +1146,6 @@ LbpCplex::_fix_variable(const unsigned &iVar, const bool fixToLowerBound)
 void
 LbpCplex::_restore_LP_coefficients_after_OBBT()
 {
-
     // Restore proper objective function and disable warm-start
     for (unsigned iVar = 0; iVar < _nvar; iVar++) {
         cplxObjective.setLinearCoef(cplxVars[iVar], 0);
@@ -1652,7 +1657,7 @@ LbpCplex::_write_LP_to_file(const std::string &fileName)
         throw MAiNGOException("  Error while exporting model from CPLEX.", e);
     }
     catch (...) {
-        throw MAiNGOException("  Unknown error while exporting model from CPLEX.";
+        throw MAiNGOException("  Unknown error while exporting model from CPLEX.");
     }
 } // GCOVR_EXCL_STOP
 #endif
diff --git a/src/lbpDagObj.cpp b/src/lbpDagObj.cpp
index 4d01644..67b0304 100644
--- a/src/lbpDagObj.cpp
+++ b/src/lbpDagObj.cpp
@@ -12,6 +12,7 @@
 #include "lbpDagObj.h"
 #include "MAiNGOException.h"
 
+#include <iterator>
 
 namespace maingo {
 
@@ -205,8 +206,14 @@ DagObj::add_subgraph_for_new_dataset(const unsigned int indexDataset)
     for (auto idxDataPoint : (*datasets)[indexDataset]) {
         obj += resultVars[idxDataPoint + indexFirstData];
     }
-    functions[0]       = obj;
-    functionsObj[0][0] = obj;
+    if (useMse) {// Use mean of summed objective per data as objective
+        functions[0]       = obj / (*datasets)[indexDataset].size();
+        functionsObj[0][0] = obj / (*datasets)[indexDataset].size();
+    }
+    else {// Use sum of objective per data as objective
+        functions[0]       = obj;
+        functionsObj[0][0] = obj;
+    }
 
     // Build new subgraphs
     storedFunctions.push_back(functions);
@@ -219,27 +226,136 @@ DagObj::add_subgraph_for_new_dataset(const unsigned int indexDataset)
 }
 
 
+/////////////////////////////////////////////////////////////////////////
+// function for adding subgraph corresponding to complementary set of reduced dataset
+void
+DagObj::add_subgraph_for_complementary_dataset(const unsigned int indexDataset)
+{
+    std::set<unsigned int> dataset;
+    std::set_difference((*datasets)[0].begin(), (*datasets)[0].end(),
+                        (*datasets)[indexDataset].begin(), (*datasets)[indexDataset].end(),
+                        std::inserter(dataset, dataset.end()));
+
+    mc::FFVar obj = 0;
+    for (auto idxDataPoint : dataset) {
+        obj += resultVars[idxDataPoint + indexFirstData];
+    }
+    if (useMse) {// Use mean of summed objective per data as objective
+        functions[0]       = obj / dataset.size();
+        functionsObj[0][0] = obj / dataset.size();
+    }
+    else {// Use sum of objective per data as objective
+        functions[0]       = obj;
+        functionsObj[0][0] = obj;
+    }
+
+    // Build new subgraphs
+    storedFunctionsCompl.push_back(functions);
+    auto pointerToSubgraph = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functions.size(), functions.data()));
+    storedSubgraphCompl.push_back(pointerToSubgraph);
+
+    storedFunctionsObjCompl.push_back(functionsObj[0]);
+    auto pointerToSubgraphObj = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functionsObj[0].size(), functionsObj[0].data()));
+    storedSubgraphObjCompl.push_back(pointerToSubgraphObj);
+}
+
+
 /////////////////////////////////////////////////////////////////////////
 // function for changing objective in dependence of a (reduced) dataset
 void
-DagObj::change_growing_objective(const unsigned int indexDataset)
+DagObj::change_growing_objective(const int indexDataset)
 {
-    // Build subgraphs if necessary
-    if (indexDataset == storedSubgraphObj.size()) {
-        add_subgraph_for_new_dataset(indexDataset);
+    if (indexDataset >= 0) {// Change to full or reduced data set
+        // Build subgraphs if necessary
+        if (indexDataset == storedSubgraphObj.size()) {
+            add_subgraph_for_new_dataset(indexDataset);
+        }
+        else if (indexDataset > storedSubgraphObj.size()) {
+            // It should not be possible to jump over a dataset
+            std::ostringstream errmsg;
+            errmsg << "  Error in LowerBoundingSolver - change of objective: subgraph for dataset with index " << indexDataset - 1 << " missing. " << std::endl;
+            throw MAiNGOException(errmsg.str());
+        }
+
+        // Update subgraphs
+        subgraph  = *storedSubgraph[indexDataset];
+        functions = storedFunctions[indexDataset];
+
+        subgraphObj[0]  = *storedSubgraphObj[indexDataset];
+        functionsObj[0] = storedFunctionsObj[indexDataset];
     }
-    else if (indexDataset > storedSubgraphObj.size()) {
-        std::ostringstream errmsg;
-        errmsg << "  Error in UpperBoundingSolver - change of objective: subgraph for dataset with index " << indexDataset - 1 << " missing. " << std::endl;
-        throw MAiNGOException(errmsg.str());
+    else {// Change to complementary set of reduced dataset
+        // negative sign just means to use complementary set; complementary set of full dataset is empty
+        unsigned int indexDatasetTransformed = -indexDataset - 1;
+
+        if (indexDatasetTransformed < 0) {
+            std::ostringstream errmsg;
+            errmsg << "  Error in LowerBoundingSolver - change of objective: calling complementary set of full dataset, i.e., an empty set. " << std::endl;
+            throw MAiNGOException(errmsg.str());
+        }
+
+        // Build subgraphs if necessary
+        if (indexDatasetTransformed >= storedSubgraphObjCompl.size()) {
+            // E.g., with augmentation rule VALSCAL it is possible to jump over a complementary dataset
+            // As an alternative to building potentially unused subgraphs, we could safe empty subgraphs (if MC++ allows) and test for it in the argument of this if-statement
+            for (auto i = storedSubgraphObjCompl.size(); i <= indexDatasetTransformed; i++) {
+                add_subgraph_for_complementary_dataset(i+1); // Reversion of index transformation: 0 < -indexDataset = indexDatasetTransformed + 1
+            }
+        }
+
+        // Update subgraphs
+        subgraph  = *storedSubgraphCompl[indexDatasetTransformed];
+        functions = storedFunctionsCompl[indexDatasetTransformed];
+
+        subgraphObj[0]  = *storedSubgraphObjCompl[indexDatasetTransformed];
+        functionsObj[0] = storedFunctionsObjCompl[indexDatasetTransformed];
     }
+}
 
-    // Update subgraphs
-    subgraph  = *storedSubgraph[indexDataset];
-    functions = storedFunctions[indexDataset];
+/////////////////////////////////////////////////////////////////////////
+// function for changing objective to resampled initial dataset
+void
+DagObj::change_growing_objective_for_resampling()
+{
+    if (storedSubgraphResampled.size() == 0) {// Subgraph for resampled dataset not built yet
+        if ((*datasetResampled).size() == 0) {
+            std::ostringstream errmsg;
+            errmsg << "  Error in LowerBoundingSolver - change of objective for resampling: datasetResampled is empty. " << std::endl;
+            throw MAiNGOException(errmsg.str());
+        }
+
+        mc::FFVar obj = 0;
+        for (auto idxDataPoint : (*datasetResampled)) {
+            obj += resultVars[idxDataPoint + indexFirstData];
+        }
+        if (useMse) {// Use mean of summed objective per data as objective
+            functions[0] = obj / (*datasetResampled).size();
+            functionsObj[0][0] = obj / (*datasetResampled).size();
+        }
+        else {// Use sum of objective per data as objective
+            functions[0] = obj;
+            functionsObj[0][0] = obj;
+        }
+
+        // Build new subgraphs
+        storedFunctionsResampled = functions;
+        auto pointerToSubgraph   = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functions.size(), functions.data()));
+        storedSubgraphResampled.push_back(pointerToSubgraph);
+        subgraph                 = *storedSubgraphResampled[0];
 
-    subgraphObj[0]  = *storedSubgraphObj[indexDataset];
-    functionsObj[0] = storedFunctionsObj[indexDataset];
+        storedFunctionsObjResampled = functionsObj[0];
+        auto pointerToSubgraphObj   = std::make_shared<mc::FFSubgraph>(DAG.subgraph(functionsObj[0].size(), functionsObj[0].data()));
+        storedSubgraphObjResampled.push_back(pointerToSubgraph);
+        subgraphObj[0]              = *storedSubgraphObjResampled[0];
+    }
+    else {
+        // Update subgraphs
+        subgraph  = *storedSubgraphResampled[0];
+        functions = storedFunctionsResampled;
+
+        subgraphObj[0]  = *storedSubgraphObjResampled[0];
+        functionsObj[0] = storedFunctionsObjResampled;
+    }
 }
 #endif    //HAVE_GROWING_DATASETS
 
diff --git a/src/lbpFactory.cpp b/src/lbpFactory.cpp
index 179b5f2..dce21f6 100644
--- a/src/lbpFactory.cpp
+++ b/src/lbpFactory.cpp
@@ -30,24 +30,28 @@ std::shared_ptr<LowerBoundingSolver>
 lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions,
                      const std::vector<babBase::OptimizationVariable> &variables, const std::vector<bool>& variableIsLinear,
                      const unsigned nineqIn, const unsigned neqIn, const unsigned nineqRelaxationOnlyIn, const unsigned neqRelaxationOnlyIn, const unsigned nineqSquashIn,
-                     std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn)
+                     std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn, std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn,
+                     bool printSolver)
 {
 
     switch (settingsIn->LBP_solver) {
         case LBP_SOLVER_MAiNGO: {
-            loggerIn->print_message("      Lower bounding: MAiNGO internal solver (McCormick relaxations for objective, intervals for constraints)\n",
-                                    VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      Lower bounding: MAiNGO internal solver (McCormick relaxations for objective, intervals for constraints)\n",
+                                        VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<LowerBoundingSolver>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn,
                                                          nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
         }
         case LBP_SOLVER_INTERVAL: {
-            loggerIn->print_message("      Lower bounding: Interval extensions\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      Lower bounding: Interval extensions\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<LbpInterval>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn,
                                                  nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
         }
         case LBP_SOLVER_CPLEX: {
 #ifdef HAVE_CPLEX
-            loggerIn->print_message("      Lower bounding: CPLEX\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      Lower bounding: CPLEX\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<LbpCplex>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn,
                                               nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
 #else
@@ -55,7 +59,8 @@ lbp::make_lbp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co
 #endif
         }
         case LBP_SOLVER_CLP: {
-            loggerIn->print_message("      Lower bounding: CLP\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      Lower bounding: CLP\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<LbpClp>(DAG, DAGvars, DAGfunctions, variables, variableIsLinear, nineqIn, neqIn,
                                             nineqRelaxationOnlyIn, neqRelaxationOnlyIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn);
         }
diff --git a/src/lbpInterval.cpp b/src/lbpInterval.cpp
index 0868e1b..66ab042 100644
--- a/src/lbpInterval.cpp
+++ b/src/lbpInterval.cpp
@@ -155,7 +155,7 @@ LbpInterval::_update_LP_ineq_squash(const MC &resultRelaxation, const std::vecto
 
 /////////////////////////////////////////////////////////////////////////////////////////////
 // solves the current linear program
-void
+LP_RETCODE
 LbpInterval::_solve_LP(const babBase::BabNode &currentNode)
 {
 
@@ -193,7 +193,7 @@ LbpInterval::_solve_LP(const babBase::BabNode &currentNode)
             case INEQ_REL_ONLY:
                 if (constraintValueL > _maingoSettings->deltaIneq) {
                     _LPstatus = LP_INFEASIBLE;
-                    return;
+                    return _LPstatus;
                 }
                 break;
             case EQ:
@@ -201,13 +201,13 @@ LbpInterval::_solve_LP(const babBase::BabNode &currentNode)
             case AUX_EQ_REL_ONLY:
                 if (constraintValueL > _maingoSettings->deltaEq || constraintValueU < -_maingoSettings->deltaEq) {
                     _LPstatus = LP_INFEASIBLE;
-                    return;
+                    return _LPstatus;
                 }
                 break;
             case INEQ_SQUASH:
                 if (constraintValueL > 0) {
                     _LPstatus = LP_INFEASIBLE;
-                    return;
+                    return _LPstatus;
                 }
                 break;
             case OBJ:
@@ -216,6 +216,7 @@ LbpInterval::_solve_LP(const babBase::BabNode &currentNode)
         }
     }
     _LPstatus = LP_OPTIMAL;
+	return _LPstatus;
 }
 
 
diff --git a/src/lbpLinearizationStrats.cpp b/src/lbpLinearizationStrats.cpp
index 58a58db..6ac9da3 100644
--- a/src/lbpLinearizationStrats.cpp
+++ b/src/lbpLinearizationStrats.cpp
@@ -10,6 +10,7 @@
  **********************************************************************************/
 
 #include "MAiNGOException.h"
+#include "instrumentor.h"
 #include "lbp.h"
 #include "lbpDagObj.h"
 #include "pointIsWithinNodeBounds.h"
@@ -25,6 +26,7 @@ using namespace lbp;
 LINEARIZATION_RETCODE
 LowerBoundingSolver::_linearize_model_at_midpoint(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds)
 {
+    PROFILE_FUNCTION()
 
     std::vector<double> linearizationPoint;
     for (unsigned int i = 0; i < _nvar; i++) {
@@ -44,6 +46,7 @@ LowerBoundingSolver::_linearize_model_at_midpoint(const std::vector<double> &low
 LINEARIZATION_RETCODE
 LowerBoundingSolver::_linearize_model_at_incumbent_or_at_midpoint(const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds)
 {
+    PROFILE_FUNCTION()
 
     _logger->print_message("  Checking if node contains incumbent.", VERB_ALL, LBP_VERBOSITY);
     if (point_is_within_node_bounds(_incumbent, lowerVarBounds, upperVarBounds)) {
@@ -68,6 +71,7 @@ void
 LowerBoundingSolver::_linearize_functions_at_linpoint(std::vector<MC> &resultRelaxation, const std::vector<double> &linearizationPoint, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds,
                                                       mc::FFSubgraph &subgraph, std::vector<mc::FFVar> &functions)
 {
+    PROFILE_FUNCTION()
 
     for (unsigned int i = 0; i < _nvar; i++) {
         _DAGobj->McPoint[i] = MC(I(lowerVarBounds[i], upperVarBounds[i]), linearizationPoint[i]);
@@ -75,6 +79,8 @@ LowerBoundingSolver::_linearize_functions_at_linpoint(std::vector<MC> &resultRel
     }
 
     try {
+        PROFILE_SCOPE("DAG evaluation for _linearize_functions_at_linpoint")
+        
         // Use the DAG interval subgradient heuristic if specified in settings
         if (!_DAGobj->intervals_already_computed && _maingoSettings->LBP_subgradientIntervals) {
             // Set required pointers
@@ -119,6 +125,7 @@ void
 LowerBoundingSolver::_linearize_functions_at_preset_vector_linpoint(std::vector<vMC> &resultRelaxationVMC, const std::vector<std::vector<double>> &linearizationPoints, const std::vector<double> &lowerVarBounds, const std::vector<double> &upperVarBounds,
                                                                     mc::FFSubgraph &subgraph, std::vector<mc::FFVar> &functions)
 {
+    PROFILE_FUNCTION()
 
     try {
         // Use the DAG interval subgradient heuristic if specified in settings
@@ -165,6 +172,7 @@ LowerBoundingSolver::_linearize_functions_at_preset_vector_linpoint(std::vector<
 LINEARIZATION_RETCODE
 LowerBoundingSolver::_linearization_points_Kelley(const babBase::BabNode &currentNode)
 {
+    PROFILE_FUNCTION()
 
     // First, compute initial point as mid point
     std::vector<double> linearizationPoint(_nvar);
@@ -186,9 +194,7 @@ LowerBoundingSolver::_linearization_points_Kelley(const babBase::BabNode &curren
     double oldSolutionValue = -_maingoSettings->infinity;
     double newSolutionValue = -_maingoSettings->infinity;
     for (unsigned iLin = 1; iLin < _maxnParticipatingVariables; iLin++) {
-        _solve_LP(currentNode);
-
-        _LPstatus = _get_LP_status();
+        _LPstatus = _solve_LP(currentNode);
         if (_LPstatus == LP_INFEASIBLE) {
             return LINEARIZATION_INFEASIBLE;
             break;
diff --git a/src/logger.cpp b/src/logger.cpp
index 4baea91..822fffb 100644
--- a/src/logger.cpp
+++ b/src/logger.cpp
@@ -227,7 +227,8 @@ Logger::create_iterations_csv_file(const bool writeCsv) const
                        << " NodesLeft,"
                        << " AbsGap,"
                        << " RelGap,"
-                       << " CPU" << std::endl;
+                       << " CPU,"
+					   << " Wall" << std::endl;
 
         iterationsFile.close();
     }
diff --git a/src/ubp.cpp b/src/ubp.cpp
index 6120b6a..f47b4df 100644
--- a/src/ubp.cpp
+++ b/src/ubp.cpp
@@ -825,12 +825,12 @@ UpperBoundingSolver::_check_ineq_squash(const std::vector<double> &modelOutput)
 SUBSOLVER_RETCODE
 UpperBoundingSolver::_check_bounds(const std::vector<double> &currentPoint) const
 {
-    _logger->print_message("  Checking feasibility with respect to original variable bounds.", VERB_ALL, UBP_VERBOSITY);
+    _logger->print_message("  Checking feasibility with respect to original variable bounds.\n", VERB_ALL, UBP_VERBOSITY);
     if (point_is_within_node_bounds(currentPoint, _originalLowerBounds, _originalUpperBounds)) {
         return SUBSOLVER_FEASIBLE;
     }
     else {
-        _logger->print_message("  No feasible point found for UBP. Variable bounds violated.", VERB_ALL, UBP_VERBOSITY);
+        _logger->print_message("  No feasible point found for UBP. Variable bounds violated.\n", VERB_ALL, UBP_VERBOSITY);
         return SUBSOLVER_INFEASIBLE;
     }
 }
@@ -973,6 +973,15 @@ UpperBoundingSolver::pass_data_position_to_solver(const std::shared_ptr<std::vec
 }
 
 
+/////////////////////////////////////////////////////////////////////////
+// passes flag indicating whether to use mean squared error as the objective function to respective DagObj routine
+void
+UpperBoundingSolver::pass_use_mse_to_solver(const bool useMseIn)
+{
+    _DAGobj->useMse = useMseIn;
+}
+
+
 #ifdef HAVE_MAiNGO_MPI
 /////////////////////////////////////////////////////////////////////////////////////////////
 // update dataset vector and add respective subgraph after adding new dataset
diff --git a/src/ubpFactory.cpp b/src/ubpFactory.cpp
index d45d321..fe9c387 100644
--- a/src/ubpFactory.cpp
+++ b/src/ubpFactory.cpp
@@ -35,7 +35,8 @@ std::shared_ptr<UpperBoundingSolver>
 ubp::make_ubp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, const std::vector<mc::FFVar> &DAGfunctions,
                      const std::vector<babBase::OptimizationVariable> &variables, const unsigned nineqIn,
                      const unsigned neqIn, const unsigned nineqSquashIn, std::shared_ptr<Settings> settingsIn, std::shared_ptr<Logger> loggerIn,
-                     std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn, UpperBoundingSolver::UBS_USE useIn)
+                     std::shared_ptr<std::vector<Constraint>> constraintPropertiesIn, UpperBoundingSolver::UBS_USE useIn,
+                     bool printSolver)
 {
     UBP_SOLVER desiredSolver;
     std::string useDescription;
@@ -54,32 +55,39 @@ ubp::make_ubp_solver(mc::FFGraph &DAG, const std::vector<mc::FFVar> &DAGvars, co
 
     switch (desiredSolver) { // GCOVR_EXCL_STOP
         case UBP_SOLVER_EVAL: {
-            loggerIn->print_message("      " + useDescription + ": Function evaluation\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": Function evaluation\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UpperBoundingSolver>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
         }
         case UBP_SOLVER_COBYLA: {
-            loggerIn->print_message("      " + useDescription + ": COBYLA\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": COBYLA\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
         }
         case UBP_SOLVER_BOBYQA: {
-            loggerIn->print_message("      " + useDescription + ": BOBYQA\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": BOBYQA\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
         }
         case UBP_SOLVER_LBFGS: {
-            loggerIn->print_message("      " + useDescription + ": LBFGS\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": LBFGS\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
         }
         case UBP_SOLVER_SLSQP: {
-            loggerIn->print_message("      " + useDescription + ": SLSQP\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": SLSQP\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UbpNLopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
         }
         case UBP_SOLVER_IPOPT: {
-            loggerIn->print_message("      " + useDescription + ": IPOPT\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": IPOPT\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UbpIpopt>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
         }
         case UBP_SOLVER_KNITRO: {
 #ifdef HAVE_KNITRO
-            loggerIn->print_message("      " + useDescription + ": KNITRO\n", VERB_NORMAL, BAB_VERBOSITY);
+            if (printSolver)
+                loggerIn->print_message("      " + useDescription + ": KNITRO\n", VERB_NORMAL, BAB_VERBOSITY);
             return std::make_shared<UbpKnitro>(DAG, DAGvars, DAGfunctions, variables, nineqIn, neqIn, nineqSquashIn, settingsIn, loggerIn, constraintPropertiesIn, useIn);
 #else
             throw MAiNGOException("  Error in UbpFactory: Cannot use upper bounding strategy UBP_SOLVER_KNITRO: Your MAiNGO build does not contain KNITRO."); // GCOVR_EXCL_LINE
diff --git a/tests/testProblems/main.cpp b/tests/testProblems/main.cpp
index 17d7621..19ffff5 100644
--- a/tests/testProblems/main.cpp
+++ b/tests/testProblems/main.cpp
@@ -39,6 +39,8 @@
 #include "problem_st_e27.h"
 #include "problem_1d.h"
 #include "problem_nonlinearCons.h"
+#include "problem_CHP_sizing_Ns1.h"
+#include "problem_twoStageIP.h"
 
 #include "MAiNGO.h"
 #include "getTime.h"
@@ -61,7 +63,7 @@
 
 
 bool
-print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE maingoStatus, const double correctSolution, const double epsilonA, const double epsilonR, double &CPUofAllProcesses)
+print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE maingoStatus, const double correctObjectiveValue, const double epsilonA, const double epsilonR, double &CPUofAllProcesses)
 {
     bool correctA = true;
     bool correctR = true;
@@ -79,10 +81,10 @@ print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE mai
             correctR = false;
         }
         else {
-            if (std::fabs(theMAiNGO->get_objective_value() - correctSolution) > epsilonA) {
+            if (std::fabs(theMAiNGO->get_objective_value() - correctObjectiveValue) > epsilonA) {
                 correctA = false;
             }
-            if ((std::fabs(theMAiNGO->get_objective_value() - correctSolution)) > epsilonR * std::fabs(theMAiNGO->get_objective_value())) {
+            if ((std::fabs(theMAiNGO->get_objective_value() - correctObjectiveValue)) > epsilonR * std::fabs(theMAiNGO->get_objective_value())) {
                 correctR = false;
             }
             if (correctA || correctR) {
@@ -93,10 +95,10 @@ print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE mai
             }
             else {
                 if (!correctA) {
-                    std::cout << "ERROR - incorrect objective value (absolute tolerance): " << theMAiNGO->get_objective_value() << " instead of " << correctSolution << std::endl;
+                    std::cout << "ERROR - incorrect objective value (absolute tolerance): " << theMAiNGO->get_objective_value() << " instead of " << correctObjectiveValue << std::endl;
                 }
                 else if (!correctR) {
-                    std::cout << "ERROR - incorrect objective value (relative tolerance): " << theMAiNGO->get_objective_value() << " instead of " << correctSolution << std::endl;
+                    std::cout << "ERROR - incorrect objective value (relative tolerance): " << theMAiNGO->get_objective_value() << " instead of " << correctObjectiveValue << std::endl;
                 }
             }
         }
@@ -105,7 +107,7 @@ print_testResults(std::shared_ptr<maingo::MAiNGO> theMAiNGO, maingo::RETCODE mai
 }
 
 bool
-run_test(std::shared_ptr<maingo::MAiNGO> theMAiNGO, const std::string &name, const double correctSolution, const double epsilonA, const double epsilonR, double &CPUofAllProcesses,
+run_test(std::shared_ptr<maingo::MAiNGO> theMAiNGO, const std::string &name, const double correctObjectiveValue, const double epsilonA, const double epsilonR, double &CPUofAllProcesses,
          bool testAle = false, bool useMinMax = true, bool useTrig = true, bool ignoreBoundingFuncs = false, bool useRelOnly = true)
 {
 
@@ -118,7 +120,7 @@ run_test(std::shared_ptr<maingo::MAiNGO> theMAiNGO, const std::string &name, con
     maingo::RETCODE solverStatus; 
     solverStatus = theMAiNGO->solve();
     const maingo::RETCODE maingoStatus = solverStatus;
-    MAiNGO_MPI_BARRIER const bool default_success = print_testResults(theMAiNGO, maingoStatus, correctSolution, epsilonA, epsilonR, CPUofAllProcesses);
+    MAiNGO_MPI_BARRIER const bool default_success = print_testResults(theMAiNGO, maingoStatus, correctObjectiveValue, epsilonA, epsilonR, CPUofAllProcesses);
     
 
 #ifdef HAVE_MAiNGO_PARSER
@@ -159,7 +161,7 @@ run_test(std::shared_ptr<maingo::MAiNGO> theMAiNGO, const std::string &name, con
     std::cout << std::left << std::setw(65) << name + " (parsed)"
               << ": ";
     const maingo::RETCODE parsed_maingoStatus = theMAiNGO->solve();
-    const bool parsed_success                 = print_testResults(theMAiNGO, parsed_maingoStatus, correctSolution, epsilonA, epsilonR, CPUofAllProcesses);
+    const bool parsed_success                 = print_testResults(theMAiNGO, parsed_maingoStatus, correctObjectiveValue, epsilonA, epsilonR, CPUofAllProcesses);
     return (default_success && parsed_success);
 #else
     return default_success;
@@ -213,7 +215,8 @@ main(int argc, char *argv[])
     std::shared_ptr<Model_st_e27> myModel_st_e27;
     std::shared_ptr<Model_1d> myModel_1d;
     std::shared_ptr<Model_nonlinearCons> myModel_nonlinearCons;
-
+    std::shared_ptr<CHP_sizing_problem> myModel_CHP_sizing;
+    std::shared_ptr<TwoStageIP_problem> myModel_twoStageIP;
 
     std::shared_ptr<maingo::MAiNGO> myMAiNGO;
     try {
@@ -244,6 +247,8 @@ main(int argc, char *argv[])
         myModel_nonlinearCons          = std::make_shared<Model_nonlinearCons>();
         myModel_growing_simple         = std::make_shared<Model_growing_simple>();    // Define a model object implemented in problem_growingDatasets_simple.h
         myModel_growing_AVM            = std::make_shared<Model_growing_AVM>();       // Define a model object implemented in problem_growingDatasets_AVM.h
+        myModel_CHP_sizing             = std::make_shared<CHP_sizing_problem>();
+        myModel_twoStageIP             = std::make_shared<TwoStageIP_problem>();
         // Start with problem_bin1 and initialize MAiNGO object
         myMAiNGO = std::shared_ptr<maingo::MAiNGO>(new maingo::MAiNGO(myModel_bin1));
     }
@@ -409,12 +414,24 @@ main(int argc, char *argv[])
                 exceptionCounter++;
             }
 
-	    // Problem LP_IN_RO (LP with relaxation only inequalities)
+	        // Problem LP_IN_RO (LP with relaxation only inequalities)
             myMAiNGO->set_model(myModel_LP_IN_RO);
             if (!(run_test(myMAiNGO, "Problem_LP_IN_RO", 0, epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
             }
 
+            // two-stage integer programming problem
+            myMAiNGO->set_model(myModel_twoStageIP);
+            if (!(run_test(myMAiNGO, "Problem_twoStageIP", -57, epsilonA, epsilonR, CPUofAllProcesses))) {
+                exceptionCounter++;
+            }
+
+            // Problem CHP Sizing (two-stage NLP)
+            myMAiNGO->set_model(myModel_CHP_sizing);
+            if (!(run_test(myMAiNGO, "Problem_CHP_sizing", 1.287948603324161, epsilonA, epsilonR, CPUofAllProcesses))) {
+                exceptionCounter++;
+            }
+
             // Problem chance, LinStrat: KELLEY_SIMPLEX; LBP_SubgradientIntervals:FALSE, probing:TRUE
             myMAiNGO->set_option("BAB_probing", 1); 
             myMAiNGO->set_option("LBP_subgradientIntervals", 0);  
@@ -424,9 +441,9 @@ main(int argc, char *argv[])
                 exceptionCounter++;
             }
             myMAiNGO->set_option("BAB_probing", 0); 
-	    myMAiNGO->set_option("LBP_subgradientIntervals", 1); 
+	        myMAiNGO->set_option("LBP_subgradientIntervals", 1); 
 
-	    // Problem nonlinear Constraints, LinStrat: KELLEY_SIMPLEX        
+	        // Problem nonlinear Constraints, LinStrat: KELLEY_SIMPLEX        
             myMAiNGO->set_model(myModel_nonlinearCons);
             if (!(run_test(myMAiNGO, "Problem_nonlinearCons_LINP_KELLEY_SIMPLEX", -2.30259, epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
@@ -446,24 +463,24 @@ main(int argc, char *argv[])
             }
 
             // Problem RelaxOnly constraints, LinStrats: SIMPLEX
-                myMAiNGO->set_model(myModel_relaxOnly);
-            	if (!(run_test(myMAiNGO, "Problem_relaxOnly_LINP_SIMPLEX", 4.35581, epsilonA, epsilonR, CPUofAllProcesses))) {
+            myMAiNGO->set_model(myModel_relaxOnly);
+            if (!(run_test(myMAiNGO, "Problem_relaxOnly_LINP_SIMPLEX", 4.35581, epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
             }
 
             // Problem squash, LinStrats: SIMPLEX, subgradientIntervals:False           
 		    myMAiNGO->set_model(myModel_Squash);
-            	if (!(run_test(myMAiNGO, "Problem_Squash_LINP_SIMPLEX", -2.30259, epsilonA, epsilonR, CPUofAllProcesses))) {
+            if (!(run_test(myMAiNGO, "Problem_Squash_LINP_SIMPLEX", -2.30259, epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
             }
 
             myMAiNGO->set_option("LBP_solver", maingo::lbp::LBP_SOLVER_CLP);
             // Problem RelaxOnly constraints, LinStrats: SIMPLEX
-                myMAiNGO->set_model(myModel_relaxOnly);
-            	if (!(run_test(myMAiNGO, "Problem_relaxOnly_LBP_CLP_LINP_SIMPLEX", 4.35581, epsilonA, epsilonR, CPUofAllProcesses))) {
+            myMAiNGO->set_model(myModel_relaxOnly);
+            if (!(run_test(myMAiNGO, "Problem_relaxOnly_LBP_CLP_LINP_SIMPLEX", 4.35581, epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
             }
-            
+
             myMAiNGO->set_option("UBP_solverPreprocessing", maingo::ubp::UBP_SOLVER_EVAL);
             myMAiNGO->set_option("UBP_solverBab", maingo::ubp::UBP_SOLVER_EVAL);
 
@@ -494,7 +511,7 @@ main(int argc, char *argv[])
                 exceptionCounter++;
             }
 
-	    myMAiNGO->set_model(myModel_chance);
+	        myMAiNGO->set_model(myModel_chance);
             if (!(run_test(myMAiNGO, "Problem_chance_LBP_CLP_LINP_KELLEY", 29.894, epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
             }
@@ -554,7 +571,7 @@ main(int argc, char *argv[])
             //Problem chance, UBP: IPOPT, LBP: MAiNGO, LinStrat: SIMPLEX, BAB_probing: TRUE
             myMAiNGO->set_option("BAB_probing", 1);
             myMAiNGO->set_model(myModel_chance);
-	         if (!(run_test(myMAiNGO, "Problem_chance_LINP_SIMPLEX_probing", 29.894 , epsilonA, epsilonR, CPUofAllProcesses))) {
+	        if (!(run_test(myMAiNGO, "Problem_chance_LINP_SIMPLEX_probing", 29.894 , epsilonA, epsilonR, CPUofAllProcesses))) {
                 exceptionCounter++;
             }
             myMAiNGO->set_option("BAB_probing", 0);
@@ -637,7 +654,7 @@ main(int argc, char *argv[])
             }
 
             // For further test cases reset AVM:
-            // myMAiNGO->set_option("LBP_addAuxiliaryVars", 0);
+            myMAiNGO->set_option("LBP_addAuxiliaryVars", 0);
         }
         catch (std::exception &e) {
             exceptionCounter++;
@@ -670,11 +687,11 @@ main(int argc, char *argv[])
         const unsigned int cpuMinutes    = std::floor((endCPU - startCPU) / 60.);
         const unsigned int wallMinutes   = std::floor((endWall - startWall) / 60.);
 #ifdef HAVE_MAiNGO_MPI
-        std::cout << "Total CPU time:  " << CPUofAllProcesses << " s = " << cpuAllMinutes << " m " << (CPUofAllProcesses)-cpuAllMinutes * (60.) << "s." << std::endl;
+        std::cout << "Total CPU time:  " << CPUofAllProcesses << " s = " << cpuAllMinutes << " m " << (CPUofAllProcesses) - (cpuAllMinutes * 60.) << "s." << std::endl;
 #else
-    std::cout << "Total CPU time:  " << endCPU - startCPU << " s = " << cpuMinutes << " m " << (endCPU - startCPU) - cpuMinutes * (60.) << "s." << std::endl;
+        std::cout << "Total CPU time:  " << endCPU - startCPU << " s = " << cpuMinutes << " m " << (endCPU - startCPU) - (cpuMinutes * 60.) << "s." << std::endl;
 #endif
-        std::cout << "Total wall time: " << endWall - startWall << " s = " << wallMinutes << " m " << (endWall - startWall) - wallMinutes * (60.) << "s." << std::endl
+        std::cout << "Total wall-clock time: " << endWall - startWall << " s = " << wallMinutes << " m " << (endWall - startWall) - (wallMinutes * 60.) << "s." << std::endl
                   << std::endl;
 
 #ifdef HAVE_MAiNGO_MPI
diff --git a/tests/testProblems/problem_CHP_sizing_Ns1.h b/tests/testProblems/problem_CHP_sizing_Ns1.h
new file mode 100644
index 0000000..5f5a2be
--- /dev/null
+++ b/tests/testProblems/problem_CHP_sizing_Ns1.h
@@ -0,0 +1,212 @@
+// A simplified example problem of CHP sizing for demonstrating two-stage structure
+// We need to size a heat-controlled CHP for the satisfaction of heat and power demands, expressed
+// through multiple scenarios with different probabilities.
+// First stage decisions are the nominal size and second stage decision is the relative load.
+// From the relative load the required fuel and thus the operational costs can be calculated.
+
+// Author Marco Langiu
+#include "TwoStageModel.h"
+#include "MAiNGO.h"
+
+#include <fstream>
+#include <sstream>
+#include <string>
+
+
+/**
+ * @brief Uncertain data for the CHP sizing problem
+ * heat and electricity demands
+ */
+std::vector<std::vector<double>> example_data = {
+// Qdot_dem, P_dem
+  {1.160934, 0.877757}
+};
+
+/**
+ * @brief Vertex form of a * x^2 + b * x + c
+ */
+Var vertex_form(const Var &x, double a, double b, double c) {
+    return c - pow(b, 2) / (4 * a) + a * pow(x + b / (2 * a), 2);
+}
+
+
+/**
+ * @brief Example for user defined TwoStageModel
+ */
+struct CHP_sizing_problem : maingo::TwoStageModel {
+
+  CHP_sizing_problem() : TwoStageModel(1, 1, example_data) {
+    std::map<std::string, double CHP_sizing_problem::*> members;
+    members["Qdot_nom_ref"] = &CHP_sizing_problem::Qdot_nom_ref;
+    members["c_ref"] =        &CHP_sizing_problem::c_ref;
+    members["M"] =            &CHP_sizing_problem::M;
+    members["c_m"] =          &CHP_sizing_problem::c_m;
+    members["Qdot_rel_min"] = &CHP_sizing_problem::Qdot_rel_min;
+    members["Qdot_nom_min"] = &CHP_sizing_problem::Qdot_nom_min;
+    members["Qdot_nom_max"] = &CHP_sizing_problem::Qdot_nom_max;
+    members["n"] =            &CHP_sizing_problem::n;
+    members["i"] =            &CHP_sizing_problem::i;
+    members["T_OP"] =         &CHP_sizing_problem::T_OP;
+    members["p_gas"] =        &CHP_sizing_problem::p_gas;
+    members["p_el_buy"] =     &CHP_sizing_problem::p_el_buy;
+    members["p_el_sell"] =    &CHP_sizing_problem::p_el_sell;
+
+    Qdot_eps   = 0.001 * Qdot_nom_max;
+    Qdot_mean  = (Qdot_nom_min + Qdot_nom_max) / 2;
+    af = (pow(1 + i, n) * i) / (pow(1 + i, n) - 1);  // annuity factor
+  };
+
+  double Qdot_nom_ref = 1;     // [MW], reference nominal power
+  double c_ref        = 1.3e6; // [€], reference cost
+  double M            = 0.9;   // [-], cost exponent
+  double c_m          = 0.05;  // [-], maintenance coefficient, (fraction of investment cost)
+  double Qdot_rel_min = 0.5;   // [-] minimum output part load
+  double Qdot_nom_min = 0.5;   // [MW], minimum nominal power
+  double Qdot_nom_max = 2.3;   // [MW], maximum nominal power
+  double Qdot_eps;
+  double Qdot_mean;
+  
+  double n = 30;   // lifetime in years
+  double i = 0.05; // annualization interst rate
+  double af;       // annuity factor
+
+  double T_OP      = 6000; // [h / a]
+  double p_gas     = 80;   // [€ / Mwh]
+  double p_el_buy  = 250;  // [€ / Mwh]
+  double p_el_sell = 100;  // [€ / Mwh]
+
+  /**
+   * @brief Compute the nominal thermal efficiency of the CHP
+   */
+  Var eff_th_nom(const Var &Qdot_nom) {
+      return 0.498 - 3.55e-2 * Qdot_nom;
+  }
+
+  /**
+   * @brief Compute the nominal electrical efficiency of the CHP
+   */
+  Var eff_el_nom(const Var &Qdot_nom) {
+      return 0.372 + 3.55e-2 * Qdot_nom;
+  }
+
+  /**
+   * @brief Compute the relative thermal efficiency of the CHP
+   */
+  Var eff_th_rel(const Var &Qdot_rel) {
+      return vertex_form(Qdot_rel, -0.0768, -0.0199, 1.0960);
+  }
+
+  /**
+   * @brief Compute the relative electrical efficiency of the CHP
+   */
+  Var eff_el_rel(const Var &Qdot_rel) {
+      return vertex_form(Qdot_rel, -0.2611, 0.6743, 0.5868);
+  }
+
+  /**
+   * @brief Compute the thermal efficiency of the CHP
+   */
+  Var eff_th(const Var &Qdot_nom, const Var &Qdot_rel) {
+      return eff_th_nom(Qdot_nom) * eff_th_rel(Qdot_rel);
+  }
+
+  /**
+   * @brief Compute the electrical efficiency of the CHP
+   */
+  Var eff_el(const Var &Qdot_nom, const Var &Qdot_rel) {
+      return eff_el_nom(Qdot_nom) * eff_el_rel(Qdot_rel);
+  }
+
+  /**
+   * @brief Function for getting the (Nx + Ns * Ny) optimization variables
+   */
+  std::vector<maingo::OptimizationVariable> get_variables() {
+    std::vector<maingo::OptimizationVariable> variables;
+    // Using branching priority ratio of 16:1
+    std::string name = "Qdot_nom";
+    variables.push_back({{Qdot_nom_min, Qdot_nom_max}, 16, name});
+    for (auto s = 0; s < Ns; ++s) {
+      name = "Qdot_rel_" + std::to_string(s);
+      variables.push_back({{0, 1}, 1, name});
+    }
+
+    return variables;
+  };
+
+  std::vector<double> get_initial_point() {
+    return {
+      0,
+      0
+    };
+  }
+
+  /**
+   * @brief Annualized investment cost in million euros/a
+   */
+  Var f1_func(Varview x) {
+    const Var & Qdot_nom = x[0];
+    // investment cost of the component
+    Var ic = std::move(c_ref * pow(Qdot_nom / Qdot_nom_ref, M));
+    // fixed cost of the component
+    Var fc = std::move(c_m * ic);
+
+    return 1e-6 * (af * ic + fc);
+  }
+
+  /**
+   * @brief Annual operating cost in million euros/a
+   */
+  Var f2_func(Varview x, Varview y, Valview p) {
+    auto Qdot_nom = x[0];
+    auto Qdot_rel     = y[0];
+    auto Qdot_dem     = p[0];
+    auto P_dem        = p[1];
+    
+    auto Qdot_out = Qdot_nom * Qdot_rel;
+
+    auto Edot_in = Qdot_out / eff_th(Qdot_nom, Qdot_rel);
+    auto P_out = Edot_in * eff_el(Qdot_nom, Qdot_rel);
+    
+    // When allowing an electric heater
+    // auto Qdot_supplied_via_electricity = max(0, Qdot_dem - Qdot_out);
+    // Otherwise
+    auto Qdot_supplied_via_electricity = 0;
+
+    auto P_grid = P_dem - P_out + Qdot_supplied_via_electricity;
+
+    // Total variable cost = purchase for gas
+    // + purchase for missing elextricity
+    // or compensation (negative cost) for selling excess electricity
+    return 1e-6 * (
+        p_gas * Edot_in
+        + p_el_buy * max(0, P_grid)
+        - p_el_sell * max(0, -P_grid)
+    ) * T_OP;
+  }
+
+  /**
+   * @brief The vector of second stage constraints
+   */
+  std::vector<std::vector<std::pair<Var, std::string>>> g2_func(Varview x, Varview y, Valview p) {
+    auto & Qdot_nom        = x[0];
+    auto & Qdot_rel        = y[0];
+    auto min_partload_viol = vertex_form(Qdot_rel, -1, Qdot_rel_min + Qdot_eps, -Qdot_rel_min * Qdot_eps);
+
+    // If not using heater
+    auto Qdot_out      = Qdot_nom * Qdot_rel;
+    auto & Qdot_dem    = p[0];
+    auto dem_violation = Qdot_dem - Qdot_out;
+
+    return {
+      {
+        {min_partload_viol, "Minimum part load violation"},
+        {dem_violation, "Heat demand satisfaction"},
+      },  // ineq
+      {},  // squash
+      {},  // eq
+      {},  // ineqRelOnly
+      {},  // eqRelOnly
+    };
+  }
+
+};
\ No newline at end of file
diff --git a/tests/testProblems/problem_case3_wnet.h b/tests/testProblems/problem_case3_wnet.h
index 1e602e1..8dff02d 100644
--- a/tests/testProblems/problem_case3_wnet.h
+++ b/tests/testProblems/problem_case3_wnet.h
@@ -373,4 +373,4 @@ Model_case3_wnet::evaluate(const std::vector<U>& currentPoint)
     // Additional Output:
 
     return result;
-}
\ No newline at end of file
+}
diff --git a/tests/testProblems/problem_growingDatasets_AVM.h b/tests/testProblems/problem_growingDatasets_AVM.h
index c91789d..d99ebca 100644
--- a/tests/testProblems/problem_growingDatasets_AVM.h
+++ b/tests/testProblems/problem_growingDatasets_AVM.h
@@ -136,4 +136,4 @@ Model_growing_AVM::evaluate(const std::vector<Var> &optVars)
     result.output.push_back(maingo::OutputVariable("slope = offset", slope1 * slope2));
 
     return result;
-}
\ No newline at end of file
+}
diff --git a/tests/testProblems/problem_growingDatasets_simple.h b/tests/testProblems/problem_growingDatasets_simple.h
index 701f03b..24a5bcc 100644
--- a/tests/testProblems/problem_growingDatasets_simple.h
+++ b/tests/testProblems/problem_growingDatasets_simple.h
@@ -124,4 +124,4 @@ Model_growing_simple::evaluate(const std::vector<Var> &optVars)
     result.output.push_back(maingo::OutputVariable("slope", sqr(sqrt_slope)));
 
     return result;
-}
\ No newline at end of file
+}
diff --git a/tests/testProblems/problem_twoStageIP.h b/tests/testProblems/problem_twoStageIP.h
new file mode 100644
index 0000000..b396bb1
--- /dev/null
+++ b/tests/testProblems/problem_twoStageIP.h
@@ -0,0 +1,116 @@
+/**
+ * Example 1 from the paper:
+ * Carøe, C. C. & Schultz, R.
+ * Dual decomposition in stochastic integer programming 
+ * Oper. Res. Lett., Elsevier, 1999, 24, 37-45
+ * DOI: 10.1016/S0167-6377(98)00050-9
+ */
+#include "TwoStageModel.h"
+#include "MAiNGO.h"
+
+#include <fstream>
+#include <sstream>
+#include <string>
+
+/**
+ * @brief Example for a two-stage integer programming problem
+ */
+struct TwoStageIP_problem : maingo::TwoStageModel {
+
+  TwoStageIP_problem() : TwoStageModel(
+    2, // Nx
+    4, // Ny
+    { /** NOTE: In the original example the data consists of all combinations of two values from 5 to 15 in steps of 0.5 */
+    // xi1, xi2
+      { 5,  5},
+      { 5, 15},
+      {15,  5},
+      {15, 15}
+    }
+  ) {};
+
+  /**
+   * @brief Function for getting the (Nx + Ns * Ny) optimization variables
+   */
+  std::vector<maingo::OptimizationVariable> get_variables() {
+    std::vector<maingo::OptimizationVariable> variables;
+    // Using branching priority ratio of 16:1
+    std::string name = "x_1";
+    variables.push_back({{0, 5}, maingo::VT_INTEGER, 1, name});
+    name = "x_2";
+    variables.push_back({{0, 5}, maingo::VT_INTEGER, 1, name});
+    for (auto s = 0; s < Ns; ++s) {
+      name = "y_1_" + std::to_string(s);
+      variables.push_back({{0, 1}, maingo::VT_BINARY, 1, name});
+
+      name = "y_2_" + std::to_string(s);
+      variables.push_back({{0, 1}, maingo::VT_BINARY, 1, name});
+
+      name = "y_3_" + std::to_string(s);
+      variables.push_back({{0, 1}, maingo::VT_BINARY, 1, name});
+
+      name = "y_4_" + std::to_string(s);
+      variables.push_back({{0, 1}, maingo::VT_BINARY, 1, name});
+    }
+
+    return variables;
+  };
+
+  std::vector<double> get_initial_point() {
+      return {
+          0, 4,       // first stage
+          0, 0, 0, 0, // second stage, scenario 1
+          0, 0, 0, 0, // second stage, scenario 2
+          0, 0, 0, 0, // second stage, scenario 3
+          0, 0, 0, 0  // second stage, scenario 4
+        };
+  }
+
+  Var f1_func(Varview x) {
+    const Var &x1 = x[0];
+    const Var &x2 = x[1];
+    return - (3/2 * x1 + 4 * x2);
+  }
+
+  Var f2_func(Varview x, Varview y, Valview p) {
+    const Var &y1 = y[0];
+    const Var &y2 = y[1];
+    const Var &y3 = y[2];
+    const Var &y4 = y[3];
+    return - (16 * y1 + 19 * y2 + 23 * y3 + 28 * y4);
+  }
+
+  std::vector<std::vector<std::pair<Var, std::string>>> g1_func(Varview x) {
+      const Var &x1 = x[0];
+      const Var &x2 = x[1];
+      return {
+          {{x1 - x2, "dummy constraint inactive at the global solution"}}, // ineq
+          {},                                                              // squash
+          {},                                                              // eq
+          {},                                                              // ineqRelOnly
+          {},                                                              // eqRelOnly
+      };
+  }
+
+  std::vector<std::vector<std::pair<Var, std::string>>> g2_func(Varview x, Varview y, Valview p) {
+    const Var &x1     = x[0];
+    const Var &x2     = x[1];
+    const Var &y1     = y[0];
+    const Var &y2     = y[1];
+    const Var &y3     = y[2];
+    const Var &y4     = y[3];
+    const double &xi1 = p[0];
+    const double &xi2 = p[1];
+    return {
+      {
+        {x1 + 2 * y1 + 3 * y2 + 4 * y3 + 5 * y4 - xi1, "con_1"},
+        {x2 + 6 * y1 + 1 * y2 + 3 * y3 + 2 * y4 - xi2, "con_2"},
+      },  // ineq
+      {},  // squash
+      {},  // eq
+      {},  // ineqRelOnly
+      {},  // eqRelOnly
+    };
+  }
+
+};
\ No newline at end of file
diff --git a/tests/unitTests/testMAiNGOException.cpp b/tests/unitTests/testMAiNGOException.cpp
index 0a05f7e..a1683bc 100644
--- a/tests/unitTests/testMAiNGOException.cpp
+++ b/tests/unitTests/testMAiNGOException.cpp
@@ -59,7 +59,7 @@ TEST(TestMAiNGOException, ConstructFromOtherException)
 ///////////////////////////////////////////////////
 TEST(TestMAiNGOException, ConstructWithBabNode)
 {
-    babBase::BabNode node(42, std::vector<double>(1, 0.), std::vector<double>(1, 1.), 0, 1, 2, true);
+    babBase::BabNode node(42, std::vector<double>(1, 0.), std::vector<double>(1, 1.), 0, 0, 1, 2, true);
     const MAiNGOException e("my error message", node);
     const std::string msg = e.what();
     EXPECT_EQ("my error message\n  Exception was thrown while processing node no. 1:\n    x(0): 0:1", msg);
@@ -69,7 +69,7 @@ TEST(TestMAiNGOException, ConstructWithBabNode)
 ///////////////////////////////////////////////////
 TEST(TestMAiNGOException, ConstructWithBabNodeFromOtherException)
 {
-    babBase::BabNode node(42, std::vector<double>(1, 0.), std::vector<double>(1, 1.), 0, 1, 2, true);
+    babBase::BabNode node(42, std::vector<double>(1, 0.), std::vector<double>(1, 1.), 0, 0, 1, 2, true);
     const std::runtime_error originalException("original error message");
     const MAiNGOException e("my error message", originalException, node);
     const std::string msg = e.what();
diff --git a/tests/unitTests/testMAiNGOsetAndGetOption.cpp b/tests/unitTests/testMAiNGOsetAndGetOption.cpp
index 8f7dc0b..a48dbdc 100644
--- a/tests/unitTests/testMAiNGOsetAndGetOption.cpp
+++ b/tests/unitTests/testMAiNGOsetAndGetOption.cpp
@@ -1011,6 +1011,72 @@ TEST(TestMAiNGOsetAndGetOption, modelWritingLanguage)
 }
 
 
+///////////////////////////////////////////////////
+TEST(TestMAiNGOsetAndGetOption, growing_approach)
+{
+    MAiNGO maingo;
+#ifdef HAVE_GROWING_DATASETS
+    EXPECT_EQ(maingo.set_option("growing_approach", 0), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 1), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 1);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 2), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 2);
+
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 1.5), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 3), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+#else
+    EXPECT_EQ(maingo.set_option("growing_approach", 0), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 1), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 2), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 1.5), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_approach", 3), true);
+    EXPECT_EQ(maingo.get_option("growing_approach"), 0);
+#endif
+}
+
+
+///////////////////////////////////////////////////
+TEST(TestMAiNGOsetAndGetOption, growing_maxTimePostprocessing)
+{
+    MAiNGO maingo;
+#ifdef HAVE_GROWING_DATASETS
+    EXPECT_EQ(maingo.set_option("growing_maxTimePostprocessing", 1e3), true);
+    EXPECT_EQ(maingo.get_option("growing_maxTimePostprocessing"), 1e3);
+
+
+    EXPECT_EQ(maingo.set_option("growing_maxTimePostprocessing", -10), true);
+    EXPECT_EQ(maingo.get_option("growing_maxTimePostprocessing"), 60);
+#else
+    EXPECT_EQ(maingo.set_option("growing_maxTimePostprocessing", 1e3), true);
+    EXPECT_EQ(maingo.get_option("growing_maxTimePostprocessing"), 60);
+
+    EXPECT_EQ(maingo.set_option("growing_maxTimePostprocessing", -10), true);
+    EXPECT_EQ(maingo.get_option("growing_maxTimePostprocessing"), 60);
+#endif
+}
+
+
 ///////////////////////////////////////////////////
 TEST(TestMAiNGOsetAndGetOption, growing_dataSizeTol)
 {
@@ -1019,6 +1085,7 @@ TEST(TestMAiNGOsetAndGetOption, growing_dataSizeTol)
     EXPECT_EQ(maingo.set_option("growing_dataSizeTol", 0.5), true);
     EXPECT_EQ(maingo.get_option("growing_dataSizeTol"), 0.5);
 
+
     EXPECT_EQ(maingo.set_option("growing_dataSizeTol", -0.5), true);
     EXPECT_EQ(maingo.get_option("growing_dataSizeTol"), 0.9);
 
@@ -1045,6 +1112,7 @@ TEST(TestMAiNGOsetAndGetOption, growing_dataSizeInit)
     EXPECT_EQ(maingo.set_option("growing_dataSizeInit", 0.5), true);
     EXPECT_EQ(maingo.get_option("growing_dataSizeInit"), 0.5);
 
+
     EXPECT_EQ(maingo.set_option("growing_dataSizeInit", -0.5), true);
     EXPECT_EQ(maingo.get_option("growing_dataSizeInit"), 0.1);
 
@@ -1063,6 +1131,66 @@ TEST(TestMAiNGOsetAndGetOption, growing_dataSizeInit)
 }
 
 
+///////////////////////////////////////////////////
+TEST(TestMAiNGOsetAndGetOption, growing_useResampling)
+{
+    MAiNGO maingo;
+#ifdef HAVE_GROWING_DATASETS
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 0), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 1), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 1);
+
+
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 2), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 0.99), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+#else
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 0), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 1), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 2), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+
+    EXPECT_EQ(maingo.set_option("growing_useResampling", 0.99), true);
+    EXPECT_EQ(maingo.get_option("growing_useResampling"), 0);
+#endif
+}
+
+
+///////////////////////////////////////////////////
+TEST(TestMAiNGOsetAndGetOption, growing_augmentPercentage)
+{
+    MAiNGO maingo;
+#ifdef HAVE_GROWING_DATASETS
+    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.5);
+
+
+    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", -0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 1.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+#else
+    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", -0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 1.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+#endif
+}
+
+
 ///////////////////////////////////////////////////
 TEST(TestMAiNGOsetAndGetOption, growing_augmentRule)
 {
@@ -1078,31 +1206,62 @@ TEST(TestMAiNGOsetAndGetOption, growing_augmentRule)
     EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 3), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 3);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 4), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 4);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 5);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 6), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 6);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 7), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 7);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 8), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
+
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", -0.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 1.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 #else
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 0), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 1), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 2), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 3), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
+    
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 4), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 6), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 7), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
+
+    EXPECT_EQ(maingo.set_option("growing_augmentRule", 8), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", -0.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 
     EXPECT_EQ(maingo.set_option("growing_augmentRule", 1.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 2);
+    EXPECT_EQ(maingo.get_option("growing_augmentRule"), 8);
 #endif
 }
 
@@ -1160,33 +1319,28 @@ TEST(TestMAiNGOsetAndGetOption, growing_augmentWeight)
 
 
 ///////////////////////////////////////////////////
-TEST(TestMAiNGOsetAndGetOption, growing_augmentPercentage)
+TEST(TestMAiNGOsetAndGetOption, growing_augmentTol)
 {
     MAiNGO maingo;
 #ifdef HAVE_GROWING_DATASETS
-    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 0.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.5);
+    EXPECT_EQ(maingo.set_option("growing_augmentTol", 0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentTol"), 0.5);
 
-    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", -0.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
 
-    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 1.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+    EXPECT_EQ(maingo.set_option("growing_augmentTol", -0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentTol"), 0.1);
 #else
-    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 0.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
-
-    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", -0.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+    EXPECT_EQ(maingo.set_option("growing_augmentTol", 0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentTol"), 0.1);
 
-    EXPECT_EQ(maingo.set_option("growing_augmentPercentage", 1.5), true);
-    EXPECT_EQ(maingo.get_option("growing_augmentPercentage"), 0.25);
+    EXPECT_EQ(maingo.set_option("growing_augmentTol", -0.5), true);
+    EXPECT_EQ(maingo.get_option("growing_augmentTol"), 0.1);
 #endif
 }
 
 
 ///////////////////////////////////////////////////
-TEST(TestMAiNGOsetAndGetOption, UnkownOption)
+TEST(TestMAiNGOsetAndGetOption, UnknownOption)
 {
     MAiNGO maingo;
     EXPECT_EQ(maingo.set_option("bogusOption", 0.5), false);
diff --git a/tests/unitTests/testPointIsWithinNodeBounds.cpp b/tests/unitTests/testPointIsWithinNodeBounds.cpp
index 877bf05..8766747 100644
--- a/tests/unitTests/testPointIsWithinNodeBounds.cpp
+++ b/tests/unitTests/testPointIsWithinNodeBounds.cpp
@@ -24,7 +24,7 @@ struct TestPointIsWithinNodeBounds: testing::Test {
     const std::vector<double> lowerBounds = {0., -1., -2., 1.};
     const std::vector<double> upperBounds = {1., 1., -1., 1.};
 
-    const babBase::BabNode node{42. /*pruning score*/, lowerBounds, upperBounds, 0 /*index data set*/, 0 /*ID*/, 0 /*depth*/, false /*augment data*/};
+    const babBase::BabNode node{42. /*pruning score*/, lowerBounds, upperBounds, 0 /*index data set*/, -1 /*parent ID*/, 0 /*ID*/, 0 /*depth*/, false /*augment data*/};
 };
 
 
-- 
GitLab