Skip to content

Commit 24b960d

Browse files
committed
compute mixing matrices from field definitions
1 parent a1f3eba commit 24b960d

File tree

5 files changed

+181
-105
lines changed

5 files changed

+181
-105
lines changed

examples/example.ipynb

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@
452452
"* A position field (`\"P\"`) for angular clustering and galaxy-galaxy lensing;\n",
453453
"* A shear field (`\"G\"`) for cosmic shear and galaxy-galaxy lensing.\n",
454454
"\n",
455-
"We also specify that the shear field should flip the sign of the \"G2\" column by adding a minus sign."
455+
"We also specify that the shear field should flip the sign of the \"G2\" column by adding a minus sign.\n",
456+
"\n",
457+
"Finally, we define the optional names of the weight functions (`\"V\"`, `\"W\"`) of the fields. These will be used further down below to compute mixing matrices for the fields."
456458
]
457459
},
458460
{
@@ -465,8 +467,8 @@
465467
"lonlat = ('RIGHT_ASCENSION', 'DECLINATION')\n",
466468
"\n",
467469
"fields = {\n",
468-
" 'P': Positions(*lonlat),\n",
469-
" 'G': Shears(*lonlat, 'G1', '-G2', 'WEIGHT'),\n",
470+
" 'P': Positions(*lonlat, weight=\"V\"),\n",
471+
" 'G': Shears(*lonlat, 'G1', '-G2', 'WEIGHT', weight=\"W\"),\n",
470472
"}"
471473
]
472474
},
@@ -520,7 +522,7 @@
520522
{
521523
"data": {
522524
"application/vnd.jupyter.widget-view+json": {
523-
"model_id": "2a0ea2d6fc02466fb00059925c44032e",
525+
"model_id": "63d6558897214c0fbad71ed81e70f7db",
524526
"version_major": 2,
525527
"version_minor": 0
526528
},
@@ -534,13 +536,13 @@
534536
{
535537
"data": {
536538
"text/html": [
537-
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">/Users/ntessore/code/heracles-ec/heracles/heracles/fields.py:247: UserWarning: position and visibility maps have \n",
539+
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">/Users/ntessore/code/heracles-ec/heracles/heracles/fields.py:254: UserWarning: position and visibility maps have \n",
538540
"different NSIDE\n",
539541
" warnings.warn(\"position and visibility maps have different NSIDE\")\n",
540542
"</pre>\n"
541543
],
542544
"text/plain": [
543-
"/Users/ntessore/code/heracles-ec/heracles/heracles/fields.py:247: UserWarning: position and visibility maps have \n",
545+
"/Users/ntessore/code/heracles-ec/heracles/heracles/fields.py:254: UserWarning: position and visibility maps have \n",
544546
"different NSIDE\n",
545547
" warnings.warn(\"position and visibility maps have different NSIDE\")\n"
546548
]
@@ -690,7 +692,7 @@
690692
{
691693
"data": {
692694
"application/vnd.jupyter.widget-view+json": {
693-
"model_id": "73b9269235fb47b9b79d26f456f8962e",
695+
"model_id": "ed443fdc7af948e1b3dfeb1e6740b8a9",
694696
"version_major": 2,
695697
"version_minor": 0
696698
},
@@ -923,7 +925,7 @@
923925
{
924926
"data": {
925927
"application/vnd.jupyter.widget-view+json": {
926-
"model_id": "7eea7c123c304cfeb5f2150ab6db9012",
928+
"model_id": "2d17b05fc35f4e95aadb24b0e1fe989c",
927929
"version_major": 2,
928930
"version_minor": 0
929931
},
@@ -1027,7 +1029,7 @@
10271029
{
10281030
"data": {
10291031
"application/vnd.jupyter.widget-view+json": {
1030-
"model_id": "32abfabf091a441b9c0b716b3d476aae",
1032+
"model_id": "1cccc3fe4f304b4fa8b25ecc5da7972f",
10311033
"version_major": 2,
10321034
"version_minor": 0
10331035
},
@@ -1114,22 +1116,22 @@
11141116
}
11151117
],
11161118
"source": [
1117-
"mms = mixing_matrices(cls_mm, l1max=lmax, l2max=lmax, l3max=lmax_mm)"
1119+
"mms = mixing_matrices(fields, cls_mm, l1max=lmax, l2max=lmax, l3max=lmax_mm)"
11181120
]
11191121
},
11201122
{
11211123
"cell_type": "markdown",
11221124
"metadata": {},
11231125
"source": [
1124-
"The mixing matrices are returned in a toc dict with slightly different names: They use a two letter code that indicates which angular power spectra are mixed by a given matrix:\n",
1126+
"The mixing matrices are returned in a toc dict with familiar names: They correspond to the _output_ angular power spectra after mixing by a given matrix:\n",
11251127
"\n",
1126-
"* `00` for `(P, P) → (P, P)`;\n",
1127-
"* `0+` for `(P, E) → (P, E)` and `(P, B) → (P, B)`;\n",
1128-
"* `++` for `(E, E) → (E, E)` and `(B, B) → (B, B)`;\n",
1129-
"* `--` for `(E, E) → (B, B)` and `(B, B) → (E, E)`;\n",
1130-
"* `+-` for `(E, B) → (E, B)`.\n",
1128+
"* `P, P` for `(P, P) → (P, P)`;\n",
1129+
"* `P, G_E` for `(P, G_E) → (P, G_E)`, as well as `(P, G_B) → (P, G_B)`;\n",
1130+
"* `G_E, G_E` for `(G_E, G_E) → (G_E, G_E)`, as well as `(G_B, G_B) → (G_B, G_B)`;\n",
1131+
"* `G_B, G_B` for `(G_E, G_E) → (G_B, G_B)`, as well as `(G_B, G_B) → (G_E, G_E)`;\n",
1132+
"* `G_E, G_B` for `(G_E, G_B) → (G_E, G_B)`.\n",
11311133
"\n",
1132-
"For more details, see the paper by Brown, Castro & Taylor (2005). Note that the `+-` mixing matrix here is not the $W^{+-}$ matrix from said paper."
1134+
"For more details, see e.g. the paper by Brown, Castro & Taylor (2005)."
11331135
]
11341136
},
11351137
{
@@ -1140,21 +1142,19 @@
11401142
{
11411143
"data": {
11421144
"text/plain": [
1143-
"[('00', 0, 0),\n",
1144-
" ('0+', 0, 0),\n",
1145-
" ('00', 0, 1),\n",
1146-
" ('0+', 0, 1),\n",
1147-
" ('00', 0, 2),\n",
1148-
" ('0+', 0, 2),\n",
1149-
" ('00', 0, 3),\n",
1150-
" ('0+', 0, 3),\n",
1151-
" ('00', 0, 4),\n",
1152-
" ('0+', 0, 4),\n",
1153-
" ('00', 0, 5),\n",
1154-
" ('0+', 0, 5),\n",
1155-
" ('00', 0, 6),\n",
1156-
" ('0+', 0, 6),\n",
1157-
" ('00', 0, 7)]"
1145+
"[('P', 'P', 0, 0),\n",
1146+
" ('P', 'G_E', 0, 0),\n",
1147+
" ('P', 'P', 0, 1),\n",
1148+
" ('P', 'G_E', 0, 1),\n",
1149+
" ('P', 'P', 0, 2),\n",
1150+
" ('P', 'G_E', 0, 2),\n",
1151+
" '...',\n",
1152+
" ('G_E', 'G_B', 8, 9),\n",
1153+
" ('P', 'P', 9, 9),\n",
1154+
" ('P', 'G_E', 9, 9),\n",
1155+
" ('G_E', 'G_E', 9, 9),\n",
1156+
" ('G_B', 'G_B', 9, 9),\n",
1157+
" ('G_E', 'G_B', 9, 9)]"
11581158
]
11591159
},
11601160
"execution_count": 37,
@@ -1163,7 +1163,7 @@
11631163
}
11641164
],
11651165
"source": [
1166-
"list(mms.keys())[:15]"
1166+
"list(mms.keys())[:6] + ['...'] + list(mms.keys())[-6:]"
11671167
]
11681168
},
11691169
{
@@ -1234,7 +1234,7 @@
12341234
}
12351235
],
12361236
"source": [
1237-
"plt.imshow(mms['++', i, i], cmap='binary',\n",
1237+
"plt.imshow(mms['G_E', 'G_E', i, i], cmap='binary',\n",
12381238
" norm=mpl.colors.LogNorm(vmin=1e-7))\n",
12391239
"plt.colorbar(pad=0.025, fraction=0.0465)\n",
12401240
"plt.show()"
@@ -1569,15 +1569,15 @@
15691569
" cl = camb_cls[f'W{i+1}xW{i+j+1}']\n",
15701570
"\n",
15711571
" if s1.source_type == 'counts' and s2.source_type == 'counts':\n",
1572-
" theory_cls['P', 'P', i1, i2] = mms['00', i1, i2] @ cl\n",
1572+
" theory_cls['P', 'P', i1, i2] = mms['P', 'P', i1, i2] @ cl\n",
15731573
" elif s1.source_type == 'lensing' and s2.source_type == 'lensing':\n",
1574-
" theory_cls['G_E', 'G_E', i1, i2] = mms['++', i1, i2] @ (cl*fl**2)\n",
1575-
" theory_cls['G_B', 'G_B', i1, i2] = mms['--', i1, i2] @ (cl*fl**2)\n",
1574+
" theory_cls['G_E', 'G_E', i1, i2] = mms['G_E', 'G_E', i1, i2] @ (cl*fl**2)\n",
1575+
" theory_cls['G_B', 'G_B', i1, i2] = mms['G_B', 'G_B', i1, i2] @ (cl*fl**2)\n",
15761576
" elif s1.source_type == 'counts' and s2.source_type == 'lensing':\n",
1577-
" theory_cls['P', 'G_E', i1, i2] = mms['0+', i1, i2] @ (cl*fl)\n",
1577+
" theory_cls['P', 'G_E', i1, i2] = mms['P', 'G_E', i1, i2] @ (cl*fl)\n",
15781578
" theory_cls['P', 'G_B', i1, i2] = np.zeros_like(cl)\n",
15791579
" elif s1.source_type == 'lensing' and s2.source_type == 'counts':\n",
1580-
" theory_cls['P', 'G_E', i2, i1] = mms['0+', i2, i1] @ (cl*fl)\n",
1580+
" theory_cls['P', 'G_E', i2, i1] = mms['P', 'G_E', i2, i1] @ (cl*fl)\n",
15811581
" theory_cls['P', 'G_B', i2, i1] = np.zeros_like(cl)"
15821582
]
15831583
},

heracles/fields.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ def __init_subclass__(cls, *, spin: int | None = None) -> None:
8282
break
8383
cls.__ncol = (ncol - nopt, ncol)
8484

85-
def __init__(self, *columns: str) -> None:
85+
def __init__(self, *columns: str, weight: str | None = None) -> None:
8686
"""Initialise the field."""
8787
super().__init__()
8888
self.__columns = self._init_columns(*columns) if columns else None
89+
self.__weight = weight
8990
self._metadata: dict[str, Any] = {}
9091
if (spin := self.__spin) is not None:
9192
self._metadata["spin"] = spin
@@ -140,6 +141,11 @@ def spin(self) -> int:
140141
raise ValueError(msg)
141142
return spin
142143

144+
@property
145+
def weight(self) -> str | None:
146+
"""Name of the weight function for this field."""
147+
return self.__weight
148+
143149
@abstractmethod
144150
async def __call__(
145151
self,
@@ -187,9 +193,10 @@ def __init__(
187193
*columns: str,
188194
overdensity: bool = True,
189195
nbar: float | None = None,
196+
weight: str | None = None,
190197
) -> None:
191198
"""Create a position field."""
192-
super().__init__(*columns)
199+
super().__init__(*columns, weight=weight)
193200
self.__overdensity = overdensity
194201
self.__nbar = nbar
195202

heracles/twopoint.py

Lines changed: 63 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import healpy as hp
2727
import numpy as np
28-
from convolvecl import mixmat, mixmat_eb
2928

3029
from .core import TocDict, toc_match, update_metadata
3130

@@ -182,65 +181,80 @@ def debias_cls(cls, bias=None, *, inplace=False):
182181
return out
183182

184183

185-
def mixing_matrices(cls, *, l1max=None, l2max=None, l3max=None):
186-
"""compute mixing matrices from a set of cls"""
184+
def mixing_matrices(fields, cls, *, l1max=None, l2max=None, l3max=None, out=None):
185+
"""compute mixing matrices for fields from a set of cls"""
187186

188-
logger.info("computing two-point mixing matrices for %d cl(s)", len(cls))
189-
t = time.monotonic()
187+
from convolvecl import mixmat, mixmat_eb
188+
189+
# output dictionary if not provided
190+
if out is None:
191+
out = TocDict()
190192

191-
logger.info("using L1MAX = %s, L2MAX = %s, L3MAX = %s", l1max, l2max, l3max)
193+
# inverse mapping of weights to fields
194+
weights = {}
195+
for key, field in fields.items():
196+
if field.weight is not None:
197+
if field.weight not in weights:
198+
weights[field.weight] = {}
199+
weights[field.weight][key] = field
192200

193-
# set of computed mixing matrices
194-
mms = TocDict()
201+
# keep track of combinations that have been done already
202+
done = set()
195203

196204
# go through the toc dict of cls and compute mixing matrices
197-
# which mixing matrix is computed depends on the combination of V/W maps
205+
# which mixing matrix is computed depends on the whts mapping
198206
for (k1, k2, i1, i2), cl in cls.items():
207+
# if the weights are not named then skip this cl
208+
try:
209+
fields1 = weights[k1]
210+
fields2 = weights[k2]
211+
except KeyError:
212+
continue
213+
214+
# deal with structured cl arrays
199215
if cl.dtype.names is not None:
200216
cl = cl["CL"]
201-
if k1 == "V" and k2 == "V":
202-
logger.info("computing 00 mixing matrix for bins %s, %s", i1, i2)
203-
w00 = mixmat(cl, l1max=l1max, l2max=l2max, l3max=l3max)
204-
mms["00", i1, i2] = w00
205-
elif k1 == "V" and k2 == "W":
206-
logger.info("computing 0+ mixing matrix for bins %s, %s", i1, i2)
207-
w0p = mixmat(cl, l1max=l1max, l2max=l2max, l3max=l3max, spin=(0, 2))
208-
mms["0+", i1, i2] = w0p
209-
elif k1 == "W" and k2 == "V":
210-
logger.info("computing 0+ mixing matrix for bins %s, %s", i2, i1)
211-
w0p = mixmat(cl, l1max=l1max, l2max=l2max, l3max=l3max, spin=(2, 0))
212-
mms["0+", i2, i1] = w0p
213-
elif k1 == "W" and k2 == "W":
214-
logger.info("computing ++, --, +- mixing matrices for bins %s, %s", i1, i2)
215-
wpp, wmm, wpm = mixmat_eb(
216-
cl,
217-
l1max=l1max,
218-
l2max=l2max,
219-
l3max=l3max,
220-
spin=(2, 2),
221-
)
222-
mms["++", i1, i2] = wpp
223-
mms["--", i1, i2] = wmm
224-
mms["+-", i1, i2] = wpm
225-
else:
226-
logger.warning(
227-
"computing unknown %s x %s mixing matrix for bins %s, %s",
228-
k1,
229-
k2,
230-
i1,
231-
i2,
232-
)
233-
w = mixmat(cl, l1max=l1max, l2max=l2max, l3max=l3max)
234-
mms[f"{k1}{k2}", i1, i2] = w
235217

236-
logger.info(
237-
"computed %d mm(s) in %s",
238-
len(mms),
239-
timedelta(seconds=(time.monotonic() - t)),
240-
)
218+
# compute mixing matrices for all fields of this weight combination
219+
for f1, f2 in product(fields1, fields2):
220+
# check if this combination has been done already
221+
if (f1, f2, i1, i2) in done or (f2, f1, i2, i1) in done:
222+
continue
223+
# otherwise, mark it as done
224+
done.add((f1, f2, i1, i2))
225+
226+
# get spins of fields
227+
spin1, spin2 = fields1[f1].spin, fields2[f2].spin
228+
229+
# if any spin is zero, then there is no need for E/B decomposition
230+
if spin1 == 0 or spin2 == 0:
231+
mm = mixmat(
232+
cl,
233+
l1max=l1max,
234+
l2max=l2max,
235+
l3max=l3max,
236+
spin=(spin1, spin2),
237+
)
238+
name1 = f1 if spin1 == 0 else f"{f1}_E"
239+
name2 = f2 if spin2 == 0 else f"{f2}_E"
240+
out[name1, name2, i1, i2] = mm
241+
del mm
242+
else:
243+
# E/B decomposition for mixing matrix
244+
mm_ee, mm_bb, mm_eb = mixmat_eb(
245+
cl,
246+
l1max=l1max,
247+
l2max=l2max,
248+
l3max=l3max,
249+
spin=(spin1, spin2),
250+
)
251+
out[f"{f1}_E", f"{f2}_E", i1, i2] = mm_ee
252+
out[f"{f1}_B", f"{f2}_B", i1, i2] = mm_bb
253+
out[f"{f1}_E", f"{f2}_B", i1, i2] = mm_eb
254+
del mm_ee, mm_bb, mm_eb
241255

242256
# return the toc dict of mixing matrices
243-
return mms
257+
return out
244258

245259

246260
def bin2pt(arr, bins, name, *, weights=None):

tests/test_fields.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,10 @@ async def __call__(self):
115115
with pytest.raises(ValueError, match="accepts 2 to 3 columns"):
116116
TestField("lon")
117117

118-
f = TestField("lon", "lat")
118+
f = TestField("lon", "lat", weight="W")
119119

120120
assert f.columns == ("lon", "lat", None)
121+
assert f.weight == "W"
121122

122123

123124
def test_visibility(mapper, vmap):

0 commit comments

Comments
 (0)