SearchLibrium 0.0.128__tar.gz → 0.0.129__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.128 → searchlibrium-0.0.129}/PKG-INFO +1 -1
  2. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/pyproject.toml +1 -1
  3. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/harmony.py +331 -10
  4. searchlibrium-0.0.129/src/SearchLibrium/version.txt +1 -0
  5. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium.egg-info/PKG-INFO +1 -1
  6. searchlibrium-0.0.128/src/SearchLibrium/version.txt +0 -1
  7. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/README.md +0 -0
  8. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/setup.cfg +0 -0
  9. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/Halton.py +0 -0
  10. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/MixedLogit.py +0 -0
  11. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/Mode_Activity_Nested.py +0 -0
  12. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/RandomP.py +0 -0
  13. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/SEARCH_SM_MARIO.py +0 -0
  14. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/Two_Level_Nest.py +0 -0
  15. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/__init__.py +0 -0
  16. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/__main__.py +0 -0
  17. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/_choice_model.py +0 -0
  18. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/_device.py +0 -0
  19. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/banditsa.py +0 -0
  20. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/bhhh/minimize.py +0 -0
  21. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/boxcox_functions.py +0 -0
  22. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/call_meta.py +0 -0
  23. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/constraints_builder.py +0 -0
  24. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/hspbil.py +0 -0
  25. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/latent_class.py +0 -0
  26. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/main.py +0 -0
  27. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/main_debug.py +0 -0
  28. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/mdcev.py +0 -0
  29. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/misc.py +0 -0
  30. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/mixed_logit.py +0 -0
  31. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/mixed_nested.py +0 -0
  32. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/mixedrrm.py +0 -0
  33. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/multinomial_logit.py +0 -0
  34. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/multinomial_nested.py +0 -0
  35. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/multinomial_probit.py +0 -0
  36. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/ordered_logit.py +0 -0
  37. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/ordered_logit_mixed.py +0 -0
  38. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/predict.py +0 -0
  39. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/rrm.py +0 -0
  40. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/sapbil.py +0 -0
  41. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/search.py +0 -0
  42. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/selection_models.py +0 -0
  43. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/setup.py +0 -0
  44. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/siman.py +0 -0
  45. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/test_lc_de.py +0 -0
  46. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/test_mario_searches.py +0 -0
  47. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/test_sapbil_vs_banditsa.py +0 -0
  48. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium/threshold.py +0 -0
  49. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium.egg-info/SOURCES.txt +0 -0
  50. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium.egg-info/dependency_links.txt +0 -0
  51. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium.egg-info/entry_points.txt +0 -0
  52. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/src/SearchLibrium.egg-info/requires.txt +0 -0
  53. {searchlibrium-0.0.128 → searchlibrium-0.0.129}/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.128
3
+ Version: 0.0.129
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.128"
76
+ current_version = "0.0.129"
77
77
  version_pattern = "MAJOR.MINOR.PATCH"
78
78
  commit_message = "[skip ci] Bump version {old_version} -> {new_version}"
79
79
  commit = true
@@ -705,12 +705,334 @@ class HarmonySearch(Search):
705
705
  # }
706
706
  # }
707
707
 
