SearchLibrium 0.0.153__tar.gz → 0.0.155__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 (64) hide show
  1. {searchlibrium-0.0.153/src/SearchLibrium.egg-info → searchlibrium-0.0.155}/PKG-INFO +1 -1
  2. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/pyproject.toml +1 -1
  3. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/MixedLogitGSE.py +33 -5
  4. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/__init__.py +2 -0
  5. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/harmony.py +22 -2
  6. searchlibrium-0.0.155/src/SearchLibrium/version.txt +1 -0
  7. {searchlibrium-0.0.153 → searchlibrium-0.0.155/src/SearchLibrium.egg-info}/PKG-INFO +1 -1
  8. searchlibrium-0.0.153/src/SearchLibrium/version.txt +0 -1
  9. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/MANIFEST.in +0 -0
  10. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/README.md +0 -0
  11. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/setup.cfg +0 -0
  12. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/BEHier.py +0 -0
  13. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/Halton.py +0 -0
  14. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/MixedLogit.py +0 -0
  15. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/Mode_Activity_Nested.py +0 -0
  16. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/RandomP.py +0 -0
  17. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/SEARCH_SM_MARIO.py +0 -0
  18. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/Two_Level_Nest.py +0 -0
  19. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/__main__.py +0 -0
  20. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/_choice_model.py +0 -0
  21. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/_device.py +0 -0
  22. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/banditsa.py +0 -0
  23. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/bhhh/minimize.py +0 -0
  24. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/boxcox_functions.py +0 -0
  25. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/call_meta.py +0 -0
  26. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/constraints_builder.py +0 -0
  27. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/data/Swissmetro_final.csv +0 -0
  28. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/data/TravelMode.csv +0 -0
  29. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/data/electricity.csv +0 -0
  30. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/hspbil.py +0 -0
  31. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/latent_class.py +0 -0
  32. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/main.py +0 -0
  33. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/main_debug.py +0 -0
  34. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/mdcev.py +0 -0
  35. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/misc.py +0 -0
  36. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/mixed_logit.py +0 -0
  37. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/mixed_nested.py +0 -0
  38. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/mixedrrm.py +0 -0
  39. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/multinomial_logit.py +0 -0
  40. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/multinomial_nested.py +0 -0
  41. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/multinomial_probit.py +0 -0
  42. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/ordered_logit.py +0 -0
  43. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/ordered_logit_mixed.py +0 -0
  44. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/predict.py +0 -0
  45. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/rrm.py +0 -0
  46. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/sample_data.py +0 -0
  47. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/sapbil.py +0 -0
  48. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/search.py +0 -0
  49. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/selection_models.py +0 -0
  50. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/setup.py +0 -0
  51. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/siman.py +0 -0
  52. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/test_lc_de.py +0 -0
  53. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/test_mario_searches.py +0 -0
  54. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/test_sapbil_vs_banditsa.py +0 -0
  55. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium/threshold.py +0 -0
  56. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium.egg-info/SOURCES.txt +0 -0
  57. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium.egg-info/dependency_links.txt +0 -0
  58. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium.egg-info/entry_points.txt +0 -0
  59. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium.egg-info/requires.txt +0 -0
  60. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/SearchLibrium.egg-info/top_level.txt +0 -0
  61. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/data_generator/demonstrate_generation.py +0 -0
  62. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/data_generator/fit_recovery.py +0 -0
  63. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/data_generator/latent_class_gen.py +0 -0
  64. {searchlibrium-0.0.153 → searchlibrium-0.0.155}/src/data_generator/print_synthetic_data.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.153
3
+ Version: 0.0.155
4
4
  Summary: A Python package for econometric models driven by search
5
5
  Author: Mario Fernando Taco Morales, Alexander Paz, Prithvi Beeramole, Robert Burdett
6
6
  Author-email: Zeke Ahern <z.ahern@qut.edu.au>
@@ -82,7 +82,7 @@ Documentation = "https://pypi.org/project/SearchLibrium/"
82
82
  realpython = "SearchLibrium.__main__:main"
83
83
 
84
84
  [tool.bumpver]
