SearchLibrium 0.0.138__tar.gz → 0.0.140__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 (53) hide show
  1. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/PKG-INFO +1 -1
  2. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/pyproject.toml +1 -1
  3. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/MixedLogit.py +62 -6
  4. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/_choice_model.py +2 -2
  5. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/latent_class.py +31 -9
  6. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/mixed_logit.py +2 -2
  7. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/mixedrrm.py +1 -1
  8. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/multinomial_logit.py +1 -1
  9. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/search.py +37 -5
  10. searchlibrium-0.0.140/src/SearchLibrium/version.txt +1 -0
  11. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium.egg-info/PKG-INFO +1 -1
  12. searchlibrium-0.0.138/src/SearchLibrium/version.txt +0 -1
  13. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/README.md +0 -0
  14. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/setup.cfg +0 -0
  15. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/Halton.py +0 -0
  16. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/Mode_Activity_Nested.py +0 -0
  17. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/RandomP.py +0 -0
  18. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/SEARCH_SM_MARIO.py +0 -0
  19. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/Two_Level_Nest.py +0 -0
  20. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/__init__.py +0 -0
  21. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/__main__.py +0 -0
  22. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/_device.py +0 -0
  23. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/banditsa.py +0 -0
  24. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/bhhh/minimize.py +0 -0
  25. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/boxcox_functions.py +0 -0
  26. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/call_meta.py +0 -0
  27. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/constraints_builder.py +0 -0
  28. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/harmony.py +0 -0
  29. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/hspbil.py +0 -0
  30. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/main.py +0 -0
  31. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/main_debug.py +0 -0
  32. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/mdcev.py +0 -0
  33. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/misc.py +0 -0
  34. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/mixed_nested.py +0 -0
  35. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/multinomial_nested.py +0 -0
  36. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/multinomial_probit.py +0 -0
  37. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/ordered_logit.py +0 -0
  38. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/ordered_logit_mixed.py +0 -0
  39. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/predict.py +0 -0
  40. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/rrm.py +0 -0
  41. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/sapbil.py +0 -0
  42. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/selection_models.py +0 -0
  43. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/setup.py +0 -0
  44. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/siman.py +0 -0
  45. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/test_lc_de.py +0 -0
  46. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/test_mario_searches.py +0 -0
  47. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/test_sapbil_vs_banditsa.py +0 -0
  48. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium/threshold.py +0 -0
  49. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium.egg-info/SOURCES.txt +0 -0
  50. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium.egg-info/dependency_links.txt +0 -0
  51. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium.egg-info/entry_points.txt +0 -0
  52. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium.egg-info/requires.txt +0 -0
  53. {searchlibrium-0.0.138 → searchlibrium-0.0.140}/src/SearchLibrium.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.138
3
+ Version: 0.0.140
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>
@@ -73,7 +73,7 @@ Documentation = "https://pypi.org/project/SearchLibrium/"
73
73
  realpython = "SearchLibrium.__main__:main"
74
74
 
75
75
  [tool.bumpver]
76
- current_version = "0.0.138"
76
+ current_version = "0.0.140"
77
77
  version_pattern = "MAJOR.MINOR.PATCH"
78
78
  commit_message = "[skip ci] Bump version {old_version} -> {new_version}"
79
79
  commit = true
@@ -134,13 +134,14 @@ class MixedLogit(DiscreteChoiceModel):
134
134
  def setup(self, X, y, varnames=None, alts=None, isvars=None, transvars=None,
135
135
  transformation="boxcox", ids=None, weights=None, avail=None,
136
136
  randvars=None, panels=None, base_alt=None, fit_intercept=False,
137
- init_coeff=None, maxiter=1000, correlated_vars=None,
137
+ init_coeff=None, maxiter=2000, correlated_vars=None,
138
138
  n_draws=1000, halton=True, minimise_func=None,
139
- batch_size=None, halton_opts=None, ftol=1e-12,
139
+ batch_size=None, halton_opts=None, ftol=1e-6,
140
140
  gtol=1e-6, return_hess=True, return_grad=True, method="slsqp",
141
141
  save_fitted_params=True, mnl_init=True,
142
142
  de_init=False, de_popsize=4, de_maxiter=3, de_tol=0.5,
143
- de_polish=False, l1_penalty=0.0):
143
+ de_polish=False, l1_penalty=0.1, l2_penalty=0.5,
144
+ sd_penalty=0.001):
144
145
  # {
