SearchLibrium 0.0.1__tar.gz → 0.0.72__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.
Files changed (61) hide show
  1. searchlibrium-0.0.72/PKG-INFO +510 -0
  2. searchlibrium-0.0.72/README.md +481 -0
  3. searchlibrium-0.0.72/pyproject.toml +70 -0
  4. {searchlibrium-0.0.1 → searchlibrium-0.0.72}/setup.cfg +4 -4
  5. searchlibrium-0.0.72/src/SearchLibrium/Halton.py +191 -0
  6. searchlibrium-0.0.72/src/SearchLibrium/MixedLogit.py +1273 -0
  7. searchlibrium-0.0.72/src/SearchLibrium/Mode_Activity_Nested.py +85 -0
  8. searchlibrium-0.0.72/src/SearchLibrium/RandomP.py +49 -0
  9. searchlibrium-0.0.72/src/SearchLibrium/SEARCH_SM_MARIO.py +73 -0
  10. searchlibrium-0.0.72/src/SearchLibrium/Two_Level_Nest.py +151 -0
  11. searchlibrium-0.0.72/src/SearchLibrium/__init__.py +130 -0
  12. searchlibrium-0.0.72/src/SearchLibrium/__main__.py +5 -0
  13. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/_choice_model.py +1310 -1362
  14. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/_device.py +145 -145
  15. searchlibrium-0.0.72/src/SearchLibrium/bhhh/minimize.py +195 -0
  16. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/boxcox_functions.py +115 -115
  17. searchlibrium-0.0.72/src/SearchLibrium/call_meta.py +453 -0
  18. searchlibrium-0.0.72/src/SearchLibrium/constraints_builder.py +447 -0
  19. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/harmony.py +1380 -1261
  20. searchlibrium-0.0.72/src/SearchLibrium/latent_class.py +353 -0
  21. searchlibrium-0.0.72/src/SearchLibrium/main.py +1196 -0
  22. searchlibrium-0.0.72/src/SearchLibrium/main_debug.py +550 -0
  23. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/misc.py +303 -303
  24. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/mixed_logit.py +1553 -1553
  25. searchlibrium-0.0.72/src/SearchLibrium/mixed_nested.py +219 -0
  26. searchlibrium-0.0.72/src/SearchLibrium/mixedrrm.py +63 -0
  27. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/multinomial_logit.py +914 -558
  28. searchlibrium-0.0.72/src/SearchLibrium/multinomial_nested.py +1715 -0
  29. searchlibrium-0.0.72/src/SearchLibrium/multinomial_probit.py +165 -0
  30. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/ordered_logit.py +1691 -1640
  31. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/ordered_logit_mixed.py +102 -102
  32. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/rrm.py +724 -520
  33. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/search.py +3608 -3485
  34. {searchlibrium-0.0.1 → searchlibrium-0.0.72/src/SearchLibrium}/setup.py +58 -58
  35. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/siman.py +1538 -1023
  36. {searchlibrium-0.0.1/old_code → searchlibrium-0.0.72/src/SearchLibrium}/threshold.py +777 -777
  37. searchlibrium-0.0.72/src/SearchLibrium/version.txt +1 -0
  38. searchlibrium-0.0.72/src/SearchLibrium.egg-info/PKG-INFO +510 -0
  39. searchlibrium-0.0.72/src/SearchLibrium.egg-info/SOURCES.txt +41 -0
  40. searchlibrium-0.0.72/src/SearchLibrium.egg-info/entry_points.txt +2 -0
  41. searchlibrium-0.0.72/src/SearchLibrium.egg-info/requires.txt +11 -0
  42. searchlibrium-0.0.72/src/SearchLibrium.egg-info/top_level.txt +1 -0
  43. searchlibrium-0.0.1/PKG-INFO +0 -21
  44. searchlibrium-0.0.1/README.md +0 -5
  45. searchlibrium-0.0.1/SearchLibrium.egg-info/PKG-INFO +0 -21
  46. searchlibrium-0.0.1/SearchLibrium.egg-info/SOURCES.txt +0 -32
  47. searchlibrium-0.0.1/SearchLibrium.egg-info/not-zip-safe +0 -1
  48. searchlibrium-0.0.1/SearchLibrium.egg-info/top_level.txt +0 -1
  49. searchlibrium-0.0.1/old_code/__init__.py +0 -8
  50. searchlibrium-0.0.1/old_code/akshay_test.py +0 -125
  51. searchlibrium-0.0.1/old_code/draws.py +0 -128
  52. searchlibrium-0.0.1/old_code/latent_class_constrained.py +0 -434
  53. searchlibrium-0.0.1/old_code/latent_class_mixed_model.py +0 -1566
  54. searchlibrium-0.0.1/old_code/latent_class_model.py +0 -1281
  55. searchlibrium-0.0.1/old_code/latent_main.py +0 -945
  56. searchlibrium-0.0.1/old_code/main.py +0 -1880
  57. searchlibrium-0.0.1/old_code/main_ol.py +0 -127
  58. searchlibrium-0.0.1/old_code/ordered_logit_multinomial.py +0 -701
  59. searchlibrium-0.0.1/old_code/r_ordered.py +0 -168
  60. searchlibrium-0.0.1/pyproject.toml +0 -18
  61. {searchlibrium-0.0.1 → searchlibrium-0.0.72/src}/SearchLibrium.egg-info/dependency_links.txt +0 -0
