SearchLibrium 0.0.262__tar.gz → 0.0.263__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.
- {searchlibrium-0.0.262/src/SearchLibrium.egg-info → searchlibrium-0.0.263}/PKG-INFO +1 -1
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/pyproject.toml +1 -1
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/Halton.py +8 -4
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/MixedLogit.py +261 -23
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/destination_distances.py +34 -11
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/multinomial_logit.py +4 -1
- searchlibrium-0.0.263/src/SearchLibrium/version.txt +1 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263/src/SearchLibrium.egg-info}/PKG-INFO +1 -1
- searchlibrium-0.0.262/src/SearchLibrium/version.txt +0 -1
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/LICENSE +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/MANIFEST.in +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/README.md +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/setup.cfg +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/BEHier.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/MixedLogitGSE.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/Mode_Activity_Nested.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/RandomP.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/SEARCH_SM_MARIO.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/Two_Level_Nest.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/__init__.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/__main__.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/_choice_model.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/_device.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/abm.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/accessibility.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/banditsa.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/boxcox_functions.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/call_meta.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/constraints_builder.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/coupled_solver.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/data/Swissmetro_final.csv +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/data/TravelMode.csv +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/data/electricity.csv +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/harmony.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/hspbil.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/jax_utils.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/larch_compat.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/larch_models.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/latent_class.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/latent_spec.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/logistic_regression.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/main.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/main_debug.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/mdcev.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/mh_choice_estimator.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/misc.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/mixed_logit.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/mixed_nested.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/mixedrrm.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/multinomial_nested.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/multinomial_probit.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/numba_engine.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/numba_mxl.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/numba_rrm.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/ordered_logit.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/ordered_logit_mixed.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/predict.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/regularization.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/rrm.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/sample_data.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/sapbil.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/search.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/selection_models.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/setup.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/siman.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/skims.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/sparseea_agds.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/test_lc_de.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/test_mario_searches.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/test_sapbil_vs_banditsa.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/threshold.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/zero_inflated_ordered_probit.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium.egg-info/SOURCES.txt +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium.egg-info/dependency_links.txt +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium.egg-info/entry_points.txt +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium.egg-info/requires.txt +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium.egg-info/top_level.txt +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_destination_distances.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_jax_mask_equivalence.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_larch_integration.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_lc_smart_init.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_mh_choice_estimator.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_mxl_random_starts.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/tests/test_per_alternative_random.py +0 -0
- {searchlibrium-0.0.262 → searchlibrium-0.0.263}/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.
|
|
3
|
+
Version: 0.0.263
|
|
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.
|
|
135
|
+
current_version = "0.0.263"
|
|
136
136
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
137
137
|
commit_message = "[skip ci] Bump version {old_version} -> {new_version}"
|
|
138
138
|
commit = true
|
|
@@ -56,12 +56,14 @@ class Halton:
|
|
|
56
56
|
Set use_sobol=True to use scrambled Sobol sequences instead.
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
|
-
def __init__(self, primes=None, drop=100, shuffled=False,
|
|
59
|
+
def __init__(self, primes=None, drop=100, shuffled=False,
|
|
60
|
+
antithetic=False, use_sobol=False, seed=None):
|
|
60
61
|
self.primes = primes
|
|
61
62
|
self.drop = drop
|
|
62
63
|
self.shuffled = shuffled
|
|
63
64
|
self.antithetic = antithetic
|
|
64
65
|
self.use_sobol = use_sobol
|
|
66
|
+
self.seed = seed
|
|
65
67
|
|
|
66
68
|
# Default primes for Halton sequence
|
|
67
69
|
if self.primes is None:
|
|
@@ -81,7 +83,9 @@ class Halton:
|
|
|
81
83
|
if self.use_sobol:
|
|
82
84
|
# Use Sobol sequence (newer method)
|
|
83
85
|
base = n_draws // 2 if self.antithetic else n_draws
|
|
84
|
-
draws = _sobol_generate(
|
|
86
|
+
draws = _sobol_generate(
|
|
87
|
+
sample_size, base, n_vars, shuffled=self.shuffled,
|
|
88
|
+
seed=self.seed)
|
|
85
89
|
if self.antithetic:
|
|
86
90
|
draws = np.concatenate([draws, 1.0 - draws], axis=2)
|
|
87
91
|
else:
|
|
@@ -110,7 +114,7 @@ class Halton:
|
|
|
110
114
|
return draws
|
|
111
115
|
|
|
112
116
|
|
|
113
|
-
def _sobol_generate(sample_size, n_draws, n_vars, shuffled=False):
|
|
117
|
+
def _sobol_generate(sample_size, n_draws, n_vars, shuffled=False, seed=None):
|
|
114
118
|
"""Generate scrambled Sobol (0,1) draws.
|
|
115
119
|
|
|
116
120
|
Returns
|
|
@@ -123,7 +127,7 @@ def _sobol_generate(sample_size, n_draws, n_vars, shuffled=False):
|
|
|
123
127
|
p2 = 1
|
|
124
128
|
while p2 < total:
|
|
125
129
|
p2 <<= 1
|
|
126
|
-
sobol = Sobol(d=n_vars, scramble=True)
|
|
130
|
+
sobol = Sobol(d=n_vars, scramble=True, seed=seed)
|
|
127
131
|
flat = sobol.random(p2)[:total] # (total, n_vars)
|
|
128
132
|
draws = flat.reshape(sample_size, n_draws, n_vars).transpose(0, 2, 1)
|
|
129
133
|
else:
|
|
@@ -234,7 +234,8 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
234
234
|
randvars=None, panels=None, base_alt=None, fit_intercept=False,
|
|
235
235
|
init_coeff=None, maxiter=1000, correlated_vars=None,
|
|
236
236
|
n_draws=1000, halton=True, minimise_func=None,
|
|
237
|
-
batch_size=None,
|
|
237
|
+
batch_size=None, person_batch_size=None, halton_opts=None,
|
|
238
|
+
draw_seed=0, ftol=1e-12,
|
|
238
239
|
gtol=1e-6, return_hess=True, return_grad=True, method="slsqp",
|
|
239
240
|
save_fitted_params=True, mnl_init=True,
|
|
240
241
|
de_init=False, de_popsize=4, de_maxiter=3, de_tol=0.5,
|
|
@@ -310,7 +311,15 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
310
311
|
self.jac = self.return_grad # scipy optimize parameter
|
|
311
312
|
self.n_draws = n_draws
|
|
312
313
|
self.batch_size = min(n_draws, batch_size) if batch_size is not None else n_draws
|
|
313
|
-
|
|
314
|
+
if person_batch_size is None:
|
|
315
|
+
self.person_batch_size = None
|
|
316
|
+
else:
|
|
317
|
+
self.person_batch_size = max(1, int(person_batch_size))
|
|
318
|
+
self._person_batch_active = False
|
|
319
|
+
self.draw_seed = int(draw_seed)
|
|
320
|
+
self.halton_opts = dict(halton_opts or {})
|
|
321
|
+
if self.person_batch_size is not None:
|
|
322
|
+
self.halton_opts.setdefault("seed", self.draw_seed)
|
|
314
323
|
|
|
315
324
|
# CRITICAL FIX: Recreate draws_generator with proper halton_opts AND rvdist
|
|
316
325
|
# The __init__ creates it with halton_opts=None, but setup() gets the actual options
|
|
@@ -318,7 +327,8 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
318
327
|
# AND to pass the correct random variable distributions (e.g., 'ln' for lognormal)
|
|
319
328
|
# IMPORTANT: Order rvdist by varnames order, not dictionary order
|
|
320
329
|
rvdist = [randvars[var] for var in varnames if var in randvars] if randvars else []
|
|
321
|
-
self.draws_generator = Draws(
|
|
330
|
+
self.draws_generator = Draws(
|
|
331
|
+
k=len(randvars or {}), halton_opts=self.halton_opts, rvdist=rvdist)
|
|
322
332
|
|
|
323
333
|
self.randvarsdict = randvars # random variables not transformed
|
|
324
334
|
|
|
@@ -821,7 +831,16 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
821
831
|
def fit(self):
|
|
822
832
|
# {
|
|
823
833
|
# Generate draws:
|
|
824
|
-
|
|
834
|
+
streaming_people = (
|
|
835
|
+
self.person_batch_size is not None
|
|
836
|
+
and self.person_batch_size < self.N
|
|
837
|
+
)
|
|
838
|
+
if streaming_people:
|
|
839
|
+
# Person chunks regenerate deterministic draws during each
|
|
840
|
+
# objective evaluation, avoiding an (N, Kr, R) draw bank.
|
|
841
|
+
draws, drawstrans = None, None
|
|
842
|
+
else:
|
|
843
|
+
draws, drawstrans = self.generate_draws(self.N, self.n_draws, self.halton)
|
|
825
844
|
self.draws, self.drawstrans = draws, drawstrans # Record generated values
|
|
826
845
|
# Explicit caller-supplied starts are respected verbatim (no ASC
|
|
827
846
|
# share seeding); the MNL block below may still overwrite
|
|
@@ -838,9 +857,15 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
838
857
|
from numba_engine import effective_engine as _mx_eff_eng
|
|
839
858
|
except ImportError:
|
|
840
859
|
from .numba_engine import effective_engine as _mx_eff_eng
|
|
841
|
-
|
|
860
|
+
# Numba is the default CPU engine. JAX must be selected
|
|
861
|
+
# explicitly; unsupported specifications fall back to SciPy.
|
|
862
|
+
use_numba = (_mx_eff_eng(self) != 'jax')
|
|
842
863
|
except Exception:
|
|
843
|
-
use_numba = (getattr(self, 'engine', None)
|
|
864
|
+
use_numba = (getattr(self, 'engine', None) != 'jax')
|
|
865
|
+
if streaming_people:
|
|
866
|
+
# The streaming path regenerates person-chunk draws and is not
|
|
867
|
+
# compatible with the current full-array Numba adapter.
|
|
868
|
+
use_numba = False
|
|
844
869
|
|
|
845
870
|
# 2x Kftrans - mean and lambda, 3x Krtrans - mean, s.d., lambda
|
|
846
871
|
# Kchol, Kbw - relate to random variables, non-transformed
|
|
@@ -1061,11 +1086,12 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1061
1086
|
if _v: print(f"[MXL] Differential evolution initialization failed: {e}")
|
|
1062
1087
|
|
|
1063
1088
|
# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
|
1064
|
-
if dev.using_gpu: # {
|
|
1089
|
+
if dev.using_gpu and draws is not None: # {
|
|
1065
1090
|
self.X, self.y = dev.convert_array_gpu(self.X), dev.convert_array_gpu(self.y)
|
|
1066
1091
|
self.panel_info = dev.convert_array_gpu(self.panel_info)
|
|
1067
1092
|
draws = dev.convert_array_gpu(draws)
|
|
1068
|
-
drawstrans
|
|
1093
|
+
if drawstrans is not None:
|
|
1094
|
+
drawstrans = dev.convert_array_gpu(drawstrans)
|
|
1069
1095
|
self.weights = dev.convert_array_gpu(self.weights) if self.weights is not None else None
|
|
1070
1096
|
self.avail = dev.convert_array_gpu(self.avail) if self.avail is not None else None
|
|
1071
1097
|
# }
|
|
@@ -1113,7 +1139,8 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1113
1139
|
# SOLVE OPTIMISATION PROBLEM - COMPUTATIONALLY TIME CONSUMING!
|
|
1114
1140
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
1115
1141
|
# ---- JAX fast path ----
|
|
1116
|
-
if (getattr(self, '_jax', False) and not
|
|
1142
|
+
if (getattr(self, '_jax', False) and not streaming_people
|
|
1143
|
+
and not use_numba
|
|
1117
1144
|
and self.minimise_func is None):
|
|
1118
1145
|
jax_result = self.optimize_jax(betas, draws, drawstrans)
|
|
1119
1146
|
if jax_result is not None:
|
|
@@ -1244,6 +1271,16 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1244
1271
|
except Exception:
|
|
1245
1272
|
pass
|
|
1246
1273
|
|
|
1274
|
+
if draws is None:
|
|
1275
|
+
# Estimation stayed streamed; materialize full draws only for the
|
|
1276
|
+
# final fitted-parameter and prediction summaries.
|
|
1277
|
+
draws, drawstrans = self.generate_draws(
|
|
1278
|
+
self.N, self.n_draws, self.halton)
|
|
1279
|
+
self.draws, self.drawstrans = draws, drawstrans
|
|
1280
|
+
self.p = self.compute_probabilities(
|
|
1281
|
+
result['x'], self.X, self.panel_info, draws, drawstrans,
|
|
1282
|
+
self.avail, self.var_list, self.chol_mat)
|
|
1283
|
+
|
|
1247
1284
|
if self.save_fitted_params:
|
|
1248
1285
|
self.compute_fitted_params(self.y, self.p, self.panel_info, self.Br)
|
|
1249
1286
|
|
|
@@ -1424,7 +1461,8 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1424
1461
|
|
|
1425
1462
|
sim_p = jnp.mean(pch, axis=1) # (N,)
|
|
1426
1463
|
sim_p = jnp.clip(sim_p, 1e-300, None)
|
|
1427
|
-
|
|
1464
|
+
valid_person = jnp.any(panel_info_jax > 0, axis=1)
|
|
1465
|
+
ll = -jnp.sum(jnp.where(valid_person, jnp.log(sim_p), 0.0))
|
|
1428
1466
|
# Tiny L2 penalty — makes the Hessian diagonally dominant so it
|
|
1429
1467
|
# never becomes singular, even with near-constant variables or
|
|
1430
1468
|
# simulation noise in the mixed-logit probability integration.
|
|
@@ -1666,6 +1704,125 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1666
1704
|
print(f"[JAX MXL optimizer] jaxopt run failed ({_e}).")
|
|
1667
1705
|
return None
|
|
1668
1706
|
|
|
1707
|
+
def _optimize_jax_streamed(self, betas):
|
|
1708
|
+
"""Fit plain MXL with bounded person chunks and deterministic draws."""
|
|
1709
|
+
try:
|
|
1710
|
+
import os
|
|
1711
|
+
import jax
|
|
1712
|
+
import jax.numpy as jnp
|
|
1713
|
+
import numpy as _np
|
|
1714
|
+
from scipy.optimize import minimize as sp_min
|
|
1715
|
+
|
|
1716
|
+
chunk_size = int(self.person_batch_size)
|
|
1717
|
+
X_np = _np.asarray(self.X)
|
|
1718
|
+
y_np = _np.asarray(self.y)
|
|
1719
|
+
if y_np.ndim > 3:
|
|
1720
|
+
y_np = y_np[..., 0]
|
|
1721
|
+
pi_np = _np.asarray(self.panel_info)
|
|
1722
|
+
N, P, J, _ = X_np.shape
|
|
1723
|
+
Kf, Kr = int(self.Kf), int(self.Kr)
|
|
1724
|
+
Kchol, Kbw = int(self.Kchol), int(self.Kbw)
|
|
1725
|
+
fxidx = jnp.asarray(self.fxidx, dtype=bool)
|
|
1726
|
+
rvidx = jnp.asarray(self.rvidx, dtype=bool)
|
|
1727
|
+
rvdist_names = [d for d in self.rvdist if d is not False]
|
|
1728
|
+
extra = self._jax_negloglik_extra_kwargs()
|
|
1729
|
+
|
|
1730
|
+
def _pad(value, shape):
|
|
1731
|
+
result = _np.zeros(shape, dtype=_np.float64)
|
|
1732
|
+
result[tuple(slice(0, n) for n in value.shape)] = value
|
|
1733
|
+
return result
|
|
1734
|
+
|
|
1735
|
+
def _chunk_inputs(chunk_index):
|
|
1736
|
+
start = chunk_index * chunk_size
|
|
1737
|
+
end = min(start + chunk_size, N)
|
|
1738
|
+
n = end - start
|
|
1739
|
+
X_chunk = _pad(X_np[start:end], (chunk_size, P, J, X_np.shape[3]))
|
|
1740
|
+
y_chunk = _pad(y_np[start:end], (chunk_size, P, J))
|
|
1741
|
+
pi_chunk = _pad(pi_np[start:end], (chunk_size, P))
|
|
1742
|
+
|
|
1743
|
+
state = _np.random.get_state()
|
|
1744
|
+
try:
|
|
1745
|
+
_np.random.seed(self.draw_seed + chunk_index)
|
|
1746
|
+
if self.halton and hasattr(self.draws_generator, "halton"):
|
|
1747
|
+
self.draws_generator.halton.seed = (
|
|
1748
|
+
self.draw_seed + chunk_index)
|
|
1749
|
+
draws_chunk, _ = self.generate_draws(
|
|
1750
|
+
n, self.n_draws, self.halton)
|
|
1751
|
+
finally:
|
|
1752
|
+
_np.random.set_state(state)
|
|
1753
|
+
draws_chunk = _pad(
|
|
1754
|
+
_np.asarray(draws_chunk),
|
|
1755
|
+
(chunk_size, int(self.Kr), self.n_draws))
|
|
1756
|
+
return (
|
|
1757
|
+
jnp.asarray(X_chunk),
|
|
1758
|
+
jnp.asarray(y_chunk),
|
|
1759
|
+
jnp.asarray(pi_chunk),
|
|
1760
|
+
jnp.asarray(draws_chunk),
|
|
1761
|
+
)
|
|
1762
|
+
|
|
1763
|
+
def _chunk_objective(b, X_j, y_j, pi_j, draws_j):
|
|
1764
|
+
return self._jax_mxl_negloglik(
|
|
1765
|
+
b, X_j, y_j, pi_j, draws_j,
|
|
1766
|
+
fxidx, rvidx, Kf, Kr, Kchol, Kbw,
|
|
1767
|
+
rvdist_names, int(self.correlationLength),
|
|
1768
|
+
**extra)
|
|
1769
|
+
|
|
1770
|
+
compiled = jax.jit(jax.value_and_grad(_chunk_objective))
|
|
1771
|
+
chunk_args = [
|
|
1772
|
+
_chunk_inputs(i)
|
|
1773
|
+
for i in range((N + chunk_size - 1) // chunk_size)
|
|
1774
|
+
]
|
|
1775
|
+
valid_counts = [
|
|
1776
|
+
min(chunk_size, N - i * chunk_size)
|
|
1777
|
+
for i in range(len(chunk_args))
|
|
1778
|
+
]
|
|
1779
|
+
|
|
1780
|
+
def _objective(betas_np):
|
|
1781
|
+
b = jnp.asarray(betas_np, dtype=jnp.float64)
|
|
1782
|
+
value = 0.0
|
|
1783
|
+
gradient = jnp.zeros_like(b)
|
|
1784
|
+
for args, valid in zip(chunk_args, valid_counts):
|
|
1785
|
+
chunk_value, chunk_gradient = compiled(b, *args)
|
|
1786
|
+
# Padded people contribute zero because their panel mask
|
|
1787
|
+
# is zero; valid is retained for clarity and future
|
|
1788
|
+
# weighted-panel support.
|
|
1789
|
+
del valid
|
|
1790
|
+
value = value + chunk_value
|
|
1791
|
+
gradient = gradient + chunk_gradient
|
|
1792
|
+
value, gradient = jax.block_until_ready((value, gradient))
|
|
1793
|
+
return float(value), _np.asarray(gradient, dtype=_np.float64)
|
|
1794
|
+
|
|
1795
|
+
options = {'maxiter': self.maxiter, 'disp': False}
|
|
1796
|
+
if self.method in ('bfgs', 'l-bfgs-b'):
|
|
1797
|
+
options['gtol'] = self.gtol
|
|
1798
|
+
options['ftol'] = self.ftol
|
|
1799
|
+
else:
|
|
1800
|
+
options['ftol'] = self.ftol
|
|
1801
|
+
result = sp_min(
|
|
1802
|
+
_objective, betas, jac=True, method=self.method,
|
|
1803
|
+
options=options)
|
|
1804
|
+
|
|
1805
|
+
if self.return_hess:
|
|
1806
|
+
def _hessian_objective(b):
|
|
1807
|
+
value = 0.0
|
|
1808
|
+
for args in chunk_args:
|
|
1809
|
+
value = value + _chunk_objective(b, *args)
|
|
1810
|
+
return value
|
|
1811
|
+
|
|
1812
|
+
hessian_fn = jax.jit(jax.hessian(_hessian_objective))
|
|
1813
|
+
hessian = _np.asarray(
|
|
1814
|
+
hessian_fn(jnp.asarray(result['x'], dtype=jnp.float64)),
|
|
1815
|
+
dtype=float)
|
|
1816
|
+
try:
|
|
1817
|
+
result['hess_inv'] = _np.linalg.inv(hessian)
|
|
1818
|
+
except _np.linalg.LinAlgError:
|
|
1819
|
+
result['hess_inv'] = _np.linalg.pinv(hessian)
|
|
1820
|
+
return result
|
|
1821
|
+
except Exception as exc:
|
|
1822
|
+
if not _SL_QUIET:
|
|
1823
|
+
print(f"[JAX streamed MXL] falling back to scipy: {exc}")
|
|
1824
|
+
return None
|
|
1825
|
+
|
|
1669
1826
|
def optimize_jax(self, betas, draws, drawstrans):
|
|
1670
1827
|
"""JAX-accelerated optimisation for the Mixed Logit model.
|
|
1671
1828
|
|
|
@@ -1681,6 +1838,10 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1681
1838
|
print("[JAX MXL optimizer] heterogeneity detected; falling back to scipy.")
|
|
1682
1839
|
return None
|
|
1683
1840
|
|
|
1841
|
+
if (self.person_batch_size is not None
|
|
1842
|
+
and self.person_batch_size < self.N):
|
|
1843
|
+
return self._optimize_jax_streamed(betas)
|
|
1844
|
+
|
|
1684
1845
|
try:
|
|
1685
1846
|
import os
|
|
1686
1847
|
os.environ.setdefault("JAX_ENABLE_X64", "True")
|
|
@@ -1940,12 +2101,88 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
1940
2101
|
print(f"[JAX MXL masked] falling back: {e}")
|
|
1941
2102
|
return None
|
|
1942
2103
|
|
|
2104
|
+
def _person_batch_slice(self, value, start, end):
|
|
2105
|
+
"""Slice an observation-level array while preserving scalar settings."""
|
|
2106
|
+
if value is None:
|
|
2107
|
+
return None
|
|
2108
|
+
try:
|
|
2109
|
+
if np.ndim(value) > 0 and np.shape(value)[0] == self.N:
|
|
2110
|
+
return value[start:end]
|
|
2111
|
+
except (TypeError, IndexError):
|
|
2112
|
+
pass
|
|
2113
|
+
return value
|
|
2114
|
+
|
|
2115
|
+
def _stream_person_batches(self, betas, X, y, panel_info, draws,
|
|
2116
|
+
drawstrans, weights, avail, batch_size):
|
|
2117
|
+
"""Evaluate the objective in bounded person chunks.
|
|
2118
|
+
|
|
2119
|
+
When ``draws`` is absent, each chunk receives deterministic random
|
|
2120
|
+
draws derived from ``draw_seed`` and its chunk index. This keeps every
|
|
2121
|
+
optimizer evaluation reproducible without retaining all draws.
|
|
2122
|
+
"""
|
|
2123
|
+
original_n = self.N
|
|
2124
|
+
total_value = 0.0
|
|
2125
|
+
total_gradient = None
|
|
2126
|
+
has_gradient = bool(self.return_grad)
|
|
2127
|
+
try:
|
|
2128
|
+
self._person_batch_active = True
|
|
2129
|
+
for chunk_index, start in enumerate(
|
|
2130
|
+
range(0, original_n, self.person_batch_size)):
|
|
2131
|
+
end = min(start + self.person_batch_size, original_n)
|
|
2132
|
+
self.N = end - start
|
|
2133
|
+
chunk_X = X[start:end]
|
|
2134
|
+
chunk_y = y[start:end]
|
|
2135
|
+
chunk_panel = panel_info[start:end]
|
|
2136
|
+
chunk_weights = self._person_batch_slice(weights, start, end)
|
|
2137
|
+
chunk_avail = self._person_batch_slice(avail, start, end)
|
|
2138
|
+
|
|
2139
|
+
if draws is None:
|
|
2140
|
+
rng_state = np.random.get_state()
|
|
2141
|
+
try:
|
|
2142
|
+
np.random.seed(self.draw_seed + chunk_index)
|
|
2143
|
+
if self.halton and hasattr(self.draws_generator, "halton"):
|
|
2144
|
+
self.draws_generator.halton.seed = (
|
|
2145
|
+
self.draw_seed + chunk_index)
|
|
2146
|
+
chunk_draws, chunk_drawstrans = self.generate_draws(
|
|
2147
|
+
self.N, self.n_draws, self.halton)
|
|
2148
|
+
finally:
|
|
2149
|
+
np.random.set_state(rng_state)
|
|
2150
|
+
else:
|
|
2151
|
+
chunk_draws = draws[start:end]
|
|
2152
|
+
chunk_drawstrans = (
|
|
2153
|
+
drawstrans[start:end] if drawstrans is not None
|
|
2154
|
+
else drawstrans
|
|
2155
|
+
)
|
|
2156
|
+
|
|
2157
|
+
result = self.get_loglik_gradient(
|
|
2158
|
+
betas, chunk_X, chunk_y, chunk_panel,
|
|
2159
|
+
chunk_draws, chunk_drawstrans, chunk_weights,
|
|
2160
|
+
chunk_avail, batch_size)
|
|
2161
|
+
total_value += float(result[0])
|
|
2162
|
+
if has_gradient and len(result) > 1:
|
|
2163
|
+
if total_gradient is None:
|
|
2164
|
+
total_gradient = np.zeros_like(result[1], dtype=float)
|
|
2165
|
+
total_gradient += np.asarray(result[1], dtype=float)
|
|
2166
|
+
finally:
|
|
2167
|
+
self.N = original_n
|
|
2168
|
+
self._person_batch_active = False
|
|
2169
|
+
|
|
2170
|
+
if has_gradient:
|
|
2171
|
+
return total_value, total_gradient
|
|
2172
|
+
return (total_value,)
|
|
2173
|
+
|
|
1943
2174
|
def get_loglik_gradient(self, betas, X, y, panel_info, draws,
|
|
1944
2175
|
drawstrans, weights, avail, batch_size):
|
|
1945
2176
|
# {
|
|
1946
2177
|
""" Fixed and random parameters are handled separately to
|
|
1947
2178
|
speed up the estimation and the results are concatenated.
|
|
1948
2179
|
"""
|
|
2180
|
+
if (not self._person_batch_active
|
|
2181
|
+
and self.person_batch_size is not None
|
|
2182
|
+
and self.person_batch_size < self.N):
|
|
2183
|
+
return self._stream_person_batches(
|
|
2184
|
+
betas, X, y, panel_info, draws, drawstrans,
|
|
2185
|
+
weights, avail, batch_size)
|
|
1949
2186
|
# Segregating initial values to fixed betas (Bf),
|
|
1950
2187
|
# random beta means (Br_b)
|
|
1951
2188
|
# for both non-transformed and transformed variables
|
|
@@ -2025,11 +2262,10 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
2025
2262
|
|
|
2026
2263
|
# _, gr_b, gr_w, pch = np.zeros((N, self.Kf)), np.zeros((N, self.Kr)), np.zeros((N, self.Kr)), [] # Batch data
|
|
2027
2264
|
|
|
2028
|
-
_ = np.zeros((N, self.Kf)) # CONCEPTUAL ERROR. WHY CREATE IF NO NAME?
|
|
2029
2265
|
gr_b = np.zeros((N, self.Kr))
|
|
2030
2266
|
gr_w = np.zeros((N, self.Kr))
|
|
2031
2267
|
g_all = np.zeros((N, len(betas)))
|
|
2032
|
-
|
|
2268
|
+
lik_sum = np.zeros(N, dtype=float)
|
|
2033
2269
|
for batch in range(n_batches):
|
|
2034
2270
|
# {
|
|
2035
2271
|
a = batch * batch_size
|
|
@@ -2051,6 +2287,8 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
2051
2287
|
|
|
2052
2288
|
# Thresholds to avoid divide by zero warnings
|
|
2053
2289
|
pch_batch = truncate_lower(pch_batch, min_comp_val)
|
|
2290
|
+
draw_count = b - a
|
|
2291
|
+
lik_sum += np.sum(pch_batch, axis=1)
|
|
2054
2292
|
|
|
2055
2293
|
# Observed probability minus predicted probability
|
|
2056
2294
|
ymp = y - p # (N, P, J, R)
|
|
@@ -2289,12 +2527,10 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
2289
2527
|
g, pch_batch = dev.to_cpu(g), dev.to_cpu(pch_batch)
|
|
2290
2528
|
# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
|
2291
2529
|
|
|
2292
|
-
|
|
2293
|
-
g_all += g
|
|
2530
|
+
g_all += g * draw_count
|
|
2294
2531
|
# }
|
|
2295
2532
|
|
|
2296
|
-
|
|
2297
|
-
lik = pch.mean(axis=1) # (N,)
|
|
2533
|
+
lik = lik_sum / float(self.n_draws) # (N,)
|
|
2298
2534
|
loglik = np.log(lik)
|
|
2299
2535
|
if weights is not None: loglik = loglik * weights
|
|
2300
2536
|
loglik = loglik.sum()
|
|
@@ -2322,7 +2558,7 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
2322
2558
|
g *= weights[:, 0][:, None] # Use only the first column and convert to column vector
|
|
2323
2559
|
# }
|
|
2324
2560
|
|
|
2325
|
-
g = np.sum(
|
|
2561
|
+
g = np.sum(g_all, axis=0) / float(self.n_draws) # (K, )
|
|
2326
2562
|
g = g - self.regularize_l1_grad(betas)
|
|
2327
2563
|
# L2 ridge penalty gradient: the objective subtracts
|
|
2328
2564
|
# reg_penalty * sum(betas^2) from the log-likelihood, so its
|
|
@@ -2671,14 +2907,10 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
2671
2907
|
|
|
2672
2908
|
# INITIALISE
|
|
2673
2909
|
XBf = np.zeros((self.N, self.P, self.J))
|
|
2674
|
-
XBr = np.zeros((self.N, self.P, self.J, self.batch_size)) # NOT USED?
|
|
2675
|
-
V = np.zeros((self.N, self.P, self.J, self.batch_size)) # NOT USED?
|
|
2676
2910
|
|
|
2677
2911
|
# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
|
2678
2912
|
if dev.using_gpu: # {
|
|
2679
2913
|
XBf = dev.convert_array_gpu(XBf)
|
|
2680
|
-
XBr = dev.convert_array_gpu(XBr)
|
|
2681
|
-
V = dev.convert_array_gpu(V)
|
|
2682
2914
|
# }
|
|
2683
2915
|
# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
|
2684
2916
|
|
|
@@ -2718,10 +2950,16 @@ class MixedLogit(DiscreteChoiceModel):
|
|
|
2718
2950
|
Br = self.draws_generator.apply_distribution(Br, self.rvdist)
|
|
2719
2951
|
self.Br = Br # save Br to use later
|
|
2720
2952
|
Xr = X[:, :, :, self.rvidx]
|
|
2721
|
-
|
|
2722
|
-
|
|
2953
|
+
# Build the utility directly. Keeping a separate XBr and a
|
|
2954
|
+
# preallocated V needlessly retains two (N, P, J, R) work arrays.
|
|
2955
|
+
V = dev.cust_einsum('npjk,nkr -> npjr', Xr, Br).astype(float)
|
|
2956
|
+
V += XBf[:, :, :, None]
|
|
2723
2957
|
else:
|
|
2724
2958
|
self.Br = Br_b[None, :, None]
|
|
2959
|
+
V = np.broadcast_to(
|
|
2960
|
+
XBf[:, :, :, None],
|
|
2961
|
+
(self.N, self.P, self.J, draws.shape[-1]),
|
|
2962
|
+
).copy()
|
|
2725
2963
|
|
|
2726
2964
|
# transformations for variables with fixed coeffs
|
|
2727
2965
|
if self.Kftrans != 0:
|
|
@@ -167,6 +167,7 @@ def build_case_alt_distances(
|
|
|
167
167
|
dest_lat_col: str = "dest_lat",
|
|
168
168
|
dest_lon_col: str = "dest_lon",
|
|
169
169
|
offset_col: str | None = "distance_offset_km",
|
|
170
|
+
member_zones_col: str | None = "member_suburbs",
|
|
170
171
|
) -> pd.DataFrame:
|
|
171
172
|
"""Build larch-style per-(case, alternative) distances.
|
|
172
173
|
|
|
@@ -204,7 +205,12 @@ def build_case_alt_distances(
|
|
|
204
205
|
raise KeyError(f"build_case_alt_distances: alternatives missing columns {missing}")
|
|
205
206
|
|
|
206
207
|
c = cases[req_cases].copy()
|
|
207
|
-
|
|
208
|
+
extra_alt_cols = []
|
|
209
|
+
if offset_col and offset_col in alternatives.columns:
|
|
210
|
+
extra_alt_cols.append(offset_col)
|
|
211
|
+
if member_zones_col and member_zones_col in alternatives.columns:
|
|
212
|
+
extra_alt_cols.append(member_zones_col)
|
|
213
|
+
a = alternatives[req_alts + extra_alt_cols].copy()
|
|
208
214
|
c["_oz"] = c[orig_zone_col].astype(str).str.strip().str.upper()
|
|
209
215
|
a["_dz"] = a[dest_zone_col].astype(str).str.strip().str.upper()
|
|
210
216
|
if offset_col and offset_col in a.columns:
|
|
@@ -225,24 +231,41 @@ def build_case_alt_distances(
|
|
|
225
231
|
# Skim index lookup (zone label -> matrix row).
|
|
226
232
|
skim_lookup: dict[str, int] = {}
|
|
227
233
|
if skim_zone_ids is not None and skim_dist_mat is not None:
|
|
228
|
-
skim_lookup = {z: i
|
|
234
|
+
skim_lookup = {str(z).strip().upper(): i
|
|
235
|
+
for i, z in enumerate(skim_zone_ids)}
|
|
229
236
|
o_idx = np.array([skim_lookup.get(z, -1) for z in c["_oz"].to_numpy()], dtype=int)
|
|
230
237
|
d_idx = np.array([skim_lookup.get(z, -1) for z in a["_dz"].to_numpy()], dtype=int)
|
|
231
238
|
|
|
232
239
|
skim_d = np.full((n_c, n_a), np.nan, dtype=float)
|
|
233
240
|
skim_t = np.full((n_c, n_a), np.nan, dtype=float)
|
|
234
|
-
if skim_dist_mat is not None and (o_idx >= 0).any()
|
|
241
|
+
if skim_dist_mat is not None and (o_idx >= 0).any():
|
|
235
242
|
sd = np.asarray(skim_dist_mat, dtype=float)
|
|
236
243
|
st = np.asarray(skim_time_mat, dtype=float) if skim_time_mat is not None else None
|
|
237
244
|
ok_c = np.where(o_idx >= 0)[0]
|
|
238
|
-
ok_a = np.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
ok_a = np.arange(n_a) if member_zones_col and member_zones_col in a.columns \
|
|
246
|
+
else np.where(d_idx >= 0)[0]
|
|
247
|
+
if member_zones_col and member_zones_col in a.columns:
|
|
248
|
+
for ci in ok_c:
|
|
249
|
+
for ai in ok_a:
|
|
250
|
+
members = [z.strip().upper() for z in str(
|
|
251
|
+
a.iloc[ai][member_zones_col] or "").split(",") if z.strip()]
|
|
252
|
+
member_idx = [skim_lookup[z] for z in members if z in skim_lookup]
|
|
253
|
+
if not member_idx:
|
|
254
|
+
member_idx = [d_idx[ai]]
|
|
255
|
+
values = sd[o_idx[ci], member_idx]
|
|
256
|
+
valid = np.isfinite(values) & (values > 0.0) & (values < float(max_km))
|
|
257
|
+
if valid.any():
|
|
258
|
+
pick = int(np.flatnonzero(valid)[np.argmin(values[valid])])
|
|
259
|
+
skim_d[ci, ai] = values[valid][pick]
|
|
260
|
+
if st is not None and st.shape == sd.shape:
|
|
261
|
+
skim_t[ci, ai] = st[o_idx[ci], member_idx[np.flatnonzero(valid)[pick]]]
|
|
262
|
+
else:
|
|
263
|
+
sub = sd[np.ix_(o_idx[ok_c], d_idx[ok_a])]
|
|
264
|
+
usable = (sub > 0.0) & (sub < float(max_km))
|
|
265
|
+
skim_d[np.ix_(ok_c, ok_a)] = np.where(usable, sub, np.nan)
|
|
266
|
+
if st is not None and st.shape == sd.shape:
|
|
267
|
+
sub_t = st[np.ix_(o_idx[ok_c], d_idx[ok_a])]
|
|
268
|
+
skim_t[np.ix_(ok_c, ok_a)] = np.where(usable, sub_t, np.nan)
|
|
246
269
|
|
|
247
270
|
# Haversine fallback from case-specific origin coords (broadcast).
|
|
248
271
|
with np.errstate(invalid="ignore"):
|
|
@@ -3,6 +3,7 @@ IMPLEMENTATION: MULTINOMIAL AND CONDITIONAL LOGIT MODEL
|
|
|
3
3
|
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
4
4
|
from sklearn.preprocessing import RobustScaler
|
|
5
5
|
from addicty import Dict
|
|
6
|
+
import os
|
|
6
7
|
|
|
7
8
|
"""
|
|
8
9
|
BACKGROUND - MULTINOMIAL LOGIT
|
|
@@ -1019,7 +1020,9 @@ class MultinomialLogit(DiscreteChoiceModel):
|
|
|
1019
1020
|
# owning model instead of capturing ``self``, so an entry never pins a
|
|
1020
1021
|
# dead model (and its X/y arrays) for the life of the process.
|
|
1021
1022
|
# ------------------------------------------------------------------
|
|
1022
|
-
_JIT_CACHE_MAXSIZE =
|
|
1023
|
+
_JIT_CACHE_MAXSIZE = max(
|
|
1024
|
+
0, int(os.environ.get("SEARCHLIBRIUM_MNL_JIT_CACHE_MAXSIZE", "4"))
|
|
1025
|
+
)
|
|
1023
1026
|
_jit_cache: "OrderedDict[tuple, tuple]" = OrderedDict()
|
|
1024
1027
|
|
|
1025
1028
|
@classmethod
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.263
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SearchLibrium
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.263
|
|
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.262
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/test_sapbil_vs_banditsa.py
RENAMED
|
File without changes
|
|
File without changes
|
{searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium/zero_inflated_ordered_probit.py
RENAMED
|
File without changes
|
|
File without changes
|
{searchlibrium-0.0.262 → searchlibrium-0.0.263}/src/SearchLibrium.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|