145
146
  self.fit_intercept = fit_intercept
146
147
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -186,6 +187,8 @@ class MixedLogit(DiscreteChoiceModel):
186
187
  self.total_fun_eval = 0
187
188
  self.method = method.lower() if hasattr(method, 'lower') else method
188
189
  self.l1_penalty = float(l1_penalty)
190
+ self.l2_penalty = float(l2_penalty)
191
+ self.sd_penalty = float(sd_penalty)
189
192
  self.jac = self.return_grad # scipy optimize parameter
190
193
  self.n_draws = n_draws
191
194
  self.batch_size = min(n_draws, batch_size) if batch_size is not None else n_draws
@@ -586,11 +589,64 @@ class MixedLogit(DiscreteChoiceModel):
586
589
  args = (self.X, self.y, self.panel_info, draws, drawstrans, self.weights, self.avail, self.batch_size)
587
590
  bounds = bnds if self.method == "L-BFGS-B" else None
588
591
  options = {'gtol': self.gtol, 'maxiter': self.maxiter, 'disp': False}
589
- result = minimise_func(self.get_loglik_gradient, betas, jac=self.jac, method=self.method,
592
+
593
+ # ── SD penalty wrapper: regularise SD params away from exactly zero ──
594
+ # When an SD parameter hits zero, the gradient vanishes and BFGS gets stuck.
595
+ # A small L2 penalty that activates below a threshold keeps SDs bounded
596
+ # away from zero without affecting the well-identified estimates.
597
+ if self.sd_penalty > 0 and self.Kbw > 0:
598
+ # SD param indices: end of beta vector, before Kftrans and Krtrans
599
+ sd_start = self.Kf + self.Kr + self.Kchol
600
+ sd_end = sd_start + self.Kbw
601
+ _orig_obj = self.get_loglik_gradient
602
+
603
+ def _penalised_obj(betas, *obj_args):
604
+ f, g = _orig_obj(betas, *obj_args)
605
+ for i in range(sd_start, sd_end):
606
+ if i >= len(betas):
607
+ break
608
+ sd_val = betas[i]
609
+ if sd_val < 0.05:
610
+ p = self.sd_penalty * (sd_val - 0.05) ** 2
611
+ f += p
612
+ if g is not None and len(g) > i:
613
+ g[i] += 2.0 * self.sd_penalty * (sd_val - 0.05)
614
+ return f, g
615
+
616
+ obj_fn = _penalised_obj
617
+ else:
618
+ obj_fn = self.get_loglik_gradient
619
+
620
+ result = minimise_func(obj_fn, betas, jac=self.jac, method=self.method,
590
621
  args=args, tol=self.ftol, bounds=bounds, options=options)
591
- print(f"[MXL] Minimization completed: success={result.get('success', None)}, fun={result.get('fun', float('nan')):.6g}, nit={result.get('nit', '?')}")
622
+ print(f"[MXL] Primary minimization ({self.method}): success={result.get('success', None)}, fun={result.get('fun', float('nan')):.6g}, nit={result.get('nit', '?')}")
592
623
  if 'x' in result:
593
- print(f"[MXL] Minimization final betas first_values={np.asarray(result['x'])[:min(8, len(result['x']))]!r}")
624
+ print(f"[MXL] Primary betas first_values={np.asarray(result['x'])[:min(8, len(result['x']))]!r}")
625
+
626
+ # ── BFGS polish: after non-BFGS methods (SLSQP, Powell, etc.),
627
+ # run a quick BFGS to get the inverse Hessian for standard errors.
628
+ if (self.method not in ('bfgs', 'l-bfgs-b')
629
+ and 'x' in result
630
+ and result.get('success', False)):
631
+ try:
632
+ polish_opts = {'gtol': self.gtol * 10, 'maxiter': min(self.maxiter // 5, 200), 'disp': False}
633
+ polish_bnds = bnds # re-use same bounds
634
+ # Use original objective (no SD penalty) for clean Hessian
635
+ polish_result = minimise_func(
636
+ self.get_loglik_gradient,
637
+ np.asarray(result['x']).copy(),
638
+ jac=True, method='L-BFGS-B',
639
+ args=args, tol=self.ftol,
640
+ bounds=polish_bnds, options=polish_opts,
641
+ )
642
+ if polish_result.get('fun', float('inf')) < result.get('fun', float('inf')) + 0.1:
643
+ print(f"[MXL] BFGS polish: success={polish_result.get('success')}, "
644
+ f"fun={polish_result.get('fun', float('nan')):.6g}")
645
+ result = polish_result
646
+ else:
647
+ print(f"[MXL] BFGS polish skipped (worse objective)")
648
+ except Exception as e:
649
+ print(f"[MXL] BFGS polish failed: {e}")
594
650
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595
651
 
596
652
  if hasattr(self, 'method') and self.method == "L-BFGS-B": # {
@@ -147,7 +147,7 @@ class DiscreteChoiceModel(ABC):
147
147
  self.reset_attributes()
148
148
  self.fit_intercept = False
149
149
  self.reg_penalty = 0.00 # L2 penalty strength (ridge)
150
- self.l1_penalty = 0.00 # L1 penalty strength (lasso)
150
+ self.l1_penalty = 0.1 # L1 penalty strength (lasso)
151
151
  self.pval_penalty = 5
152
152
  logging.info(f'pval penalty set to {self.pval_penalty}')
153
153
  # NOTE: The reg_penalty value is tricky to define. If too high, convergence is restricted.
@@ -167,7 +167,7 @@ class DiscreteChoiceModel(ABC):
167
167
  self.converged = False
168
168
  self.return_grad, self.return_hess, self.fit_intercept = True, True, False
169
169
  self.scipy_optimisation = True
170
- self.method, self.transformation = "bfgs", "boxcox"
170
+ self.method, self.transformation = "slsqp", "boxcox"
171
171
 
172
172
  self.trans_func = None # NEW. CHECK VALIDITY!
173
173
  self.varnames, self.isvars, self.transvars = None, None, None
@@ -57,6 +57,10 @@ class LatentClassMixedLogit:
57
57
  self.descr = "LC-MXL"
58
58
  self.coeff_est = None
59
59
  self.coeff_names = None
60
+ self.stderr = None
61
+ self.zvalues = None
62
+ self.pvalues = None
63
+ self.pvalues_member = None
60
64
  self.class_betas = None
61
65
  self.class_probs = None
62
66
  self.class_gammas = None
@@ -1454,16 +1458,24 @@ class LatentClassMixedLogit:
1454
1458
  params = np.concatenate([phi_vals, beta_flat])
1455
1459
  n_gamma = 0
1456
1460
 
1457
- # ── JAX autograd Hessian → covariance (strict: no numerical fallback) ──
1461
+ # ── JAX autograd Hessian → covariance (numerical fallback) ──
1458
1462
  H_a = self._autograd_hessian(params)
1459
- if H_a is None or not np.isfinite(H_a).all():
1460
- raise RuntimeError(
1461
- "JAX autograd Hessian unavailable — cannot compute standard errors. "
1462
- "Ensure JAX is installed, the model was fit with _jax=True (default), "
1463
- "and all latent classes share the same variable set."
1464
- )
1465
- info = H_a # hessian(-negloglik) = -hessian(loglik) = observed info
1466
- se_method = "autograd-hessian"
1463
+ if H_a is not None and np.isfinite(H_a).all():
1464
+ info = H_a # hessian(-negloglik) = -hessian(loglik) = observed info
1465
+ se_method = "autograd-hessian"
1466
+ else:
1467
+ # JAX unavailable (or classes with differing variable sets):
1468
+ # fall back to a finite-difference Hessian so searches always
1469
+ # get standard errors and p-values.
1470
+ H_num = self._numerical_hessian(params, eps=eps)
1471
+ if not np.isfinite(H_num).all():
1472
+ raise RuntimeError(
1473
+ "Hessian could not be computed (autograd unavailable and "
1474
+ "finite-difference Hessian is non-finite) — standard errors "
1475
+ "are not available for this model."
1476
+ )
1477
+ info = -H_num # observed info = -hessian(loglik)
1478
+ se_method = "numerical-hessian (finite differences)"
1467
1479
 
1468
1480
  cond_number = np.nan
1469
1481
  cov = None
@@ -1592,6 +1604,16 @@ class LatentClassMixedLogit:
1592
1604
  gamma_names.append(f"gamma_class_{c + 1}_{v}")
1593
1605
  param_names += gamma_names
1594
1606
 
1607
+ # ── Standard attributes aligned with coeff_est / coeff_names ──────
1608
+ # The specification search reads model.stderr / model.pvalues to drive
1609
+ # significance-based refinement and PBIL probability updates, so the
1610
+ # class-beta block (which is what coeff_est holds) must be exposed here.
1611
+ n_beta = beta_flat.size
1612
+ self.stderr = se[n_phi:n_phi + n_beta]
1613
+ self.zvalues = t_stats[n_phi:n_phi + n_beta]
1614
+ self.pvalues = p_values[n_phi:n_phi + n_beta]
1615
+ self.pvalues_member = gamma_p_values
1616
+
1595
1617
  return {
1596
1618
  "params": params,
1597
1619
  "se": se,
@@ -261,9 +261,9 @@ class MixedLogit(DiscreteChoiceModel):
261
261
  init_coeff=None, maxiter=2000, correlated_vars=None,
262
262
  n_draws=1000, halton=True, minimise_func=None,
263
263
  batch_size=None, halton_opts=None, ftol=1e-6,
264
- gtol=1e-6, return_hess=True, return_grad=True, method="bfgs",
264
+ gtol=1e-6, return_hess=True, return_grad=True, method="slsqp",
265
265
  save_fitted_params=True, mnl_init=True, fixed_thetas=None,
266
- l1_penalty=0.0):
266
+ l1_penalty=0.1, l2_penalty=0.5):
267
267
  # {
268
268
 
269
269
  # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -58,6 +58,6 @@ class MixedRandomRegret(RandomRegret, MixedLogit):
58
58
  return -loglik
59
59
 
60
60
  # Optimize the negative log-likelihood
61
- self.result = minimize(neg_log_likelihood, self.beta, method='BFGS', tol=1e-6)
61
+ self.result = minimize(neg_log_likelihood, self.beta, method='SLSQP', tol=1e-6)
62
62
  self.beta = self.result.x
63
63
  self.post_process()
@@ -246,7 +246,7 @@ class MultinomialLogit(DiscreteChoiceModel):
246
246
  transformation="boxcox", ids=None, weights=None, avail=None,
247
247
  base_alt=None, fit_intercept=False, init_coeff=None, maxiter=2000,
248
248
  ftol=1e-6, gtol=1e-6, return_grad=True, return_hess=True,
249
- method="bfgs", scipy_optimisation=True, l2_penalty=0.0, l1_penalty=0.0):
249
+ method="slsqp", scipy_optimisation=True, l2_penalty=0.5, l1_penalty=0.1):
250
250
  # {