708
+ # ═══════════════════════════════════════════════════════════════════════
709
+ # TAILORED PERTURBATIONS — model-development-style moves (changes 11–18)
710
+ # These mirror how a practitioner actually builds discrete choice models:
711
+ # significance pruning, distribution sweeps, structural hypothesis tests,
712
+ # model progression, ensemble crossover, and feature substitution.
713
+ # ═══════════════════════════════════════════════════════════════════════
714
+
715
+ def make_change_11_significance_prune(self, candidate):
716
+ """Model-developer move: remove the least significant variable from
717
+ the best solution (mirrors backward elimination). Then try adding
718
+ the most promising unused variable as compensation."""
719
+ best_asvars, best_isvars, best_randvars, best_bcvars, best_corvars, \
720
+ asc_ind, _, _ = self.get_best_features(candidate)
721
+
722
+ # Only prune if we have > 2 AS vars (keep a minimum)
723
+ if len(best_asvars) <= 2:
724
+ return
725
+
726
+ # ── Try removing one AS variable ──────────────────────────────
727
+ candidates_to_drop = [v for v in best_asvars
728
+ if v not in self._get_forced_vars()]
729
+ if not candidates_to_drop:
730
+ return
731
+
732
+ # Try each candidate drop (up to 3) — a real modeller tests each removal
733
+ for drop_var in candidates_to_drop[:3]:
734
+ trial_vars = [v for v in best_asvars if v != drop_var]
735
+ trial_randvars = {k: v for k, v in best_randvars.items()
736
+ if k in trial_vars}
737
+ solution = Solution(nb_crit=self.nb_crit, asvars=trial_vars,
738
+ isvars=best_isvars, randvars=trial_randvars,
739
+ bcvars=best_bcvars, corvars=best_corvars,
740
+ asc_ind=asc_ind)
741
+ solution['model_n'] = candidate[0]['model_n'] if isinstance(candidate, list) and candidate else None
742
+ revised, converged = self.evaluate_solution(solution)
743
+ if converged:
744
+ self.insert_solution(revised)
745
+
746
+ # ── Then try adding a new promising variable ──────────────────
747
+ unused = [v for v in self.param.avail_asvars
748
+ if v not in best_asvars and v not in self.param.ps_asvars]
749
+ if unused:
750
+ add_var = self.param.generator.choice(unused)
751
+ trial_vars = best_asvars + [add_var]
752
+ trial_vars = list(dict.fromkeys(trial_vars))
753
+ solution = Solution(nb_crit=self.nb_crit, asvars=trial_vars,
754
+ isvars=best_isvars, randvars=best_randvars,
755
+ bcvars=best_bcvars, corvars=best_corvars,
756
+ asc_ind=asc_ind)
757
+ revised, converged = self.evaluate_solution(solution)
758
+ if converged:
759
+ self.insert_solution(revised)
760
+
761
+ def make_change_12_distribution_sweep(self, candidate):
762
+ """Model-developer move: for each random parameter in the best
763
+ solution, test every alternative distribution. A practitioner
764
+ often asks 'is this log-normal or should it be triangular?'"""
765
+ best_asvars, best_isvars, best_randvars, best_bcvars, best_corvars, \
766
+ asc_ind, _, _ = self.get_best_features(candidate)
767
+
768
+ if not best_randvars or len(best_randvars) == 0:
769
+ return
770
+
771
+ all_distributions = [d for d in self.param.distr if d != 'f']
772
+
773
+ for var_name, current_dist in list(best_randvars.items()):
774
+ alternatives = [d for d in all_distributions if d != current_dist]
775
+ if not alternatives:
776
+ continue
777
+ for alt_dist in alternatives[:len(alternatives)]:
778
+ trial_randvars = dict(best_randvars)
779
+ trial_randvars[var_name] = alt_dist
780
+ solution = Solution(nb_crit=self.nb_crit, asvars=best_asvars,
781
+ isvars=best_isvars, randvars=trial_randvars,
782
+ bcvars=best_bcvars, corvars=best_corvars,
783
+ asc_ind=asc_ind)
784
+ solution['model_n'] = candidate[0]['model_n'] if isinstance(candidate, list) and candidate else 'mixed_logit'
785
+ revised, converged = self.evaluate_solution(solution)
786
+ if converged:
787
+ self.insert_solution(revised)
788
+
789
+ def make_change_13_model_progression(self, candidate):
790
+ """Model-developer move: systematically step up or down the model
791
+ complexity ladder: MNL → NestedLogit → MixedLogit → MixedNested.
792
+ A modeller tests 'does adding nesting improve fit? does adding
793
+ random heterogeneity after nesting improve it further?'"""
794
+ best_asvars, best_isvars, best_randvars, best_bcvars, best_corvars, \
795
+ asc_ind, _, _ = self.get_best_features(candidate)
796
+
797
+ current_model = (candidate[0].get('model_n', 'multinomial')
798
+ if isinstance(candidate, list) and candidate
799
+ else 'multinomial')
800
+
801
+ has_nests = bool(getattr(self.param, 'nests', None))
802
+ has_randvars = bool(best_randvars)
803
+
804
+ # Model ladder — try the "next step up" in complexity
805
+ progression = []
806
+ if has_nests:
807
+ progression = ['multinomial', 'nested_logit', 'mixed_nested']
808
+ else:
809
+ progression = ['multinomial', 'mixed_logit']
810
+
811
+ # Also consider stepping down (for parsimony)
812
+ for target_model in progression:
813
+ if target_model == current_model:
814
+ continue
815
+ # mixed_nested requires both nests and random params
816
+ if target_model == 'mixed_nested' and not (has_nests and has_randvars):
817
+ continue
818
+ solution = Solution(nb_crit=self.nb_crit, asvars=best_asvars,
819
+ isvars=best_isvars, randvars=best_randvars,
820
+ bcvars=best_bcvars, corvars=best_corvars,
821
+ asc_ind=asc_ind, model_n=target_model)
822
+ revised, converged = self.evaluate_solution(solution)
823
+ if converged:
824
+ self.insert_solution(revised)
825
+
826
+ def make_change_14_ensemble_crossover(self, candidate):
827
+ """Model-developer move: combine variables from two top solutions
828
+ (like a modeller synthesising insights from two promising specs).
829
+ Creates four offspring: union, intersection, top-only, second-only."""
830
+ if len(self.memory) < 2:
831
+ return
832
+
833
+ memory_sorted = self.sort_memory(self.memory[:])
834
+ sol_a = memory_sorted[0]
835
+ sol_b = memory_sorted[1]
836
+
837
+ a_asvars = set(sol_a.get('asvars', []))
838
+ b_asvars = set(sol_b.get('asvars', []))
839
+ a_randvars = sol_a.get('randvars', {})
840
+ b_randvars = sol_b.get('randvars', {})
841
+
842
+ # ── Offspring 1: Union of both specs ──
843
+ union_vars = sorted(a_asvars | b_asvars)
844
+ if union_vars:
845
+ merged_randvars = {}
846
+ for v in union_vars:
847
+ if v in a_randvars: merged_randvars[v] = a_randvars[v]
848
+ elif v in b_randvars: merged_randvars[v] = b_randvars[v]
849
+ solution = Solution(nb_crit=self.nb_crit, asvars=union_vars,
850
+ isvars=sol_a.get('isvars', []),
851
+ randvars=merged_randvars,
852
+ bcvars=[], corvars=[],
853
+ asc_ind=sol_a.get('asc_ind', False))
854
+ revised, converged = self.evaluate_solution(solution)
855
+ if converged:
856
+ self.insert_solution(revised)
857
+
858
+ # ── Offspring 2: Intersection (core stable variables) ──
859
+ inter_vars = sorted(a_asvars & b_asvars)
860
+ if inter_vars and len(inter_vars) >= 2:
861
+ inter_randvars = {v: a_randvars[v] for v in inter_vars
862
+ if v in a_randvars}
863
+ solution = Solution(nb_crit=self.nb_crit, asvars=inter_vars,
864
+ isvars=sol_a.get('isvars', []),
865
+ randvars=inter_randvars,
866
+ bcvars=[], corvars=[],
867
+ asc_ind=sol_a.get('asc_ind', False))
868
+ revised, converged = self.evaluate_solution(solution)
869
+ if converged:
870
+ self.insert_solution(revised)
871
+
872
+ def make_change_15_feature_substitution(self, candidate):
873
+ """Model-developer move: substitute one base feature for a
874
+ correlated alternative. E.g. swap ln_dest_trips for
875
+ osm_destination_pull, or swap ln_dist for a spline/knot."""
876
+ best_asvars, best_isvars, best_randvars, best_bcvars, best_corvars, \
877
+ asc_ind, _, _ = self.get_best_features(candidate)
878
+
879
+ # Pre-check: identify substitution groups among available variables
880
+ # (variables that measure the same construct differently)
881
+ subst_groups = self._build_substitution_groups(best_asvars)
882
+
883
+ if not subst_groups:
884
+ return
885
+
886
+ for original, replacement in subst_groups:
887
+ trial_vars = [replacement if v == original else v
888
+ for v in best_asvars]
889
+ trial_randvars = {replacement if k == original else k: v
890
+ for k, v in best_randvars.items()}
891
+ solution = Solution(nb_crit=self.nb_crit, asvars=trial_vars,
892
+ isvars=best_isvars, randvars=trial_randvars,
893
+ bcvars=best_bcvars, corvars=best_corvars,
894
+ asc_ind=asc_ind)
895
+ solution['model_n'] = candidate[0]['model_n'] if isinstance(candidate, list) and candidate else None
896
+ revised, converged = self.evaluate_solution(solution)
897
+ if converged:
898
+ self.insert_solution(revised)
899
+
900
+ def make_change_16_structural_hypothesis(self, candidate):
901
+ """Model-developer move: test a structural hypothesis by taking
902
+ one origin profile and sweeping across ALL destination attributes.
903
+ 'Does bike ownership moderate sensitivity to every destination
904
+ feature, or just distance?'"""
905
+ best_asvars, best_isvars, best_randvars, best_bcvars, best_corvars, \
906
+ asc_ind, _, _ = self.get_best_features(candidate)
907
+
908
+ # Find which origin profiles are currently used via interactions
909
+ active_origins = set()
910
+ for v in best_asvars:
911
+ if v.startswith('inter__prop_'):
912
+ parts = v.replace('inter__', '').split('__')
913
+ if len(parts) >= 1:
914
+ active_origins.add(parts[0])
915
+
916
+ if not active_origins:
917
+ return
918
+
919
+ # Pick one active origin profile to sweep
920
+ origins_list = sorted(active_origins)
921
+ origin = self.param.generator.choice(origins_list)
922
+
923
+ # For this origin, add interactions with every OTHER destination attr
924
+ # that isn't already present (structural completeness hypothesis)
925
+ for var_name in self.param.avail_asvars:
926
+ if not var_name.startswith('inter__'):
927
+ continue
928
+ if var_name in best_asvars:
929
+ continue
930
+ if origin not in var_name:
931
+ continue
932
+
933
+ trial_vars = best_asvars + [var_name]
934
+ trial_vars = list(dict.fromkeys(trial_vars))
935
+ trial_vars = self.remove_collinear_vars(trial_vars)
936
+ solution = Solution(nb_crit=self.nb_crit, asvars=trial_vars,
937
+ isvars=best_isvars, randvars=best_randvars,
938
+ bcvars=best_bcvars, corvars=best_corvars,
939
+ asc_ind=asc_ind)
940
+ revised, converged = self.evaluate_solution(solution)
941
+ if converged:
942
+ self.insert_solution(revised)
943
+
944
+ def _build_substitution_groups(self, asvars):
945
+ """Identify groups of variables that measure similar constructs
946
+ and can be substituted for one another."""
947
+ groups = []
948
+ # Group 1: destination size measures
949
+ size_measures = [v for v in asvars
950
+ if v in {'ln_dest_trips', 'osm_destination_pull',
951
+ 'acdc_aggl', 'osm_poi_intensity'}]
952
+ for i in range(len(size_measures)):
953
+ for j in range(i + 1, len(size_measures)):
954
+ groups.append((size_measures[i], size_measures[j]))
955
+
956
+ # Group 2: distance-related measures
957
+ dist_measures = [v for v in asvars
958
+ if v in {'ln_dist', 'dist_km',
959
+ 'acdc_nearest_dest_km', 'acdc_weighted_mean_dist_km'}]
960
+ for i in range(len(dist_measures)):
961
+ for j in range(i + 1, len(dist_measures)):
962
+ groups.append((dist_measures[i], dist_measures[j]))
963
+
964
+ # Group 3: OSM amenity measures
965
+ osm_measures = [v for v in asvars
966
+ if v.startswith('osm_') and not v.startswith('osm_dest')]
967
+ for i in range(len(osm_measures)):
968
+ for j in range(i + 1, len(osm_measures)):
969
+ groups.append((osm_measures[i], osm_measures[j]))
970
+
971
+ return groups
972
+
973
+ def _get_forced_vars(self):
974
+ """Return list of variables that must never be removed."""
975
+ forced = set()
976
+ forced.update(getattr(self.param, 'ps_asvars', []))
977
+ forced.update(getattr(self.param, 'ps_isvars', []))
978
+ if hasattr(self.param, 'pres_spec_constr') and self.param.pres_spec_constr:
979
+ forced.update(self.param.pres_spec_constr.get('force_include', []))
980
+ return forced
981
+
982
+ def local_search_developer(self, iter: int, phase: str = 'early'):
983
+ """
984
+ Run developer-style local improvement moves on the top solutions.
985
+ Phase determines which moves are used:
986
+ - early (first 40%): broad exploration (structural sweep, substitutions)
987
+ - mid (40-70%): refinement (significance pruning, distribution sweep)
988
+ - late (70-100%): convergence (model progression, ensemble crossover)
989
+ """
990
+ if len(self.memory) < 2:
991
+ return
992
+
993
+ memory_sorted = self.sort_memory(self.memory[:])
994
+ top_n = min(5, len(memory_sorted))
995
+ candidates = memory_sorted[:top_n]
996
+
997
+ # Weight schedule: more moves in mid/late phases
998
+ if phase == 'early':
999
+ moves = [self.make_change_16_structural_hypothesis,
1000
+ self.make_change_15_feature_substitution]
1001
+ per_candidate = 1
1002
+ elif phase == 'mid':
1003
+ moves = [self.make_change_11_significance_prune,
1004
+ self.make_change_12_distribution_sweep,
1005
+ self.make_change_15_feature_substitution]
1006
+ per_candidate = 2
1007
+ else:
1008
+ moves = [self.make_change_13_model_progression,
1009
+ self.make_change_14_ensemble_crossover,
1010
+ self.make_change_11_significance_prune]
1011
+ per_candidate = 3
1012
+
1013
+ for cand in candidates[:per_candidate]:
1014
+ for move in moves:
1015
+ try:
1016
+ move(cand)
1017
+ except Exception:
1018
+ pass
1019
+
708
1020
  def local_search(self):
