The basic problem was that AST was not saving the inverse coefficients for MatrixMap and there were subtle numerical differences when they were computed when un-persisting. David Berry fixed that in starlink_ast.
I didn't see this earlier because I had mis-written assertWcsAlmostEqual... to require positive maximum error instead of allowing 0. I fixed that and changed two tests in afw and test_coaddBoundedField in meas_algorithms to use an error of 0.
In addition I added accessors to CoaddBoundedField in order to make debugging problems such as this simpler. I also modified operator== for CoaddBoundedField and CoaddBoundedFieldElement to test value equality, not pointer equality (provided neither pointer is null) and wrapped CoaddBoundedField::operator== with pybind11.
Please make sure you are comfortable with my changes to CoaddBoundedField in meas_algorithms. I hope you will find the changes in afw innocuous. There is no need to review the starlink_ast changes, though I made a pull request in case you want to look at them.
Based on tests it appears that everything except the "elements" vector is round tripping correctly.
The pybind11 CoaddBoundedFieldElement wrapper should wrap operator== and operator!= but does not. If I wrap that I see that the elements are not round tripping exactly. Beyond that it gets a bit weird: in Python each component of the first element compares equal, but in C++ the field and wcs do not (I have not compared later elements). That is very surprising: usually if one forgets to wrap operator== then Python will fail to compare equal because it falls back to something like is, even if C++ compares equal. I don't recall ever seeing the opposite occur.
In order to compare the elements I had to add an accessor to CoaddBoundedField to return the elements, and I added accessors for the other components as well, e.g. coaddWcs. If Jim Bosch has no objection I'd like to make these accessors part of the public API, in order to support debugging issues such as these.