diff --git a/news/94.rst b/news/94.rst new file mode 100644 index 0000000..3fcaafe --- /dev/null +++ b/news/94.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Fixed `PowderPattern.AddPowderPatternDiffraction()` so a no-reflections failure does not leave a failed diffraction component attached to the powder pattern. + +**Security:** + +* diff --git a/src/extensions/powderpattern_ext.cpp b/src/extensions/powderpattern_ext.cpp index 28646eb..c40f71a 100644 --- a/src/extensions/powderpattern_ext.cpp +++ b/src/extensions/powderpattern_ext.cpp @@ -24,6 +24,7 @@ #include #undef B0 +#include #include #include @@ -39,7 +40,6 @@ using namespace ObjCryst; namespace { - // This creates a C++ PowderPattern object PowderPattern* _CreatePowderPatternFromCIF(bp::object input) { @@ -117,11 +117,30 @@ PowderPatternBackground& addppbackground(PowderPattern& pp) PowderPatternDiffraction& addppdiffraction(PowderPattern& pp, Crystal& crst) { - PowderPatternDiffraction* ppc = new PowderPatternDiffraction(); + std::unique_ptr ppc(new PowderPatternDiffraction()); ppc->SetCrystal(crst); + // Prepare against the target powder-pattern context before final + // registration so a no-reflections failure cannot leave a broken + // partially attached component behind. + ppc->SetParentPowderPattern(pp); + ppc->GenHKLFullSpace(); + if(ppc->GetNbReflBelowMaxSinThetaOvLambda() == 0) + { + throw ObjCrystException( + "PowderPatternDiffraction::CalcSinThetaLambda(): there are no reflections!" + ); + } pp.AddPowderPatternComponent(*ppc); - pp.Prepare(); - return *ppc; + try + { + pp.Prepare(); + } + catch(...) + { + pp.RemovePowderPatternComponent(*ppc); + throw; + } + return *ppc.release(); } diff --git a/src/extensions/powderpatterndiffraction_ext.cpp b/src/extensions/powderpatterndiffraction_ext.cpp index 50ca04b..d29b822 100644 --- a/src/extensions/powderpatterndiffraction_ext.cpp +++ b/src/extensions/powderpatterndiffraction_ext.cpp @@ -77,4 +77,5 @@ void wrap_powderpatterndiffraction() .def("GetFhklObsSq", &PowderPatternDiffraction::GetFhklObsSq, return_value_policy()) ; + } diff --git a/tests/test_powderpattern.py b/tests/test_powderpattern.py index 7acb666..e80ea89 100644 --- a/tests/test_powderpattern.py +++ b/tests/test_powderpattern.py @@ -18,6 +18,7 @@ import numpy as np import pytest +from testutils import makeCrystal, makeScatterer from pyobjcryst import ObjCrystException from pyobjcryst.indexing import CrystalCentering, CrystalSystem, quick_index @@ -74,6 +75,20 @@ def test_GetPowderPatternX(self): self.assertTrue(np.array_equal([], self.pp.GetPowderPatternX())) return + def test_AddPowderPatternDiffraction_rollback_on_prepare_error(self): + pp = self.pp + crystal = makeCrystal(*makeScatterer()) + pp.SetWavelength(1.54056) + # Keep the 2theta window below the first reflection so setup raises + # and we can verify the failed diffraction component is not retained. + pp.SetPowderPatternPar(np.deg2rad(0.1), np.deg2rad(0.01), 41) + + with self.assertRaisesRegex(ObjCrystException, "no reflections"): + pp.AddPowderPatternDiffraction(crystal) + + self.assertEqual(0, pp.GetNbPowderPatternComponent()) + return + # def test_GetScaleFactor(self): assert False # def test_ImportPowderPattern2ThetaObs(self): assert False # def test_ImportPowderPattern2ThetaObsSigma(self): assert False