SearchLibrium 0.0.261__tar.gz → 0.0.262__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 (85) hide show
  1. {searchlibrium-0.0.261/src/SearchLibrium.egg-info → searchlibrium-0.0.262}/PKG-INFO +1 -1
  2. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/pyproject.toml +1 -1
  3. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/MixedLogit.py +30 -4
  4. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/latent_class.py +21 -4
  5. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/mixed_logit.py +10 -1
  6. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/ordered_logit.py +17 -1
  7. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/test_lc_de.py +1 -0
  8. searchlibrium-0.0.262/src/SearchLibrium/version.txt +1 -0
  9. {searchlibrium-0.0.261 → searchlibrium-0.0.262/src/SearchLibrium.egg-info}/PKG-INFO +1 -1
  10. searchlibrium-0.0.261/src/SearchLibrium/version.txt +0 -1
  11. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/LICENSE +0 -0
  12. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/MANIFEST.in +0 -0
  13. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/README.md +0 -0
  14. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/setup.cfg +0 -0
  15. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/BEHier.py +0 -0
  16. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/Halton.py +0 -0
  17. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/MixedLogitGSE.py +0 -0
  18. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/Mode_Activity_Nested.py +0 -0
  19. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/RandomP.py +0 -0
  20. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/SEARCH_SM_MARIO.py +0 -0
  21. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/Two_Level_Nest.py +0 -0
  22. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/__init__.py +0 -0
  23. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/__main__.py +0 -0
  24. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/_choice_model.py +0 -0
  25. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/_device.py +0 -0
  26. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/abm.py +0 -0
  27. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/accessibility.py +0 -0
  28. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/banditsa.py +0 -0
  29. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/boxcox_functions.py +0 -0
  30. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/call_meta.py +0 -0
  31. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/constraints_builder.py +0 -0
  32. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/coupled_solver.py +0 -0
  33. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/data/Swissmetro_final.csv +0 -0
  34. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/data/TravelMode.csv +0 -0
  35. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/data/electricity.csv +0 -0
  36. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/destination_distances.py +0 -0
  37. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/harmony.py +0 -0
  38. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/hspbil.py +0 -0
  39. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/jax_utils.py +0 -0
  40. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/larch_compat.py +0 -0
  41. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/larch_models.py +0 -0
  42. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/latent_spec.py +0 -0
  43. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/logistic_regression.py +0 -0
  44. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/main.py +0 -0
  45. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/main_debug.py +0 -0
  46. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/mdcev.py +0 -0
  47. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/mh_choice_estimator.py +0 -0
  48. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/misc.py +0 -0
  49. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/mixed_nested.py +0 -0
  50. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/mixedrrm.py +0 -0
  51. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/multinomial_logit.py +0 -0
  52. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/multinomial_nested.py +0 -0
  53. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/multinomial_probit.py +0 -0
  54. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/numba_engine.py +0 -0
  55. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/numba_mxl.py +0 -0
  56. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/numba_rrm.py +0 -0
  57. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/ordered_logit_mixed.py +0 -0
  58. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/predict.py +0 -0
  59. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/regularization.py +0 -0
  60. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/rrm.py +0 -0
  61. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/sample_data.py +0 -0
  62. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/sapbil.py +0 -0
  63. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/search.py +0 -0
  64. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/selection_models.py +0 -0
  65. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/setup.py +0 -0
  66. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/siman.py +0 -0
  67. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/skims.py +0 -0
  68. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/sparseea_agds.py +0 -0
  69. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/test_mario_searches.py +0 -0
  70. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/test_sapbil_vs_banditsa.py +0 -0
  71. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/threshold.py +0 -0
  72. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium/zero_inflated_ordered_probit.py +0 -0
  73. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium.egg-info/SOURCES.txt +0 -0
  74. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium.egg-info/dependency_links.txt +0 -0
  75. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium.egg-info/entry_points.txt +0 -0
  76. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium.egg-info/requires.txt +0 -0
  77. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/src/SearchLibrium.egg-info/top_level.txt +0 -0
  78. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_destination_distances.py +0 -0
  79. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_jax_mask_equivalence.py +0 -0
  80. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_larch_integration.py +0 -0
  81. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_lc_smart_init.py +0 -0
  82. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_mh_choice_estimator.py +0 -0
  83. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_mxl_random_starts.py +0 -0
  84. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_per_alternative_random.py +0 -0
  85. {searchlibrium-0.0.261 → searchlibrium-0.0.262}/tests/test_zero_inflated_ordered_probit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.261
