When the extension module is built and tested on an emulated 64-bit ARM architecture (specificly, an emulated QEMU cortex-a57 machine), the test function test_check_binary_solution in test/combina_test.py appears to consistently fail. This appears to not be due to an actual malfunction. Rather, PyCombina appears to find an equivalent solution or solution with indistinguishable objective.
Similar observations can be made on some configurations of emulated 32-bit x86 Linux machines. They may be caused by the effect of subtle differences in floating point arithmetic between these CPU architectures on tree search decisions.
Reproducing this problem requires either a hardware ARM64 system (might work on an Apple M1). Here, we will discuss the emulation setup on an up-to-date Ubuntu Linux system. These steps were tested on only one specific host system (as well as a Gitlab Action runner of unknown hardware specifications). The following program outputs describe the test systems CPU, Kernel, and OS distribution version:
cpuid.txt
lscpu.txt
lsb_release.txt
uname.txt
We also require an installation image for a reasonably sized Linux distribution. Alpine Linux' virt installation ISO will work:
Remember to verify checksums.
This will guide you through the installation process. For most options, you can give the default response. The following settings are important:
============================= test session starts ==============================
platform linux -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0 -- /root/pycombina/venv/bin/python
cachedir: .pytest_cache
rootdir: /root/pycombina
configfile: pyproject.toml
collecting ... collected 14 items
test/combina_test.py::CombinaTestSingleInputBnB::test_check_binary_solution FAILED [ 7%]
test/combina_test.py::CombinaTestSingleInputBnB::test_check_n_max_switches PASSED [ 14%]
test/combina_test.py::CombinaTestSingleInputBnB::test_check_objective PASSED [ 21%]
test/combina_test.py::CombinaTestSingleInputMILP::test_check_binary_solution SKIPPED [ 28%]
test/combina_test.py::CombinaTestSingleInputMILP::test_check_n_max_switches SKIPPED [ 35%]
test/combina_test.py::CombinaTestSingleInputMILP::test_check_objective SKIPPED [ 42%]
test/input_test.py::InputTest::test_manual_extend_sos1_fulfilled PASSED [ 50%]
test/input_test.py::InputTest::test_manual_scale_sos1_fulfilled PASSED [ 57%]
test/input_test.py::InputTest::test_multiple_controls_sos1_fulfilled_manual PASSED [ 64%]
test/input_test.py::InputTest::test_multiple_controls_sos1_violated PASSED [ 71%]
test/input_test.py::InputTest::test_single_control_T_not_increasing PASSED [ 78%]
test/input_test.py::InputTest::test_single_control_b_rel_not_relaxed_binary_solution PASSED [ 85%]
test/input_test.py::InputTest::test_single_control_invalid_dimensions PASSED [ 92%]
test/input_test.py::InputTest::test_single_control_sos1_violated PASSED [100%]
=================================== FAILURES ===================================
_____________ CombinaTestSingleInputBnB.test_check_binary_solution _____________
self = <test.combina_test.CombinaTestSingleInputBnB testMethod=test_check_binary_solution>
def test_check_binary_solution(self):
b_bin = self.binapprox.b_bin
> assert_array_equal(np.squeeze(b_bin[0,:]), self.b_bin_check)
test/combina_test.py:141:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
args = (<built-in function eq>, array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0.,...0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0.]))
kwds = {'err_msg': '', 'header': 'Arrays are not equal', 'strict': False, 'verbose': True}
@wraps(func)
def inner(*args, **kwds):
with self._recreate_cm():
> return func(*args, **kwds)
E AssertionError:
E Arrays are not equal
E
E Mismatched elements: 4 / 359 (1.11%)
E Max absolute difference: 1.
E Max relative difference: 1.
E x: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
E 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
E 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,...
E y: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
E 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
E 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,...
/usr/lib/python3.11/contextlib.py:81: AssertionError
---------------------------- Captured stdout setup -----------------------------
Running Branch and Bound ...
Iteration Upper bound Branches Runtime (s)
U 359 1.492254e+04 193 2.174400e-02
U 686 1.492254e+04 113 3.404000e-02
U 1092 1.491668e+04 113 5.070000e-02
U 1335 1.490628e+04 217 6.454100e-02
U 1681 1.466628e+04 217 7.791400e-02
U 2026 1.442628e+04 215 9.027400e-02
U 2368 1.418628e+04 213 1.015990e-01
U 2707 1.394628e+04 211 1.135950e-01
U 3043 1.370628e+04 209 1.265310e-01
U 3376 1.346628e+04 207 1.378230e-01
Iteration Upper bound Branches Runtime (s)
U 3706 1.322628e+04 205 1.479860e-01
U 4033 1.298628e+04 203 1.587170e-01
U 4357 1.274628e+04 201 1.695000e-01
U 4678 1.250628e+04 199 1.792410e-01
U 4996 1.226628e+04 197 1.901060e-01
U 5311 1.202628e+04 195 2.004120e-01
U 5623 1.178628e+04 193 2.107990e-01
U 5932 1.154628e+04 191 2.214780e-01
U 6238 1.130628e+04 189 2.317100e-01
U 6541 1.106628e+04 187 2.418450e-01
Iteration Upper bound Branches Runtime (s)
U 6841 1.082628e+04 185 2.521730e-01
U 7138 1.058628e+04 183 2.613710e-01
U 7432 1.034628e+04 181 2.720020e-01
U 7723 1.010628e+04 179 2.816850e-01
U 8011 9.866278e+03 177 2.906280e-01
U 8296 9.626278e+03 175 3.002270e-01
U 8578 9.386278e+03 173 3.086740e-01
U 8857 9.146278e+03 171 3.184470e-01
U 9133 8.906278e+03 169 3.287310e-01
U 9406 8.666278e+03 167 3.371320e-01
Iteration Upper bound Branches Runtime (s)
U 9676 8.426278e+03 165 3.471150e-01
U 9943 8.186278e+03 163 3.557620e-01
U 10207 7.946278e+03 161 3.651910e-01
U 10468 7.706278e+03 159 3.738520e-01
U 10726 7.466278e+03 157 3.826940e-01
U 10981 7.226278e+03 155 3.906110e-01
U 11233 6.986278e+03 153 3.987310e-01
U 11482 6.746278e+03 151 4.073570e-01
U 11728 6.506278e+03 149 4.156230e-01
U 11971 6.266278e+03 147 4.240400e-01
Iteration Upper bound Branches Runtime (s)
U 12211 6.026278e+03 145 4.329160e-01
U 12448 5.786278e+03 143 4.424950e-01
U 12682 5.546278e+03 141 4.510750e-01
U 12913 5.306278e+03 139 4.601110e-01
U 13141 5.066278e+03 137 4.677880e-01
U 13366 4.826278e+03 135 4.779070e-01
U 13588 4.586278e+03 133 4.875270e-01
U 13807 4.346278e+03 131 4.957500e-01
U 14023 4.286637e+03 129 5.037530e-01
U 15852 4.106278e+03 136 5.732390e-01
Iteration Upper bound Branches Runtime (s)
U 17578 3.960137e+03 177 6.420570e-01
U 19330 3.720137e+03 179 7.137420e-01
U 20906 3.480137e+03 181 7.721740e-01
U 22401 3.240137e+03 181 8.301330e-01
U 23835 3.000137e+03 178 8.876220e-01
U 25101 2.760137e+03 176 9.337170e-01
U 26230 2.520137e+03 174 9.740980e-01
U 27186 2.280137e+03 174 1.005934e+00
U 27874 2.040137e+03 182 1.028818e+00
U 45928 2.038044e+03 186 1.673825e+00
Iteration Upper bound Branches Runtime (s)
U 51400 1.988518e+03 187 1.870376e+00
U 57075 1.938991e+03 186 2.104009e+00
U 62048 1.889460e+03 184 2.309029e+00
U 67361 1.850574e+03 175 2.498778e+00
U 67529 1.831332e+03 175 2.506058e+00
U 87803 1.805342e+03 181 3.247767e+00
U 94015 1.725159e+03 181 3.495085e+00
U 98200 1.644977e+03 180 3.655375e+00
U 103439 1.643323e+03 169 3.846041e+00
U 114933 1.643323e+03 166 4.256921e+00
Iteration Upper bound Branches Runtime (s)
U 116162 1.603330e+03 176 4.300309e+00
U 123397 1.603330e+03 177 4.574120e+00
U 154495 1.603330e+03 183 5.718500e+00
Optimal solution found
Best solution: 1.603330e+03
Total iterations: 972202
Total runtime: 4.043268e+01 s
=========================== short test summary info ============================
SKIPPED [3] venv/lib/python3.11/site-packages/_pytest/unittest.py:371: CombinaMILP not available, skipping tests.
FAILED test/combina_test.py::CombinaTestSingleInputBnB::test_check_binary_solution
=================== 1 failed, 10 passed, 3 skipped in 52.06s ===================
============================================== test session starts ==============================================
platform linux -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0 -- [REDACTED]/pycombina/venv/bin/python
cachedir: .pytest_cache
rootdir: [REDACTED]/pycombina
configfile: pyproject.toml
collecting ... - gurobipy version > 8.0.0 not found, CombinaMILP disabled.
collected 14 items
test/combina_test.py::CombinaTestSingleInputBnB::test_check_binary_solution Running Branch and Bound ...
Iteration Upper bound Branches Runtime (s)
U 359 1.492254e+04 193 8.659000e-03
U 686 1.492254e+04 113 1.504800e-02
U 1092 1.491668e+04 113 2.321100e-02
U 1335 1.490628e+04 217 2.919900e-02
U 1681 1.466628e+04 217 3.544900e-02
U 2026 1.442628e+04 215 4.165600e-02
U 2368 1.418628e+04 213 4.783100e-02
U 2707 1.394628e+04 211 5.282100e-02
U 3043 1.370628e+04 209 5.855300e-02
U 3376 1.346628e+04 207 6.384500e-02
Iteration Upper bound Branches Runtime (s)
U 3706 1.322628e+04 205 6.954500e-02
U 4033 1.298628e+04 203 7.339800e-02
U 4357 1.274628e+04 201 7.408000e-02
U 4678 1.250628e+04 199 7.463300e-02
U 4996 1.226628e+04 197 7.532400e-02
U 5311 1.202628e+04 195 7.609100e-02
U 5623 1.178628e+04 193 7.702400e-02
U 5932 1.154628e+04 191 7.770700e-02
U 6238 1.130628e+04 189 7.833100e-02
U 6541 1.106628e+04 187 7.878200e-02
Iteration Upper bound Branches Runtime (s)
U 6841 1.082628e+04 185 7.916500e-02
U 7138 1.058628e+04 183 7.950800e-02
U 7432 1.034628e+04 181 7.981100e-02
U 7723 1.010628e+04 179 8.025100e-02
U 8011 9.866278e+03 177 8.061600e-02
U 8296 9.626278e+03 175 8.092300e-02
U 8578 9.386278e+03 173 8.123700e-02
U 8857 9.146278e+03 171 8.153900e-02
U 9133 8.906278e+03 169 8.184000e-02
U 9406 8.666278e+03 167 8.214900e-02
Iteration Upper bound Branches Runtime (s)
U 9676 8.426278e+03 165 8.244900e-02
U 9943 8.186278e+03 163 8.275000e-02
U 10207 7.946278e+03 161 8.305800e-02
U 10468 7.706278e+03 159 8.334100e-02
U 10726 7.466278e+03 157 8.362000e-02
U 10981 7.226278e+03 155 8.389900e-02
U 11233 6.986278e+03 153 8.418300e-02
U 11482 6.746278e+03 151 8.444700e-02
U 11728 6.506278e+03 149 8.470900e-02
U 11971 6.266278e+03 147 8.498200e-02
Iteration Upper bound Branches Runtime (s)
U 12211 6.026278e+03 145 8.525300e-02
U 12448 5.786278e+03 143 8.555400e-02
U 12682 5.546278e+03 141 8.592300e-02
U 12913 5.306278e+03 139 8.620100e-02
U 13141 5.066278e+03 137 8.645400e-02
U 13366 4.826278e+03 135 8.671500e-02
U 13588 4.586278e+03 133 8.700200e-02
U 13807 4.346278e+03 131 8.726500e-02
U 14023 4.286637e+03 129 8.752100e-02
U 15852 4.106278e+03 136 8.938200e-02
Iteration Upper bound Branches Runtime (s)
U 17578 3.960137e+03 177 9.116600e-02
U 19330 3.720137e+03 179 9.328900e-02
U 20906 3.480137e+03 181 9.540100e-02
U 22401 3.240137e+03 181 9.703000e-02
U 23835 3.000137e+03 178 9.850400e-02
U 25101 2.760137e+03 176 9.978900e-02
U 26230 2.520137e+03 174 1.009040e-01
U 27186 2.280137e+03 174 1.018510e-01
U 27874 2.040137e+03 182 1.025610e-01
U 45928 2.038044e+03 186 1.233910e-01
Iteration Upper bound Branches Runtime (s)
U 51400 1.988518e+03 187 1.303660e-01
U 57075 1.938991e+03 186 1.364580e-01
U 62048 1.889460e+03 184 1.438050e-01
U 67361 1.850574e+03 175 1.497500e-01
U 67529 1.831332e+03 175 1.500290e-01
U 87803 1.805342e+03 181 1.750400e-01
U 94015 1.725159e+03 181 1.840320e-01
U 98200 1.644977e+03 180 1.885830e-01
U 103439 1.643323e+03 169 1.948660e-01
U 115999 1.603330e+03 176 2.091900e-01
Iteration Upper bound Branches Runtime (s)
U 154293 1.603330e+03 183 2.556200e-01
U 180267 1.603330e+03 181 2.856690e-01
Optimal solution found
Best solution: 1.603330e+03
Total iterations: 972039
Total runtime: 1.157639e+00 s
PASSED
test/combina_test.py::CombinaTestSingleInputBnB::test_check_n_max_switches PASSED
test/combina_test.py::CombinaTestSingleInputBnB::test_check_objective PASSED
test/combina_test.py::CombinaTestSingleInputMILP::test_check_binary_solution SKIPPED (CombinaMILP not
available, skipping tests.)
test/combina_test.py::CombinaTestSingleInputMILP::test_check_n_max_switches SKIPPED (CombinaMILP not
available, skipping tests.)
test/combina_test.py::CombinaTestSingleInputMILP::test_check_objective SKIPPED (CombinaMILP not
available, skipping tests.)
test/input_test.py::InputTest::test_manual_extend_sos1_fulfilled PASSED
test/input_test.py::InputTest::test_manual_scale_sos1_fulfilled PASSED
test/input_test.py::InputTest::test_multiple_controls_sos1_fulfilled_manual PASSED
test/input_test.py::InputTest::test_multiple_controls_sos1_violated PASSED
test/input_test.py::InputTest::test_single_control_T_not_increasing PASSED
test/input_test.py::InputTest::test_single_control_b_rel_not_relaxed_binary_solution PASSED
test/input_test.py::InputTest::test_single_control_invalid_dimensions PASSED
test/input_test.py::InputTest::test_single_control_sos1_violated PASSED
============================================ short test summary info ============================================
SKIPPED [3] venv/lib/python3.11/site-packages/_pytest/unittest.py:371: CombinaMILP not available, skipping tests.
========================================= 11 passed, 3 skipped in 1.38s =========================================
As we can see, both solutions have the same objective value up to the sixth decimal.
Summary
When the extension module is built and tested on an emulated 64-bit ARM architecture (specificly, an emulated QEMU
cortex-a57machine), the test functiontest_check_binary_solutionintest/combina_test.pyappears to consistently fail. This appears to not be due to an actual malfunction. Rather, PyCombina appears to find an equivalent solution or solution with indistinguishable objective.Similar observations can be made on some configurations of emulated 32-bit x86 Linux machines. They may be caused by the effect of subtle differences in floating point arithmetic between these CPU architectures on tree search decisions.
Steps to Reproduce
Reproducing this problem requires either a hardware ARM64 system (might work on an Apple M1). Here, we will discuss the emulation setup on an up-to-date Ubuntu Linux system. These steps were tested on only one specific host system (as well as a Gitlab Action runner of unknown hardware specifications). The following program outputs describe the test systems CPU, Kernel, and OS distribution version:
cpuid.txt
lscpu.txt
lsb_release.txt
uname.txt
For the emulation setup, we require the following packages:
libvirt-clientslibvirt-daemonlibvirt-daemon-driver-qemuvirtinstqemu-system-armqemu-efi-aarch64We also require an installation image for a reasonably sized Linux distribution. Alpine Linux'
virtinstallation ISO will work:Remember to verify checksums.
We can then set up the virtual machine with the command
virt-install \ --connect qemu:///session \ --name pycombina-test-env \ --arch aarch64 \ --machine virt \ --virt-type qemu \ --boot uefi \ --boot loader=/usr/share/AAVMF/AAVMF_CODE.fd,loader_ro=yes,loader_type=pflash \ --cdrom alpine-virt-3.18.4-aarch64.iso \ --disk size=5 \ --osinfo detect=on \ --network user \ --autoconsole textThe
virt-installcall should automatically open a text console interface to the virtual machine. You can log in asrootwithout password and run the commandThis will guide you through the installation process. For most options, you can give the default response. The following settings are important:
vdashould be used for the purposesys(otherwise, you will run out of space on your RAM disk);Afterwards, you can reboot the VM using the
rebootcommand. Take note that all of this will take a lot longer than you might expect. This is normal on an emulated CPU.After the reboot, log in as root and run the following commands:
Finally, you can execute the test suite with
pytest -vvv .We can subsequently remove the VM from the host machine by calling
Observed output
Expected output
The following output was collected on the host machine with
pytest -s -vvv .:As we can see, both solutions have the same objective value up to the sixth decimal.