hitspy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hitspy-0.1.0/LICENSE +21 -0
- hitspy-0.1.0/MANIFEST.in +1 -0
- hitspy-0.1.0/PKG-INFO +160 -0
- hitspy-0.1.0/README.md +143 -0
- hitspy-0.1.0/pyproject.toml +26 -0
- hitspy-0.1.0/setup.cfg +4 -0
- hitspy-0.1.0/setup.py +45 -0
- hitspy-0.1.0/src/hitspy/EvaluateScore1.cpp +43 -0
- hitspy-0.1.0/src/hitspy/__init__.py +4 -0
- hitspy-0.1.0/src/hitspy/bsort.cpp +26 -0
- hitspy-0.1.0/src/hitspy/evaluate_neighbourhood.cpp +159 -0
- hitspy-0.1.0/src/hitspy/gelim.cpp +60 -0
- hitspy-0.1.0/src/hitspy/tabu_api.cpp +274 -0
- hitspy-0.1.0/src/hitspy/tabu_bindings.cpp +86 -0
- hitspy-0.1.0/src/hitspy/tabu_search.hpp +69 -0
- hitspy-0.1.0/src/hitspy.egg-info/PKG-INFO +160 -0
- hitspy-0.1.0/src/hitspy.egg-info/SOURCES.txt +25 -0
- hitspy-0.1.0/src/hitspy.egg-info/dependency_links.txt +1 -0
- hitspy-0.1.0/src/hitspy.egg-info/requires.txt +1 -0
- hitspy-0.1.0/src/hitspy.egg-info/top_level.txt +1 -0
- hitspy-0.1.0/tests/test_tabu.py +1 -0
hitspy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kostas Florios
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hitspy-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
global-include *.cpp *.hpp *.h
|
hitspy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hitspy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast OpenMP-parallelized discrete tabu search with pybind11 C++ bindings for maximum score estimator
|
|
5
|
+
Author-email: Kostas Florios <cflorios@aueb.gr>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kflorios/hitspy
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: C++
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: numpy>=1.20.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# hitspy
|
|
19
|
+
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
|
|
22
|
+
**hitspy** is a high-performance Python package providing C++ implementation of discrete Tabu Search algorithm for maximum score estimation. Powered by **pybind11** and parallelized with **OpenMP**, `hitspy` accelerates Hyperplanes Intersection evaluation and search routines across large search spaces.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Key Features
|
|
27
|
+
|
|
28
|
+
- ⚡ **High Performance**: Core optimization routines written in C++ for maximum throughput.
|
|
29
|
+
- 🚀 **Parallel Computing**: OpenMP multi-threading support for fast neighborhood evaluations.
|
|
30
|
+
- 🐍 **Seamless Python API**: Native `numpy` array integration using `pybind11`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### From Source (Locally)
|
|
37
|
+
|
|
38
|
+
Download the main folder of the package, cd to it and provide
|
|
39
|
+
in your preferred environment:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e .
|
|
43
|
+
```
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### Requirements
|
|
47
|
+
Python 3.8 and above, MS Visual Studio 2019 and above
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
|
|
51
|
+
### `hitspy.run_tabu_search`
|
|
52
|
+
|
|
53
|
+
Executes an OpenMP-accelerated discrete Tabu Search algorithm for maximum score estimator computation.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
hitspy.run_tabu_search(
|
|
57
|
+
X: numpy.ndarray,
|
|
58
|
+
y: numpy.ndarray,
|
|
59
|
+
b0: float,
|
|
60
|
+
d: float,
|
|
61
|
+
iSeed: int
|
|
62
|
+
) -> numpy.ndarray
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Complete Example: Multi-Seed Benchmark (20 Random Starts)
|
|
66
|
+
|
|
67
|
+
This benchmark demonstrates how to execute 20 distinct random restarts using `hitspy`, summarize overall solution quality, and compute selection frequency statistics for each attribute.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import numpy as np
|
|
71
|
+
import pandas as pd
|
|
72
|
+
from hitspy import run_tabu_search
|
|
73
|
+
|
|
74
|
+
# 1. Load and preprocess data
|
|
75
|
+
X_raw = np.loadtxt("X.txt")
|
|
76
|
+
y_raw = np.loadtxt("y.txt")
|
|
77
|
+
|
|
78
|
+
X = X_raw[:, 1:]
|
|
79
|
+
y = y_raw[:, 1].astype(int)
|
|
80
|
+
X=np.transpose(X)
|
|
81
|
+
X = np.ascontiguousarray(X, dtype=np.float64)
|
|
82
|
+
|
|
83
|
+
# 2. Parameters
|
|
84
|
+
b0 = -1.0
|
|
85
|
+
d = 10000
|
|
86
|
+
num_runs = 20
|
|
87
|
+
|
|
88
|
+
# Generate 20 distinct random seeds
|
|
89
|
+
np.random.seed(42) # For reproducible seed generation
|
|
90
|
+
seeds = np.random.randint(0, 1000000, size=num_runs)
|
|
91
|
+
|
|
92
|
+
# 3. Storage for results
|
|
93
|
+
results_list = []
|
|
94
|
+
|
|
95
|
+
print("Starting 20 runs...")
|
|
96
|
+
|
|
97
|
+
# 4. Run loop
|
|
98
|
+
for i in range(num_runs):
|
|
99
|
+
print('run...:',i+1)
|
|
100
|
+
current_seed = int(seeds[i])
|
|
101
|
+
|
|
102
|
+
# Execute Tabu Search
|
|
103
|
+
#attrs, coeffs, score = run_tabu_search(X=X, y=y, b0=b0, d=d, iSeed=current_seed)
|
|
104
|
+
attrs, coeffs, score = run_tabu_search(X=X, y=y, b0=b0, d=d, iSeed=12345+i+1)
|
|
105
|
+
|
|
106
|
+
# hitspy returns 'attributes' as a 1D numpy array/list and 'score' / 'coeffs' as keys in the dict
|
|
107
|
+
|
|
108
|
+
# Save metrics
|
|
109
|
+
results_list.append(
|
|
110
|
+
{
|
|
111
|
+
"Run": i + 1,
|
|
112
|
+
"Seed": 12345+i+1,
|
|
113
|
+
"Score": score,
|
|
114
|
+
"Coeffs": coeffs,
|
|
115
|
+
"Attrs": attrs
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# 5. Combine into a single DataFrame
|
|
120
|
+
results_df = pd.DataFrame(results_list)
|
|
121
|
+
|
|
122
|
+
# 6. Display results table
|
|
123
|
+
print(results_df.to_string(index=False))
|
|
124
|
+
|
|
125
|
+
# 7. Summary statistics
|
|
126
|
+
print("\n=== Summary Across 20 Runs ===")
|
|
127
|
+
print("Best Score Found: ", results_df["Score"].max())
|
|
128
|
+
print("Mean Score: ", results_df["Score"].mean())
|
|
129
|
+
print("Score Std Dev: ", results_df["Score"].std(ddof=1))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Acknowledgements
|
|
133
|
+
|
|
134
|
+
I would like to thank Dr. **Alexandros Louka** and Professor **Yannis Bilias** for their valuable help and the shared ideas for the development of HITSpy. The development of the HITSpy software was supported by the Hellenic Foundation for Research and Innovation (H.F.R.I.) under the '2nd Call for H.F.R.I. Research Projects to support Post-Doctoral Researchers' (Project Number: 902).
|
|
135
|
+
|
|
136
|
+
## Citation
|
|
137
|
+
|
|
138
|
+
If you use `hitspy` in your research, please cite the underlying methodology papers:
|
|
139
|
+
|
|
140
|
+
```bibtex
|
|
141
|
+
@article{florios2025hits,
|
|
142
|
+
title={HITS: Hyperplanes intersection tabu search for maximum score estimation},
|
|
143
|
+
author={Florios, Kostas and Louka, Alexandros and Bilias, Yannis},
|
|
144
|
+
journal={SoftwareX},
|
|
145
|
+
volume={30},
|
|
146
|
+
pages={102164},
|
|
147
|
+
year={2025},
|
|
148
|
+
publisher={Elsevier}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@article{manski1975maximum,
|
|
152
|
+
title={Maximum score estimation of the stochastic utility model of choice},
|
|
153
|
+
author={Manski, Charles F},
|
|
154
|
+
journal={Journal of econometrics},
|
|
155
|
+
volume={3},
|
|
156
|
+
number={3},
|
|
157
|
+
pages={205--228},
|
|
158
|
+
year={1975},
|
|
159
|
+
publisher={Elsevier}
|
|
160
|
+
}
|
hitspy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# hitspy
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
|
|
5
|
+
**hitspy** is a high-performance Python package providing C++ implementation of discrete Tabu Search algorithm for maximum score estimation. Powered by **pybind11** and parallelized with **OpenMP**, `hitspy` accelerates Hyperplanes Intersection evaluation and search routines across large search spaces.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- ⚡ **High Performance**: Core optimization routines written in C++ for maximum throughput.
|
|
12
|
+
- 🚀 **Parallel Computing**: OpenMP multi-threading support for fast neighborhood evaluations.
|
|
13
|
+
- 🐍 **Seamless Python API**: Native `numpy` array integration using `pybind11`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### From Source (Locally)
|
|
20
|
+
|
|
21
|
+
Download the main folder of the package, cd to it and provide
|
|
22
|
+
in your preferred environment:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### Requirements
|
|
30
|
+
Python 3.8 and above, MS Visual Studio 2019 and above
|
|
31
|
+
|
|
32
|
+
## API Reference
|
|
33
|
+
|
|
34
|
+
### `hitspy.run_tabu_search`
|
|
35
|
+
|
|
36
|
+
Executes an OpenMP-accelerated discrete Tabu Search algorithm for maximum score estimator computation.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
hitspy.run_tabu_search(
|
|
40
|
+
X: numpy.ndarray,
|
|
41
|
+
y: numpy.ndarray,
|
|
42
|
+
b0: float,
|
|
43
|
+
d: float,
|
|
44
|
+
iSeed: int
|
|
45
|
+
) -> numpy.ndarray
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Complete Example: Multi-Seed Benchmark (20 Random Starts)
|
|
49
|
+
|
|
50
|
+
This benchmark demonstrates how to execute 20 distinct random restarts using `hitspy`, summarize overall solution quality, and compute selection frequency statistics for each attribute.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import numpy as np
|
|
54
|
+
import pandas as pd
|
|
55
|
+
from hitspy import run_tabu_search
|
|
56
|
+
|
|
57
|
+
# 1. Load and preprocess data
|
|
58
|
+
X_raw = np.loadtxt("X.txt")
|
|
59
|
+
y_raw = np.loadtxt("y.txt")
|
|
60
|
+
|
|
61
|
+
X = X_raw[:, 1:]
|
|
62
|
+
y = y_raw[:, 1].astype(int)
|
|
63
|
+
X=np.transpose(X)
|
|
64
|
+
X = np.ascontiguousarray(X, dtype=np.float64)
|
|
65
|
+
|
|
66
|
+
# 2. Parameters
|
|
67
|
+
b0 = -1.0
|
|
68
|
+
d = 10000
|
|
69
|
+
num_runs = 20
|
|
70
|
+
|
|
71
|
+
# Generate 20 distinct random seeds
|
|
72
|
+
np.random.seed(42) # For reproducible seed generation
|
|
73
|
+
seeds = np.random.randint(0, 1000000, size=num_runs)
|
|
74
|
+
|
|
75
|
+
# 3. Storage for results
|
|
76
|
+
results_list = []
|
|
77
|
+
|
|
78
|
+
print("Starting 20 runs...")
|
|
79
|
+
|
|
80
|
+
# 4. Run loop
|
|
81
|
+
for i in range(num_runs):
|
|
82
|
+
print('run...:',i+1)
|
|
83
|
+
current_seed = int(seeds[i])
|
|
84
|
+
|
|
85
|
+
# Execute Tabu Search
|
|
86
|
+
#attrs, coeffs, score = run_tabu_search(X=X, y=y, b0=b0, d=d, iSeed=current_seed)
|
|
87
|
+
attrs, coeffs, score = run_tabu_search(X=X, y=y, b0=b0, d=d, iSeed=12345+i+1)
|
|
88
|
+
|
|
89
|
+
# hitspy returns 'attributes' as a 1D numpy array/list and 'score' / 'coeffs' as keys in the dict
|
|
90
|
+
|
|
91
|
+
# Save metrics
|
|
92
|
+
results_list.append(
|
|
93
|
+
{
|
|
94
|
+
"Run": i + 1,
|
|
95
|
+
"Seed": 12345+i+1,
|
|
96
|
+
"Score": score,
|
|
97
|
+
"Coeffs": coeffs,
|
|
98
|
+
"Attrs": attrs
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# 5. Combine into a single DataFrame
|
|
103
|
+
results_df = pd.DataFrame(results_list)
|
|
104
|
+
|
|
105
|
+
# 6. Display results table
|
|
106
|
+
print(results_df.to_string(index=False))
|
|
107
|
+
|
|
108
|
+
# 7. Summary statistics
|
|
109
|
+
print("\n=== Summary Across 20 Runs ===")
|
|
110
|
+
print("Best Score Found: ", results_df["Score"].max())
|
|
111
|
+
print("Mean Score: ", results_df["Score"].mean())
|
|
112
|
+
print("Score Std Dev: ", results_df["Score"].std(ddof=1))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Acknowledgements
|
|
116
|
+
|
|
117
|
+
I would like to thank Dr. **Alexandros Louka** and Professor **Yannis Bilias** for their valuable help and the shared ideas for the development of HITSpy. The development of the HITSpy software was supported by the Hellenic Foundation for Research and Innovation (H.F.R.I.) under the '2nd Call for H.F.R.I. Research Projects to support Post-Doctoral Researchers' (Project Number: 902).
|
|
118
|
+
|
|
119
|
+
## Citation
|
|
120
|
+
|
|
121
|
+
If you use `hitspy` in your research, please cite the underlying methodology papers:
|
|
122
|
+
|
|
123
|
+
```bibtex
|
|
124
|
+
@article{florios2025hits,
|
|
125
|
+
title={HITS: Hyperplanes intersection tabu search for maximum score estimation},
|
|
126
|
+
author={Florios, Kostas and Louka, Alexandros and Bilias, Yannis},
|
|
127
|
+
journal={SoftwareX},
|
|
128
|
+
volume={30},
|
|
129
|
+
pages={102164},
|
|
130
|
+
year={2025},
|
|
131
|
+
publisher={Elsevier}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@article{manski1975maximum,
|
|
135
|
+
title={Maximum score estimation of the stochastic utility model of choice},
|
|
136
|
+
author={Manski, Charles F},
|
|
137
|
+
journal={Journal of econometrics},
|
|
138
|
+
volume={3},
|
|
139
|
+
number={3},
|
|
140
|
+
pages={205--228},
|
|
141
|
+
year={1975},
|
|
142
|
+
publisher={Elsevier}
|
|
143
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel", "pybind11>=2.10.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hitspy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Fast OpenMP-parallelized discrete tabu search with pybind11 C++ bindings for maximum score estimator"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Kostas Florios", email = "cflorios@aueb.gr" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: C++",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy>=1.20.0"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/kflorios/hitspy"
|
hitspy-0.1.0/setup.cfg
ADDED
hitspy-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from setuptools import setup, Extension
|
|
3
|
+
from setuptools.command.build_ext import build_ext
|
|
4
|
+
|
|
5
|
+
class BuildExt(build_ext):
|
|
6
|
+
"""Custom build extension to handle compiler-specific flags."""
|
|
7
|
+
def build_extensions(self):
|
|
8
|
+
compiler = self.compiler.compiler_type
|
|
9
|
+
for ext in self.extensions:
|
|
10
|
+
if compiler == "msvc":
|
|
11
|
+
ext.extra_compile_args = ["/O2", "/openmp", "/std:c++14"]
|
|
12
|
+
else:
|
|
13
|
+
ext.extra_compile_args = ["-O3", "-fopenmp", "-std=c++14"]
|
|
14
|
+
ext.extra_link_args = ["-fopenmp"]
|
|
15
|
+
super().build_extensions()
|
|
16
|
+
|
|
17
|
+
class get_pybind_include:
|
|
18
|
+
"""Helper class to delay importing pybind11 until build time."""
|
|
19
|
+
def __str__(self):
|
|
20
|
+
import pybind11
|
|
21
|
+
return pybind11.get_include()
|
|
22
|
+
|
|
23
|
+
ext_modules = [
|
|
24
|
+
Extension(
|
|
25
|
+
"hitspy._hitspy",
|
|
26
|
+
sources=[
|
|
27
|
+
"src/hitspy/tabu_bindings.cpp",
|
|
28
|
+
"src/hitspy/tabu_api.cpp",
|
|
29
|
+
"src/hitspy/bsort.cpp",
|
|
30
|
+
"src/hitspy/evaluate_neighbourhood.cpp",
|
|
31
|
+
"src/hitspy/EvaluateScore1.cpp",
|
|
32
|
+
"src/hitspy/gelim.cpp",
|
|
33
|
+
],
|
|
34
|
+
include_dirs=[
|
|
35
|
+
get_pybind_include(),
|
|
36
|
+
"src/hitspy",
|
|
37
|
+
],
|
|
38
|
+
language="c++",
|
|
39
|
+
)
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
setup(
|
|
43
|
+
ext_modules=ext_modules,
|
|
44
|
+
cmdclass={"build_ext": BuildExt},
|
|
45
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#include "tabu_search.hpp"
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// EvaluateScore1
|
|
5
|
+
// sump(i) = sum_j beta(j)*Z(j+1,i) + b0*Z(1,i)
|
|
6
|
+
// firep(i) = sign(sump(i))
|
|
7
|
+
// sfire = sum_i data_y(i)*firep(i)
|
|
8
|
+
// h = (1 + sfire/T) / 2
|
|
9
|
+
// -----------------------------------------------------------------------------
|
|
10
|
+
void EvaluateScore1(
|
|
11
|
+
const double w[pmax],
|
|
12
|
+
int T,
|
|
13
|
+
int p,
|
|
14
|
+
double &h,
|
|
15
|
+
const double Z[pmax][Tmax],
|
|
16
|
+
double b0,
|
|
17
|
+
const int data_y[Tmax]
|
|
18
|
+
)
|
|
19
|
+
{
|
|
20
|
+
double beta[pmax];
|
|
21
|
+
int firep[Tmax];
|
|
22
|
+
|
|
23
|
+
for (int j = 0; j < p; ++j)
|
|
24
|
+
beta[j] = w[j];
|
|
25
|
+
|
|
26
|
+
int sfire = 0;
|
|
27
|
+
|
|
28
|
+
for (int i = 0; i < T; ++i)
|
|
29
|
+
{
|
|
30
|
+
double s = 0.0;
|
|
31
|
+
|
|
32
|
+
for (int j = 0; j < p; ++j)
|
|
33
|
+
s += beta[j] * Z[j + 1][i];
|
|
34
|
+
|
|
35
|
+
s += b0 * Z[0][i];
|
|
36
|
+
|
|
37
|
+
firep[i] = (s >= 0.0 ? 1 : -1);
|
|
38
|
+
|
|
39
|
+
sfire += data_y[i] * firep[i];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
h = (1.0 + double(sfire) / double(T)) * 0.5;
|
|
43
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#include "tabu_search.hpp"
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// bsort: sort nat[0..np-1] into snat[0..np-1] in increasing order
|
|
5
|
+
// Fortran original:
|
|
6
|
+
// do i=1,np
|
|
7
|
+
// do i=1,np-1
|
|
8
|
+
// do j=i+1,np
|
|
9
|
+
// -----------------------------------------------------------------------------
|
|
10
|
+
void bsort(int np, const int nat[], int snat[])
|
|
11
|
+
{
|
|
12
|
+
// copy input
|
|
13
|
+
for (int i = 0; i < np; ++i)
|
|
14
|
+
snat[i] = nat[i];
|
|
15
|
+
|
|
16
|
+
// simple bubble/selection hybrid as in Fortran
|
|
17
|
+
for (int i = 0; i < np - 1; ++i) {
|
|
18
|
+
for (int j = i + 1; j < np; ++j) {
|
|
19
|
+
if (snat[i] > snat[j]) {
|
|
20
|
+
int tmp = snat[i];
|
|
21
|
+
snat[i] = snat[j];
|
|
22
|
+
snat[j] = tmp;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#include "tabu_search.hpp"
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// evaluate_neighbourhood
|
|
5
|
+
// 0-based C++ translation of the Fortran routine.
|
|
6
|
+
//
|
|
7
|
+
// This routine evaluates all possible substitutions of attribute i_idx
|
|
8
|
+
// with every candidate id[j], j = 0..n-1, except those already in natsol.
|
|
9
|
+
//
|
|
10
|
+
// It performs:
|
|
11
|
+
// - triviality check
|
|
12
|
+
// - tabu check
|
|
13
|
+
// - local system solve (gelim)
|
|
14
|
+
// - bounds check
|
|
15
|
+
// - score computation (EvaluateScore1)
|
|
16
|
+
// - tracking of best dz_i and MaxScoreEver_i
|
|
17
|
+
//
|
|
18
|
+
// This version is called inside an OpenMP parallel loop in main.cpp.
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
void evaluate_neighbourhood(
|
|
21
|
+
int i_idx,
|
|
22
|
+
int p,
|
|
23
|
+
int n,
|
|
24
|
+
const int natsol[pmax],
|
|
25
|
+
const int Mat[pmax][Itmax],
|
|
26
|
+
const double Z[pmax][Tmax],
|
|
27
|
+
const double y[Tmax],
|
|
28
|
+
const int id[Tmax],
|
|
29
|
+
const double xlbound[pmax],
|
|
30
|
+
const double xubound[pmax],
|
|
31
|
+
double b0,
|
|
32
|
+
double MaxScore,
|
|
33
|
+
int ntenure,
|
|
34
|
+
int csubstitutionstotal,
|
|
35
|
+
int T,
|
|
36
|
+
double &dz_i,
|
|
37
|
+
int natcur_i[pmax],
|
|
38
|
+
double &MaxScoreEver_i,
|
|
39
|
+
int natever_i[pmax],
|
|
40
|
+
int &row_i,
|
|
41
|
+
int &cbd_i,
|
|
42
|
+
int &cinfsd_i,
|
|
43
|
+
const int data_y[Tmax]
|
|
44
|
+
)
|
|
45
|
+
{
|
|
46
|
+
row_i = 0;
|
|
47
|
+
cbd_i = 0;
|
|
48
|
+
cinfsd_i = 0;
|
|
49
|
+
dz_i = -1.0;
|
|
50
|
+
MaxScoreEver_i = -1.0e99;
|
|
51
|
+
|
|
52
|
+
int nat_local[pmax];
|
|
53
|
+
int snat_local[pmax];
|
|
54
|
+
double b_local[pmax];
|
|
55
|
+
double A_local[pmax][pmax];
|
|
56
|
+
double bb_local[pmax];
|
|
57
|
+
double x_local[pmax];
|
|
58
|
+
|
|
59
|
+
for (int j = 0; j < n; ++j)
|
|
60
|
+
{
|
|
61
|
+
bool trivial = false;
|
|
62
|
+
for (int iii = 0; iii < p; ++iii)
|
|
63
|
+
{
|
|
64
|
+
if (natsol[iii] == id[j])
|
|
65
|
+
{
|
|
66
|
+
trivial = true;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (trivial)
|
|
71
|
+
continue;
|
|
72
|
+
|
|
73
|
+
row_i++;
|
|
74
|
+
|
|
75
|
+
for (int iii = 0; iii < p; ++iii)
|
|
76
|
+
nat_local[iii] = (iii == i_idx ? id[j] : natsol[iii]);
|
|
77
|
+
|
|
78
|
+
bsort(p, nat_local, snat_local);
|
|
79
|
+
|
|
80
|
+
bool tabu = false;
|
|
81
|
+
for (int k = 0; k < ntenure; ++k)
|
|
82
|
+
{
|
|
83
|
+
int ct = 0;
|
|
84
|
+
for (int iii = 0; iii < p; ++iii)
|
|
85
|
+
{
|
|
86
|
+
if (snat_local[iii] == Mat[iii][csubstitutionstotal + k])
|
|
87
|
+
ct++;
|
|
88
|
+
}
|
|
89
|
+
if (ct == p)
|
|
90
|
+
{
|
|
91
|
+
tabu = true;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (tabu)
|
|
96
|
+
continue;
|
|
97
|
+
|
|
98
|
+
for (int iii = 0; iii < p; ++iii)
|
|
99
|
+
b_local[iii] = (y[nat_local[iii]] >= 0.0 ? 1.0 : 0.0);
|
|
100
|
+
|
|
101
|
+
for (int iii = 0; iii < p; ++iii)
|
|
102
|
+
{
|
|
103
|
+
for (int jjj = 0; jjj < p; ++jjj)
|
|
104
|
+
{
|
|
105
|
+
A_local[jjj][iii] =
|
|
106
|
+
(1.0 - 2.0 * b_local[jjj]) *
|
|
107
|
+
Z[iii + 1][id[nat_local[jjj]]];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (int iii = 0; iii < p; ++iii)
|
|
112
|
+
{
|
|
113
|
+
bb_local[iii] =
|
|
114
|
+
b0 * (-1.0 + 2.0 * b_local[iii]) *
|
|
115
|
+
Z[0][id[nat_local[iii]]];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
gelim(p, A_local, bb_local, x_local);
|
|
119
|
+
|
|
120
|
+
bool infeasible = false;
|
|
121
|
+
for (int iii = 0; iii < p; ++iii)
|
|
122
|
+
{
|
|
123
|
+
if (x_local[iii] < xlbound[iii] ||
|
|
124
|
+
x_local[iii] > xubound[iii])
|
|
125
|
+
{
|
|
126
|
+
cinfsd_i++;
|
|
127
|
+
infeasible = true;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (infeasible)
|
|
132
|
+
continue;
|
|
133
|
+
|
|
134
|
+
double score1;
|
|
135
|
+
EvaluateScore1(
|
|
136
|
+
x_local, T, p, score1,
|
|
137
|
+
Z, b0, data_y
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
double dz = score1 - MaxScore;
|
|
141
|
+
|
|
142
|
+
if (dz > 0.0)
|
|
143
|
+
cbd_i++;
|
|
144
|
+
|
|
145
|
+
if (score1 > MaxScoreEver_i)
|
|
146
|
+
{
|
|
147
|
+
MaxScoreEver_i = score1;
|
|
148
|
+
for (int iii = 0; iii < p; ++iii)
|
|
149
|
+
natever_i[iii] = nat_local[iii];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (dz > dz_i)
|
|
153
|
+
{
|
|
154
|
+
dz_i = dz;
|
|
155
|
+
for (int iii = 0; iii < p; ++iii)
|
|
156
|
+
natcur_i[iii] = nat_local[iii];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#include "tabu_search.hpp"
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// gelim: Gaussian elimination (Fortran version preserved exactly)
|
|
5
|
+
// 0-based C++ translation of the Fortran routine:
|
|
6
|
+
//
|
|
7
|
+
// do i=1,np-1
|
|
8
|
+
// do j=i+1,np
|
|
9
|
+
// p = sk(j,i)/sk(i,i)
|
|
10
|
+
// r1(j) = r1(j) - p*r1(i)
|
|
11
|
+
// do k=1,np
|
|
12
|
+
// sk(j,k) = sk(j,k) - p*sk(i,k)
|
|
13
|
+
// enddo
|
|
14
|
+
// enddo
|
|
15
|
+
// enddo
|
|
16
|
+
//
|
|
17
|
+
// Back substitution, then restore original sk and r1
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
void gelim(int np,
|
|
20
|
+
double sk[pmax][pmax],
|
|
21
|
+
double r1[pmax],
|
|
22
|
+
double u[pmax])
|
|
23
|
+
{
|
|
24
|
+
double bsk[pmax][pmax];
|
|
25
|
+
double br1[pmax];
|
|
26
|
+
|
|
27
|
+
// Save original sk and r1 (Fortran behavior)
|
|
28
|
+
for (int i = 0; i < np; ++i) {
|
|
29
|
+
br1[i] = r1[i];
|
|
30
|
+
for (int j = 0; j < np; ++j)
|
|
31
|
+
bsk[i][j] = sk[i][j];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Forward elimination
|
|
35
|
+
for (int i = 0; i < np - 1; ++i) {
|
|
36
|
+
for (int j = i + 1; j < np; ++j) {
|
|
37
|
+
double p = sk[j][i] / sk[i][i];
|
|
38
|
+
r1[j] -= p * r1[i];
|
|
39
|
+
for (int k = 0; k < np; ++k)
|
|
40
|
+
sk[j][k] -= p * sk[i][k];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Back substitution
|
|
45
|
+
for (int l = np - 1; l >= 0; --l) {
|
|
46
|
+
u[l] = r1[l] / sk[l][l];
|
|
47
|
+
for (int j = l - 1; j >= 0; --j) {
|
|
48
|
+
double p1 = sk[j][l] / sk[l][l];
|
|
49
|
+
sk[j][l] = 0.0;
|
|
50
|
+
r1[j] -= p1 * r1[l];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Restore original sk and r1 (Fortran semantics)
|
|
55
|
+
for (int i = 0; i < np; ++i) {
|
|
56
|
+
r1[i] = br1[i];
|
|
57
|
+
for (int j = 0; j < np; ++j)
|
|
58
|
+
sk[i][j] = bsk[i][j];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#include "tabu_search.hpp"
|
|
2
|
+
|
|
3
|
+
// Portable RNG
|
|
4
|
+
static inline double get_random() {
|
|
5
|
+
return double(rand()) / double(RAND_MAX);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
// Public API entry point callable from Python (pybind11)
|
|
10
|
+
// -----------------------------------------------------------------------------
|
|
11
|
+
extern "C" int run_tabu_search(
|
|
12
|
+
double* X,
|
|
13
|
+
int T,
|
|
14
|
+
int p_in,
|
|
15
|
+
int* y_in,
|
|
16
|
+
double b0,
|
|
17
|
+
double d,
|
|
18
|
+
int iSeed, // <-- NEW ARGUMENT
|
|
19
|
+
int* out_attributes,
|
|
20
|
+
double* out_coeffs,
|
|
21
|
+
double* out_score
|
|
22
|
+
)
|
|
23
|
+
{
|
|
24
|
+
srand(iSeed);
|
|
25
|
+
//srand(12345); // or use time(NULL) if you want nondeterministic runs
|
|
26
|
+
//srand((unsigned)time(NULL)); //If you want randomness each run:
|
|
27
|
+
|
|
28
|
+
int p = p_in - 1; // number of attributes (excluding intercept)
|
|
29
|
+
|
|
30
|
+
static double Z[pmax][Tmax]; // now pmax and Tmax are known
|
|
31
|
+
static double y[Tmax];
|
|
32
|
+
static int data_y[Tmax];
|
|
33
|
+
static int id[Tmax];
|
|
34
|
+
|
|
35
|
+
if (p <= 0 || p > pmax)
|
|
36
|
+
return -10;
|
|
37
|
+
if (T <= 0 || T > Tmax)
|
|
38
|
+
return -11;
|
|
39
|
+
|
|
40
|
+
// Core data
|
|
41
|
+
/*double Z[pmax][Tmax];
|
|
42
|
+
double y[Tmax];
|
|
43
|
+
int data_y[Tmax];
|
|
44
|
+
int id[Tmax];*/
|
|
45
|
+
|
|
46
|
+
// Working arrays
|
|
47
|
+
double A[pmax][pmax], b[pmax], bb[pmax];
|
|
48
|
+
double mA[pmax][pmax], mb[pmax], bmb[pmax];
|
|
49
|
+
double x[pmax], score1, MaxScore0, MaxScore, MaxScoreEver;
|
|
50
|
+
double Af[pmax][pmax], bf[pmax], bbf[pmax];
|
|
51
|
+
double ArgMax[pmax];
|
|
52
|
+
|
|
53
|
+
int nat[pmax], natsol[pmax], natcur[pmax], natever[pmax];
|
|
54
|
+
int Mat[pmax][Itmax], snat[pmax], snatsol[pmax];
|
|
55
|
+
|
|
56
|
+
double xlbound[pmax], xubound[pmax];
|
|
57
|
+
|
|
58
|
+
int ntenure, niterover, niternotimproved;
|
|
59
|
+
int csubstitutionstotal, cbdsubseq, cbd, cinfsd;
|
|
60
|
+
double dzmax;
|
|
61
|
+
int row;
|
|
62
|
+
|
|
63
|
+
// -------------------------------------------------------------------------
|
|
64
|
+
// Copy inputs into local arrays
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
for (int i = 0; i < T; ++i) {
|
|
67
|
+
id[i] = i;
|
|
68
|
+
data_y[i] = y_in[i];
|
|
69
|
+
y[i] = double(y_in[i]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (int j = 0; j < p_in; ++j) {
|
|
73
|
+
for (int i = 0; i < T; ++i) {
|
|
74
|
+
Z[j][i] = X[j*T + i];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// -------------------------------------------------------------------------
|
|
79
|
+
// Bounds (you can later tie these to d if desired)
|
|
80
|
+
// -------------------------------------------------------------------------
|
|
81
|
+
for (int j = 0; j < p; ++j) {
|
|
82
|
+
xlbound[j] = -d;
|
|
83
|
+
xubound[j] = d;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
// Initial nat (first p indices)
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
/*for (int j = 0; j < p; ++j)
|
|
90
|
+
nat[j] = j;*/
|
|
91
|
+
for (int j = 0; j < p; ++j) {
|
|
92
|
+
double rr = get_random();
|
|
93
|
+
nat[j] = int(std::floor(rr * T));
|
|
94
|
+
}
|
|
95
|
+
printf("Initial nat: ");
|
|
96
|
+
for (int j = 0; j < p; ++j) printf("%d ", nat[j]);
|
|
97
|
+
printf("\n");
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
bsort(p, nat, snat);
|
|
101
|
+
for (int j = 0; j < p; ++j)
|
|
102
|
+
natsol[j] = snat[j];
|
|
103
|
+
|
|
104
|
+
// -------------------------------------------------------------------------
|
|
105
|
+
// Build mb
|
|
106
|
+
// -------------------------------------------------------------------------
|
|
107
|
+
for (int j = 0; j < p; ++j)
|
|
108
|
+
mb[j] = (y[id[natsol[j]]] >= 0.0 ? 1.0 : 0.0);
|
|
109
|
+
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
// Build mA and bmb
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
for (int j = 0; j < p; ++j)
|
|
114
|
+
for (int i = 0; i < p; ++i)
|
|
115
|
+
mA[i][j] = (1.0 - 2.0 * mb[i]) * Z[j + 1][ id[natsol[i]] ];
|
|
116
|
+
|
|
117
|
+
for (int i = 0; i < p; ++i)
|
|
118
|
+
bmb[i] = b0 * (-1.0 + 2.0 * mb[i]) * Z[0][ id[natsol[i]] ];
|
|
119
|
+
|
|
120
|
+
// -------------------------------------------------------------------------
|
|
121
|
+
// Solve initial system
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
gelim(p, mA, bmb, x);
|
|
124
|
+
|
|
125
|
+
for (int i = 0; i < p; ++i) {
|
|
126
|
+
if (x[i] < xlbound[i] || x[i] > xubound[i]) {
|
|
127
|
+
return -2; // infeasible initial solution
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
// Evaluate initial score
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
EvaluateScore1(
|
|
135
|
+
x, T, p, score1,
|
|
136
|
+
Z, b0, data_y
|
|
137
|
+
);
|
|
138
|
+
MaxScore0 = score1;
|
|
139
|
+
MaxScore = MaxScore0;
|
|
140
|
+
MaxScoreEver = MaxScore0;
|
|
141
|
+
|
|
142
|
+
// -------------------------------------------------------------------------
|
|
143
|
+
// Tabu parameters
|
|
144
|
+
// -------------------------------------------------------------------------
|
|
145
|
+
ntenure = 200;
|
|
146
|
+
niterover = 200;
|
|
147
|
+
niternotimproved = 200;
|
|
148
|
+
|
|
149
|
+
for (int j = 0; j < p; ++j)
|
|
150
|
+
for (int i = 0; i < ntenure; ++i)
|
|
151
|
+
Mat[j][i] = -1;
|
|
152
|
+
|
|
153
|
+
csubstitutionstotal = 0;
|
|
154
|
+
cbdsubseq = 0;
|
|
155
|
+
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
// Main tabu loop
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
while (true) {
|
|
160
|
+
|
|
161
|
+
cbd = 0;
|
|
162
|
+
cinfsd = 0;
|
|
163
|
+
dzmax = -1.0;
|
|
164
|
+
row = 0;
|
|
165
|
+
|
|
166
|
+
bsort(p, natsol, snatsol);
|
|
167
|
+
for (int i = 0; i < p; ++i)
|
|
168
|
+
natsol[i] = snatsol[i];
|
|
169
|
+
|
|
170
|
+
for (int i = 0; i < p; ++i)
|
|
171
|
+
b[i] = (y[id[natsol[i]]] >= 0.0 ? 1.0 : 0.0);
|
|
172
|
+
|
|
173
|
+
for (int j = 0; j < p; ++j)
|
|
174
|
+
for (int i = 0; i < p; ++i)
|
|
175
|
+
A[i][j] = (1.0 - 2.0 * b[i]) * Z[j + 1][ id[natsol[i]] ];
|
|
176
|
+
|
|
177
|
+
for (int i = 0; i < p; ++i)
|
|
178
|
+
bb[i] = b0 * (-1.0 + 2.0 * b[i]) * Z[0][ id[natsol[i]] ];
|
|
179
|
+
|
|
180
|
+
int row_i_arr[pmax];
|
|
181
|
+
int cbd_i_arr[pmax];
|
|
182
|
+
int cinfsd_i_arr[pmax];
|
|
183
|
+
double dz_i_arr[pmax];
|
|
184
|
+
double MaxScoreEver_i_arr[pmax];
|
|
185
|
+
int natcur_i_arr[pmax][pmax];
|
|
186
|
+
int natever_i_arr[pmax][pmax];
|
|
187
|
+
|
|
188
|
+
#pragma omp parallel for default(shared)
|
|
189
|
+
for (int i = 0; i < p; ++i) {
|
|
190
|
+
evaluate_neighbourhood(
|
|
191
|
+
i, p, T,
|
|
192
|
+
natsol, Mat,
|
|
193
|
+
Z, y, id,
|
|
194
|
+
xlbound, xubound,
|
|
195
|
+
b0, MaxScore,
|
|
196
|
+
ntenure, csubstitutionstotal, T,
|
|
197
|
+
dz_i_arr[i], natcur_i_arr[i],
|
|
198
|
+
MaxScoreEver_i_arr[i], natever_i_arr[i],
|
|
199
|
+
row_i_arr[i], cbd_i_arr[i], cinfsd_i_arr[i],
|
|
200
|
+
data_y
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (int i = 0; i < p; ++i) {
|
|
205
|
+
row += row_i_arr[i];
|
|
206
|
+
cbd += cbd_i_arr[i];
|
|
207
|
+
cinfsd += cinfsd_i_arr[i];
|
|
208
|
+
|
|
209
|
+
if (MaxScoreEver_i_arr[i] > MaxScoreEver) {
|
|
210
|
+
MaxScoreEver = MaxScoreEver_i_arr[i];
|
|
211
|
+
for (int j = 0; j < p; ++j)
|
|
212
|
+
natever[j] = natever_i_arr[i][j];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (dz_i_arr[i] > dzmax) {
|
|
216
|
+
dzmax = dz_i_arr[i];
|
|
217
|
+
for (int j = 0; j < p; ++j)
|
|
218
|
+
natcur[j] = natcur_i_arr[i][j];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
csubstitutionstotal++;
|
|
223
|
+
|
|
224
|
+
if (cbd == 0)
|
|
225
|
+
cbdsubseq++;
|
|
226
|
+
|
|
227
|
+
if (csubstitutionstotal == niterover ||
|
|
228
|
+
cbdsubseq == niternotimproved)
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
for (int j = 0; j < p; ++j)
|
|
232
|
+
natsol[j] = natcur[j];
|
|
233
|
+
|
|
234
|
+
MaxScore += dzmax;
|
|
235
|
+
|
|
236
|
+
bsort(p, natsol, snatsol);
|
|
237
|
+
for (int j = 0; j < p; ++j)
|
|
238
|
+
Mat[j][ntenure + csubstitutionstotal] = snatsol[j];
|
|
239
|
+
|
|
240
|
+
printf("iteration...%d...:",csubstitutionstotal);
|
|
241
|
+
printf("current natsol: ");
|
|
242
|
+
for (int j = 0; j < p; ++j) printf("%d ", natsol[j]);
|
|
243
|
+
printf("\n");
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// -------------------------------------------------------------------------
|
|
248
|
+
// Final evaluation
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
for (int j = 0; j < p; ++j)
|
|
251
|
+
bf[j] = (y[id[natever[j]]] >= 0.0 ? 1.0 : 0.0);
|
|
252
|
+
|
|
253
|
+
for (int j = 0; j < p; ++j)
|
|
254
|
+
for (int l = 0; l < p; ++l)
|
|
255
|
+
Af[l][j] = (1.0 - 2.0 * bf[l]) * Z[j + 1][ id[natever[l]] ];
|
|
256
|
+
|
|
257
|
+
for (int j = 0; j < p; ++j)
|
|
258
|
+
bbf[j] = b0 * (-1.0 + 2.0 * bf[j]) * Z[0][ id[natever[j]] ];
|
|
259
|
+
|
|
260
|
+
gelim(p, Af, bbf, x);
|
|
261
|
+
EvaluateScore1(
|
|
262
|
+
x, T, p, score1,
|
|
263
|
+
Z, b0, data_y
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
for (int j = 0; j < p; ++j) {
|
|
267
|
+
out_attributes[j] = natever[j];
|
|
268
|
+
out_coeffs[j] = x[j];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
*out_score = score1;
|
|
272
|
+
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#include <pybind11/pybind11.h>
|
|
2
|
+
#include <pybind11/numpy.h>
|
|
3
|
+
#include "tabu_search.hpp"
|
|
4
|
+
|
|
5
|
+
extern "C" int run_tabu_search(
|
|
6
|
+
double* X,
|
|
7
|
+
int T,
|
|
8
|
+
int p_in,
|
|
9
|
+
int* y_in,
|
|
10
|
+
double b0,
|
|
11
|
+
double d,
|
|
12
|
+
int iSeed, // <-- NEW ARGUMENT
|
|
13
|
+
int* out_attributes,
|
|
14
|
+
double* out_coeffs,
|
|
15
|
+
double* out_score
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
namespace py = pybind11;
|
|
19
|
+
|
|
20
|
+
py::tuple py_run_tabu_search(py::array_t<double> X,
|
|
21
|
+
py::array_t<int> y,
|
|
22
|
+
double b0,
|
|
23
|
+
double d,
|
|
24
|
+
int iSeed)
|
|
25
|
+
{
|
|
26
|
+
if (X.ndim() != 2)
|
|
27
|
+
throw std::runtime_error("X must be 2D (p_in x T)");
|
|
28
|
+
if (y.ndim() != 1)
|
|
29
|
+
throw std::runtime_error("y must be 1D (T,)");
|
|
30
|
+
|
|
31
|
+
int p_in = static_cast<int>(X.shape(0));
|
|
32
|
+
int T = static_cast<int>(X.shape(1));
|
|
33
|
+
|
|
34
|
+
if (y.shape(0) != T)
|
|
35
|
+
throw std::runtime_error("y length must match X.shape[1]");
|
|
36
|
+
|
|
37
|
+
auto X_buf = X.request();
|
|
38
|
+
auto y_buf = y.request();
|
|
39
|
+
|
|
40
|
+
double* X_ptr = static_cast<double*>(X_buf.ptr);
|
|
41
|
+
int* y_ptr = static_cast<int*>(y_buf.ptr);
|
|
42
|
+
|
|
43
|
+
int p = p_in - 1;
|
|
44
|
+
if (p <= 0 || p > pmax)
|
|
45
|
+
throw std::runtime_error("p_in-1 must be in [1, pmax]");
|
|
46
|
+
|
|
47
|
+
py::array_t<int> attrs({p});
|
|
48
|
+
py::array_t<double> coeffs({p});
|
|
49
|
+
double score = 0.0;
|
|
50
|
+
|
|
51
|
+
auto attrs_buf = attrs.request();
|
|
52
|
+
auto coeffs_buf = coeffs.request();
|
|
53
|
+
|
|
54
|
+
int* attrs_ptr = static_cast<int*>(attrs_buf.ptr);
|
|
55
|
+
double* coeffs_ptr = static_cast<double*>(coeffs_buf.ptr);
|
|
56
|
+
|
|
57
|
+
int ret = run_tabu_search(
|
|
58
|
+
X_ptr,
|
|
59
|
+
T,
|
|
60
|
+
p_in,
|
|
61
|
+
y_ptr,
|
|
62
|
+
b0,
|
|
63
|
+
d,
|
|
64
|
+
iSeed, // <-- pass seed
|
|
65
|
+
attrs_ptr,
|
|
66
|
+
coeffs_ptr,
|
|
67
|
+
&score
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (ret != 0)
|
|
71
|
+
throw std::runtime_error("run_tabu_search failed with code " + std::to_string(ret));
|
|
72
|
+
|
|
73
|
+
return py::make_tuple(attrs, coeffs, score);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
PYBIND11_MODULE(_hitspy, m) {
|
|
77
|
+
m.doc() = "Pybind11 bindings for hitspy discrete tabu search";
|
|
78
|
+
|
|
79
|
+
m.def("run_tabu_search", &py_run_tabu_search,
|
|
80
|
+
py::arg("X"),
|
|
81
|
+
py::arg("y"),
|
|
82
|
+
py::arg("b0"),
|
|
83
|
+
py::arg("d"),
|
|
84
|
+
py::arg("iSeed"),
|
|
85
|
+
"Run discrete tabu search with user-specified RNG seed.");
|
|
86
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#ifndef TABU_SEARCH_HPP
|
|
2
|
+
#define TABU_SEARCH_HPP
|
|
3
|
+
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include <cmath>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <algorithm>
|
|
9
|
+
#include <omp.h>
|
|
10
|
+
|
|
11
|
+
// -----------------------------------------------------------------------------
|
|
12
|
+
// Global constants (Fortran PARAMETER)
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
static const int Tmax = 50000;
|
|
15
|
+
static const int pmax = 20;
|
|
16
|
+
static const int Itmax = 1000;
|
|
17
|
+
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
// Function prototypes
|
|
20
|
+
// -----------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
// bsort: sort nat[0..np-1] into snat[0..np-1]
|
|
23
|
+
void bsort(int np, const int nat[], int snat[]);
|
|
24
|
+
|
|
25
|
+
// Gaussian elimination (Fortran gelim)
|
|
26
|
+
void gelim(int np,
|
|
27
|
+
double sk[pmax][pmax],
|
|
28
|
+
double r1[pmax],
|
|
29
|
+
double u[pmax]);
|
|
30
|
+
|
|
31
|
+
// EvaluateScore1: compute score h
|
|
32
|
+
void EvaluateScore1(
|
|
33
|
+
const double w[pmax],
|
|
34
|
+
int T,
|
|
35
|
+
int p,
|
|
36
|
+
double &h,
|
|
37
|
+
const double Z[pmax][Tmax],
|
|
38
|
+
double b0,
|
|
39
|
+
const int data_y[Tmax]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// evaluate_neighbourhood: OpenMP-parallelized neighborhood evaluation
|
|
43
|
+
void evaluate_neighbourhood(
|
|
44
|
+
int i_idx,
|
|
45
|
+
int p,
|
|
46
|
+
int n,
|
|
47
|
+
const int natsol[pmax],
|
|
48
|
+
const int Mat[pmax][Itmax],
|
|
49
|
+
const double Z[pmax][Tmax],
|
|
50
|
+
const double y[Tmax],
|
|
51
|
+
const int id[Tmax],
|
|
52
|
+
const double xlbound[pmax],
|
|
53
|
+
const double xubound[pmax],
|
|
54
|
+
double b0,
|
|
55
|
+
double MaxScore,
|
|
56
|
+
int ntenure,
|
|
57
|
+
int csubstitutionstotal,
|
|
58
|
+
int T,
|
|
59
|
+
double &dz_i,
|
|
60
|
+
int natcur_i[pmax],
|
|
61
|
+
double &MaxScoreEver_i,
|
|
62
|
+
int natever_i[pmax],
|
|
63
|
+
int &row_i,
|
|
64
|
+
int &cbd_i,
|
|
65
|
+
int &cinfsd_i,
|
|
66
|
+
const int data_y[Tmax]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
#endif // TABU_SEARCH_HPP
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hitspy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast OpenMP-parallelized discrete tabu search with pybind11 C++ bindings for maximum score estimator
|
|
5
|
+
Author-email: Kostas Florios <cflorios@aueb.gr>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kflorios/hitspy
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: C++
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: numpy>=1.20.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# hitspy
|
|
19
|
+
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
|
|
22
|
+
**hitspy** is a high-performance Python package providing C++ implementation of discrete Tabu Search algorithm for maximum score estimation. Powered by **pybind11** and parallelized with **OpenMP**, `hitspy` accelerates Hyperplanes Intersection evaluation and search routines across large search spaces.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Key Features
|
|
27
|
+
|
|
28
|
+
- ⚡ **High Performance**: Core optimization routines written in C++ for maximum throughput.
|
|
29
|
+
- 🚀 **Parallel Computing**: OpenMP multi-threading support for fast neighborhood evaluations.
|
|
30
|
+
- 🐍 **Seamless Python API**: Native `numpy` array integration using `pybind11`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### From Source (Locally)
|
|
37
|
+
|
|
38
|
+
Download the main folder of the package, cd to it and provide
|
|
39
|
+
in your preferred environment:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e .
|
|
43
|
+
```
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### Requirements
|
|
47
|
+
Python 3.8 and above, MS Visual Studio 2019 and above
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
|
|
51
|
+
### `hitspy.run_tabu_search`
|
|
52
|
+
|
|
53
|
+
Executes an OpenMP-accelerated discrete Tabu Search algorithm for maximum score estimator computation.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
hitspy.run_tabu_search(
|
|
57
|
+
X: numpy.ndarray,
|
|
58
|
+
y: numpy.ndarray,
|
|
59
|
+
b0: float,
|
|
60
|
+
d: float,
|
|
61
|
+
iSeed: int
|
|
62
|
+
) -> numpy.ndarray
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Complete Example: Multi-Seed Benchmark (20 Random Starts)
|
|
66
|
+
|
|
67
|
+
This benchmark demonstrates how to execute 20 distinct random restarts using `hitspy`, summarize overall solution quality, and compute selection frequency statistics for each attribute.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import numpy as np
|
|
71
|
+
import pandas as pd
|
|
72
|
+
from hitspy import run_tabu_search
|
|
73
|
+
|
|
74
|
+
# 1. Load and preprocess data
|
|
75
|
+
X_raw = np.loadtxt("X.txt")
|
|
76
|
+
y_raw = np.loadtxt("y.txt")
|
|
77
|
+
|
|
78
|
+
X = X_raw[:, 1:]
|
|
79
|
+
y = y_raw[:, 1].astype(int)
|
|
80
|
+
X=np.transpose(X)
|
|
81
|
+
X = np.ascontiguousarray(X, dtype=np.float64)
|
|
82
|
+
|
|
83
|
+
# 2. Parameters
|
|
84
|
+
b0 = -1.0
|
|
85
|
+
d = 10000
|
|
86
|
+
num_runs = 20
|
|
87
|
+
|
|
88
|
+
# Generate 20 distinct random seeds
|
|
89
|
+
np.random.seed(42) # For reproducible seed generation
|
|
90
|
+
seeds = np.random.randint(0, 1000000, size=num_runs)
|
|
91
|
+
|
|
92
|
+
# 3. Storage for results
|
|
93
|
+
results_list = []
|
|
94
|
+
|
|
95
|
+
print("Starting 20 runs...")
|
|
96
|
+
|
|
97
|
+
# 4. Run loop
|
|
98
|
+
for i in range(num_runs):
|
|
99
|
+
print('run...:',i+1)
|
|
100
|
+
current_seed = int(seeds[i])
|
|
101
|
+
|
|
102
|
+
# Execute Tabu Search
|
|
103
|
+
#attrs, coeffs, score = run_tabu_search(X=X, y=y, b0=b0, d=d, iSeed=current_seed)
|
|
104
|
+
attrs, coeffs, score = run_tabu_search(X=X, y=y, b0=b0, d=d, iSeed=12345+i+1)
|
|
105
|
+
|
|
106
|
+
# hitspy returns 'attributes' as a 1D numpy array/list and 'score' / 'coeffs' as keys in the dict
|
|
107
|
+
|
|
108
|
+
# Save metrics
|
|
109
|
+
results_list.append(
|
|
110
|
+
{
|
|
111
|
+
"Run": i + 1,
|
|
112
|
+
"Seed": 12345+i+1,
|
|
113
|
+
"Score": score,
|
|
114
|
+
"Coeffs": coeffs,
|
|
115
|
+
"Attrs": attrs
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# 5. Combine into a single DataFrame
|
|
120
|
+
results_df = pd.DataFrame(results_list)
|
|
121
|
+
|
|
122
|
+
# 6. Display results table
|
|
123
|
+
print(results_df.to_string(index=False))
|
|
124
|
+
|
|
125
|
+
# 7. Summary statistics
|
|
126
|
+
print("\n=== Summary Across 20 Runs ===")
|
|
127
|
+
print("Best Score Found: ", results_df["Score"].max())
|
|
128
|
+
print("Mean Score: ", results_df["Score"].mean())
|
|
129
|
+
print("Score Std Dev: ", results_df["Score"].std(ddof=1))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Acknowledgements
|
|
133
|
+
|
|
134
|
+
I would like to thank Dr. **Alexandros Louka** and Professor **Yannis Bilias** for their valuable help and the shared ideas for the development of HITSpy. The development of the HITSpy software was supported by the Hellenic Foundation for Research and Innovation (H.F.R.I.) under the '2nd Call for H.F.R.I. Research Projects to support Post-Doctoral Researchers' (Project Number: 902).
|
|
135
|
+
|
|
136
|
+
## Citation
|
|
137
|
+
|
|
138
|
+
If you use `hitspy` in your research, please cite the underlying methodology papers:
|
|
139
|
+
|
|
140
|
+
```bibtex
|
|
141
|
+
@article{florios2025hits,
|
|
142
|
+
title={HITS: Hyperplanes intersection tabu search for maximum score estimation},
|
|
143
|
+
author={Florios, Kostas and Louka, Alexandros and Bilias, Yannis},
|
|
144
|
+
journal={SoftwareX},
|
|
145
|
+
volume={30},
|
|
146
|
+
pages={102164},
|
|
147
|
+
year={2025},
|
|
148
|
+
publisher={Elsevier}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@article{manski1975maximum,
|
|
152
|
+
title={Maximum score estimation of the stochastic utility model of choice},
|
|
153
|
+
author={Manski, Charles F},
|
|
154
|
+
journal={Journal of econometrics},
|
|
155
|
+
volume={3},
|
|
156
|
+
number={3},
|
|
157
|
+
pages={205--228},
|
|
158
|
+
year={1975},
|
|
159
|
+
publisher={Elsevier}
|
|
160
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
src/hitspy/EvaluateScore1.cpp
|
|
7
|
+
src/hitspy/bsort.cpp
|
|
8
|
+
src/hitspy/evaluate_neighbourhood.cpp
|
|
9
|
+
src/hitspy/gelim.cpp
|
|
10
|
+
src/hitspy/tabu_api.cpp
|
|
11
|
+
src/hitspy/tabu_bindings.cpp
|
|
12
|
+
src/hitspy/EvaluateScore1.cpp
|
|
13
|
+
src/hitspy/__init__.py
|
|
14
|
+
src/hitspy/bsort.cpp
|
|
15
|
+
src/hitspy/evaluate_neighbourhood.cpp
|
|
16
|
+
src/hitspy/gelim.cpp
|
|
17
|
+
src/hitspy/tabu_api.cpp
|
|
18
|
+
src/hitspy/tabu_bindings.cpp
|
|
19
|
+
src/hitspy/tabu_search.hpp
|
|
20
|
+
src/hitspy.egg-info/PKG-INFO
|
|
21
|
+
src/hitspy.egg-info/SOURCES.txt
|
|
22
|
+
src/hitspy.egg-info/dependency_links.txt
|
|
23
|
+
src/hitspy.egg-info/requires.txt
|
|
24
|
+
src/hitspy.egg-info/top_level.txt
|
|
25
|
+
tests/test_tabu.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy>=1.20.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hitspy
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|