85
- current_version = "0.0.153"
85
+ current_version = "0.0.155"
86
86
  version_pattern = "MAJOR.MINOR.PATCH"
87
87
  commit_message = "[skip ci] Bump version {old_version} -> {new_version}"
88
88
  commit = true
@@ -11,6 +11,16 @@ Usage:
11
11
  mxl = MixedLogitGSE()
12
12
  mxl.setup(..., gradient_scores=g_avg, random_gamma=False)
13
13
  mxl.fit()
14
+
15
+ Larch equivalence (kept in sync with test_runs_tours stage-5 mode model):
16
+ The model β_nk = μ_k + γ_k·g_nk + σ_k·η_nk decomposes term-by-term into a
17
+ plain Larch mixed logit, since β_nk multiplies x_k in the utility:
18
+ μ_k·x_k → ordinary fixed utility term P('c_k') * X('x_k')
19
+ γ_k·(g_nk·x_k) → systematic gradient interaction P('g_k') * X('x_k * g')
20
+ σ_k·(η_nk·x_k) → Normal random coefficient mixtures.append(Normal('c_k','s_k'))
21
+ random_gamma=True adds τ_k·z_nk·g_nk, i.e. promoting the γ_k loadings to
22
+ random coefficients too (Normal mixtures on the g_k params in the Larch build).
23
+ See stage5_destination_choice._estimate_mixed_logit for the mirrored build.
14
24
  """
15
25
 
16
26
  import numpy as np
@@ -37,13 +47,24 @@ class MixedLogitGSE(MixedLogit):
37
47
  def setup(self, *args, gradient_scores=None, random_gamma=False, **kwargs):
38
48
  self.random_gamma = bool(random_gamma)
39
49
  if gradient_scores is not None:
40
- # Standardise to O(1) to prevent numerical overflow
50
+ # Standardise each gradient column to mean 0 / std 1 (O(1)) so the
51
+ # γ loadings are on a common scale and can't numerically overflow.
52
+ # Centering matters: an uncentred score folds a spurious γ·mean/std
53
+ # shift into μ; centring keeps μ interpretable as the population mean.
54
+ # A constant (zero-variance) column would divide by ~0 and blow up to
55
+ # ~1e8 — guard it by leaving such columns at 0 (they carry no gradient
56
+ # information anyway).
41
57
  g = np.asarray(gradient_scores, dtype=float)
42
- self.gradient_scores = g / (g.std(axis=0, keepdims=True) + 1e-8)
43
- gstd = g.std(axis=0)
58
+ gmu = g.mean(axis=0, keepdims=True)
59
+ gsd = g.std(axis=0, keepdims=True)
60
+ gsd_safe = np.where(gsd < 1e-8, 1.0, gsd)
61
+ self.gradient_scores = (g - gmu) / gsd_safe
62
+ n_const = int(np.sum(gsd < 1e-8))
44
63
  print(f"[MixedLogitGSE] Gradient loadings enabled (Kgrad={len(kwargs.get('randvars', {}))}, "
45
64
  f"random_gamma={self.random_gamma}, "
46
- f"gradient std range=[{gstd.min():.2f}, {gstd.max():.2f}] -> standardised)")
65
+ f"raw std range=[{float(gsd.min()):.2f}, {float(gsd.max()):.2f}] -> "
66
+ f"centred+standardised"
67
+ + (f", {n_const} constant column(s) zeroed" if n_const else "") + ")")
47
68
  else:
48
69
  self.gradient_scores = None
49
70
  super().setup(*args, **kwargs)
@@ -87,7 +108,14 @@ class MixedLogitGSE(MixedLogit):
87
108
  self.Kgrad = self.Kr
88
109
  self.Kgrad_w = self.Kr if self.random_gamma else 0
89
110
  if self.Kgrad_w > 0:
90
- self.gamma_draws = np.random.randn(self.N, self.Kgrad)
111
+ # Seed the random(τ·z) draws so a fit is reproducible across
112
+ # runs — unseeded np.random.randn made every fit non-deterministic
113
+ # (and non-comparable in a search). Reuse the model's own seed
114
+ # when set, mixing in Kgrad so it differs from the η draws' seed.
115
+ _seed = getattr(self, "seed", None)
116
+ rng = np.random.default_rng(None if _seed is None
117
+ else int(_seed) + 10007 * self.Kgrad)
118
+ self.gamma_draws = rng.standard_normal((self.N, self.Kgrad))
91
119
  # Force JAX-only (scipy fallback doesn't handle gradient loadings)
92
120
  self._jax = True
93
121
  super().fit()
@@ -106,6 +106,7 @@ try:
106
106
  from .latent_class import LatentClassMixedLogit
107
107
  from .mdcev import MDCEVFitResult, MDCEVModel
108
108
  from .multinomial_probit import MultinomialProbit
109
+ from .MixedLogitGSE import MixedLogitGSE
109
110
  from .RandomP import RandomParameters
110
111
  from .constraints_builder import ConstraintBuilder, create_constraints
111
112
  from .search import Parameters
@@ -131,6 +132,7 @@ except ImportError as e:
131
132
  from latent_class import LatentClassMixedLogit
132
133
  from mdcev import MDCEVFitResult, MDCEVModel
133
134
  from multinomial_probit import MultinomialProbit
135
+ from MixedLogitGSE import MixedLogitGSE
134
136
  from RandomP import RandomParameters
135
137
  from constraints_builder import ConstraintBuilder, create_constraints
136
138
  from search import Parameters
@@ -949,10 +949,11 @@ class HarmonySearch(Search):
949
949
  """Identify groups of variables that measure similar constructs