251
251
 
252
252
  self.l2_penalty = float(l2_penalty)
@@ -592,6 +592,7 @@ class Parameters:
592
592
  maxiter=2000, n_draws=1000, p_val=0.05, chosen_alts_test=None,
593
593
  test_weight_var=None, allow_random=False, allow_bcvars=False, allow_corvars=False, models = None,
594
594
  de_init=False, de_popsize=4, de_maxiter=3, de_tol=0.5, de_polish=False,
595
+ sd_penalty=0.001,
595
596
  intercept_opts=None, base_alt=None, val_share=0.25, grad = True, hess = False, *args, **kwargs):
596
597
 
597
598
 
@@ -683,9 +684,10 @@ class Parameters:
683
684
  self.de_maxiter = de_maxiter
684
685
  self.de_tol = de_tol
685
686
  self.de_polish = de_polish
687
+ self.sd_penalty = sd_penalty
686
688
 
687
689
  # ── Regularisation (primarily for latent class) ──────────────
688
- self.l1_penalty = kwargs.get('l1_penalty', 0.0)
690
+ self.l1_penalty = kwargs.get('l1_penalty', 0.1)
689
691
  self.l2_penalty = kwargs.get('l2_penalty', 0.5)
690
692
 
691
693
  self.intercept_opts = intercept_opts
