diff --git a/clevar/catalog/catalog.py b/clevar/catalog/catalog.py index b307f31..c74b602 100644 --- a/clevar/catalog/catalog.py +++ b/clevar/catalog/catalog.py @@ -699,7 +699,8 @@ def __getitem__(self, item): kwargs["mt_input"] = self.mt_input[item] if self.members is not None and isinstance(item, (list, np.ndarray)): cl_mask = np.zeros(self.size, dtype=bool) - cl_mask[item] = True + if len(item) > 0: + cl_mask[item] = True kwargs["members"] = self.members[cl_mask[self.members["ind_cl"]]] return self._getitem_base(DataType=ClCatalog, **kwargs) diff --git a/clevar/match/parent.py b/clevar/match/parent.py index f293217..9492c0b 100644 --- a/clevar/match/parent.py +++ b/clevar/match/parent.py @@ -75,7 +75,7 @@ def _set_unique_matching_function(self, preference, **kwargs): raise NotImplementedError def _init_matching_extra_cols(self, cat1, cat2, preference): - if preference == "shared_member_fraction": + if self.type == "Membership": cat1["mt_frac_self"] = np.zeros(cat1.size) cat2["mt_frac_other"] = np.zeros(cat2.size) if "mt_frac_other" not in cat1.colnames: @@ -136,7 +136,8 @@ def set_unique(*args): set_unique = self._set_unique_matching_function( preference, minimum_share_fraction=minimum_share_fraction ) - self._init_matching_extra_cols(cat1, cat2, preference) + + self._init_matching_extra_cols(cat1, cat2, preference) # Run matching print(f"Unique Matches ({cat1.name})") @@ -176,6 +177,40 @@ def match_from_config(self, cat1, cat2, match_config, cosmo=None): """ raise NotImplementedError + def _link_matched_pairs(self, cat1, cat2, ind1, ind2, shared_frac=None): + """ + Sets mt_* columns for a given matched pair. + + Parameters + ---------- + cat1: clevar.ClCatalog + ClCatalog 1 + cat2: clevar.ClCatalog + ClCatalog 2 + ind1: int + Index of the cluster from cat1 + ind2: int + Index of the cluster from cat2 corresponding to cat1[ind1] + shared_frac: float, None + Richness shared fraction of clusters. If None and membership + matching is used, it is computed from cat1.mt_input columns. + + Note + ---- + Also adds share fraction to preference!=shared_member_fraction cases + when membership matching is used. + """ + cat1["mt_self"][ind1] = cat2["id"][ind2] + cat2["mt_other"][ind2] = cat1["id"][ind1] + + if shared_frac is None and "share_mems" in cat1.mt_input.colnames: + id2 = cat2["id"][ind2] + shared_frac = cat1.mt_input["share_mems"][ind1][id2] / cat1.mt_input["nmem"][ind1] + + if shared_frac is not None: + cat1["mt_frac_self"][ind1] = shared_frac + cat2["mt_frac_other"][ind2] = shared_frac + def _match_mpref(self, cat1, ind1, cat2): """ Make the unique match by mass preference @@ -198,8 +233,7 @@ def _match_mpref(self, cat1, ind1, cat2): if len(inds2) > 0: for ind2 in self._sorted2[np.isin(self._sorted2, inds2)]: if cat2["mt_other"][ind2] is None: - cat1["mt_self"][ind1] = cat2["id"][ind2] - cat2["mt_other"][ind2] = cat1["id"][ind1] + self._link_matched_pairs(cat1, cat2, ind1, ind2) return True return False @@ -224,20 +258,24 @@ def _match_apref(self, cat1, ind1, cat2, match_pref): Tells if the cluster was matched """ inds2 = cat2.ids2inds(cat1["mt_multi_self"][ind1]) - dists = self._get_dist_mt(cat1[ind1], cat2[inds2], match_pref) + + dists = self._get_dist_mt(cat1.raw()[ind1], cat2.raw()[inds2], match_pref) sort_d = np.argsort(dists) for dist, ind2 in zip(dists[sort_d], inds2[sort_d]): i1_replace = cat1.id_dict[cat2["mt_other"][ind2]] if cat2["mt_other"][ind2] else None - if i1_replace is None: - cat1["mt_self"][ind1] = cat2["id"][ind2] - cat2["mt_other"][ind2] = cat1["id"][ind1] - return True - if dist < self._get_dist_mt(cat1[i1_replace], cat2[ind2], match_pref): - cat1["mt_self"][ind1] = cat2["id"][ind2] - cat2["mt_other"][ind2] = cat1["id"][ind1] - cat1["mt_self"][i1_replace] = None - self._match_apref(cat1, i1_replace, cat2, match_pref) + + _do_match = True + if i1_replace is not None: + _do_match = dist < self._get_dist_mt( + cat1.raw()[i1_replace], cat2.raw()[ind2], match_pref + ) + + if _do_match: + self._link_matched_pairs(cat1, cat2, ind1, ind2) + if i1_replace is not None: + cat1["mt_self"][i1_replace] = None return True + return False def _match_sharepref(self, cat1, ind1, cat2, minimum_share_fraction=0): @@ -282,14 +320,15 @@ def _match_sharepref(self, cat1, ind1, cat2, minimum_share_fraction=0): and cat1["mass"][ind1] <= cat1["mass"][i1_replace] ): return False - cat1["mt_self"][ind1] = id2 - cat1["mt_frac_self"][ind1] = shared_frac - cat2["mt_other"][ind2] = id1 - cat2["mt_frac_other"][ind2] = shared_frac + + self._link_matched_pairs(cat1, cat2, ind1, ind2, shared_frac) + + # try to rematch other candidate that lost the counterpart if i1_replace is not None: cat1["mt_self"][i1_replace] = None cat1["mt_frac_other"][i1_replace] = 0 self._match_sharepref(cat1, i1_replace, cat2, minimum_share_fraction) + return True return False diff --git a/clevar/version.py b/clevar/version.py index cb680df..37064ac 100644 --- a/clevar/version.py +++ b/clevar/version.py @@ -1,3 +1,3 @@ """Version of ClEvaR""" -__version__ = "0.18.1" +__version__ = "0.18.2" diff --git a/tests/test_match.py b/tests/test_match.py index 13de8db..32ceab0 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -273,6 +273,9 @@ def get_test_data_mem(): input1 = { "id": [f"CL{i}" for i in range(ncl)], "mass": [30 + i for i in range(ncl)], + "ra": np.arange(ncl), + "dec": np.zeros(ncl), + "z": np.ones(ncl), } input2 = {k: v[:-1] for k, v in input1.items()} # members @@ -459,6 +462,22 @@ def test_membership(): print(cat1.members) print(cat2.members) + # Test shared fracthion is computed with other preferences + for pref in ( + "more_massive", + "angular_proximity", + "redshift_proximity", + "shared_member_fraction", + ): + cat1, cat2 = get_test_data_mem() + mt.match_members(cat1.members, cat2.members, method="id") + mt.fill_shared_members(cat1, cat2) + mt.multiple(cat1, cat2) + mt.multiple(cat2, cat1) + mt.unique(cat1, cat2, pref) + assert cat1["mt_frac_self"].max() > 0 + assert cat2["mt_frac_other"].max() > 0 + def test_membership_cfg(CosmoClass): cat1, cat2 = get_test_data_mem()