3
+ Version: 0.0.262
4
4
  Summary: Automated specification search for discrete choice models (logit, mixed logit, nested logit, RRM and more) using metaheuristic optimisation
5
5
  Author: Zeke Ahern, Mario Fernando Taco Morales, Alexander Paz, Prithvi Beeramole, Robert Burdett
6
6
  Maintainer-email: Zeke Ahern <z.ahern@qut.edu.au>
@@ -132,7 +132,7 @@ Documentation = "https://pypi.org/project/SearchLibrium/"
132
132
  realpython = "SearchLibrium.__main__:main"
133
133
 
134
134
  [tool.bumpver]
135
- current_version = "0.0.261"
135
+ current_version = "0.0.262"
136
136
  version_pattern = "MAJOR.MINOR.PATCH"
137
137
  commit_message = "[skip ci] Bump version {old_version} -> {new_version}"
138
138
  commit = true
@@ -122,10 +122,19 @@ def _panel_weighted_shares(prob_np, panel_info):
122
122
  return ind, np.mean(ind, axis=0)
123
123
 
124
124
  class MixedLogit(DiscreteChoiceModel):
125
- def __init__(self, halton_opts=None, distributions=['n', 'ln', 'nln', 't', 'tn', 'u'], _jax=None):
125
+ def __init__(self, halton_opts=None, distributions=['n', 'ln', 'nln', 't', 'tn', 'u'], _jax=None,
126
+ save_prob_full=None):
126
127
  super().__init__(_jax)
127
128
  self.descr = "MXL"
128
129
  self.halton_opts = halton_opts
130
+ # MEMORY: prob_full is the full (N, P, J, R) probability tensor built in
131
+ # post-process for the summary/plots. For a 13k-case/20-alt/1000-draw
132
+ # model that is ~2 GB, and it is never read during estimation, so it is
133
+ # now opt-in. Pass save_prob_full=True (or set
134
+ # SEARCHLIBRIUM_SAVE_PROB_FULL=1) to retain the previous behaviour.
135
+ if save_prob_full is None:
136
+ save_prob_full = bool(os.environ.get("SEARCHLIBRIUM_SAVE_PROB_FULL", ""))
137
+ self.save_prob_full = bool(save_prob_full)
129
138
  self.draws_generator = Draws(k=len(distributions), halton_opts=halton_opts, rvdist=distributions)
130
139
  self.random_parameters = RandomParameters(distributions or [])
131
140
  self.softmax_r = True
@@ -1173,7 +1182,8 @@ class MixedLogit(DiscreteChoiceModel):
1173
1182
  self.choice_pred_prob = np.mean(p, axis=3)
1174
1183
  self.ind_pred_prob, self.pred_prob = _panel_weighted_shares(
1175
1184
  self.choice_pred_prob, self.panel_info)
1176
- self.prob_full = p
1185
+ if self.save_prob_full:
1186
+ self.prob_full = p
1177
1187
  self._refresh_corr_from_varlist()
1178
1188
  self.post_process(jax_result, self.Xnames, self.N)
1179
1189
  return
@@ -1251,7 +1261,8 @@ class MixedLogit(DiscreteChoiceModel):
1251
1261
  self.ind_pred_prob, self.pred_prob = _panel_weighted_shares(
1252
1262
  self.choice_pred_prob, self.panel_info)
1253
1263
 
1254
- self.prob_full = p
1264
+ if self.save_prob_full:
1265
+ self.prob_full = p
1255
1266
  # }
1256
1267
  self._refresh_corr_from_varlist()
1257
1268
  self.post_process(result, self.Xnames, self.N)
@@ -1499,7 +1510,11 @@ class MixedLogit(DiscreteChoiceModel):
1499
1510
  # process; during a heterogeneous GA search that is one retained model
1500
1511
  # per distinct parameter-count combination.
1501
1512
  # ------------------------------------------------------------------