@@ -787,7 +789,7 @@ class Parameters:
787
789
  acceptable_keys = [
788
790
  'LCR', 'verbose', 'asc_ind', 'nests', 'lambdas', 'varnest',
789
791
  '_jax', 'all_sig', 'de_init', 'de_popsize', 'de_maxiter',
790
- 'de_tol', 'de_polish', 'halton_opts'
792
+ 'de_tol', 'de_polish', 'sd_penalty', 'halton_opts'
791
793
  ]
792
794
 
793
795
  # Assign all kwargs to self, but only if the key is in the acceptable_keys list
@@ -1001,6 +1003,27 @@ class Solution(UserDict):
1001
1003
  setattr(self, key, value)
1002
1004
  # }
1003
1005
 
1006
+ def __deepcopy__(self, memo):
1007
+ # {
1008
+ # Fitted model objects stored under 'model' hold module/JAX references
1009
+ # that cannot be deep-copied (TypeError: cannot pickle 'module' object).
1010
+ # The fitted model is read-only after estimation, so the copy shares it
1011
+ # by reference; everything else is deep-copied as usual.
1012
+ cls = self.__class__
1013
+ new = cls.__new__(cls)
1014
+ memo[id(self)] = new
1015
+ new.data = {}
1016
+ for k, v in self.data.items():
1017
+ new.data[k] = v if k == 'model' else copy.deepcopy(v, memo)
1018
+ for k, v in self.__dict__.items():
1019
+ if k == 'data':
1020
+ continue
1021
+ try:
1022
+ setattr(new, k, copy.deepcopy(v, memo))
1023
+ except TypeError:
1024
+ setattr(new, k, v) # share unpicklable attributes by reference
1025
+ return new
1026
+ # }
1004
1027
 
