Skip to content

Commit 949a54d

Browse files
Merge branch 'main' into JP-4151
2 parents 618096f + bf7d548 commit 949a54d

File tree

9 files changed

+164
-125
lines changed

9 files changed

+164
-125
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Please contact the [JWST Help Desk](https://jwsthelp.stsci.edu) for installation
3131

3232
The easiest way to install the latest `jwst` release into a fresh virtualenv or conda environment is
3333

34-
pip install jwst==1.20.0
34+
pip install jwst==1.20.1
3535

3636
### Detailed Installation
3737

changes/9914.associations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved association diff error messages.

changes/9939.source_catalog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a bug where fluxes were not being converted from Mjy/sr to Jy.
2+
Fix a bug where fluxes included background flux when they shouldn't.

jwst/associations/lib/diff.py

Lines changed: 116 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ def err_types(self):
115115
return err_types
116116

117117
def __str__(self):
118-
message = ["Following diffs found:\n"]
119-
for diff in self:
120-
message.extend(["\n****\n", str(diff), "\n"])
118+
message = ["Following diffs found:\n"] + [
119+
f"\n**** {diff.__class__.__name__}\n{str(diff)}\n" for diff in self
120+
]
121121
return "".join(message)
122122

123123

@@ -192,43 +192,58 @@ def compare_asn_lists(left_asns, right_asns):
192192
if left_duplicates:
193193
try:
194194
check_duplicate_products(
195-
left_asns, product_names=left_product_names, dup_names=left_duplicates
195+
left_asns, product_names=left_product_names, dup_names=left_duplicates, pfx="Left "
196196
)
197197
except MultiDiffError as dup_errors:
198198
diffs.extend(dup_errors)
199199
if right_duplicates:
200200
try:
201201
check_duplicate_products(
202-
right_asns, product_names=right_product_names, dup_names=right_duplicates
202+
right_asns,
203+
product_names=right_product_names,
204+
dup_names=right_duplicates,
205+
pfx="Right ",
203206
)
204207
except MultiDiffError as dup_errors:
205208
diffs.extend(dup_errors)
206209

207210
# Ensure that the product name lists are the same.
211+
errmsg_not_in = []
208212
left_not_right = sorted(left_product_names - right_product_names)
213+
if left_not_right:
214+
errmsg_not_in.append(
215+
"Products in left but not right:\n " + "\n ".join(left_not_right)
216+
)
209217
right_not_left = sorted(right_product_names - left_product_names)
210-
if left_not_right or right_not_left:
211-
left_msg = "Products in left but not right:\n " + "\n ".join(left_not_right)
212-
right_msg = "Products in right but not left:\n " + "\n ".join(right_not_left)
213-
diffs.append(DifferentProductSetsError(f"{left_msg}\n{right_msg}\n"))
218+
if right_not_left:
219+
errmsg_not_in.append(
220+
"Products in right but not left:\n " + "\n ".join(right_not_left)
221+
)
222+
if errmsg_not_in:
223+
diffs.append(DifferentProductSetsError("\n".join(errmsg_not_in)))
214224

215225
# Compare like product associations
226+
skip_top_level_checks = False
216227
left_asns_by_product = {asn["products"][0]["name"]: asn for asn in left_asns}
217228
right_asns_by_product = {asn["products"][0]["name"]: asn for asn in right_asns}
218-
for product_name in left_product_names:
229+
for product_name in sorted(left_product_names):
230+
if product_name not in right_asns_by_product:
231+
continue
219232
try:
220-
compare_asns(left_asns_by_product[product_name], right_asns_by_product[product_name])
233+
compare_asns(
234+
left_asns_by_product[product_name],
235+
right_asns_by_product[product_name],
236+
skip_top_level_checks=skip_top_level_checks,
237+
)
221238
except MultiDiffError as compare_diffs:
222239
diffs.extend(compare_diffs)
223-
except KeyError:
224-
# Most likely due to a previous error. Ignore
225-
pass
240+
skip_top_level_checks = True
226241

227242
if diffs:
228-
raise diffs
243+
raise diffs from None
229244

230245

231-
def compare_asns(left, right):
246+
def compare_asns(left, right, skip_top_level_checks=False):
232247
"""
233248
Compare two associations.
234249
@@ -240,30 +255,15 @@ def compare_asns(left, right):
240255
left, right : dict
241256
Two, individual, associations to compare.
242257
243-
Raises
244-
------
245-
MultiDiffError
246-
If there is a difference.
247-
"""
248-
_compare_asns(left, right)
249-
250-
251-
def _compare_asns(left, right):
252-
"""
253-
Compare two associations.
254-
255-
This comparison will include metadata such as
256-
``asn_type`` and membership.
257-
258-
Parameters
259-
----------
260-
left, right : dict
261-
Two, individual, associations to compare.
258+
skip_top_level_checks : bool
259+
Skip checks for ``asn_type`` and ``asn_id``.
260+
Set to `True` when this is called in a loop for subsequent products
261+
to avoid duplicate exceptions.
262262
263263
Raises
264264
------
265265
MultiDiffError
266-
If there are differences. The message will contain
266+
If there are differences, the message will contain
267267
all the differences.
268268
269269
Notes
@@ -288,18 +288,17 @@ def _compare_asns(left, right):
288288
diffs = MultiDiffError()
289289

290290
# Assert that the same result type is the same.
291-
if left["asn_type"] != right["asn_type"]:
291+
if (not skip_top_level_checks) and (left["asn_type"] != right["asn_type"]):
292292
diffs.append(
293-
TypeMismatchError("Type mismatch {} != {}".format(left["asn_type"], right["asn_type"]))
293+
TypeMismatchError(f"ASN type mismatch: {left['asn_type']} != {right['asn_type']}")
294294
)
295295

296296
# Assert that the level of association candidate is the same.
297297
# Cannot guarantee value, but that the 'a'/'c'/'o' levels are similar.
298-
if left["asn_id"][0] != right["asn_id"][0]:
298+
if (not skip_top_level_checks) and (left["asn_id"][0] != right["asn_id"][0]):
299299
diffs.append(
300300
CandidateLevelError(
301-
f"Candidate level mismatch left '{left['asn_id'][0]}' != "
302-
f"right '{right['asn_id'][0]}'"
301+
f"Candidate level mismatch: {left['asn_id'][0]} != {right['asn_id'][0]}"
303302
)
304303
)
305304

@@ -310,7 +309,7 @@ def _compare_asns(left, right):
310309
diffs.extend(compare_diffs)
311310

312311
if diffs:
313-
raise diffs
312+
raise diffs from None
314313

315314

316315
def compare_membership(left, right):
@@ -339,9 +338,9 @@ def compare_membership(left, right):
339338
)
340339
)
341340

342-
for _left_idx, left_product in enumerate(products_left):
341+
for left_product in products_left:
343342
left_product_name = components(left_product["name"])
344-
for _right_idx, right_product in enumerate(products_right):
343+
for right_product in products_right:
345344
if components(right_product["name"]) != left_product_name:
346345
continue
347346
try:
@@ -357,7 +356,7 @@ def compare_membership(left, right):
357356
diffs.append(DifferentProductSetsError(f"Right has {len(products_right)} left over"))
358357

359358
if diffs:
360-
raise diffs
359+
raise diffs from None
361360

362361

363362
def compare_product_membership(left, right, strict_expname=True):
@@ -423,76 +422,95 @@ def munge_expname(expname):
423422
except DuplicateMembersError as dup_member_error:
424423
diffs.append(dup_member_error)
425424

426-
if len(right["members"]) != len(left["members"]):
425+
len_left = len(left["members"])
426+
len_right = len(right["members"])
427+
if len_right != len_left:
427428
diffs.append(
428429
MemberLengthDifferenceError(
429-
"Product Member length differs:"
430-
" Left Product {left_product_name} len {left_len} != "
431-
" Right Product {right_product_name} len {right_len}"
432-
"".format(
433-
left_product_name=left["name"],
434-
left_len=len(left["members"]),
435-
right_product_name=right["name"],
436-
right_len=len(right["members"]),
437-
)
430+
"Product Member length differs:\n"
431+
f" Left Product : {left['name']} ({len_left})\n"
432+
f" Right Product: {right['name']} ({len_right})"
438433
)
439434
)
435+
err_pfx = ""
436+
else:
437+
good_pfx = (
438+
"Comparing these products:\n"
439+
f" Left Product : {left['name']} ({len_left})\n"
440+
f" Right Product: {right['name']} ({len_right})\n\n"
441+
)
442+
err_pfx = good_pfx
440443

441444
members_right = copy(right["members"])
442445
left_unaccounted_members = []
443446
for left_member in left["members"]:
447+
left_expname = left_member["expname"]
448+
left_exptype = left_member["exptype"]
444449
for right_member in members_right:
445-
if munge_expname(left_member["expname"]) != munge_expname(right_member["expname"]):
450+
right_expname = right_member["expname"]
451+
right_exptype = right_member["exptype"]
452+
if munge_expname(left_expname) != munge_expname(right_expname):
446453
continue
447454

448-
if left_member["exptype"] != right_member["exptype"]:
455+
if left_exptype != right_exptype:
449456
diffs.append(
450457
MemberMismatchError(
451-
"Left {left_expname}:{left_exptype}"
452-
" != Right {right_expname}:{right_exptype}"
453-
"".format(
454-
left_expname=left_member["expname"],
455-
left_exptype=left_member["exptype"],
456-
right_expname=right_member["expname"],
457-
right_exptype=right_member["exptype"],
458-
)
458+
f"{err_pfx}"
459+
f" Left {left_expname}: {left_exptype}\n"
460+
f" Right {right_expname}: {right_exptype}"
459461
)
460462
)
463+
err_pfx = ""
461464

462465
members_right.remove(right_member)
463466
break
464467
else:
465468
left_unaccounted_members.append(left_member)
466469

467-
if len(left_unaccounted_members):
468-
diffs.append(
469-
UnaccountedMembersError(
470-
f"Left has {len(left_unaccounted_members)} unaccounted members. "
471-
f"Members are {left_unaccounted_members}"
472-
)
470+
def pprint_mems(unaccounted_members, indent=8):
471+
s = []
472+
spc = " " * indent
473+
for m in unaccounted_members:
474+
s.append(f"{spc}{m['expname']} ({m['exptype']})")
475+
return "\n".join(s)
476+
477+
errmsg = []
478+
unaccounted_left = len(left_unaccounted_members)
479+
if unaccounted_left:
480+
errmsg.append(
481+
f" Left has {unaccounted_left} unaccounted members.\n"
482+
f"{pprint_mems(left_unaccounted_members, indent=8)}"
473483
)
474484

475-
if len(members_right) != 0:
476-
diffs.append(
477-
UnaccountedMembersError(
478-
f"Right has {len(members_right)} unaccounted members. Members are {members_right}"
479-
)
485+
unaccounted_right = len(members_right)
486+
if unaccounted_right != 0:
487+
errmsg.append(
488+
f" Right has {unaccounted_right} unaccounted members.\n"
489+
f"{pprint_mems(members_right, indent=8)}"
480490
)
481491

492+
if errmsg:
493+
diffs.append(UnaccountedMembersError(err_pfx + "\n".join(errmsg)))
494+
err_pfx = ""
495+
482496
# Check if one is a subset of the other.
483497
err_types = diffs.err_types
484-
is_subset = (
498+
if (
485499
(len(diffs) == 2)
486500
and (MemberLengthDifferenceError in err_types)
487501
and (UnaccountedMembersError in err_types)
488-
)
489-
if is_subset:
490-
diffs = MultiDiffError(
491-
[SubsetError(f"Products are subsets: {left['name']} {right['name']}")]
492-
)
502+
): # is_subset
503+
if unaccounted_left > 0:
504+
errmsg = f"{err_pfx} Right is a subset of left"
505+
err_pfx = ""
506+
else: # unaccounted_right > 0
507+
errmsg = f"{err_pfx} Left is a subset of right"
508+
# Uncomment if more error handling added below:
509+
# err_pfx = ""
510+
diffs.append(SubsetError(errmsg))
493511

494512
if diffs:
495-
raise diffs
513+
raise diffs from None
496514

497515

498516
def check_duplicate_members(product):
@@ -511,19 +529,16 @@ def check_duplicate_members(product):
511529
MultiDiffError
512530
If the product has duplicate members.
513531
"""
514-
seen = set()
515-
dups = []
516-
for expname in [member["expname"] for member in product["members"]]:
517-
if expname in seen:
518-
dups.append(expname)
519-
else:
520-
seen.add(expname)
532+
prod_count = Counter([member["expname"] for member in product["members"]])
533+
dups = sorted(item for item, count in prod_count.items() if count > 1)
521534

522535
if dups:
523-
raise DuplicateMembersError(f"Product {product['name']} has duplicate members {dups}")
536+
raise DuplicateMembersError(
537+
f"Product {product['name']} has duplicate members: {', '.join(dups)}"
538+
) from None
524539

525540

526-
def check_duplicate_products(asns, product_names=None, dup_names=None):
541+
def check_duplicate_products(asns, product_names=None, dup_names=None, pfx=""):
527542
"""
528543
Check for duplicate products in a list of associations.
529544
@@ -561,6 +576,9 @@ def check_duplicate_products(asns, product_names=None, dup_names=None):
561576
Duplicate product names in the given associations.
562577
If None, will be generated internally.
563578
579+
pfx : str
580+
Prefix to error message. Particularly useful during left/right diff.
581+
564582
Raises
565583
------
566584
MultiDiffError
@@ -600,11 +618,12 @@ def check_duplicate_products(asns, product_names=None, dup_names=None):
600618
# Associations are exactly the same. Pure duplicate.
601619
diffs.append(
602620
DuplicateProductError(
603-
f"Associations share product name {product}", asns=[current_asn, asn]
621+
f"{pfx}Associations has duplicate product name: {product}",
622+
asns=[current_asn, asn],
604623
)
605624
)
606625
if diffs:
607-
raise diffs
626+
raise diffs from None
608627

609628

610629
# #########
@@ -683,6 +702,9 @@ def separate_products(asn):
683702
separated : [Association[, ...]]
684703
The list of separated associations.
685704
"""
705+
if len(asn["products"]) == 1: # noop
706+
return [asn]
707+
686708
separated = []
687709
for product in asn["products"]:
688710
new_asn = copy(asn)

jwst/associations/lib/prune.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def prune_duplicate_products(asns):
139139
diff.compare_product_membership(asn["products"][0], entrant["products"][0])
140140
except diff.MultiDiffError as diffs:
141141
# If one is a pure subset, remove the smaller association.
142-
if len(diffs) == 1 and isinstance(diffs[0], diff.SubsetError):
142+
if diff.SubsetError in diffs.err_types:
143143
if len(entrant["products"][0]["members"]) > len(
144144
asn["products"][0]["members"]
145145
):

0 commit comments

Comments
 (0)