1502
- _JIT_CACHE_MAXSIZE = 16
1513
+ # MEMORY: cache size is in *live models*, each of which can pin its X/y
1514
+ # arrays and a compiled XLA executable. The old default of 16 was tuned for
1515
+ # speed; 4 keeps most of the reuse benefit while capping the worst case.
1516
+ # Override with SEARCHLIBRIUM_JIT_CACHE_MAXSIZE (0 disables the cache).
1517
+ _JIT_CACHE_MAXSIZE = max(0, int(os.environ.get("SEARCHLIBRIUM_JIT_CACHE_MAXSIZE", "4")))
1503
1518
  _mxl_jit_cache: "OrderedDict[tuple, tuple]" = OrderedDict()
1504
1519
 
1505
1520
  @staticmethod
@@ -1562,6 +1577,17 @@ class MixedLogit(DiscreteChoiceModel):
1562
1577
  pass
1563
1578
  gc.collect()
1564
1579
 
1580
+ def release_memory(self):
1581
+ """Free per-fit caches and large post-process tensors this model holds.
1582
+
1583
+ Safe to call between metaheuristic candidates: it clears the process-
1584
+ wide JIT/XLA cache and drops ``prob_full`` unless it was explicitly
1585
+ saved. Fitted coefficients, standard errors and covariance are kept.
1586
+ """
1587
+ if not getattr(self, "save_prob_full", False):
1588
+ self.prob_full = None
1589
+ type(self)._clear_jit_cache()
1590
+
1565
1591
  def _run_jaxopt(self, betas, X_jax, y_jax, pi_jax, draws_jax,
1566
1592
  _fn, _cache_key):
1567
1593
  """JAX likelihood + ``jaxopt.LBFGS`` solver (experiment).
@@ -1780,6 +1780,7 @@ class LatentClassMixedLogit(DiscreteChoiceModel):
1780
1780
  member_params_spec=None,
1781
1781
  class_params_spec=None,
1782
1782
  panels=None,
1783
+ retain_all=False,
1783
1784
  ident_penalty_kwargs=None,
1784
1785
  **kwargs,
1785
1786
  ):
@@ -1797,8 +1798,17 @@ class LatentClassMixedLogit(DiscreteChoiceModel):
1797
1798
  Variables used in the class membership equation.
1798
1799
  member_params_spec : list or array, optional
1799
1800
  Per-class membership variable specification.
1801
+ retain_all : bool
1802
+ MEMORY: when False (default) only the best model is kept alive and
1803
+ the returned list contains just that model; every intermediate
1804
+ class-count model is released as the search advances. Each model
1805
+ otherwise pins its X/y/draws design, JAX device copies and cached
1806
+ JIT objectives, so retaining all of them across a GA search leaks
1807
+ hundreds of MB per candidate. Set True to recover the previous
1808
+ behaviour (all models returned and stored on
1809
+ ``best_model.search_results``).
1800
1810
  """
1801
- fitted_models = []
1811
+ fitted_models = [] if retain_all else None
1802
1812
  best_model = None
1803
1813
  _best_nsig = float('inf')
1804
1814
  prev_model = None
@@ -1833,7 +1843,8 @@ class LatentClassMixedLogit(DiscreteChoiceModel):
1833
1843
  de_seed=de_seed,
1834
1844
  )
1835
1845
  model.get_loglik_null()
1836
- fitted_models.append(model)
1846
+ if fitted_models is not None:
1847
+ fitted_models.append(model)
1837
1848
 
1838
1849
  # Pareto-style significance prioritisation:
1839
1850
  # 1) prefer fewer insignificant variable groups,
@@ -1858,8 +1869,14 @@ class LatentClassMixedLogit(DiscreteChoiceModel):
1858
1869
  _best_nsig = nsig
1859
1870
  prev_model = model
1860
1871
 
1861
- best_model.search_results = fitted_models
1862
- return best_model, fitted_models
1872
+ if fitted_models is not None:
1873
+ best_model.search_results = fitted_models
1874
+ return best_model, fitted_models
1875
+ # Default path: keep only the best model alive. Drop the warm-start
1876
+ # chain's trailing reference so intermediate models can be collected.
1877
+ best_model.search_results = None
1878
+ prev_model = None
1879
+ return best_model, [best_model]
1863
1880
 
1864
1881
  # ------------------------------------------------------------------
1865
1882
  # Internal helpers for numerical standard errors
@@ -54,6 +54,7 @@ in a panel or longitudinal dataset.
54
54
  ''' LIBRARIES '''
55
55
  ''' ---------------------------------------------------------- '''
56
56
  import itertools
57
+ import os
57
58
  import numpy as np
58
59
  import scipy.stats as ss