1005
1028
  def __eq__(self, other):
1006
1029
  """
@@ -3985,7 +4008,8 @@ class Search():
3985
4008
  de_popsize=getattr(self.param, 'de_popsize', 4),
3986
4009
  de_maxiter=getattr(self.param, 'de_maxiter', 3),
3987
4010
  de_tol=getattr(self.param, 'de_tol', 0.5),
3988
- de_polish=getattr(self.param, 'de_polish', False))
4011
+ de_polish=getattr(self.param, 'de_polish', False),
4012
+ sd_penalty=getattr(self.param, 'sd_penalty', 0.001))
3989
4013
  model.fit()
3990
4014
 
3991
4015
  return model
@@ -4017,8 +4041,8 @@ class Search():
4017
4041
  random_state=seed if seed is not None else 0,
4018
4042
  optimise_membership=optimise_membership,
4019
4043
  membership_maxiter=100,
4020
- l1_penalty=getattr(self.param, 'l1_penalty', 0.0),
4021
- l2_penalty=getattr(self.param, 'l2_penalty', 0.0),
4044
+ l1_penalty=getattr(self.param, 'l1_penalty', 0.1),
4045
+ l2_penalty=getattr(self.param, 'l2_penalty', 0.5),
4022
4046
  )
4023
4047
 
4024
4048
  membership_vars = None
@@ -4267,6 +4291,14 @@ class Search():
4267
4291
  sol['model_n'] = 'latent_class'
4268
4292
  converged = model.converged
4269
4293
 
4294
+ # Standard errors / p-values: needed by significance-based refinement
4295
+ # and PBIL updates. A failed Hessian must not abort the search.
4296
+ if converged and getattr(model, 'pvalues', None) is None:
4297
+ try:
4298
+ model.compute_standard_errors()
4299
+ except Exception as exc:
4300
+ print(f"[LC] standard errors unavailable for this candidate: {exc}")
4301
+
4270
4302
  aic = getattr(model, 'aic', float('inf'))
4271
4303
  bic = getattr(model, 'bic', float('inf'))
4272
4304
  loglik = getattr(model, 'loglik', float('-inf'))
@@ -0,0 +1 @@
1
+ 0.0.140
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.138
3
+ Version: 0.0.140
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.138