950
950
  and can be substituted for one another."""
951
951
  groups = []
952
- # Group 1: destination size measures
952
+ # Group 1: destination size measures (now includes per-type ACDC)
953
953
  size_measures = [v for v in asvars
954
954
  if v in {'ln_dest_trips', 'osm_destination_pull',
955
- 'acdc_aggl', 'osm_poi_intensity'}]
955
+ 'acdc_aggl', 'osm_poi_intensity'}
956
+ or v.startswith('acdc_aggl_')]
956
957
  for i in range(len(size_measures)):
957
958
  for j in range(i + 1, len(size_measures)):
958
959
  groups.append((size_measures[i], size_measures[j]))
@@ -972,6 +973,25 @@ class HarmonySearch(Search):
972
973
  for j in range(i + 1, len(osm_measures)):
973
974
  groups.append((osm_measures[i], osm_measures[j]))
974
975
 
976
+ # Group 4: Per-type ACDC agglomeration terms (substitutable by OSM type)
977
+ per_type_aggl = [v for v in asvars if v.startswith('acdc_aggl_')]
978
+ for i in range(len(per_type_aggl)):
979
+ for j in range(i + 1, len(per_type_aggl)):
980
+ groups.append((per_type_aggl[i], per_type_aggl[j]))
981
+
982
+ # Group 5: Per-type ACDC competition terms (substitutable by OSM type)
983
+ per_type_comp = [v for v in asvars if v.startswith('acdc_comp_')]
984
+ for i in range(len(per_type_comp)):
985
+ for j in range(i + 1, len(per_type_comp)):
986
+ groups.append((per_type_comp[i], per_type_comp[j]))
987
+
988
+ # Group 6: Cross-substitution — agglomeration <-> competition for same type
989
+ for t in ['retail', 'food', 'leisure', 'parks', 'transit', 'cycleway', 'culture']:
990
+ aggl_var = f'acdc_aggl_{t}'
991
+ comp_var = f'acdc_comp_{t}'
992
+ if aggl_var in asvars and comp_var in asvars:
993
+ groups.append((aggl_var, comp_var))
994
+
975
995
  return groups
976
996
 
977
997
  def _get_forced_vars(self):
@@ -0,0 +1 @@
1
+ 0.0.155
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.153
3
+ Version: 0.0.155
4
4
  Summary: A Python package for econometric models driven by search
5
5
  Author: Mario Fernando Taco Morales, Alexander Paz, Prithvi Beeramole, Robert Burdett
6
6
  Author-email: Zeke Ahern <z.ahern@qut.edu.au>
@@ -1 +0,0 @@
1
- 0.0.153