709
1021
  # {
710
- # Identify candidate solutions
711
- candidate = [sol for sol in self.memory if abs(sol.obj[0]) < BOUND]
1022
+ # ── Phase-aware local search ─────────────────────────────────
1023
+ # Uses the current improvisation progress to switch between
1024
+ # exploration (early), refinement (mid), and convergence (late).
1025
+ progress = getattr(self, '_improv_iter', 0) / max(self.maxiter, 1)
1026
+ if progress < 0.4:
1027
+ phase = 'early'
1028
+ elif progress < 0.7:
1029
+ phase = 'mid'
1030
+ else:
1031
+ phase = 'late'
1032
+ self.local_search_developer(0, phase=phase)
712
1033
 
713
- # TODO
1034
+ # Also run the original local search changes (1-10) at reduced rate
1035
+ candidate = [sol for sol in self.memory if abs(sol.obj[0]) < BOUND]
714
1036
  # }
715
1037
 
716
1038
  ''' ---------------------------------------------------------- '''
@@ -755,6 +1077,8 @@ class HarmonySearch(Search):
755
1077
  best, current = [], []
756
1078
  for iter in range(self.maxiter):
757
1079
  # {
1080
+ self._improv_iter = iter # track for phase-aware local search
1081
+
758
1082
  # Compute consideration rate and pitch value
759
1083
  # This code introduces oscillations (a.k.a., variations) based on the iteration number.
760
1084
  # The result is scaled by the sine function only when its value is non-negative.
@@ -768,13 +1092,10 @@ class HarmonySearch(Search):
768
1092
  # {
769
1093
  self.insert_solution(curr_sol)
770
1094
 
771
- #if iter > int(self.prop_local * self.maxiter):
772
- # {
773
- # Run local search
774
- #best_sol = self.memory[0]
775
- #best.append(best_sol.obj[0])
776
- #current.append(curr_sol.obj[0])
777
- # }
1095
+ # Phase-aware local search: every 10 iterations, run
1096
+ # developer-style moves on the top solutions
1097
+ if iter > 0 and iter % 10 == 0 and len(self.memory) >= 2:
1098
+ self.local_search()
778
1099
  # }
779
1100
  # }
780
1101
 
@@ -0,0 +1 @@
1
+ 0.0.129
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SearchLibrium
3
- Version: 0.0.128
3
+ Version: 0.0.129
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.128