@@ -0,0 +1,510 @@
1
+ Metadata-Version: 2.4
2
+ Name: SearchLibrium
3
+ Version: 0.0.72
4
+ Summary: A Python package for econometric models driven by search
5
+ Author: Alexander Paz Prithvi Beeramole, Robert Burdett
6
+ Author-email: Zeke Ahern <z.ahern@qut.edu.au>
7
+ Project-URL: Homepage, https://github.com/zahern/HypothesisX
8
+ Keywords: econometric models,search,discrete choice,logit,probit
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: numpy>2.0.0
20
+ Requires-Dist: pandas>=2.0.0
21
+ Requires-Dist: scikit-learn>=1.3.1
22
+ Requires-Dist: statsmodels
23
+ Provides-Extra: dev
24
+ Requires-Dist: black; extra == "dev"
25
+ Requires-Dist: bumpver; extra == "dev"
26
+ Requires-Dist: isort; extra == "dev"
27
+ Requires-Dist: pip-tools; extra == "dev"
28
+ Requires-Dist: pytest; extra == "dev"
29
+
30
+ # SearchLibrium
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/SearchLibrium.svg)](https://pypi.org/project/SearchLibrium/)
33
+ [![Python](https://img.shields.io/pypi/pyversions/SearchLibrium.svg)](https://pypi.org/project/SearchLibrium/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
35
+ [![CI](https://github.com/zahern/HypothesisX/actions/workflows/ci.yml/badge.svg)](https://github.com/zahern/HypothesisX/actions/workflows/ci.yml)
36
+
37
+ **Automated discrete choice model search powered by Simulated Annealing, Harmony Search, and JAX-accelerated MLE.**
38
+
39
+ SearchLibrium searches over model specifications — which variables to include, whether parameters should be random, which transformations to apply, and which model class to use — and returns the best converged, all-significant model according to your chosen criterion (BIC, AIC, log-likelihood, MAE, or multi-objective combinations).
40
+
41
+ ---
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install SearchLibrium --upgrade
47
+ ```
48
+
49
+ **Requirements:** Python ≥ 3.10, numpy ≥ 2.0, scipy ≥ 1.10, pandas ≥ 2.0, scikit-learn ≥ 1.3.1, statsmodels
50
+
51
+ ### Install in Jupyter Notebook
52
+
53
+ ```python
54
+ # Run in a notebook cell
55
+ import subprocess
56
+ import sys
57
+
58
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "SearchLibrium", "--upgrade"])
59
+
60
+ # Then import
61
+ from SearchLibrium import Parameters, call_siman
62
+ print("✓ SearchLibrium installed and ready!")
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Quick start
68
+
69
+ ```python
70
+ import numpy as np
71
+ import pandas as pd
72
+ from SearchLibrium import Parameters, call_siman
73
+
74
+ df = pd.read_csv("https://raw.githubusercontent.com/zahern/HypothesisX/refs/heads/main/data/Swissmetro_final.csv")
75
+ varnames = ["TIME", "COST", "HEADWAY", "SEATS"]
76
+ choice_set = np.unique(df["alt"]).tolist()
77
+
78
+ params = Parameters(
79
+ criterions = [("bic", -1)], # minimise BIC
80
+ df = df,
81
+ varnames = varnames,
82
+ asvarnames = varnames,
83
+ isvarnames = [],
84
+ choice_set = choice_set,
85
+ choices = df["CHOICE"].values,
86
+ alt_var = df["alt"].values,
87
+ choice_id = df["custom_id"].values,
88
+ ind_id = df["ID"].values,
89
+ base_alt = "SM",
90
+ models = ["multinomial", "mixed_logit"],
91
+ allow_random = True,
92
+ p_val = 0.05,
93
+ )
94
+
95
+ best = call_siman(params, init_sol=None, id_num=1)
96
+ ```
97
+
98
+ A **run dashboard** is printed automatically at the end of every search, showing BIC, log-likelihood, AIC, MAE, variables, model type, and (if multi-objective) the full Pareto archive.
99
+
100
+ ---
101
+
102
+ ## Example Notebooks
103
+
104
+ | Model | Notebook |
105
+ | ----- | -------- |
106
+ | Multinomial Logit — standalone fit + search | [notebooks/mnl_example.ipynb](src/SearchLibrium/notebooks/mnl_example.ipynb) |
107
+ | Mixed Logit — standalone fit + search | [notebooks/mixed_logit_example.ipynb](src/SearchLibrium/notebooks/mixed_logit_example.ipynb) |
108
+ | Random Regret Minimisation — standalone fit + search | [notebooks/rrm_example.ipynb](src/SearchLibrium/notebooks/rrm_example.ipynb) |
109
+ | Mixed Random Regret — standalone fit + search | [notebooks/mixed_rrm_example.ipynb](src/SearchLibrium/notebooks/mixed_rrm_example.ipynb) |
110
+ | Nested Logit — standalone fit + search | [notebooks/Data_Nest.ipynb](src/SearchLibrium/notebooks/Data_Nest.ipynb) |
111
+ | HPC Batch Jobs & PyPI Publishing | [notebooks/pbs_batch_jobs_guide.ipynb](src/SearchLibrium/notebooks/pbs_batch_jobs_guide.ipynb) |
112
+
113
+ ---
114
+
115
+ ## How the search works
116
+
117
+ The search uses **Simulated Annealing (SA)** to explore the space of model specifications:
118
+
119
+ ```text
120
+ generate starting solution
121
+ └─ for each SA temperature step
122
+ └─ perturb current specification → guaranteed distinct from current
123
+ ├─ fit model with JAX-accelerated MLE
124
+ ├─ run backward elimination (remove insignificant vars, refit)
125
+ ├─ accept if converged + Metropolis criterion satisfied
126
+ └─ update best solution
127
+ print dashboard
128
+ ```
129
+
130
+ **Key guarantees:**
131
+
132
+ - Only **converged** solutions are accepted
133
+ - Every accepted solution has **all variables statistically significant** (p < `p_val`, backward elimination)
134
+ - Each perturbation is guaranteed to produce a **genuinely different specification** — a distribution-only swap (e.g. normal → lognormal) without any structural change does not count
135
+
136
+ ---
137
+
138
+ ## Data format
139
+
140
+ Your dataframe must be in **long format** — one row per alternative per observation:
141
+
142
+ | obs_id | alt | choice | TIME | COST | ... |
143
+ | ------ | ----- | ------ | ---- | ---- | --- |
144
+ | 1 | car | 1 | 35 | 12 | ... |
145
+ | 1 | train | 0 | 60 | 8 | ... |
146
+ | 1 | bus | 0 | 55 | 5 | ... |
147
+ | 2 | car | 0 | 40 | 14 | ... |
148
+
149
+ ---
150
+
151
+ ## Model types
152
+
153
+ | Model name | Description | JAX MLE |
154
+ | ---------- | ----------- | ------- |
155
+ | `"multinomial"` | Multinomial Logit (MNL) | ✓ |
156
+ | `"mixed_logit"` | Mixed Logit with simulation-based integration | ✓ |
157
+ | `"random_regret"` | Random Regret Minimisation (RRM) | ✓ |
158
+ | `"mixed_random_regret"` | Mixed-RRM with random parameters | ✓ |
159
+ | `"nested_logit"` | Nested Logit (requires `nests=` and `lambdas=` kwargs) | ✓ |
160
+ | `"ordered_logit"` | Ordered Logit | ✓ |
161
+
162
+ ---
163
+
164
+ ## Search examples by model type
165
+
166
+ ### Multinomial Logit
167
+
168
+ ```python
169
+ params = Parameters(
170
+ criterions = [("bic", -1)],
171
+ df = df,
172
+ varnames = ["TIME", "COST", "HEADWAY"],
173
+ asvarnames = ["TIME", "COST", "HEADWAY"],
174
+ isvarnames = [],
175
+ choice_set = choice_set,
176
+ choices = df["CHOICE"].values,
177
+ alt_var = df["alt"].values,
178
+ choice_id = df["custom_id"].values,
179
+ base_alt = "SM",
180
+ models = ["multinomial"],
181
+ p_val = 0.05,
182
+ )
183
+ best = call_siman(params, init_sol=None, id_num=1)
184
+ ```
185
+
186
+ ### Mixed Logit (random parameters)
187
+
188
+ ```python
189
+ params = Parameters(
190
+ criterions = [("bic", -1)],
191
+ df = df,
192
+ varnames = ["TIME", "COST", "HEADWAY"],
193
+ asvarnames = ["TIME", "COST", "HEADWAY"],
194
+ isvarnames = [],
195
+ choice_set = choice_set,
196
+ choices = df["CHOICE"].values,
197
+ alt_var = df["alt"].values,
198
+ choice_id = df["custom_id"].values,
199
+ ind_id = df["ID"].values,
200
+ base_alt = "SM",
201
+ models = ["mixed_logit"],
202
+ allow_random = True, # enable random parameters
203
+ allow_bcvars = True, # enable Box-Cox transformations
204
+ n_draws = 500, # Halton draws for simulation
205
+ p_val = 0.05,
206
+ )
207
+ best = call_siman(params, init_sol=None, id_num=1)
208
+ ```
209
+
210
+ ### Random Regret Minimisation (RRM)
211
+
212
+ ```python
213
+ params = Parameters(
214
+ criterions = [("bic", -1)],
215
+ df = df,
216
+ varnames = ["TIME", "COST", "HEADWAY"],
217
+ asvarnames = ["TIME", "COST", "HEADWAY"],
218
+ isvarnames = [],
219
+ choice_set = choice_set,
220
+ choices = df["CHOICE"].values,
221
+ alt_var = df["alt"].values,
222
+ choice_id = df["custom_id"].values,
223
+ base_alt = "SM",
224
+ models = ["random_regret"],
225
+ p_val = 0.05,
226
+ )
227
+ best = call_siman(params, init_sol=None, id_num=1)
228
+ ```
229
+
230
+ ### Mixed Random Regret (regret + heterogeneity)
231
+
232
+ ```python
233
+ params = Parameters(
234
+ criterions = [("bic", -1)],
235
+ df = df,
236
+ varnames = ["TIME", "COST", "HEADWAY"],
237
+ asvarnames = ["TIME", "COST", "HEADWAY"],
238
+ isvarnames = [],
239
+ choice_set = choice_set,
240
+ choices = df["CHOICE"].values,
241
+ alt_var = df["alt"].values,
242
+ choice_id = df["custom_id"].values,
243
+ ind_id = df["ID"].values,
244
+ base_alt = "SM",
245
+ models = ["mixed_random_regret"],
246
+ allow_random = True,
247
+ n_draws = 500,
248
+ p_val = 0.05,
249
+ )
250
+ best = call_siman(params, init_sol=None, id_num=1)
251
+ ```
252
+
253
+ ### Nested Logit
254
+
255
+ ```python
256
+ nests = {"PublicTransport": [0, 1], "Private": [2, 3]}
257
+ lambdas = {"PublicTransport": 0.8, "Private": 1.0}
258
+
259
+ params = Parameters(
260
+ criterions = [("bic", -1)],
261
+ df = df,
262
+ varnames = ["TIME", "COST", "HEADWAY"],
263
+ asvarnames = ["TIME", "COST", "HEADWAY"],
264
+ choice_set = choice_set,
265
+ choices = df["CHOICE"].values,
266
+ alt_var = df["alt"].values,
267
+ choice_id = df["custom_id"].values,
268
+ base_alt = "SM",
269
+ models = ["nested_logit"],
270
+ nests = nests,
271
+ lambdas = lambdas,
272
+ p_val = 0.05,
273
+ )
274
+ best = call_siman(params, init_sol=None, id_num=1)
275
+ ```
276
+
277
+ ### Multi-objective search (BIC + MAE)
278
+
279
+ ```python
280
+ params = Parameters(
281
+ criterions = [("bic", -1), ("mae", -1)], # minimise both
282
+ df = df,
283
+ df_test = df_test, # required for MAE
284
+ varnames = varnames,
285
+ asvarnames = varnames,
286
+ choice_set = choice_set,
287
+ choices = df["CHOICE"].values,
288
+ alt_var = df["alt"].values,
289
+ choice_id = df["custom_id"].values,
290
+ base_alt = "SM",
291
+ models = ["multinomial", "mixed_logit"],
292
+ allow_random = True,
293
+ )
294
+ best = call_siman(params, init_sol=None, id_num=1)
295
+ # Returns a Pareto-optimal solution; full archive is printed in the dashboard
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Key parameters
301
+
302
+ | Parameter | Type | Default | Description |
303
+ | --------- | ---- | ------- | ----------- |
304
+ | `criterions` | list of `(name, sign)` | required | Objectives: `"bic"`, `"aic"`, `"loglik"`, `"mae"`. Sign: `-1` = minimise, `+1` = maximise |
305
+ | `models` | list of str | all | Model classes to search over |
306
+ | `allow_random` | bool | `False` | Enable random parameters (required for mixed models) |
307
+ | `allow_bcvars` | bool | `False` | Enable Box-Cox variable transformations |
308
+ | `allow_corvars` | bool | `False` | Enable correlated random parameters |
309
+ | `p_val` | float | `0.05` | Significance threshold — variables with p > p_val are eliminated |
310
+ | `all_sig` | bool | `True` | Enforce all-significant via backward elimination at each evaluation |
311
+ | `n_draws` | int | `1000` | Halton draws for mixed model simulation |
312
+ | `maxiter` | int | `2000` | Maximum MLE iterations per model evaluation |
313
+
314
+ ### Random parameter distributions
315
+
316
+ | Code | Distribution |
317
+ | ---- | ------------ |
318
+ | `"n"` | Normal |
319
+ | `"ln"` | Log-normal |
320
+ | `"t"` | Triangular |
321
+ | `"tn"` | Truncated normal |
322
+ | `"u"` | Uniform |
323
+
324
+ ### SA control parameters
325
+
326
+ Pass `ctrl=(tI, tF, max_temp_steps, max_iter)` to `call_siman`:
327
+
328
+ ```python
329
+ best = call_siman(params, ctrl=(500, 0.001, 100, 20), id_num=1)
330
+ ```
331
+
332
+ | Parameter | Description |
333
+ | --------- | ----------- |
334
+ | `tI` | Initial temperature — higher = more exploration early on |
335
+ | `tF` | Final temperature — lower = more exploitation at the end |
336
+ | `max_temp_steps` | Number of cooling steps |
337
+ | `max_iter` | Iterations evaluated at each temperature step |
338
+
339
+ ---
340
+
341
+ ## Standalone model fitting (no search)
342
+
343
+ ```python
344
+ from SearchLibrium import MultinomialLogit, MixedLogit, RandomRegret, MixedRandomRegret
345
+
346
+ # MNL
347
+ mnl = MultinomialLogit()
348
+ mnl.setup(X, y, varnames=varnames, alts=alts, ids=ids)
349
+ mnl.fit()
350
+ mnl.summarise()
351
+
352
+ # Mixed Logit
353
+ mxl = MixedLogit()
354
+ mxl.setup(X, y, varnames=varnames, alts=alts, ids=ids, panels=panels,
355
+ randvars={"TIME": "n", "COST": "ln"}, n_draws=500)
356
+ mxl.fit()
357
+ mxl.summarise()
358
+
359
+ # RRM
360
+ rrm = RandomRegret(df=df, short=False)
361
+ rrm.fit()
362
+ rrm.report()
363
+
364
+ # Mixed RRM
365
+ mrrm = MixedRandomRegret(df=df)
366
+ mrrm.fit()
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Interpreting the dashboard
372
+
373
+ After every `call_siman` run a dashboard is printed:
374
+
375
+ ```text
376
+ ╔══════════════════════════════════════════════════════╗
377
+ ║ SEARCHLIBRIUM — RUN DASHBOARD ║
378
+ ╠══════════════════════════════════════════════════════╣
379
+ ║ Model type : mixed_logit ║
380
+ ║ Variables : TIME, COST, HEADWAY ║
381
+ ║ Random params: TIME~n, COST~ln ║
382
+ ╠══════════════════════════════════════════════════════╣
383
+ ║ Log-likelihood : -312.45 ║
384
+ ║ AIC : 634.90 ║
385
+ ║ BIC : 658.22 ◄ best ║
386
+ ║ MAE : 0.1843 ║
387
+ ╠══════════════════════════════════════════════════════╣
388
+ ║ Evaluations : 247 Converged : 198 Accepted : 43 ║
389
+ ╚══════════════════════════════════════════════════════╝
390
+ ```
391
+
392
+ - **Lower BIC / AIC** = better fit-complexity tradeoff
393
+ - All retained variables are **statistically significant** (p < `p_val`)
394
+ - **Random parameters** indicate heterogeneity in that attribute's taste
395
+ - **RRM** models suit contexts where regret-avoidance drives choice behaviour
396
+ - For multi-objective runs the full Pareto archive is shown with one row per non-dominated solution
397
+
398
+ ---
399
+
400
+ ## Bundled datasets
401
+
402
+ ```python
403
+ import SearchLibrium as sl
404
+ sl.main.preview_dataset() # prints head of each dataset
405
+ ```
406
+
407
+ | Name | Description |
408
+ | ---- | ----------- |
409
+ | `electricity` | Stated-preference electricity plan choice |
410
+ | `travel_mode` | Mode choice: air / train / bus / car |
411
+ | `swiss_metro` | Swiss Metro SP study (SM / train / car) |
412
+
413
+ ---
414
+
415
+ ## CLI
416
+
417
+ ```bash
418
+ python -m SearchLibrium --info # print package guide
419
+ python -m SearchLibrium --preview_datasets # preview bundled datasets
420
+ python -m SearchLibrium --test_search # run MNL/MXL search on travel_mode
421
+ python -m SearchLibrium --test_search_nest # run nested logit search
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Search algorithms
427
+
428
+ Both algorithms share a **consistent interface** through `call_search`:
429
+
430
+ ```python
431
+ from SearchLibrium import call_search, estimate_ctrl
432
+
433
+ # Auto-estimate hyperparameters from problem size (recommended)
434
+ best = call_search(params) # SA by default
435
+ best = call_search(params, algorithm='hs') # Harmony Search
436
+
437
+ # Manual hyperparameters
438
+ best = call_search(params, ctrl=(1000, 0.001, 100, 20)) # SA
439
+ best = call_search(params, algorithm='hs',
440
+ ctrl=(20, 500, 0.9, 0.6, 0.85, 0.3)) # HS
441
+
442
+ # Inspect auto-estimated ctrl before running
443
+ ctrl = estimate_ctrl(params, algorithm='sa')
444
+ print(ctrl)
445
+ ```
446
+
447
+ ### Simulated Annealing (`call_siman` / `algorithm='sa'`)
448
+
449
+ | Parameter | Meaning |
450
+ | --------- | ------- |
451
+ | `tI` | Initial temperature — higher → more exploration |
452
+ | `tF` | Final temperature — lower → more exploitation |
453
+ | `max_temp_steps` | Number of cooling steps |
454
+ | `max_iter` | Evaluations per cooling step |
455
+
456
+ ```python
457
+ best = call_siman(params, ctrl=(1000, 0.001, 100, 20), id_num=1)
458
+ ```
459
+
460
+ ### Harmony Search (`call_harmony` / `algorithm='hs'`)
461
+
462
+ | Parameter | Meaning |
463
+ | --------- | ------- |
464
+ | `max_mem` | Harmony memory size (population) |
465
+ | `maxiter` | Improvisation iterations |
466
+ | `max_harm` | Max harmony consideration rate |
467
+ | `min_harm` | Min harmony consideration rate |
468
+ | `max_pitch` | Max pitch adjustment rate |
469
+ | `min_pitch` | Min pitch adjustment rate |
470
+
471
+ ```python
472
+ best = call_harmony(params, ctrl=(20, 400, 0.9, 0.6, 0.85, 0.3), id_num=1)
473
+ ```
474
+
475
+ ### Auto hyperparameter estimation
476
+
477
+ If `ctrl` is omitted, the library estimates appropriate defaults from the
478
+ problem complexity (`n_vars × n_alts × n_models`, doubled for random params):
479
+
480
+ ```python
481
+ from SearchLibrium import estimate_ctrl
482
+ ctrl_sa = estimate_ctrl(params, algorithm='sa')
483
+ ctrl_hs = estimate_ctrl(params, algorithm='hs')
484
+ print('SA ctrl:', ctrl_sa)
485
+ print('HS ctrl:', ctrl_hs)
486
+ ```
487
+
488
+ Complexity buckets:
489
+
490
+ | Complexity | SA tI | SA steps | SA iter/step | HS mem | HS iters |
491
+ | ---------- | ----- | -------- | ------------ | ------ | -------- |
492
+ | < 50 | 500 | 50 | 10 | 10 | 100 |
493
+ | 50–200 | 1 000 | 100 | 15 | 15 | 300 |
494
+ | 200–600 | 2 000 | 150 | 20 | 20 | 500 |
495
+ | > 600 | 5 000 | 250 | 30 | 25 | 800 |
496
+
497
+
498
+
499
+ ## License
500
+
501
+ MIT — see [LICENSE](LICENSE) for details.
502
+
503
+ ## Citation
504
+
505
+ If you use SearchLibrium in academic work, please cite the repository:
506
+
507
+ ```text
508
+ Ahern, Z. (2025). SearchLibrium: Automated discrete choice model search.
509
+ https://github.com/zahern/HypothesisX
510
+ ```