59
60
  from scipy.optimize import minimize
@@ -273,6 +274,8 @@ class MixedLogit(DiscreteChoiceModel):
273
274
  self.rvdist = None # Distributions for randvar
274
275
  self.correlated_vars, self.halton_opts = None, None
275
276
  self.batch_size, self.minimise_func = None, None
277
+ if not hasattr(self, "save_prob_full"):
278
+ self.save_prob_full = bool(os.environ.get("SEARCHLIBRIUM_SAVE_PROB_FULL", ""))
276
279
  # }
277
280
 
278
281
  ''' -------------------------------------------------------------------- '''
@@ -781,11 +784,17 @@ class MixedLogit(DiscreteChoiceModel):
781
784
  self.ind_pred_prob, self.pred_prob = _panel_weighted_shares(
782
785
  self.choice_pred_prob, self.panel_info)
783
786
 
784
- self.prob_full = p
787
+ if self.save_prob_full:
788
+ self.prob_full = p
785
789
  # }
786
790
  self.post_process(result, self.Xnames, self.N)
787
791
  # }
788
792
 
793
+ def release_memory(self):
794
+ """Drop prob_full if not explicitly saved. Safe between GA candidates."""
795
+ if not getattr(self, "save_prob_full", False):
796
+ self.prob_full = None
797
+
789
798
  ''' ---------------------------------------------------------- '''
790
799
  ''' Function. This code segments the betas array into different
791
800
  parts and stores these parts in var_list under keys from
@@ -69,6 +69,8 @@ from scipy import stats
69
69
  from scipy.optimize import minimize
70
70
 
71
71
  import inspect
72
+ import os
73
+ from collections import OrderedDict
72
74
 
73
75
  minval = 1E-30
74
76
 
@@ -2481,7 +2483,15 @@ class SA_ORDLOG():
2481
2483
  self.mod = OrderedLogit(X=X, y=y, J=J, distr='logit', start=None, fit_intercept=True)
2482
2484
 
2483
2485
  # SA specific parameters
2484
- self.archive = {} # Define a dictionary
2486
+ # MEMORY: the archive memoises spec -> loglik so repeated specs skip
2487
+ # the expensive refit. It used to be an unbounded dict, so a long run
2488
+ # retained one entry per distinct predictor mask (2**K in the limit).
2489
+ # It is now an LRU-bounded OrderedDict; evicting an entry only costs a
2490
+ # future refit, never correctness. Set
2491
+ # SEARCHLIBRIUM_ORDLOG_ARCHIVE_MAX=0 to restore unbounded behaviour.
2492
+ self.archive = OrderedDict()
2493
+ self.archive_maxsize = int(
2494
+ os.environ.get("SEARCHLIBRIUM_ORDLOG_ARCHIVE_MAX", "50000"))
2485
2495
  self.tI = 100
2486
2496
  self.tF = 0.001
2487
2497
  self.t = self.tI
@@ -2544,6 +2554,12 @@ class SA_ORDLOG():
2544
2554
  print("Solution already seen")
2545
2555
  candidate_score = self.archive[key]
2546
2556
 
2557
+ # LRU touch + bounded eviction (see archive_maxsize above).
2558
+ self.archive.move_to_end(key)
2559
+ _amax = getattr(self, "archive_maxsize", 0)
2560
+ while _amax and len(self.archive) > _amax:
2561
+ self.archive.popitem(last=False)
2562
+
2547
2563
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2548
2564
 
2549
2565
  if accept_change(self.current_score, candidate_score, self.t):
@@ -231,6 +231,7 @@ def run_class_search(
231
231
  max_classes=max_classes,
232
232
  criterion="bic",
233
233
  warm_start=True,
234
+ retain_all=True,
234
235
  de_init=de_init,
235
236
  de_popsize=de_popsize,
236
237
  de_maxiter=de_maxiter,
@@ -0,0 +1 @@
1
+ 0.0.262
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.261
3
+ Version: 0.0.262
4
4
  Summary: Automated specification search for discrete choice models (logit, mixed logit, nested logit, RRM and more) using metaheuristic optimisation
5
5
  Author: Zeke Ahern, Mario Fernando Taco Morales, Alexander Paz, Prithvi Beeramole, Robert Burdett
6
6
  Maintainer-email: Zeke Ahern <z.ahern@qut.edu.au>
@@ -1 +0,0 @@
1
- 0.0.261
File without changes