From 31695bc956796eb4e9d114e83d1c557fa75db98f Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 14 Jun 2025 16:59:03 -0400 Subject: [PATCH 01/45] chore: move tests --- {src/diffpy/srfit/tests => tests}/__init__.py | 0 {src/diffpy/srfit/tests => tests}/debug.py | 0 {src/diffpy/srfit/tests => tests}/run.py | 0 {src/diffpy/srfit/tests => tests}/speedtest.py | 0 {src/diffpy/srfit/tests => tests}/testbuilder.py | 0 {src/diffpy/srfit/tests => tests}/testcharacteristicfunctions.py | 0 {src/diffpy/srfit/tests => tests}/testconstraint.py | 0 {src/diffpy/srfit/tests => tests}/testcontribution.py | 0 {src/diffpy/srfit/tests => tests}/testdata/LaMnO3.stru | 0 {src/diffpy/srfit/tests => tests}/testdata/ni-q27r100-neutron.gr | 0 {src/diffpy/srfit/tests => tests}/testdata/ni.cif | 0 {src/diffpy/srfit/tests => tests}/testdata/results.res | 0 {src/diffpy/srfit/tests => tests}/testdata/sas_ascii_test_1.txt | 0 .../srfit/tests => tests}/testdata/sas_ellipsoid_testdata.txt | 0 {src/diffpy/srfit/tests => tests}/testdata/si-q27r60-xray.gr | 0 {src/diffpy/srfit/tests => tests}/testdata/testdata.txt | 0 {src/diffpy/srfit/tests => tests}/testdiffpyparset.py | 0 {src/diffpy/srfit/tests => tests}/testequation.py | 0 {src/diffpy/srfit/tests => tests}/testfitrecipe.py | 0 {src/diffpy/srfit/tests => tests}/testfitresults.py | 0 {src/diffpy/srfit/tests => tests}/testliterals.py | 0 {src/diffpy/srfit/tests => tests}/testobjcrystparset.py | 0 {src/diffpy/srfit/tests => tests}/testparameter.py | 0 {src/diffpy/srfit/tests => tests}/testparameterset.py | 0 {src/diffpy/srfit/tests => tests}/testpdf.py | 0 {src/diffpy/srfit/tests => tests}/testprofile.py | 0 {src/diffpy/srfit/tests => tests}/testprofilegenerator.py | 0 {src/diffpy/srfit/tests => tests}/testrecipeorganizer.py | 0 {src/diffpy/srfit/tests => tests}/testrestraint.py | 0 {src/diffpy/srfit/tests => tests}/testsas.py | 0 {src/diffpy/srfit/tests => tests}/testsgconstraints.py | 0 {src/diffpy/srfit/tests => tests}/testtagmanager.py | 0 {src/diffpy/srfit/tests => tests}/testvisitors.py | 0 {src/diffpy/srfit/tests => tests}/testweakrefcallable.py | 0 {src/diffpy/srfit/tests => tests}/utils.py | 0 35 files changed, 0 insertions(+), 0 deletions(-) rename {src/diffpy/srfit/tests => tests}/__init__.py (100%) rename {src/diffpy/srfit/tests => tests}/debug.py (100%) rename {src/diffpy/srfit/tests => tests}/run.py (100%) rename {src/diffpy/srfit/tests => tests}/speedtest.py (100%) rename {src/diffpy/srfit/tests => tests}/testbuilder.py (100%) rename {src/diffpy/srfit/tests => tests}/testcharacteristicfunctions.py (100%) rename {src/diffpy/srfit/tests => tests}/testconstraint.py (100%) rename {src/diffpy/srfit/tests => tests}/testcontribution.py (100%) rename {src/diffpy/srfit/tests => tests}/testdata/LaMnO3.stru (100%) rename {src/diffpy/srfit/tests => tests}/testdata/ni-q27r100-neutron.gr (100%) rename {src/diffpy/srfit/tests => tests}/testdata/ni.cif (100%) rename {src/diffpy/srfit/tests => tests}/testdata/results.res (100%) rename {src/diffpy/srfit/tests => tests}/testdata/sas_ascii_test_1.txt (100%) rename {src/diffpy/srfit/tests => tests}/testdata/sas_ellipsoid_testdata.txt (100%) rename {src/diffpy/srfit/tests => tests}/testdata/si-q27r60-xray.gr (100%) rename {src/diffpy/srfit/tests => tests}/testdata/testdata.txt (100%) rename {src/diffpy/srfit/tests => tests}/testdiffpyparset.py (100%) rename {src/diffpy/srfit/tests => tests}/testequation.py (100%) rename {src/diffpy/srfit/tests => tests}/testfitrecipe.py (100%) rename {src/diffpy/srfit/tests => tests}/testfitresults.py (100%) rename {src/diffpy/srfit/tests => tests}/testliterals.py (100%) rename {src/diffpy/srfit/tests => tests}/testobjcrystparset.py (100%) rename {src/diffpy/srfit/tests => tests}/testparameter.py (100%) rename {src/diffpy/srfit/tests => tests}/testparameterset.py (100%) rename {src/diffpy/srfit/tests => tests}/testpdf.py (100%) rename {src/diffpy/srfit/tests => tests}/testprofile.py (100%) rename {src/diffpy/srfit/tests => tests}/testprofilegenerator.py (100%) rename {src/diffpy/srfit/tests => tests}/testrecipeorganizer.py (100%) rename {src/diffpy/srfit/tests => tests}/testrestraint.py (100%) rename {src/diffpy/srfit/tests => tests}/testsas.py (100%) rename {src/diffpy/srfit/tests => tests}/testsgconstraints.py (100%) rename {src/diffpy/srfit/tests => tests}/testtagmanager.py (100%) rename {src/diffpy/srfit/tests => tests}/testvisitors.py (100%) rename {src/diffpy/srfit/tests => tests}/testweakrefcallable.py (100%) rename {src/diffpy/srfit/tests => tests}/utils.py (100%) diff --git a/src/diffpy/srfit/tests/__init__.py b/tests/__init__.py similarity index 100% rename from src/diffpy/srfit/tests/__init__.py rename to tests/__init__.py diff --git a/src/diffpy/srfit/tests/debug.py b/tests/debug.py similarity index 100% rename from src/diffpy/srfit/tests/debug.py rename to tests/debug.py diff --git a/src/diffpy/srfit/tests/run.py b/tests/run.py similarity index 100% rename from src/diffpy/srfit/tests/run.py rename to tests/run.py diff --git a/src/diffpy/srfit/tests/speedtest.py b/tests/speedtest.py similarity index 100% rename from src/diffpy/srfit/tests/speedtest.py rename to tests/speedtest.py diff --git a/src/diffpy/srfit/tests/testbuilder.py b/tests/testbuilder.py similarity index 100% rename from src/diffpy/srfit/tests/testbuilder.py rename to tests/testbuilder.py diff --git a/src/diffpy/srfit/tests/testcharacteristicfunctions.py b/tests/testcharacteristicfunctions.py similarity index 100% rename from src/diffpy/srfit/tests/testcharacteristicfunctions.py rename to tests/testcharacteristicfunctions.py diff --git a/src/diffpy/srfit/tests/testconstraint.py b/tests/testconstraint.py similarity index 100% rename from src/diffpy/srfit/tests/testconstraint.py rename to tests/testconstraint.py diff --git a/src/diffpy/srfit/tests/testcontribution.py b/tests/testcontribution.py similarity index 100% rename from src/diffpy/srfit/tests/testcontribution.py rename to tests/testcontribution.py diff --git a/src/diffpy/srfit/tests/testdata/LaMnO3.stru b/tests/testdata/LaMnO3.stru similarity index 100% rename from src/diffpy/srfit/tests/testdata/LaMnO3.stru rename to tests/testdata/LaMnO3.stru diff --git a/src/diffpy/srfit/tests/testdata/ni-q27r100-neutron.gr b/tests/testdata/ni-q27r100-neutron.gr similarity index 100% rename from src/diffpy/srfit/tests/testdata/ni-q27r100-neutron.gr rename to tests/testdata/ni-q27r100-neutron.gr diff --git a/src/diffpy/srfit/tests/testdata/ni.cif b/tests/testdata/ni.cif similarity index 100% rename from src/diffpy/srfit/tests/testdata/ni.cif rename to tests/testdata/ni.cif diff --git a/src/diffpy/srfit/tests/testdata/results.res b/tests/testdata/results.res similarity index 100% rename from src/diffpy/srfit/tests/testdata/results.res rename to tests/testdata/results.res diff --git a/src/diffpy/srfit/tests/testdata/sas_ascii_test_1.txt b/tests/testdata/sas_ascii_test_1.txt similarity index 100% rename from src/diffpy/srfit/tests/testdata/sas_ascii_test_1.txt rename to tests/testdata/sas_ascii_test_1.txt diff --git a/src/diffpy/srfit/tests/testdata/sas_ellipsoid_testdata.txt b/tests/testdata/sas_ellipsoid_testdata.txt similarity index 100% rename from src/diffpy/srfit/tests/testdata/sas_ellipsoid_testdata.txt rename to tests/testdata/sas_ellipsoid_testdata.txt diff --git a/src/diffpy/srfit/tests/testdata/si-q27r60-xray.gr b/tests/testdata/si-q27r60-xray.gr similarity index 100% rename from src/diffpy/srfit/tests/testdata/si-q27r60-xray.gr rename to tests/testdata/si-q27r60-xray.gr diff --git a/src/diffpy/srfit/tests/testdata/testdata.txt b/tests/testdata/testdata.txt similarity index 100% rename from src/diffpy/srfit/tests/testdata/testdata.txt rename to tests/testdata/testdata.txt diff --git a/src/diffpy/srfit/tests/testdiffpyparset.py b/tests/testdiffpyparset.py similarity index 100% rename from src/diffpy/srfit/tests/testdiffpyparset.py rename to tests/testdiffpyparset.py diff --git a/src/diffpy/srfit/tests/testequation.py b/tests/testequation.py similarity index 100% rename from src/diffpy/srfit/tests/testequation.py rename to tests/testequation.py diff --git a/src/diffpy/srfit/tests/testfitrecipe.py b/tests/testfitrecipe.py similarity index 100% rename from src/diffpy/srfit/tests/testfitrecipe.py rename to tests/testfitrecipe.py diff --git a/src/diffpy/srfit/tests/testfitresults.py b/tests/testfitresults.py similarity index 100% rename from src/diffpy/srfit/tests/testfitresults.py rename to tests/testfitresults.py diff --git a/src/diffpy/srfit/tests/testliterals.py b/tests/testliterals.py similarity index 100% rename from src/diffpy/srfit/tests/testliterals.py rename to tests/testliterals.py diff --git a/src/diffpy/srfit/tests/testobjcrystparset.py b/tests/testobjcrystparset.py similarity index 100% rename from src/diffpy/srfit/tests/testobjcrystparset.py rename to tests/testobjcrystparset.py diff --git a/src/diffpy/srfit/tests/testparameter.py b/tests/testparameter.py similarity index 100% rename from src/diffpy/srfit/tests/testparameter.py rename to tests/testparameter.py diff --git a/src/diffpy/srfit/tests/testparameterset.py b/tests/testparameterset.py similarity index 100% rename from src/diffpy/srfit/tests/testparameterset.py rename to tests/testparameterset.py diff --git a/src/diffpy/srfit/tests/testpdf.py b/tests/testpdf.py similarity index 100% rename from src/diffpy/srfit/tests/testpdf.py rename to tests/testpdf.py diff --git a/src/diffpy/srfit/tests/testprofile.py b/tests/testprofile.py similarity index 100% rename from src/diffpy/srfit/tests/testprofile.py rename to tests/testprofile.py diff --git a/src/diffpy/srfit/tests/testprofilegenerator.py b/tests/testprofilegenerator.py similarity index 100% rename from src/diffpy/srfit/tests/testprofilegenerator.py rename to tests/testprofilegenerator.py diff --git a/src/diffpy/srfit/tests/testrecipeorganizer.py b/tests/testrecipeorganizer.py similarity index 100% rename from src/diffpy/srfit/tests/testrecipeorganizer.py rename to tests/testrecipeorganizer.py diff --git a/src/diffpy/srfit/tests/testrestraint.py b/tests/testrestraint.py similarity index 100% rename from src/diffpy/srfit/tests/testrestraint.py rename to tests/testrestraint.py diff --git a/src/diffpy/srfit/tests/testsas.py b/tests/testsas.py similarity index 100% rename from src/diffpy/srfit/tests/testsas.py rename to tests/testsas.py diff --git a/src/diffpy/srfit/tests/testsgconstraints.py b/tests/testsgconstraints.py similarity index 100% rename from src/diffpy/srfit/tests/testsgconstraints.py rename to tests/testsgconstraints.py diff --git a/src/diffpy/srfit/tests/testtagmanager.py b/tests/testtagmanager.py similarity index 100% rename from src/diffpy/srfit/tests/testtagmanager.py rename to tests/testtagmanager.py diff --git a/src/diffpy/srfit/tests/testvisitors.py b/tests/testvisitors.py similarity index 100% rename from src/diffpy/srfit/tests/testvisitors.py rename to tests/testvisitors.py diff --git a/src/diffpy/srfit/tests/testweakrefcallable.py b/tests/testweakrefcallable.py similarity index 100% rename from src/diffpy/srfit/tests/testweakrefcallable.py rename to tests/testweakrefcallable.py diff --git a/src/diffpy/srfit/tests/utils.py b/tests/utils.py similarity index 100% rename from src/diffpy/srfit/tests/utils.py rename to tests/utils.py From 66dc96281d3bc84b4b659aa67fd380f8079bface Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 06:30:22 -0400 Subject: [PATCH 02/45] fix: rename all test files to pytest standard --- tests/{testbuilder.py => test_builder.py} | 4 ++-- ...acteristicfunctions.py => test_characteristicfunctions.py} | 0 tests/{testconstraint.py => test_constraint.py} | 0 tests/{testcontribution.py => test_contribution.py} | 0 tests/{testdata => test_data}/LaMnO3.stru | 0 tests/{testdata => test_data}/ni-q27r100-neutron.gr | 0 tests/{testdata => test_data}/ni.cif | 0 tests/{testdata => test_data}/results.res | 0 tests/{testdata => test_data}/sas_ascii_test_1.txt | 0 tests/{testdata => test_data}/sas_ellipsoid_testdata.txt | 0 tests/{testdata => test_data}/si-q27r60-xray.gr | 0 tests/{testdata => test_data}/testdata.txt | 0 tests/{testdiffpyparset.py => test_diffpyparset.py} | 0 tests/{testequation.py => test_equation.py} | 0 tests/{testfitrecipe.py => test_fitrecipe.py} | 0 tests/{testfitresults.py => test_fitresults.py} | 0 tests/{testliterals.py => test_literals.py} | 0 tests/{testobjcrystparset.py => test_objcrystparset.py} | 0 tests/{testparameter.py => test_parameter.py} | 0 tests/{testparameterset.py => test_parameterset.py} | 0 tests/{testpdf.py => test_pdf.py} | 0 tests/{testprofile.py => test_profile.py} | 0 tests/{testprofilegenerator.py => test_profilegenerator.py} | 0 tests/{testrecipeorganizer.py => test_recipeorganizer.py} | 0 tests/{testrestraint.py => test_restraint.py} | 0 tests/{testsas.py => test_sas.py} | 0 tests/{testsgconstraints.py => test_sgconstraints.py} | 0 tests/{speedtest.py => test_speed.py} | 0 tests/{testtagmanager.py => test_tagmanager.py} | 0 tests/{testvisitors.py => test_visitors.py} | 0 tests/{testweakrefcallable.py => test_weakrefcallable.py} | 0 tests/utils.py | 2 +- 32 files changed, 3 insertions(+), 3 deletions(-) rename tests/{testbuilder.py => test_builder.py} (98%) rename tests/{testcharacteristicfunctions.py => test_characteristicfunctions.py} (100%) rename tests/{testconstraint.py => test_constraint.py} (100%) rename tests/{testcontribution.py => test_contribution.py} (100%) rename tests/{testdata => test_data}/LaMnO3.stru (100%) rename tests/{testdata => test_data}/ni-q27r100-neutron.gr (100%) rename tests/{testdata => test_data}/ni.cif (100%) rename tests/{testdata => test_data}/results.res (100%) rename tests/{testdata => test_data}/sas_ascii_test_1.txt (100%) rename tests/{testdata => test_data}/sas_ellipsoid_testdata.txt (100%) rename tests/{testdata => test_data}/si-q27r60-xray.gr (100%) rename tests/{testdata => test_data}/testdata.txt (100%) rename tests/{testdiffpyparset.py => test_diffpyparset.py} (100%) rename tests/{testequation.py => test_equation.py} (100%) rename tests/{testfitrecipe.py => test_fitrecipe.py} (100%) rename tests/{testfitresults.py => test_fitresults.py} (100%) rename tests/{testliterals.py => test_literals.py} (100%) rename tests/{testobjcrystparset.py => test_objcrystparset.py} (100%) rename tests/{testparameter.py => test_parameter.py} (100%) rename tests/{testparameterset.py => test_parameterset.py} (100%) rename tests/{testpdf.py => test_pdf.py} (100%) rename tests/{testprofile.py => test_profile.py} (100%) rename tests/{testprofilegenerator.py => test_profilegenerator.py} (100%) rename tests/{testrecipeorganizer.py => test_recipeorganizer.py} (100%) rename tests/{testrestraint.py => test_restraint.py} (100%) rename tests/{testsas.py => test_sas.py} (100%) rename tests/{testsgconstraints.py => test_sgconstraints.py} (100%) rename tests/{speedtest.py => test_speed.py} (100%) rename tests/{testtagmanager.py => test_tagmanager.py} (100%) rename tests/{testvisitors.py => test_visitors.py} (100%) rename tests/{testweakrefcallable.py => test_weakrefcallable.py} (100%) diff --git a/tests/testbuilder.py b/tests/test_builder.py similarity index 98% rename from tests/testbuilder.py rename to tests/test_builder.py index cb46c5ee..7e61834f 100644 --- a/tests/testbuilder.py +++ b/tests/test_builder.py @@ -21,8 +21,8 @@ import diffpy.srfit.equation.builder as builder import diffpy.srfit.equation.literals as literals -from diffpy.srfit.tests.utils import _makeArgs -from diffpy.srfit.tests.utils import noObserversInGlobalBuilders +from .utils import _makeArgs +from .utils import noObserversInGlobalBuilders class TestBuilder(unittest.TestCase): diff --git a/tests/testcharacteristicfunctions.py b/tests/test_characteristicfunctions.py similarity index 100% rename from tests/testcharacteristicfunctions.py rename to tests/test_characteristicfunctions.py diff --git a/tests/testconstraint.py b/tests/test_constraint.py similarity index 100% rename from tests/testconstraint.py rename to tests/test_constraint.py diff --git a/tests/testcontribution.py b/tests/test_contribution.py similarity index 100% rename from tests/testcontribution.py rename to tests/test_contribution.py diff --git a/tests/testdata/LaMnO3.stru b/tests/test_data/LaMnO3.stru similarity index 100% rename from tests/testdata/LaMnO3.stru rename to tests/test_data/LaMnO3.stru diff --git a/tests/testdata/ni-q27r100-neutron.gr b/tests/test_data/ni-q27r100-neutron.gr similarity index 100% rename from tests/testdata/ni-q27r100-neutron.gr rename to tests/test_data/ni-q27r100-neutron.gr diff --git a/tests/testdata/ni.cif b/tests/test_data/ni.cif similarity index 100% rename from tests/testdata/ni.cif rename to tests/test_data/ni.cif diff --git a/tests/testdata/results.res b/tests/test_data/results.res similarity index 100% rename from tests/testdata/results.res rename to tests/test_data/results.res diff --git a/tests/testdata/sas_ascii_test_1.txt b/tests/test_data/sas_ascii_test_1.txt similarity index 100% rename from tests/testdata/sas_ascii_test_1.txt rename to tests/test_data/sas_ascii_test_1.txt diff --git a/tests/testdata/sas_ellipsoid_testdata.txt b/tests/test_data/sas_ellipsoid_testdata.txt similarity index 100% rename from tests/testdata/sas_ellipsoid_testdata.txt rename to tests/test_data/sas_ellipsoid_testdata.txt diff --git a/tests/testdata/si-q27r60-xray.gr b/tests/test_data/si-q27r60-xray.gr similarity index 100% rename from tests/testdata/si-q27r60-xray.gr rename to tests/test_data/si-q27r60-xray.gr diff --git a/tests/testdata/testdata.txt b/tests/test_data/testdata.txt similarity index 100% rename from tests/testdata/testdata.txt rename to tests/test_data/testdata.txt diff --git a/tests/testdiffpyparset.py b/tests/test_diffpyparset.py similarity index 100% rename from tests/testdiffpyparset.py rename to tests/test_diffpyparset.py diff --git a/tests/testequation.py b/tests/test_equation.py similarity index 100% rename from tests/testequation.py rename to tests/test_equation.py diff --git a/tests/testfitrecipe.py b/tests/test_fitrecipe.py similarity index 100% rename from tests/testfitrecipe.py rename to tests/test_fitrecipe.py diff --git a/tests/testfitresults.py b/tests/test_fitresults.py similarity index 100% rename from tests/testfitresults.py rename to tests/test_fitresults.py diff --git a/tests/testliterals.py b/tests/test_literals.py similarity index 100% rename from tests/testliterals.py rename to tests/test_literals.py diff --git a/tests/testobjcrystparset.py b/tests/test_objcrystparset.py similarity index 100% rename from tests/testobjcrystparset.py rename to tests/test_objcrystparset.py diff --git a/tests/testparameter.py b/tests/test_parameter.py similarity index 100% rename from tests/testparameter.py rename to tests/test_parameter.py diff --git a/tests/testparameterset.py b/tests/test_parameterset.py similarity index 100% rename from tests/testparameterset.py rename to tests/test_parameterset.py diff --git a/tests/testpdf.py b/tests/test_pdf.py similarity index 100% rename from tests/testpdf.py rename to tests/test_pdf.py diff --git a/tests/testprofile.py b/tests/test_profile.py similarity index 100% rename from tests/testprofile.py rename to tests/test_profile.py diff --git a/tests/testprofilegenerator.py b/tests/test_profilegenerator.py similarity index 100% rename from tests/testprofilegenerator.py rename to tests/test_profilegenerator.py diff --git a/tests/testrecipeorganizer.py b/tests/test_recipeorganizer.py similarity index 100% rename from tests/testrecipeorganizer.py rename to tests/test_recipeorganizer.py diff --git a/tests/testrestraint.py b/tests/test_restraint.py similarity index 100% rename from tests/testrestraint.py rename to tests/test_restraint.py diff --git a/tests/testsas.py b/tests/test_sas.py similarity index 100% rename from tests/testsas.py rename to tests/test_sas.py diff --git a/tests/testsgconstraints.py b/tests/test_sgconstraints.py similarity index 100% rename from tests/testsgconstraints.py rename to tests/test_sgconstraints.py diff --git a/tests/speedtest.py b/tests/test_speed.py similarity index 100% rename from tests/speedtest.py rename to tests/test_speed.py diff --git a/tests/testtagmanager.py b/tests/test_tagmanager.py similarity index 100% rename from tests/testtagmanager.py rename to tests/test_tagmanager.py diff --git a/tests/testvisitors.py b/tests/test_visitors.py similarity index 100% rename from tests/testvisitors.py rename to tests/test_visitors.py diff --git a/tests/testweakrefcallable.py b/tests/test_weakrefcallable.py similarity index 100% rename from tests/testweakrefcallable.py rename to tests/test_weakrefcallable.py diff --git a/tests/utils.py b/tests/utils.py index 6ac35c1c..37c95376 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -20,7 +20,7 @@ import diffpy.srfit.equation.literals as literals from diffpy.srfit.sas.sasimport import sasimport -from diffpy.srfit.tests import logger +from . import logger # Resolve availability of optional third-party packages. From 4a00cb823598a5d8c13c83e71919e162e4b7da19 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 06:38:07 -0400 Subject: [PATCH 03/45] fix: rename test_data back to testdata and import utils locally --- tests/test_characteristicfunctions.py | 2 +- tests/test_contribution.py | 2 +- tests/test_diffpyparset.py | 2 +- tests/test_equation.py | 4 ++-- tests/test_fitrecipe.py | 2 +- tests/test_fitresults.py | 2 +- tests/test_objcrystparset.py | 2 +- tests/test_pdf.py | 6 +++--- tests/test_profile.py | 2 +- tests/test_recipeorganizer.py | 2 +- tests/test_sas.py | 4 ++-- tests/test_sgconstraints.py | 6 +++--- tests/test_speed.py | 2 +- tests/test_visitors.py | 2 +- tests/{test_data => testdata}/LaMnO3.stru | 0 tests/{test_data => testdata}/ni-q27r100-neutron.gr | 0 tests/{test_data => testdata}/ni.cif | 0 tests/{test_data => testdata}/results.res | 0 tests/{test_data => testdata}/sas_ascii_test_1.txt | 0 tests/{test_data => testdata}/sas_ellipsoid_testdata.txt | 0 tests/{test_data => testdata}/si-q27r60-xray.gr | 0 tests/{test_data => testdata}/testdata.txt | 0 22 files changed, 20 insertions(+), 20 deletions(-) rename tests/{test_data => testdata}/LaMnO3.stru (100%) rename tests/{test_data => testdata}/ni-q27r100-neutron.gr (100%) rename tests/{test_data => testdata}/ni.cif (100%) rename tests/{test_data => testdata}/results.res (100%) rename tests/{test_data => testdata}/sas_ascii_test_1.txt (100%) rename tests/{test_data => testdata}/sas_ellipsoid_testdata.txt (100%) rename tests/{test_data => testdata}/si-q27r60-xray.gr (100%) rename tests/{test_data => testdata}/testdata.txt (100%) diff --git a/tests/test_characteristicfunctions.py b/tests/test_characteristicfunctions.py index 7b5eb4c4..c7d01035 100644 --- a/tests/test_characteristicfunctions.py +++ b/tests/test_characteristicfunctions.py @@ -19,7 +19,7 @@ import numpy -from diffpy.srfit.tests.utils import has_sas, _msg_nosas +from .utils import has_sas, _msg_nosas from diffpy.srfit.sas.sasimport import sasimport # Global variables to be assigned in setUp diff --git a/tests/test_contribution.py b/tests/test_contribution.py index 19dbdaa9..8e7cf177 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -24,7 +24,7 @@ from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.tests.utils import noObserversInGlobalBuilders +from .utils import noObserversInGlobalBuilders class TestContribution(unittest.TestCase): diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index 71701aa6..fafb1619 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -20,7 +20,7 @@ import numpy -from diffpy.srfit.tests.utils import has_structure, _msg_nostructure +from .utils import has_structure, _msg_nostructure # Global variables to be assigned in setUp Atom = Lattice = Structure = DiffpyStructureParSet = None diff --git a/tests/test_equation.py b/tests/test_equation.py index 34b79361..6877de8f 100644 --- a/tests/test_equation.py +++ b/tests/test_equation.py @@ -19,8 +19,8 @@ import diffpy.srfit.equation.literals as literals from diffpy.srfit.equation import Equation -from diffpy.srfit.tests.utils import _makeArgs -from diffpy.srfit.tests.utils import noObserversInGlobalBuilders +from .utils import _makeArgs +from .utils import noObserversInGlobalBuilders class TestEquation(unittest.TestCase): diff --git a/tests/test_fitrecipe.py b/tests/test_fitrecipe.py index 75bf7c28..490753ac 100644 --- a/tests/test_fitrecipe.py +++ b/tests/test_fitrecipe.py @@ -23,7 +23,7 @@ from diffpy.srfit.fitbase.fitcontribution import FitContribution from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.tests.utils import capturestdout +from .utils import capturestdout class TestFitRecipe(unittest.TestCase): diff --git a/tests/test_fitresults.py b/tests/test_fitresults.py index c5d6c9cc..43deb3a8 100644 --- a/tests/test_fitresults.py +++ b/tests/test_fitresults.py @@ -19,7 +19,7 @@ from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitresults import initializeRecipe -from diffpy.srfit.tests.utils import datafile +from .utils import datafile class TestInitializeRecipe(unittest.TestCase): diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index a8e142fc..8af66b0c 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -19,7 +19,7 @@ import numpy -from diffpy.srfit.tests.utils import has_pyobjcryst, _msg_nopyobjcryst +from .utils import has_pyobjcryst, _msg_nopyobjcryst # Global variables to be assigned in setUp ObjCrystCrystalParSet = spacegroups = None diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 1c2695a9..fb67158e 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -21,9 +21,9 @@ import numpy -from diffpy.srfit.tests.utils import datafile -from diffpy.srfit.tests.utils import has_srreal, _msg_nosrreal -from diffpy.srfit.tests.utils import has_structure, _msg_nostructure +from .utils import datafile +from .utils import has_srreal, _msg_nosrreal +from .utils import has_structure, _msg_nostructure from diffpy.srfit.pdf import PDFGenerator, PDFParser, PDFContribution from diffpy.srfit.exceptions import SrFitError diff --git a/tests/test_profile.py b/tests/test_profile.py index bd7217a8..ded0f38d 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -23,7 +23,7 @@ from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.tests.utils import datafile +from .utils import datafile class TestProfile(unittest.TestCase): diff --git a/tests/test_recipeorganizer.py b/tests/test_recipeorganizer.py index c39e2626..35746a79 100644 --- a/tests/test_recipeorganizer.py +++ b/tests/test_recipeorganizer.py @@ -23,7 +23,7 @@ from diffpy.srfit.fitbase.recipeorganizer import equationFromString from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer from diffpy.srfit.fitbase.recipeorganizer import RecipeOrganizer -from diffpy.srfit.tests.utils import capturestdout +from .utils import capturestdout import numpy diff --git a/tests/test_sas.py b/tests/test_sas.py index 9122cf97..02bb61eb 100644 --- a/tests/test_sas.py +++ b/tests/test_sas.py @@ -20,8 +20,8 @@ import numpy from diffpy.srfit.sas import SASGenerator, SASParser, SASProfile -from diffpy.srfit.tests.utils import datafile -from diffpy.srfit.tests.utils import has_sas, _msg_nosas +from .utils import datafile +from .utils import has_sas, _msg_nosas from diffpy.srfit.sas.sasimport import sasimport # ---------------------------------------------------------------------------- diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 00eafea7..c6a29212 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -19,9 +19,9 @@ import numpy -from diffpy.srfit.tests.utils import datafile -from diffpy.srfit.tests.utils import has_pyobjcryst, _msg_nopyobjcryst -from diffpy.srfit.tests.utils import has_structure, _msg_nostructure +from .utils import datafile +from .utils import has_pyobjcryst, _msg_nopyobjcryst +from .utils import has_structure, _msg_nostructure # ---------------------------------------------------------------------------- diff --git a/tests/test_speed.py b/tests/test_speed.py index db43b77a..7329c2f6 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -23,7 +23,7 @@ import diffpy.srfit.equation.visitors as visitors import diffpy.srfit.equation.literals as literals -from diffpy.srfit.tests.utils import _makeArgs +from .utils import _makeArgs x = numpy.arange(0, 20, 0.05) diff --git a/tests/test_visitors.py b/tests/test_visitors.py index 0250f064..880801ed 100644 --- a/tests/test_visitors.py +++ b/tests/test_visitors.py @@ -19,7 +19,7 @@ import diffpy.srfit.equation.visitors as visitors import diffpy.srfit.equation.literals as literals -from diffpy.srfit.tests.utils import _makeArgs +from .utils import _makeArgs class TestValidator(unittest.TestCase): diff --git a/tests/test_data/LaMnO3.stru b/tests/testdata/LaMnO3.stru similarity index 100% rename from tests/test_data/LaMnO3.stru rename to tests/testdata/LaMnO3.stru diff --git a/tests/test_data/ni-q27r100-neutron.gr b/tests/testdata/ni-q27r100-neutron.gr similarity index 100% rename from tests/test_data/ni-q27r100-neutron.gr rename to tests/testdata/ni-q27r100-neutron.gr diff --git a/tests/test_data/ni.cif b/tests/testdata/ni.cif similarity index 100% rename from tests/test_data/ni.cif rename to tests/testdata/ni.cif diff --git a/tests/test_data/results.res b/tests/testdata/results.res similarity index 100% rename from tests/test_data/results.res rename to tests/testdata/results.res diff --git a/tests/test_data/sas_ascii_test_1.txt b/tests/testdata/sas_ascii_test_1.txt similarity index 100% rename from tests/test_data/sas_ascii_test_1.txt rename to tests/testdata/sas_ascii_test_1.txt diff --git a/tests/test_data/sas_ellipsoid_testdata.txt b/tests/testdata/sas_ellipsoid_testdata.txt similarity index 100% rename from tests/test_data/sas_ellipsoid_testdata.txt rename to tests/testdata/sas_ellipsoid_testdata.txt diff --git a/tests/test_data/si-q27r60-xray.gr b/tests/testdata/si-q27r60-xray.gr similarity index 100% rename from tests/test_data/si-q27r60-xray.gr rename to tests/testdata/si-q27r60-xray.gr diff --git a/tests/test_data/testdata.txt b/tests/testdata/testdata.txt similarity index 100% rename from tests/test_data/testdata.txt rename to tests/testdata/testdata.txt From 72b49580696f636570648625c6fdb09e57183226 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 07:10:47 -0400 Subject: [PATCH 04/45] fix: change error raised by badly formed input for numpy.add operator in literals tests --- src/diffpy/srfit/equation/literals/operators.py | 2 +- tests/test_literals.py | 4 ++-- tests/test_visitors.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/srfit/equation/literals/operators.py b/src/diffpy/srfit/equation/literals/operators.py index 3a4e9eaa..12ef1ff2 100644 --- a/src/diffpy/srfit/equation/literals/operators.py +++ b/src/diffpy/srfit/equation/literals/operators.py @@ -41,7 +41,7 @@ class Operator(Literal, OperatorABC): """Abstract class for specifying a general operator. This class provides several methods that are common to a derived - classes for concrete concrete operations. + classes for concrete operations. Class Attributes ---------------- diff --git a/tests/test_literals.py b/tests/test_literals.py index 6c193a61..4bc52c5b 100644 --- a/tests/test_literals.py +++ b/tests/test_literals.py @@ -118,7 +118,7 @@ def testAddLiteral(self): """Test adding a literal to an operator node.""" op = self.op - self.assertRaises(ValueError, op.getValue) + self.assertRaises(TypeError, op.getValue) op._value = 1 self.assertEqual(op.getValue(), 1) @@ -127,7 +127,7 @@ def testAddLiteral(self): b = literals.Argument(name = "b", value = 0) op.addLiteral(a) - self.assertRaises(ValueError, op.getValue) + self.assertRaises(TypeError, op.getValue) op.addLiteral(b) self.assertAlmostEqual(0, op.value) diff --git a/tests/test_visitors.py b/tests/test_visitors.py index 880801ed..28ea05b1 100644 --- a/tests/test_visitors.py +++ b/tests/test_visitors.py @@ -194,7 +194,7 @@ def testSimpleFunction(self): self.assertTrue(plus2.hasObserver(mult._flush)) # plus2 has no arguments yet. Verify this. - self.assertRaises(ValueError, mult.getValue) + self.assertRaises(TypeError, mult.getValue) # Add the arguments to plus2. plus2.addLiteral(v4) plus2.addLiteral(v5) From 7b6d45efdcc1e7de0fd6a0469937120082758094 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 08:12:22 -0400 Subject: [PATCH 05/45] chore: requirements and new version infrastructure updated --- requirements/build.txt | 0 requirements/conda.txt | 3 +++ requirements/docs.txt | 5 +++++ requirements/pip.txt | 3 +++ requirements/test.txt | 6 ++++++ tests/conftest.py | 19 +++++++++++++++++++ tests/test_version.py | 10 ++++++++++ 7 files changed, 46 insertions(+) create mode 100644 requirements/build.txt create mode 100644 requirements/conda.txt create mode 100644 requirements/docs.txt create mode 100644 requirements/pip.txt create mode 100644 requirements/test.txt create mode 100644 tests/conftest.py create mode 100644 tests/test_version.py diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 00000000..e69de29b diff --git a/requirements/conda.txt b/requirements/conda.txt new file mode 100644 index 00000000..5306b5d1 --- /dev/null +++ b/requirements/conda.txt @@ -0,0 +1,3 @@ +matplotlib-base +numpy +scipy diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 00000000..5f34c6ed --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,5 @@ +sphinx +sphinx_rtd_theme +sphinx-copybutton +doctr +m2r diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 00000000..74fa65e6 --- /dev/null +++ b/requirements/pip.txt @@ -0,0 +1,3 @@ +matplotlib +numpy +scipy diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 00000000..a7277865 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,6 @@ +flake8 +pytest +codecov +coverage +pytest-cov +pytest-env diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..e3b63139 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..ee1efa9b --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,10 @@ +"""Unit tests for __version__.py.""" + +import diffpy.srfit # noqa + + +def test_package_version(): + """Ensure the package version is defined and not set to the initial + placeholder.""" + assert hasattr(diffpy.srfit, "__version__") + assert diffpy.srfit.__version__ != "0.0.0" From 2f1aabe47e2e6aa267ec7d8533c6a9e366e1f1fb Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 08:24:00 -0400 Subject: [PATCH 06/45] chore: news and doc make --- doc/make.bat | 36 ++++++++++++++++++++++++++++++++++++ news/TEMPLATE.rst | 23 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 doc/make.bat create mode 100644 news/TEMPLATE.rst diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 00000000..2be83069 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=PackagingScientificPython + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 00000000..790d30b1 --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 78eece96a09fb661a4d3c4e8985b5a2243f98165 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 08:35:42 -0400 Subject: [PATCH 07/45] chore: initial commit of doc/source --- doc/source/_static/.placeholder | 0 .../api/diffpy.srfit.example_package.rst | 31 ++ doc/source/api/diffpy.srfit.rst | 30 ++ doc/source/conf.py | 322 ++++++++++++++++++ 4 files changed, 383 insertions(+) create mode 100644 doc/source/_static/.placeholder create mode 100644 doc/source/api/diffpy.srfit.example_package.rst create mode 100644 doc/source/api/diffpy.srfit.rst create mode 100644 doc/source/conf.py diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/api/diffpy.srfit.example_package.rst b/doc/source/api/diffpy.srfit.example_package.rst new file mode 100644 index 00000000..60910070 --- /dev/null +++ b/doc/source/api/diffpy.srfit.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.srfit.example_package package + +.. automodule:: diffpy.srfit.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.srfit.example_package.foo module + +.. automodule:: diffpy.srfit.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.srfit.example_package.bar module + +.. automodule:: diffpy.srfit.example_package.foo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.srfit.rst b/doc/source/api/diffpy.srfit.rst new file mode 100644 index 00000000..5b2b697b --- /dev/null +++ b/doc/source/api/diffpy.srfit.rst @@ -0,0 +1,30 @@ +:tocdepth: -1 + +|title| +======= + +.. |title| replace:: diffpy.srfit package + +.. automodule:: diffpy.srfit + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + diffpy.srfit.example_package + +Submodules +---------- + +|module| +-------- + +.. |module| replace:: diffpy.srfit.example_submodule module + +.. automodule:: diffpy.srfit.example_submodule + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..b0a86f3c --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# diffpy.srfit documentation build configuration file, created by # noqa: E501 +# sphinx-quickstart on Thu Jan 30 15:49:41 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import time +from importlib.metadata import version +from pathlib import Path + +# Attempt to import the version dynamically from GitHub tag. +try: + fullversion = version("diffpy.srfit") +except Exception: + fullversion = "No version found. The correct version will appear in the released version." # noqa: E501 + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use Path().resolve() to make it absolute, like shown here. # noqa: E501 +# sys.path.insert(0, str(Path(".").resolve())) +sys.path.insert(0, str(Path("../..").resolve())) +sys.path.insert(0, str(Path("../../src").resolve())) + +# abbreviations +ab_authors = "Christopher Farrow, Pavol Juhas, and members of the Billinge Group" + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", + "sphinx_copybutton", + "m2r", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "diffpy.srfit" +copyright = "%Y, The Trustees of Columbia University in the City of New York" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# The short X.Y version. +version = "".join(fullversion.split(".post")[:1]) +# The full version, including alpha/beta/rc tags. +release = fullversion + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +today = time.strftime("%B %d, %Y", time.localtime()) +year = today.split()[-1] +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' +# substitute YEAR in the copyright string +copyright = copyright.replace("%Y", year) + +# For sphinx_copybutton extension. +# Do not copy "$" for shell commands in code-blocks. +copybutton_prompt_text = r"^\$ " +copybutton_prompt_is_regexp = True + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ["build"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +modindex_common_prefix = ["diffpy.srfit"] + +# Display all warnings for missing links. +nitpicky = True + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +html_context = { + "display_github": True, + "github_user": "diffpy", + "github_repo": "diffpy.srfit", + "github_version": "main", + "conf_py_path": "/doc/source/", +} + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + "navigation_with_keys": "true", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +basename = "diffpy.srfit".replace(" ", "").replace(".", "") +htmlhelp_basename = basename + "doc" + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + "index", + "diffpy.srfit.tex", + "diffpy.srfit Documentation", + ab_authors, + "manual", + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + "index", + "diffpy.srfit", + "diffpy.srfit Documentation", + ab_authors, + 1, + ) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + "index", + "diffpy.srfit", + "diffpy.srfit Documentation", + ab_authors, + "diffpy.srfit", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +# intersphinx_mapping = {'http://docs.python.org/': None} From 613e0377cef82760773cc112ee92a7be84d4c075 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 08:39:44 -0400 Subject: [PATCH 08/45] chore: add new img, snippets etc. --- doc/source/api/.placeholder | 0 doc/source/img/.placeholder | 0 doc/source/snippets/.placeholder | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/source/api/.placeholder create mode 100644 doc/source/img/.placeholder create mode 100644 doc/source/snippets/.placeholder diff --git a/doc/source/api/.placeholder b/doc/source/api/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/img/.placeholder b/doc/source/img/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/snippets/.placeholder b/doc/source/snippets/.placeholder new file mode 100644 index 00000000..e69de29b From 6a57d3ff38645f680eea75c33a46de3b91777c52 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 08:53:24 -0400 Subject: [PATCH 09/45] chore: move over doc/source files --- doc/manual/source/api/diffpy.srfit.rst | 38 --- doc/manual/source/conf.py | 274 ------------------ .../api/diffpy.srfit.equation.literals.rst | 0 .../source/api/diffpy.srfit.equation.rst | 0 .../api/diffpy.srfit.equation.visitors.rst | 0 .../source/api/diffpy.srfit.fitbase.rst | 0 .../source/api/diffpy.srfit.interface.rst | 0 .../source/api/diffpy.srfit.pdf.rst | 0 .../source/api/diffpy.srfit.sas.rst | 0 .../source/api/diffpy.srfit.structure.rst | 0 .../source/api/diffpy.srfit.util.rst | 0 doc/{manual => }/source/examples.rst | 0 .../source/examples/coreshellnp.rst | 0 .../source/examples/crystalpdf.rst | 0 .../source/examples/crystalpdfall.rst | 0 .../source/examples/crystalpdfobjcryst.rst | 0 .../source/examples/crystalpdftwodata.rst | 0 .../source/examples/crystalpdftwophase.rst | 0 .../source/examples/debyemodel.rst | 0 .../source/examples/debyemodelII.rst | 0 .../source/examples/ellipsoidsas.rst | 0 .../source/examples/gaussiangenerator.rst | 0 .../source/examples/gaussianrecipe.rst | 0 .../source/examples/interface.rst | 0 .../source/examples/npintensity.rst | 0 .../source/examples/npintensityII.rst | 0 .../source/examples/nppdfcrystal.rst | 0 .../source/examples/nppdfobjcryst.rst | 0 doc/{manual => }/source/examples/nppdfsas.rst | 0 .../source/examples/simplepdf.rst | 0 .../source/examples/simplepdftwophase.rst | 0 .../source/examples/simplerecipe.rst | 0 doc/{manual => }/source/extending.rst | 0 doc/{manual => }/source/index.rst | 0 doc/{manual => }/source/license.rst | 0 doc/{manual => }/source/release.rst | 0 36 files changed, 312 deletions(-) delete mode 100644 doc/manual/source/api/diffpy.srfit.rst delete mode 100644 doc/manual/source/conf.py rename doc/{manual => }/source/api/diffpy.srfit.equation.literals.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.equation.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.equation.visitors.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.fitbase.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.interface.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.pdf.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.sas.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.structure.rst (100%) rename doc/{manual => }/source/api/diffpy.srfit.util.rst (100%) rename doc/{manual => }/source/examples.rst (100%) rename doc/{manual => }/source/examples/coreshellnp.rst (100%) rename doc/{manual => }/source/examples/crystalpdf.rst (100%) rename doc/{manual => }/source/examples/crystalpdfall.rst (100%) rename doc/{manual => }/source/examples/crystalpdfobjcryst.rst (100%) rename doc/{manual => }/source/examples/crystalpdftwodata.rst (100%) rename doc/{manual => }/source/examples/crystalpdftwophase.rst (100%) rename doc/{manual => }/source/examples/debyemodel.rst (100%) rename doc/{manual => }/source/examples/debyemodelII.rst (100%) rename doc/{manual => }/source/examples/ellipsoidsas.rst (100%) rename doc/{manual => }/source/examples/gaussiangenerator.rst (100%) rename doc/{manual => }/source/examples/gaussianrecipe.rst (100%) rename doc/{manual => }/source/examples/interface.rst (100%) rename doc/{manual => }/source/examples/npintensity.rst (100%) rename doc/{manual => }/source/examples/npintensityII.rst (100%) rename doc/{manual => }/source/examples/nppdfcrystal.rst (100%) rename doc/{manual => }/source/examples/nppdfobjcryst.rst (100%) rename doc/{manual => }/source/examples/nppdfsas.rst (100%) rename doc/{manual => }/source/examples/simplepdf.rst (100%) rename doc/{manual => }/source/examples/simplepdftwophase.rst (100%) rename doc/{manual => }/source/examples/simplerecipe.rst (100%) rename doc/{manual => }/source/extending.rst (100%) rename doc/{manual => }/source/index.rst (100%) rename doc/{manual => }/source/license.rst (100%) rename doc/{manual => }/source/release.rst (100%) diff --git a/doc/manual/source/api/diffpy.srfit.rst b/doc/manual/source/api/diffpy.srfit.rst deleted file mode 100644 index bbe6224d..00000000 --- a/doc/manual/source/api/diffpy.srfit.rst +++ /dev/null @@ -1,38 +0,0 @@ -:tocdepth: 2 - -diffpy.srfit package -==================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 2 - - diffpy.srfit.equation - diffpy.srfit.fitbase - diffpy.srfit.interface - diffpy.srfit.pdf - diffpy.srfit.sas - diffpy.srfit.structure - diffpy.srfit.util - -Submodules ----------- - -diffpy.srfit.version module ---------------------------- - -.. automodule:: diffpy.srfit.version - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: diffpy.srfit - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/manual/source/conf.py b/doc/manual/source/conf.py deleted file mode 100644 index 53398e50..00000000 --- a/doc/manual/source/conf.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# diffpy.srfit documentation build configuration file, created by -# sphinx-quickstart on Fri Dec 6 18:09:01 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -import time - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) -# sys.path.insert(0, os.path.abspath('../../..')) - -# abbreviations -ab_authors = u'Christopher L. Farrow, Pavol Juhás, Simon J.L. Billinge group' - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx.ext.intersphinx', - 'm2r', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = ['.rst', '.md'] - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'diffpy.srfit' -copyright = u'%Y, Brookhaven National Laboratory' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -sys.path.insert(0, os.path.abspath('../../..')) -from setup import versiondata -fullversion = versiondata.get('DEFAULT', 'version') -# The short X.Y version. -version = ''.join(fullversion.split('.post')[:1]) -# The full version, including alpha/beta/rc tags. -release = fullversion - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -today_seconds = versiondata.getint('DEFAULT', 'timestamp') -today = time.strftime('%B %d, %Y', time.localtime(today_seconds)) -year = today.split()[-1] -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' -# substitute YEAR in the copyright string -copyright = copyright.replace('%Y', year) - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['diffpy.srfit.'] - -# Display all warnings for missing links. -nitpicky = True - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'sphinx_py3doc_enhanced_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { - 'collapsiblesidebar' : 'true', - 'navigation_with_keys' : 'true', -} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -html_split_index = True - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'srfitdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -# 'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -# 'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -# 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'srfit_manual.tex', u'diffpy.srfit documentation', - ab_authors, 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'diffpy.srfit', u'diffpy.srfit documentation', - ab_authors, 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'diffpy.srfit', u'diffpy.srfit documentation', - ab_authors, 'diffpy.srfit', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'numpy': ('https://docs.scipy.org/doc/numpy', None), - 'python' : ('https://docs.python.org/3.7', None), -} diff --git a/doc/manual/source/api/diffpy.srfit.equation.literals.rst b/doc/source/api/diffpy.srfit.equation.literals.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.equation.literals.rst rename to doc/source/api/diffpy.srfit.equation.literals.rst diff --git a/doc/manual/source/api/diffpy.srfit.equation.rst b/doc/source/api/diffpy.srfit.equation.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.equation.rst rename to doc/source/api/diffpy.srfit.equation.rst diff --git a/doc/manual/source/api/diffpy.srfit.equation.visitors.rst b/doc/source/api/diffpy.srfit.equation.visitors.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.equation.visitors.rst rename to doc/source/api/diffpy.srfit.equation.visitors.rst diff --git a/doc/manual/source/api/diffpy.srfit.fitbase.rst b/doc/source/api/diffpy.srfit.fitbase.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.fitbase.rst rename to doc/source/api/diffpy.srfit.fitbase.rst diff --git a/doc/manual/source/api/diffpy.srfit.interface.rst b/doc/source/api/diffpy.srfit.interface.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.interface.rst rename to doc/source/api/diffpy.srfit.interface.rst diff --git a/doc/manual/source/api/diffpy.srfit.pdf.rst b/doc/source/api/diffpy.srfit.pdf.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.pdf.rst rename to doc/source/api/diffpy.srfit.pdf.rst diff --git a/doc/manual/source/api/diffpy.srfit.sas.rst b/doc/source/api/diffpy.srfit.sas.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.sas.rst rename to doc/source/api/diffpy.srfit.sas.rst diff --git a/doc/manual/source/api/diffpy.srfit.structure.rst b/doc/source/api/diffpy.srfit.structure.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.structure.rst rename to doc/source/api/diffpy.srfit.structure.rst diff --git a/doc/manual/source/api/diffpy.srfit.util.rst b/doc/source/api/diffpy.srfit.util.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.util.rst rename to doc/source/api/diffpy.srfit.util.rst diff --git a/doc/manual/source/examples.rst b/doc/source/examples.rst similarity index 100% rename from doc/manual/source/examples.rst rename to doc/source/examples.rst diff --git a/doc/manual/source/examples/coreshellnp.rst b/doc/source/examples/coreshellnp.rst similarity index 100% rename from doc/manual/source/examples/coreshellnp.rst rename to doc/source/examples/coreshellnp.rst diff --git a/doc/manual/source/examples/crystalpdf.rst b/doc/source/examples/crystalpdf.rst similarity index 100% rename from doc/manual/source/examples/crystalpdf.rst rename to doc/source/examples/crystalpdf.rst diff --git a/doc/manual/source/examples/crystalpdfall.rst b/doc/source/examples/crystalpdfall.rst similarity index 100% rename from doc/manual/source/examples/crystalpdfall.rst rename to doc/source/examples/crystalpdfall.rst diff --git a/doc/manual/source/examples/crystalpdfobjcryst.rst b/doc/source/examples/crystalpdfobjcryst.rst similarity index 100% rename from doc/manual/source/examples/crystalpdfobjcryst.rst rename to doc/source/examples/crystalpdfobjcryst.rst diff --git a/doc/manual/source/examples/crystalpdftwodata.rst b/doc/source/examples/crystalpdftwodata.rst similarity index 100% rename from doc/manual/source/examples/crystalpdftwodata.rst rename to doc/source/examples/crystalpdftwodata.rst diff --git a/doc/manual/source/examples/crystalpdftwophase.rst b/doc/source/examples/crystalpdftwophase.rst similarity index 100% rename from doc/manual/source/examples/crystalpdftwophase.rst rename to doc/source/examples/crystalpdftwophase.rst diff --git a/doc/manual/source/examples/debyemodel.rst b/doc/source/examples/debyemodel.rst similarity index 100% rename from doc/manual/source/examples/debyemodel.rst rename to doc/source/examples/debyemodel.rst diff --git a/doc/manual/source/examples/debyemodelII.rst b/doc/source/examples/debyemodelII.rst similarity index 100% rename from doc/manual/source/examples/debyemodelII.rst rename to doc/source/examples/debyemodelII.rst diff --git a/doc/manual/source/examples/ellipsoidsas.rst b/doc/source/examples/ellipsoidsas.rst similarity index 100% rename from doc/manual/source/examples/ellipsoidsas.rst rename to doc/source/examples/ellipsoidsas.rst diff --git a/doc/manual/source/examples/gaussiangenerator.rst b/doc/source/examples/gaussiangenerator.rst similarity index 100% rename from doc/manual/source/examples/gaussiangenerator.rst rename to doc/source/examples/gaussiangenerator.rst diff --git a/doc/manual/source/examples/gaussianrecipe.rst b/doc/source/examples/gaussianrecipe.rst similarity index 100% rename from doc/manual/source/examples/gaussianrecipe.rst rename to doc/source/examples/gaussianrecipe.rst diff --git a/doc/manual/source/examples/interface.rst b/doc/source/examples/interface.rst similarity index 100% rename from doc/manual/source/examples/interface.rst rename to doc/source/examples/interface.rst diff --git a/doc/manual/source/examples/npintensity.rst b/doc/source/examples/npintensity.rst similarity index 100% rename from doc/manual/source/examples/npintensity.rst rename to doc/source/examples/npintensity.rst diff --git a/doc/manual/source/examples/npintensityII.rst b/doc/source/examples/npintensityII.rst similarity index 100% rename from doc/manual/source/examples/npintensityII.rst rename to doc/source/examples/npintensityII.rst diff --git a/doc/manual/source/examples/nppdfcrystal.rst b/doc/source/examples/nppdfcrystal.rst similarity index 100% rename from doc/manual/source/examples/nppdfcrystal.rst rename to doc/source/examples/nppdfcrystal.rst diff --git a/doc/manual/source/examples/nppdfobjcryst.rst b/doc/source/examples/nppdfobjcryst.rst similarity index 100% rename from doc/manual/source/examples/nppdfobjcryst.rst rename to doc/source/examples/nppdfobjcryst.rst diff --git a/doc/manual/source/examples/nppdfsas.rst b/doc/source/examples/nppdfsas.rst similarity index 100% rename from doc/manual/source/examples/nppdfsas.rst rename to doc/source/examples/nppdfsas.rst diff --git a/doc/manual/source/examples/simplepdf.rst b/doc/source/examples/simplepdf.rst similarity index 100% rename from doc/manual/source/examples/simplepdf.rst rename to doc/source/examples/simplepdf.rst diff --git a/doc/manual/source/examples/simplepdftwophase.rst b/doc/source/examples/simplepdftwophase.rst similarity index 100% rename from doc/manual/source/examples/simplepdftwophase.rst rename to doc/source/examples/simplepdftwophase.rst diff --git a/doc/manual/source/examples/simplerecipe.rst b/doc/source/examples/simplerecipe.rst similarity index 100% rename from doc/manual/source/examples/simplerecipe.rst rename to doc/source/examples/simplerecipe.rst diff --git a/doc/manual/source/extending.rst b/doc/source/extending.rst similarity index 100% rename from doc/manual/source/extending.rst rename to doc/source/extending.rst diff --git a/doc/manual/source/index.rst b/doc/source/index.rst similarity index 100% rename from doc/manual/source/index.rst rename to doc/source/index.rst diff --git a/doc/manual/source/license.rst b/doc/source/license.rst similarity index 100% rename from doc/manual/source/license.rst rename to doc/source/license.rst diff --git a/doc/manual/source/release.rst b/doc/source/release.rst similarity index 100% rename from doc/manual/source/release.rst rename to doc/source/release.rst From 2c732c10b89b754284db59366dd4c0b9efffff29 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 08:59:35 -0400 Subject: [PATCH 10/45] chore:move over doc images --- doc/{images => source/img}/fitting_architecture.dia | Bin doc/{images => source/img}/fitting_architecture.png | Bin doc/{images => source/img}/overview.odg | Bin doc/{images => source/img}/partition_equation.dia | Bin doc/{images => source/img}/partition_equation.png | Bin doc/{images => source/img}/simple_equation.dia | Bin doc/{images => source/img}/simple_equation.png | Bin doc/{images => source/img}/visitor_interaction.dia | Bin doc/{images => source/img}/visitor_interaction.png | Bin 9 files changed, 0 insertions(+), 0 deletions(-) rename doc/{images => source/img}/fitting_architecture.dia (100%) rename doc/{images => source/img}/fitting_architecture.png (100%) rename doc/{images => source/img}/overview.odg (100%) rename doc/{images => source/img}/partition_equation.dia (100%) rename doc/{images => source/img}/partition_equation.png (100%) rename doc/{images => source/img}/simple_equation.dia (100%) rename doc/{images => source/img}/simple_equation.png (100%) rename doc/{images => source/img}/visitor_interaction.dia (100%) rename doc/{images => source/img}/visitor_interaction.png (100%) diff --git a/doc/images/fitting_architecture.dia b/doc/source/img/fitting_architecture.dia similarity index 100% rename from doc/images/fitting_architecture.dia rename to doc/source/img/fitting_architecture.dia diff --git a/doc/images/fitting_architecture.png b/doc/source/img/fitting_architecture.png similarity index 100% rename from doc/images/fitting_architecture.png rename to doc/source/img/fitting_architecture.png diff --git a/doc/images/overview.odg b/doc/source/img/overview.odg similarity index 100% rename from doc/images/overview.odg rename to doc/source/img/overview.odg diff --git a/doc/images/partition_equation.dia b/doc/source/img/partition_equation.dia similarity index 100% rename from doc/images/partition_equation.dia rename to doc/source/img/partition_equation.dia diff --git a/doc/images/partition_equation.png b/doc/source/img/partition_equation.png similarity index 100% rename from doc/images/partition_equation.png rename to doc/source/img/partition_equation.png diff --git a/doc/images/simple_equation.dia b/doc/source/img/simple_equation.dia similarity index 100% rename from doc/images/simple_equation.dia rename to doc/source/img/simple_equation.dia diff --git a/doc/images/simple_equation.png b/doc/source/img/simple_equation.png similarity index 100% rename from doc/images/simple_equation.png rename to doc/source/img/simple_equation.png diff --git a/doc/images/visitor_interaction.dia b/doc/source/img/visitor_interaction.dia similarity index 100% rename from doc/images/visitor_interaction.dia rename to doc/source/img/visitor_interaction.dia diff --git a/doc/images/visitor_interaction.png b/doc/source/img/visitor_interaction.png similarity index 100% rename from doc/images/visitor_interaction.png rename to doc/source/img/visitor_interaction.png From a9e898920430abf9c251986ea8096f25d8aae5fe Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 09:05:46 -0400 Subject: [PATCH 11/45] chore: delete conda-recipe, setup.py and devutils from git database --- conda-recipe/bld.bat | 7 -- conda-recipe/build.sh | 8 -- conda-recipe/conda_build_config.yaml | 5 -- conda-recipe/meta.yaml | 66 -------------- conda-recipe/run_test.py | 4 - devutils/makesdist | 54 ------------ setup.py | 127 --------------------------- 7 files changed, 271 deletions(-) delete mode 100644 conda-recipe/bld.bat delete mode 100644 conda-recipe/build.sh delete mode 100644 conda-recipe/conda_build_config.yaml delete mode 100644 conda-recipe/meta.yaml delete mode 100644 conda-recipe/run_test.py delete mode 100755 devutils/makesdist delete mode 100755 setup.py diff --git a/conda-recipe/bld.bat b/conda-recipe/bld.bat deleted file mode 100644 index 0a79fa23..00000000 --- a/conda-recipe/bld.bat +++ /dev/null @@ -1,7 +0,0 @@ -"%PYTHON%" setup.py install -if errorlevel 1 exit 1 - -:: Add more build steps here, if they are necessary. - -:: See http://docs.continuum.io/conda/build.html -:: for a list of environment variables that are set during the build process. diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh deleted file mode 100644 index b7920393..00000000 --- a/conda-recipe/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -$PYTHON setup.py install - -# Add more build steps here, if they are necessary. - -# See http://docs.continuum.io/conda/build.html -# for a list of environment variables that are set during the build process. diff --git a/conda-recipe/conda_build_config.yaml b/conda-recipe/conda_build_config.yaml deleted file mode 100644 index 8d446529..00000000 --- a/conda-recipe/conda_build_config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -python: - - 3.7 - - 3.6 - - 3.5 - - 2.7 diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml deleted file mode 100644 index d3c86a23..00000000 --- a/conda-recipe/meta.yaml +++ /dev/null @@ -1,66 +0,0 @@ -{% set setupdata = load_setup_py_data() %} - -package: - name: diffpy.srfit - version: {{ setupdata['version'] }} - -source: - git_url: .. - -build: - preserve_egg_dir: True - - # If this is a new build for the same version, increment the build - # number. If you do not include this key, it defaults to 0. - # number: 0 - -requirements: - build: - - python {{ python }} - - setuptools - - six - - run: - - python - - setuptools - - numpy >=1.11 - - six - -test: - # Python imports - imports: - - diffpy.srfit - - diffpy.srfit.equation - - diffpy.srfit.equation.literals - - diffpy.srfit.equation.visitors - - diffpy.srfit.fitbase - - diffpy.srfit.interface - - diffpy.srfit.pdf - - diffpy.srfit.sas - - diffpy.srfit.structure - - diffpy.srfit.tests - - diffpy.srfit.util - - commands: - # You can put test commands to be run here. Use this to test that the - # entry points work. - - # You can also put a file called run_test.py in the recipe that will be run - # at test time. - - requires: - # Put any additional test requirements here. For example - - diffpy.structure - - pyobjcryst - - diffpy.srreal - # FIXME - correct when packages become available for Python 3. - - srfit-sasview # [py2k] - -about: - home: https://github.com/diffpy/diffpy.srfit/ - summary: Framework for complex modeling and atomic structure optimization. - license: Modified BSD License - license_file: LICENSE.txt - -# See http://docs.continuum.io/conda/build.html -# for more information about meta.yaml. diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py deleted file mode 100644 index 545d32fa..00000000 --- a/conda-recipe/run_test.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python - -import diffpy.srfit.tests -assert diffpy.srfit.tests.test().wasSuccessful() diff --git a/devutils/makesdist b/devutils/makesdist deleted file mode 100755 index 6aaae616..00000000 --- a/devutils/makesdist +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -'''Create source distribution tar.gz archive, where each file belongs -to a root user and modification time is set to the git commit time. -''' - -import sys -import os -import subprocess -import glob -import tarfile -import gzip - -BASEDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -sys.path.insert(0, BASEDIR) - -from setup import versiondata, FALLBACK_VERSION -timestamp = versiondata.getint('DEFAULT', 'timestamp') - -vfb = versiondata.get('DEFAULT', 'version').split('.post')[0] + '.post0' -emsg = "Invalid FALLBACK_VERSION. Expected %r got %r." -assert vfb == FALLBACK_VERSION, emsg % (vfb, FALLBACK_VERSION) - -def inform(s): - sys.stdout.write(s) - sys.stdout.flush() - return - -inform('Run "setup.py sdist --formats=tar" ') -cmd_sdist = [sys.executable] + 'setup.py sdist --formats=tar'.split() -ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, 'w')) -if ec: sys.exit(ec) -inform("[done]\n") - -tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime) - -tfin = tarfile.open(tarname) -fpout = gzip.GzipFile(tarname + '.gz', 'w', mtime=0) -tfout = tarfile.open(fileobj=fpout, mode='w') - -def fixtarinfo(tinfo): - tinfo.uid = tinfo.gid = 0 - tinfo.uname = tinfo.gname = 'root' - tinfo.mtime = timestamp - tinfo.mode &= ~0o022 - return tinfo - -inform('Filter %s --> %s.gz ' % (2 * (os.path.basename(tarname),))) -for ti in tfin: - tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti)) - -tfin.close() -os.remove(tarname) -inform("[done]\n") diff --git a/setup.py b/setup.py deleted file mode 100755 index d3577dd2..00000000 --- a/setup.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python - -# Installation script for diffpy.srfit - -"""diffpy.srfit - framework for setting up complex modeling refinements. - -Packages: diffpy.srfit -""" - -import os -import re -import sys -from setuptools import setup, find_packages - -# Use this version when git data are not available, like in git zip archive. -# Update when tagging a new release. -FALLBACK_VERSION = '3.0.0.post0' - -# determine if we run with Python 3. -PY3 = (sys.version_info[0] == 3) - -# versioncfgfile holds version data for git commit hash and date. -# It must reside in the same directory as version.py. -MYDIR = os.path.dirname(os.path.abspath(__file__)) -versioncfgfile = os.path.join(MYDIR, 'src/diffpy/srfit/version.cfg') -gitarchivecfgfile = os.path.join(MYDIR, '.gitarchive.cfg') - -def gitinfo(): - from subprocess import Popen, PIPE - kw = dict(stdout=PIPE, cwd=MYDIR, universal_newlines=True) - proc = Popen(['git', 'describe', '--match=v[[:digit:]]*'], **kw) - desc = proc.stdout.read() - proc = Popen(['git', 'log', '-1', '--format=%H %ct %ci'], **kw) - glog = proc.stdout.read() - rv = {} - rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v') - rv['commit'], rv['timestamp'], rv['date'] = glog.strip().split(None, 2) - return rv - - -def getversioncfg(): - if PY3: - from configparser import RawConfigParser - else: - from ConfigParser import RawConfigParser - vd0 = dict(version=FALLBACK_VERSION, commit='', date='', timestamp=0) - # first fetch data from gitarchivecfgfile, ignore if it is unexpanded - g = vd0.copy() - cp0 = RawConfigParser(vd0) - cp0.read(gitarchivecfgfile) - if len(cp0.get('DEFAULT', 'commit')) > 20: - g = cp0.defaults() - mx = re.search(r'\btag: v(\d[^,]*)', g.pop('refnames')) - if mx: - g['version'] = mx.group(1) - # then try to obtain version data from git. - gitdir = os.path.join(MYDIR, '.git') - if os.path.exists(gitdir) or 'GIT_DIR' in os.environ: - try: - g = gitinfo() - except OSError: - pass - # finally, check and update the active version file - cp = RawConfigParser() - cp.read(versioncfgfile) - d = cp.defaults() - rewrite = not d or (g['commit'] and ( - g['version'] != d.get('version') or g['commit'] != d.get('commit'))) - if rewrite: - cp.set('DEFAULT', 'version', g['version']) - cp.set('DEFAULT', 'commit', g['commit']) - cp.set('DEFAULT', 'date', g['date']) - cp.set('DEFAULT', 'timestamp', g['timestamp']) - with open(versioncfgfile, 'w') as fp: - cp.write(fp) - return cp - -versiondata = getversioncfg() - -with open(os.path.join(MYDIR, 'README.rst')) as fp: - long_description = fp.read() - -# define distribution -setup_args = dict( - name = "diffpy.srfit", - version = versiondata.get('DEFAULT', 'version'), - packages = find_packages(os.path.join(MYDIR, 'src')), - package_dir = {'' : 'src'}, - test_suite = 'diffpy.srfit.tests', - include_package_data = True, - install_requires = ['six'], - zip_safe = False, - author = "Simon J.L. Billinge", - author_email = "sb2896@columbia.edu", - maintainer = "Pavol Juhas", - maintainer_email = "pavol.juhas@gmail.com", - description = "SrFit - Structure refinement from diffraction data", - long_description = long_description, - long_description_content_type = 'text/x-rst', - license = 'BSD-style license', - url = "https://github.com/diffpy/diffpy.srfit", - keywords = "optimization constraints restraints structure refinement complex modeling", - classifiers = [ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Scientific/Engineering :: Chemistry', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Software Development :: Libraries', - ], -) - -if __name__ == '__main__': - setup(**setup_args) - -# End of file From 5c068f387a62ad23c6e294d72d89ce4f5f1b91a1 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 09:14:17 -0400 Subject: [PATCH 12/45] chore: update all the inits --- src/diffpy/__init__.py | 26 +++++------------ src/diffpy/srfit/__init__.py | 20 ++++++------- src/diffpy/srfit/version.py | 55 +++++++++--------------------------- 3 files changed, 30 insertions(+), 71 deletions(-) diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 11098213..406751a6 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,26 +1,14 @@ #!/usr/bin/env python ############################################################################## # -# diffpy by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2008-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and Billinge Group members and community contributors. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srfit/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## - -"""diffpy - tools for structure analysis by diffraction. - -Blank namespace package. -""" - - -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) - - -# End of file diff --git a/src/diffpy/srfit/__init__.py b/src/diffpy/srfit/__init__.py index 76d70de4..203c1e15 100644 --- a/src/diffpy/srfit/__init__.py +++ b/src/diffpy/srfit/__init__.py @@ -1,18 +1,17 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2008-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Christopher Farrow, Pavol Juhas, and members of the Billinge Group. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srfit/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## - """Complex modeling framework for structure refinement and solution. SrFit is a tool for coherently combining known information about a material to @@ -31,9 +30,10 @@ learn how to use and customize the various parts of SrFit. """ -__all__ = ["__version__"] - # package version from diffpy.srfit.version import __version__ +# silence the pyflakes syntax checker +assert __version__ or True + # End of file diff --git a/src/diffpy/srfit/version.py b/src/diffpy/srfit/version.py index 406e31bb..a0caae47 100644 --- a/src/diffpy/srfit/version.py +++ b/src/diffpy/srfit/version.py @@ -1,54 +1,25 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2008-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Christopher Farrow, Pavol Juhas, and members of the Billinge Group. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srfit/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## +"""Definition of __version__.""" -"""Definition of __version__, __date__, __timestamp__, __git_commit__. - -Notes ------ -Variable `__gitsha__` is deprecated as of version 3.0. -Use `__git_commit__` instead. -""" - -__all__ = ['__date__', '__git_commit__', '__timestamp__', '__version__'] - -import os.path - -from pkg_resources import resource_filename - - -# obtain version information from the version.cfg file -cp = dict(version='', date='', commit='', timestamp='0') -fcfg = resource_filename(__name__, 'version.cfg') -if not os.path.isfile(fcfg): # pragma: no cover - from warnings import warn - warn('Package metadata not found, execute "./setup.py egg_info".') - fcfg = os.devnull -with open(fcfg) as fp: - kwords = [[w.strip() for w in line.split(' = ', 1)] - for line in fp if line[:1].isalpha() and ' = ' in line] -assert all(w[0] in cp for w in kwords), "received unrecognized keyword" -cp.update(kwords) - -__version__ = cp['version'] -__date__ = cp['date'] -__git_commit__ = cp['commit'] -__timestamp__ = int(cp['timestamp']) +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] -# TODO remove deprecated __gitsha__ in version 3.1. -__gitsha__ = __git_commit__ +# obtain version information +from importlib.metadata import version -del cp, fcfg, fp, kwords +__version__ = version("diffpy.srfit") # End of file From 4f03fbde6db14c142a3b1c6c6c05640d51856eae Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 09:24:09 -0400 Subject: [PATCH 13/45] chore: add new files from skpkg to database --- .codespell/ignore_lines.txt | 2 + .codespell/ignore_words.txt | 8 ++ .flake8 | 13 +++ .github/ISSUE_TEMPLATE/bug_feature.md | 16 ++++ .github/ISSUE_TEMPLATE/release_checklist.md | 46 ++++++++++ .../pull_request_template.md | 15 ++++ .../workflows/build-wheel-release-upload.yml | 18 ++++ .github/workflows/check-news-item.yml | 12 +++ .../matrix-and-codecov-on-merge-to-main.yml | 21 +++++ .github/workflows/publish-docs-on-release.yml | 12 +++ .github/workflows/tests-on-pr.yml | 15 ++++ .gitignore | 89 ++++++++++++++----- .isort.cfg | 5 ++ .pre-commit-config.yaml | 66 ++++++++++++++ .readthedocs.yaml | 13 +++ pyproject.toml | 80 +++++++++++++++++ 16 files changed, 411 insertions(+), 20 deletions(-) create mode 100644 .codespell/ignore_lines.txt create mode 100644 .codespell/ignore_words.txt create mode 100644 .flake8 create mode 100644 .github/ISSUE_TEMPLATE/bug_feature.md create mode 100644 .github/ISSUE_TEMPLATE/release_checklist.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md create mode 100644 .github/workflows/build-wheel-release-upload.yml create mode 100644 .github/workflows/check-news-item.yml create mode 100644 .github/workflows/matrix-and-codecov-on-merge-to-main.yml create mode 100644 .github/workflows/publish-docs-on-release.yml create mode 100644 .github/workflows/tests-on-pr.yml create mode 100644 .isort.cfg create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yaml create mode 100644 pyproject.toml diff --git a/.codespell/ignore_lines.txt b/.codespell/ignore_lines.txt new file mode 100644 index 00000000..07fa7c8c --- /dev/null +++ b/.codespell/ignore_lines.txt @@ -0,0 +1,2 @@ +;; Please include filenames and explanations for each ignored line. +;; See https://docs.openverse.org/meta/codespell.html for docs. diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt new file mode 100644 index 00000000..04b4fcfa --- /dev/null +++ b/.codespell/ignore_words.txt @@ -0,0 +1,8 @@ +;; Please include explanations for each ignored word (lowercase). +;; See https://docs.openverse.org/meta/codespell.html for docs. + +;; abbreviation for "materials" often used in a journal title +mater + +;; Frobenius norm used in np.linalg.norm +fro diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..7b2865c1 --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + doc/source/conf.py +max-line-length = 79 +# Ignore some style 'errors' produced while formatting by 'black' +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +extend-ignore = E203 diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md new file mode 100644 index 00000000..b3454deb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_feature.md @@ -0,0 +1,16 @@ +--- +name: Bug Report or Feature Request +about: Report a bug or suggest a new feature! +title: "" +labels: "" +assignees: "" +--- + +### Problem + + + +### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 00000000..6107962c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,46 @@ +--- +name: Release +about: Checklist and communication channel for PyPI and GitHub release +title: "Ready for PyPI/GitHub release" +labels: "release" +assignees: "" +--- + +### PyPI/GitHub rc-release preparation checklist: + +- [ ] All PRs/issues attached to the release are merged. +- [ ] All the badges on the README are passing. +- [ ] License information is verified as correct. If you are unsure, please comment below. +- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are + missing), tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation, and the website are updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. +- [ ] Grammar and writing quality are checked (no typos). +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. + +Please tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: + +### PyPI/GitHub full-release preparation checklist: + +- [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) +- [ ] License information on PyPI is correct. +- [ ] Docs are deployed successfully to `https:///`. +- [ ] Successfully run all tests, tutorial examples or do functional testing. + +Please let the maintainer know that all checks are done and the package is ready for full release. + +### conda-forge release preparation checklist: + + + +- [ ] Ensure that the full release has appeared on PyPI successfully. +- [ ] New package dependencies listed in `conda.txt` and `test.txt` are added to `meta.yaml` in the feedstock. +- [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. +- [ ] Tag the maintainer for conda-forge release. + +### Post-release checklist + + + +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..1099d862 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml new file mode 100644 index 00000000..4d66f1b5 --- /dev/null +++ b/.github/workflows/build-wheel-release-upload.yml @@ -0,0 +1,18 @@ +name: Release (GitHub/PyPI) and Deploy Docs + +on: + workflow_dispatch: + push: + tags: + - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + +jobs: + release: + uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + with: + project: diffpy.srfit + c_extension: false + maintainer_GITHUB_username: sbillinge + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml new file mode 100644 index 00000000..bd352c24 --- /dev/null +++ b/.github/workflows/check-news-item.yml @@ -0,0 +1,12 @@ +name: Check for News + +on: + pull_request_target: + branches: + - main + +jobs: + check-news-item: + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 + with: + project: diffpy.srfit diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml new file mode 100644 index 00000000..c4a683c5 --- /dev/null +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: + - main + release: + types: + - prereleased + - published + workflow_dispatch: + +jobs: + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + with: + project: diffpy.srfit + c_extension: false + headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 00000000..54922a28 --- /dev/null +++ b/.github/workflows/publish-docs-on-release.yml @@ -0,0 +1,12 @@ +name: Deploy Documentation on Release + +on: + workflow_dispatch: + +jobs: + docs: + uses: scikit-package/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + with: + project: diffpy.srfit + c_extension: false + headless: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml new file mode 100644 index 00000000..190b19cc --- /dev/null +++ b/.github/workflows/tests-on-pr.yml @@ -0,0 +1,15 @@ +name: Tests on PR + +on: + pull_request: + workflow_dispatch: + +jobs: + tests-on-pr: + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + with: + project: diffpy.srfit + c_extension: false + headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 9f5ecbf0..099e2948 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,93 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] +*$py.class # C extensions *.so -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -temp -develop-eggs +# Distribution / packaging +.Python +env/ +build/ +_build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +venv/ +*.egg-info/ .installed.cfg -lib -lib64 -tags +*.egg +bin/ +temp/ +tags/ errors.err +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + # Installer logs pip-log.txt +pip-delete-this-directory.txt MANIFEST # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache nosetests.xml +coverage.xml +*,cover +.hypothesis/ # Translations *.mo +*.pot # Mr Developer .mr.developer.cfg .project .pydevproject -.settings -# version information -setup.cfg -/src/diffpy/*/version.cfg +# Django stuff: +*.log + +# Sphinx documentation +docs/build/ +docs/source/generated/ + +# pytest +.pytest_cache/ + +# PyBuilder +target/ + +# Editor files +# mac +.DS_Store +*~ + +# vim +*.swp +*.swo + +# pycharm +.idea/ + +# VSCode +.vscode/ + +# Ipython Notebook +.ipynb_checkpoints diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..86f162b8 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,5 @@ +[settings] +# Keep import statement below line_length character limit +line_length = 79 +multi_line_output = 3 +include_trailing_comma = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..0e4a84d1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,66 @@ +default_language_version: + python: python3 +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: "pre-commit-autoupdate" + autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-added-large-files + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + name: Prevent Commit to Main Branch + args: ["--branch", "main"] + stages: [pre-commit] + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + additional_dependencies: + - tomli + # prettier - multi formatter for .json, .yml, and .md files + - repo: https://github.com/pre-commit/mirrors-prettier + rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + hooks: + - id: prettier + additional_dependencies: + - "prettier@^3.2.4" + # docformatter - PEP 257 compliant docstring formatter + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..47f7a017 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "latest" + +python: + install: + - requirements: requirements/docs.txt + +sphinx: + configuration: doc/source/conf.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3bdcbb03 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning>=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.srfit" +dynamic=['version', 'dependencies'] +authors = [ + { name="Simon Billinge", email="sb2896@columbia.edu" }, +] +maintainers = [ + { name="Simon Billinge", email="sb2896@columbia.edu" }, +] +description = "Configurable code for solving atomic structures." +keywords = ['regression', 'modelling', 'fitting', 'diffraction', 'PDF'] +readme = "README.rst" +requires-python = ">=3.11, <3.14" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.srfit/" +Issues = "https://github.com/diffpy/diffpy.srfit/issues/" + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements/pip.txt"]} + +[tool.codespell] +exclude-file = ".codespell/ignore_lines.txt" +ignore-words = ".codespell/ignore_words.txt" +skip = "*.cif,*.dat" + +[tool.black] +line-length = 79 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.rst + | \.txt + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' From b0008f2e936a607f255dbc4d0f0cf9c5b7ede75d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:25:04 +0000 Subject: [PATCH 14/45] [pre-commit.ci] auto fixes from pre-commit hooks --- .codecov.yml | 6 +- .travis.yml | 43 +- doc/Makefile | 1 - doc/examples/README | 156 +++---- doc/examples/coreshellnp.py | 34 +- doc/examples/crystalpdf.py | 48 +- doc/examples/crystalpdfall.py | 90 ++-- doc/examples/crystalpdfobjcryst.py | 27 +- doc/examples/crystalpdftwodata.py | 50 ++- doc/examples/crystalpdftwophase.py | 57 +-- doc/examples/data/ni.iq | 1 - doc/examples/debyemodel.py | 95 ++-- doc/examples/debyemodelII.py | 43 +- doc/examples/ellipsoidsas.py | 27 +- doc/examples/gaussiangenerator.py | 53 +-- doc/examples/gaussianrecipe.py | 48 +- doc/examples/interface.py | 19 +- doc/examples/npintensity.py | 124 ++--- doc/examples/npintensityII.py | 112 ++--- doc/examples/nppdfcrystal.py | 48 +- doc/examples/nppdfobjcryst.py | 36 +- doc/examples/nppdfsas.py | 63 +-- doc/examples/simplepdf.py | 22 +- doc/examples/simplepdftwophase.py | 31 +- doc/examples/simplerecipe.py | 4 +- doc/examples/threedoublepeaks.py | 107 +++-- doc/source/conf.py | 4 +- src/diffpy/srfit/__init__.py | 27 +- src/diffpy/srfit/equation/__init__.py | 12 +- src/diffpy/srfit/equation/builder.py | 171 ++++--- src/diffpy/srfit/equation/equationmod.py | 70 ++- .../srfit/equation/literals/__init__.py | 75 ++-- src/diffpy/srfit/equation/literals/abcs.py | 16 +- .../srfit/equation/literals/argument.py | 13 +- src/diffpy/srfit/equation/literals/literal.py | 13 +- .../srfit/equation/literals/operators.py | 66 +-- .../srfit/equation/visitors/__init__.py | 23 +- .../srfit/equation/visitors/argfinder.py | 4 +- src/diffpy/srfit/equation/visitors/printer.py | 23 +- src/diffpy/srfit/equation/visitors/swapper.py | 10 +- .../srfit/equation/visitors/validator.py | 26 +- src/diffpy/srfit/equation/visitors/visitor.py | 10 +- src/diffpy/srfit/exceptions.py | 3 +- src/diffpy/srfit/fitbase/__init__.py | 35 +- src/diffpy/srfit/fitbase/calculator.py | 30 +- src/diffpy/srfit/fitbase/configurable.py | 6 +- src/diffpy/srfit/fitbase/constraint.py | 22 +- src/diffpy/srfit/fitbase/fitcontribution.py | 57 ++- src/diffpy/srfit/fitbase/fithook.py | 54 ++- src/diffpy/srfit/fitbase/fitrecipe.py | 215 +++++---- src/diffpy/srfit/fitbase/fitresults.py | 139 +++--- src/diffpy/srfit/fitbase/parameter.py | 38 +- src/diffpy/srfit/fitbase/parameterset.py | 4 +- src/diffpy/srfit/fitbase/profile.py | 117 +++-- src/diffpy/srfit/fitbase/profilegenerator.py | 60 ++- src/diffpy/srfit/fitbase/profileparser.py | 13 +- src/diffpy/srfit/fitbase/recipeorganizer.py | 189 ++++---- src/diffpy/srfit/fitbase/restraint.py | 17 +- src/diffpy/srfit/fitbase/simplerecipe.py | 43 +- src/diffpy/srfit/fitbase/validatable.py | 15 +- src/diffpy/srfit/interface/__init__.py | 10 +- src/diffpy/srfit/interface/interface.py | 37 +- src/diffpy/srfit/pdf/__init__.py | 3 +- src/diffpy/srfit/pdf/basepdfgenerator.py | 47 +- .../srfit/pdf/characteristicfunctions.py | 209 ++++++--- src/diffpy/srfit/pdf/debyepdfgenerator.py | 17 +- src/diffpy/srfit/pdf/pdfcontribution.py | 30 +- src/diffpy/srfit/pdf/pdfgenerator.py | 16 +- src/diffpy/srfit/pdf/pdfparser.py | 47 +- src/diffpy/srfit/sas/__init__.py | 12 +- src/diffpy/srfit/sas/prcalculator.py | 16 +- src/diffpy/srfit/sas/sasgenerator.py | 2 +- src/diffpy/srfit/sas/sasimport.py | 25 +- src/diffpy/srfit/sas/sasparameter.py | 6 +- src/diffpy/srfit/sas/sasparser.py | 7 +- src/diffpy/srfit/sas/sasprofile.py | 3 +- src/diffpy/srfit/structure/__init__.py | 5 +- .../srfit/structure/basestructureparset.py | 23 +- src/diffpy/srfit/structure/bvsrestraint.py | 19 +- src/diffpy/srfit/structure/cctbxparset.py | 98 ++-- src/diffpy/srfit/structure/diffpyparset.py | 76 ++-- src/diffpy/srfit/structure/objcrystparset.py | 425 +++++++++++------- src/diffpy/srfit/structure/sgconstraints.py | 232 +++++++--- src/diffpy/srfit/structure/srrealparset.py | 13 +- src/diffpy/srfit/util/__init__.py | 9 +- src/diffpy/srfit/util/argbinders.py | 4 +- src/diffpy/srfit/util/inpututils.py | 4 +- src/diffpy/srfit/util/nameutils.py | 4 +- src/diffpy/srfit/util/observable.py | 3 + src/diffpy/srfit/util/tagmanager.py | 9 +- src/diffpy/srfit/util/weakrefcallable.py | 12 +- tests/__init__.py | 19 +- tests/debug.py | 7 +- tests/run.py | 9 +- tests/test_builder.py | 72 ++- tests/test_characteristicfunctions.py | 47 +- tests/test_constraint.py | 5 +- tests/test_contribution.py | 74 ++- tests/test_diffpyparset.py | 32 +- tests/test_equation.py | 37 +- tests/test_fitrecipe.py | 46 +- tests/test_fitresults.py | 10 +- tests/test_literals.py | 56 +-- tests/test_objcrystparset.py | 229 +++++----- tests/test_parameter.py | 26 +- tests/test_parameterset.py | 1 - tests/test_pdf.py | 154 ++++--- tests/test_profile.py | 72 +-- tests/test_profilegenerator.py | 9 +- tests/test_recipeorganizer.py | 137 +++--- tests/test_restraint.py | 8 +- tests/test_sas.py | 83 +++- tests/test_sgconstraints.py | 82 ++-- tests/test_speed.py | 154 ++++--- tests/test_tagmanager.py | 13 +- tests/test_visitors.py | 8 +- tests/test_weakrefcallable.py | 37 +- tests/utils.py | 39 +- 118 files changed, 3316 insertions(+), 2538 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 70686df3..d6c556b8 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -10,7 +10,7 @@ coverage: status: patch: default: - target: '80' + target: "80" if_no_uploads: error if_not_found: success if_ci_failed: failure @@ -21,11 +21,11 @@ coverage: if_no_uploads: error if_not_found: success if_ci_failed: failure - paths: '!*/tests/.*' + paths: "!*/tests/.*" tests: target: 97.9% - paths: '*/tests/.*' + paths: "*/tests/.*" flags: tests: diff --git a/.travis.yml b/.travis.yml index a3469af5..3de77be9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,6 @@ branches: except: - /^v[0-9]/ - before_install: - MYNAME=diffpy.srfit - MYCOMMIT="$(git rev-parse HEAD)" @@ -28,25 +27,25 @@ before_install: - MYPYTHON=python; MYPIP=pip - NOSYS=true; NOAPT=true; NOBREW=true; NOMC=true - if ${MYUSEMC}; then - NOMC=false; + NOMC=false; elif [[ ${TRAVIS_OS_NAME} == linux ]]; then - NOAPT=false; NOSYS=false; - MYPIPFLAGS="--user"; + NOAPT=false; NOSYS=false; + MYPIPFLAGS="--user"; elif [[ ${TRAVIS_OS_NAME} == osx ]]; then - NOBREW=false; NOSYS=false; - MYPYTHON=python3; - MYPIP=pip3; - MYPIPFLAGS="--user"; + NOBREW=false; NOSYS=false; + MYPYTHON=python3; + MYPIP=pip3; + MYPIPFLAGS="--user"; fi - MYMCREPO=https://repo.anaconda.com/miniconda - case ${TRAVIS_OS_NAME} in linux) - MYMCBUNDLE=Miniconda3-latest-Linux-x86_64.sh ;; + MYMCBUNDLE=Miniconda3-latest-Linux-x86_64.sh ;; osx) - MYMCBUNDLE=Miniconda3-latest-MacOSX-x86_64.sh ;; + MYMCBUNDLE=Miniconda3-latest-MacOSX-x86_64.sh ;; *) - echo "Unsupported operating system." >&2; - exit 2 ;; + echo "Unsupported operating system." >&2; + exit 2 ;; esac - MYRUNDIR=${PWD}/build/rundir @@ -67,11 +66,11 @@ before_install: - $NOAPT || test "${TRAVIS_OS_NAME}" = "linux" || exit $? - $NOAPT || PATH="$(echo "$PATH" | sed 's,:/opt/pyenv/[^:]*,,g')" - $NOAPT || test "$(which python)" = "/usr/bin/python" || ( - which python; exit 1) + which python; exit 1) - $NOAPT || sudo apt-get update -qq - $NOAPT || sudo apt-get install -y - python-dev python-setuptools python-numpy - build-essential + python-dev python-setuptools python-numpy + build-essential - $NOBREW || test "${TRAVIS_OS_NAME}" = "osx" || exit $? - $NOBREW || brew update @@ -81,18 +80,17 @@ before_install: - $NOSYS || devutils/makesdist - $NOSYS || MYTARBUNDLE="$(ls -t "${PWD}"/dist/*.tar.gz | head -1)" - install: - $NOMC || conda build --python=${MYPYTHON_VERSION} conda-recipe - $NOMC || conda render --python=${MYPYTHON_VERSION} --output conda-recipe | - sed 's,.*/,,; s/[.]tar[.]bz2$//; s/-/=/g' > /tmp/mypackage.txt + sed 's,.*/,,; s/[.]tar[.]bz2$//; s/-/=/g' > /tmp/mypackage.txt - $NOMC || source activate testenv - $NOMC || conda install --yes --use-local --file=/tmp/mypackage.txt - $NOMC || conda install --yes - diffpy.structure pyobjcryst "diffpy.srreal>=1.3.0" + diffpy.structure pyobjcryst "diffpy.srreal>=1.3.0" # TODO - always install srfit-sasview when ready for Python 3. - if $MYUSEMC && [[ "$MYPYTHON_VERSION" == 2.7 ]]; then - conda install --yes srfit-sasview; + conda install --yes srfit-sasview; fi - $NOSYS || $MYPIP install $MYPIPFLAGS coverage @@ -103,20 +101,17 @@ install: - cd ${MYRUNDIR} - MYGIT_REV=$($MYPYTHON -c "import ${MYNAME}.version as v; print(v.__git_commit__)") - if [[ "${MYCOMMIT}" != "${MYGIT_REV}" ]]; then - echo "Version mismatch ${MYCOMMIT} vs ${MYGIT_REV}."; - exit 1; + echo "Version mismatch ${MYCOMMIT} vs ${MYGIT_REV}."; + exit 1; fi - before_script: - $NOBREW || USER_BASE="$(python3 -c 'import site; print(site.USER_BASE)')" - $NOBREW || PATH="${USER_BASE}/bin:${PATH}" - script: - coverage run --source ${MYNAME} -m ${MYNAME}.tests.run - after_success: # do not post coverage reports when testing with system Python. - $NOMC || $MYPIP install $MYPIPFLAGS codecov diff --git a/doc/Makefile b/doc/Makefile index b9c30f1e..a265fe5d 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -31,4 +31,3 @@ clean: rm -rf diffpy.srfitapi rm -f srfit_examples.zip $(MAKE) -C devmanual $@ - diff --git a/doc/examples/README b/doc/examples/README index 0d151e2d..92b43cb0 100644 --- a/doc/examples/README +++ b/doc/examples/README @@ -1,26 +1,23 @@ -Purpose ----------- +## Purpose These example scripts are intended to help a developer get acquainted with the SrFit programming interface. Although one can write scripts like these to drive optimization, these scripts do not represent the SrFit user interface. That interface will be made available in a future release. -A secondary purpose of these tutorials is to generate interest in SrFit. By +A secondary purpose of these tutorials is to generate interest in SrFit. By reading through the examples we hope that you are inspired to think of exciting new ways to get the most out of your scientific data. If you think SrFit can help you with that, please feel free to contact us through the DiffPy website. http://www.diffpy.org - -Overview ----------- +## Overview Three things are required for optimization: a function that generates a quantity to be minimized, variables that can be used to manipulate that function and an algorithm that can drive the function output to a smaller value -by steering the variables. For scientific purposes, the quantity to be +by steering the variables. For scientific purposes, the quantity to be minimized is the disagreement between a measured profile and a theoretical profile. The scientific understanding of the system under consideration partly determines the suitability of the theoretical profile generator and the @@ -30,38 +27,36 @@ The purpose of SrFit is to give users the means to combine known information about a system of interest in order to extract scientifically relevant quantities, and thus understanding from it. Various experimental procedures and theoretical calculations may be needed to gain the desired understanding of the -system. SrFit helps users combine these views of the system in a coherent and +system. SrFit helps users combine these views of the system in a coherent and consistent manner. To achieve this purpose, SrFit provides: -1) a function to be optimized (the residual) given one or more measured +1. a function to be optimized (the residual) given one or more measured profiles, one or more profile generators and variables to be steered by an optimizer. -2) constraints and restraints that encapsulate known information about the +2. constraints and restraints that encapsulate known information about the system. -3) a clearly defined programming interface that developers can use to add their +3. a clearly defined programming interface that developers can use to add their own profile generators, thereby enabling the combination of more views of a system. -4) an equation building interface that allows users to tweak profile generators +4. an equation building interface that allows users to tweak profile generators when scientific understanding of a system is more advanced than the existing software. The examples described below will go into detail about each of these points. +## Examples -Examples ----------- - -The following examples are contained in the *doc/examples/* directory of the -SrFit source distribution. They can be downloaded from +The following examples are contained in the _doc/examples/_ directory of the +SrFit source distribution. They can be downloaded from http://dev.danse.us/packages/srfit_examples-alpha9.zip For each example, start by running the example by typing in the command line :: python example.py -where *example.py* represents the example file. The output will show on screen +where _example.py_ represents the example file. The output will show on screen and a plot window will display. Once you've studied the output and plot, close the plot window and open the example file. In the file there will be a description of what the script is doing and the purpose of the example. By @@ -73,97 +68,94 @@ them in the order listed below. Basic: -* gaussianrecipe.py_ - Introductory recipe building and configuration. This introduces the - fundamental classes in SrFit. +- gaussianrecipe.py\_ + Introductory recipe building and configuration. This introduces the + fundamental classes in SrFit. -* debyemodel.py_ - Introductory recipe building and configuration. This shows how to use a - function created by someone else in a refinement. This example also - introduces restraints. +- debyemodel.py\_ + Introductory recipe building and configuration. This shows how to use a + function created by someone else in a refinement. This example also + introduces restraints. -* debyemodelII.py_ - Refine two different values of a variable from two different regions of a - profile. This example introduces constraints and working with multiple - contributions to a fit. +- debyemodelII.py\_ + Refine two different values of a variable from two different regions of a + profile. This example introduces constraints and working with multiple + contributions to a fit. Advanced: -* gaussiangenerator.py_ - Create a custom ProfileGenerator and use it in a refinement. This is an - instructive extension to gaussianrecipe.py_. - -* npintensity.py_ - Use diffpy.structure to build a nanoparticle intensity generator, and use - it to refine a structure to simulated data. +- gaussiangenerator.py* + Create a custom ProfileGenerator and use it in a refinement. This is an + instructive extension to gaussianrecipe.py*. -* npintensityII.py_ - Use the calculator built in npintensity.py to simultaneously refine a - structure to two data sets. +- npintensity.py\_ + Use diffpy.structure to build a nanoparticle intensity generator, and use + it to refine a structure to simulated data. +- npintensityII.py\_ + Use the calculator built in npintensity.py to simultaneously refine a + structure to two data sets. -Use Cases ------------ +## Use Cases There are several examples that demonstrate various SrFit use cases. These do not adopt the tutorial format of the previous examples. Regardless, developers should read through these use cases to gain an understanding of PDF and SAS refinement with SrFit. -* crystalpdf.py_ - Refine a diffpy.structure crystal to PDF data using automatic explicit - space group constraints. +- crystalpdf.py\_ + Refine a diffpy.structure crystal to PDF data using automatic explicit + space group constraints. -* simplepdf.py_ - As crystalpdf.py_, but with a simplified interface. +- simplepdf.py* + As crystalpdf.py*, but with a simplified interface. -* crystalpdfobjcryst.py_ - Refine a pyobjcryst crystal to PDF data using automatic implicit space - group constraints. +- crystalpdfobjcryst.py\_ + Refine a pyobjcryst crystal to PDF data using automatic implicit space + group constraints. -* crystalpdftwophase.py_ - Refine a two-phase structure to PDF data using two profile generators. +- crystalpdftwophase.py\_ + Refine a two-phase structure to PDF data using two profile generators. -* simplepdftwophase.py_ - crystalpdftwophase.py_ using the simplified PDFContribution interface. +- simplepdftwophase.py* + crystalpdftwophase.py* using the simplified PDFContribution interface. -* crystalpdftwodata.py_ - Refine a single structure to x-ray and neutron data simultaneously. +- crystalpdftwodata.py\_ + Refine a single structure to x-ray and neutron data simultaneously. -* crystalpdfall.py_ - Refine a two-phase structure using four data sets. +- crystalpdfall.py\_ + Refine a two-phase structure using four data sets. -* nppdfobjcryst.py_ - Refine the C60 structure to real data. +- nppdfobjcryst.py\_ + Refine the C60 structure to real data. -* nppdfcrystal.py_ - Fit a nanoparticle PDF as a crystal PDF attenuated by a nanoparticle form - factor. +- nppdfcrystal.py\_ + Fit a nanoparticle PDF as a crystal PDF attenuated by a nanoparticle form + factor. -* coreshellnp.py_ - As above, but fit the PDF from core-shell nanoparticles. +- coreshellnp.py\_ + As above, but fit the PDF from core-shell nanoparticles. -* ellipsoidsas.py_ - Refine an ellipsoid SAS model to ideal data. +- ellipsoidsas.py\_ + Refine an ellipsoid SAS model to ideal data. -* nppdfsas.py_ - Refine PDF from nanoparticle of assumed shape using a crystal model and SAS - data from the same system. +- nppdfsas.py\_ + Refine PDF from nanoparticle of assumed shape using a crystal model and SAS + data from the same system. -Miscellaneous --------------- +## Miscellaneous These demonstrate other SrFit features. These are in flux and may not be in future versions. -* simplerecipe.py_ - This introduces the SimpleRecipe class that is a FitRecipe with an embedded - Profile and FitContribution. SimpleRecipe exposes methods from the Profile - and FitContribution, and adds other methods so it is easy to set up a - simple fit. - -* interface.py_ - This example introduces some interface enhancements that allow the SrFit - recipes to be written with less code. This is not the same as a - full-featured scripting interface, but rather somewhere in between a - scripting interface and the API. +- simplerecipe.py\_ + This introduces the SimpleRecipe class that is a FitRecipe with an embedded + Profile and FitContribution. SimpleRecipe exposes methods from the Profile + and FitContribution, and adds other methods so it is easy to set up a + simple fit. + +- interface.py\_ + This example introduces some interface enhancements that allow the SrFit + recipes to be written with less code. This is not the same as a + full-featured scripting interface, but rather somewhere in between a + scripting interface and the API. diff --git a/doc/examples/coreshellnp.py b/doc/examples/coreshellnp.py index 2f1da057..2ac0a2c5 100644 --- a/doc/examples/coreshellnp.py +++ b/doc/examples/coreshellnp.py @@ -12,24 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Refine the structure of a core-shell nanoparticle. -This applies the characteristic function formalism described in nppdfcrystal.py -to the case of a spherical core-shell nanoparticle. The modeling approach we -use is to refine the core and shell as two different phases, each with an -appropriate characteristic function. +This applies the characteristic function formalism described in +nppdfcrystal.py to the case of a spherical core-shell nanoparticle. The +modeling approach we use is to refine the core and shell as two +different phases, each with an appropriate characteristic function. """ import numpy -from scipy.optimize import leastsq - from pyobjcryst import loadCrystal +from scipy.optimize import leastsq +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults # Example Code @@ -73,7 +74,8 @@ def makeRecipe(stru1, stru2, datname): # and a spherical shell CF for the shell. Since this is set up as two # phases, we implicitly assume that the core-shell correlations contribute # very little to the PDF. - from diffpy.srfit.pdf.characteristicfunctions import sphericalCF, shellCF + from diffpy.srfit.pdf.characteristicfunctions import shellCF, sphericalCF + contribution.registerFunction(sphericalCF, name="f_CdS") contribution.registerFunction(shellCF, name="f_ZnS") @@ -140,10 +142,11 @@ def plotResults(recipe): diff = g - gcalc + diffzero import pylab - pylab.plot(r, g, 'bo', label="G(r) Data") - pylab.plot(r, gcalc, 'r-', label="G(r) Fit") - pylab.plot(r, diff, 'g-', label="G(r) diff") - pylab.plot(r, diffzero, 'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -165,6 +168,7 @@ def main(): stru2 = loadCrystal(znsciffile) recipe = makeRecipe(stru1, stru2, data) from diffpy.srfit.fitbase.fithook import PlotFitHook + recipe.pushFitHook(PlotFitHook()) recipe.fithooks[0].verbose = 3 diff --git a/doc/examples/crystalpdf.py b/doc/examples/crystalpdf.py index ced49e9a..d770b11c 100644 --- a/doc/examples/crystalpdf.py +++ b/doc/examples/crystalpdf.py @@ -12,31 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement using diffpy.structure and PDFGenerator. -This is example of fitting the fcc nickel structure to measured PDF data. The -purpose of this example is to demonstrate and describe the classes in -configuraiton options involved with setting up a fit in this way. The main -benefit of using SrFit for PDF refinement is the flexibility of modifying the -PDF profile function for specific needs, adding restraints to a fit and the -ability to simultaneously refine a structure to PDF data and data from other -sources. This example demonstrates only the basic configuration. - +This is example of fitting the fcc nickel structure to measured PDF +data. The purpose of this example is to demonstrate and describe the +classes in configuraiton options involved with setting up a fit in this +way. The main benefit of using SrFit for PDF refinement is the +flexibility of modifying the PDF profile function for specific needs, +adding restraints to a fit and the ability to simultaneously refine a +structure to PDF data and data from other sources. This example +demonstrates only the basic configuration. """ import numpy +from gaussianrecipe import scipyOptimize -from diffpy.structure import Structure +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize +from diffpy.structure import Structure ####### Example Code + def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" @@ -53,7 +55,7 @@ def makeRecipe(ciffile, datname): parser = PDFParser() parser.parseFile(datname) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) ## The ProfileGenerator # The PDFGenerator is for configuring and calculating a PDF profile. Here, @@ -72,7 +74,7 @@ def makeRecipe(ciffile, datname): # before. contribution = FitContribution("nickel") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") ## Make the FitRecipe and add the FitContribution. recipe = FitRecipe() @@ -95,6 +97,7 @@ def makeRecipe(ciffile, datname): # documentation for more details. The 'constrainAsSpaceGroup' method may # create new Parameters, which it returns in a SpaceGroupParameters object. from diffpy.srfit.structure import constrainAsSpaceGroup + sgpars = constrainAsSpaceGroup(phase, "Fm-3m") # The SpaceGroupParameters object returned by 'constrainAsSpaceGroup' holds @@ -124,6 +127,7 @@ def makeRecipe(ciffile, datname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -135,10 +139,11 @@ def plotResults(recipe): diff = g - gcalc + diffzero import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r,diff,'g-',label="G(r) diff") - pylab.plot(r,diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -146,6 +151,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/crystalpdfall.py b/doc/examples/crystalpdfall.py index c79cf6c5..ea2990bb 100644 --- a/doc/examples/crystalpdfall.py +++ b/doc/examples/crystalpdfall.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement of two-phase structure. This example uses PDFGenerator to refine a the two phase nickel-silicon @@ -20,36 +19,41 @@ """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeProfile(datafile): """Make an place data within a Profile.""" profile = Profile() parser = PDFParser() parser.parseFile(datafile) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) return profile + def makeContribution(name, generator, profile): """Make a FitContribution and add a generator and profile.""" contribution = FitContribution(name) contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") return contribution -def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, - xdata_sini): + +def makeRecipe( + ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, xdata_sini +): """Create a fitting recipe for crystalline PDF data.""" ## The Profiles @@ -85,8 +89,9 @@ def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, xcontribution_ni = makeContribution("xnickel", xgenerator_ni, xprofile_ni) xcontribution_si = makeContribution("xsilicon", xgenerator_si, xprofile_si) ncontribution_ni = makeContribution("nnickel", ngenerator_ni, nprofile_ni) - xcontribution_sini = makeContribution("xsini", xgenerator_sini_ni, - xprofile_sini) + xcontribution_sini = makeContribution( + "xsini", xgenerator_sini_ni, xprofile_sini + ) xcontribution_sini.addProfileGenerator(xgenerator_sini_si) xcontribution_sini.setEquation("scale * (xG_sini_ni + xG_sini_si)") @@ -105,22 +110,22 @@ def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, # Now we vary and constrain Parameters as before. for par in phase_ni.sgpars: - recipe.addVar(par, name = par.name + "_ni") + recipe.addVar(par, name=par.name + "_ni") delta2_ni = recipe.newVar("delta2_ni", 2.5) recipe.constrain(xgenerator_ni.delta2, delta2_ni) recipe.constrain(ngenerator_ni.delta2, delta2_ni) recipe.constrain(xgenerator_sini_ni.delta2, delta2_ni) for par in phase_si.sgpars: - recipe.addVar(par, name = par.name + "_si") + recipe.addVar(par, name=par.name + "_si") delta2_si = recipe.newVar("delta2_si", 2.5) recipe.constrain(xgenerator_si.delta2, delta2_si) recipe.constrain(xgenerator_sini_si.delta2, delta2_si) # Now the experimental parameters - recipe.addVar(xgenerator_ni.scale, name = "xscale_ni") - recipe.addVar(xgenerator_si.scale, name = "xscale_si") - recipe.addVar(ngenerator_ni.scale, name = "nscale_ni") + recipe.addVar(xgenerator_ni.scale, name="xscale_ni") + recipe.addVar(xgenerator_si.scale, name="xscale_si") + recipe.addVar(ngenerator_ni.scale, name="nscale_ni") recipe.addVar(xcontribution_sini.scale, 1.0, "xscale_sini") recipe.newVar("pscale_sini_ni", 0.8) recipe.constrain(xgenerator_sini_ni.scale, "pscale_sini_ni") @@ -137,6 +142,7 @@ def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -145,65 +151,66 @@ def plotResults(recipe): xr_ni = xnickel.profile.x xg_ni = xnickel.profile.y xgcalc_ni = xnickel.profile.ycalc - xdiffzero_ni = -0.8 * max(xg_ni) * numpy.ones_like(xg_ni) + xdiffzero_ni = -0.8 * max(xg_ni) * numpy.ones_like(xg_ni) xdiff_ni = xg_ni - xgcalc_ni + xdiffzero_ni xsilicon = recipe.xsilicon xr_si = xsilicon.profile.x xg_si = xsilicon.profile.y xgcalc_si = xsilicon.profile.ycalc - xdiffzero_si = -0.8 * max(xg_si) * numpy.ones_like(xg_si) + xdiffzero_si = -0.8 * max(xg_si) * numpy.ones_like(xg_si) xdiff_si = xg_si - xgcalc_si + xdiffzero_si nnickel = recipe.nnickel nr_ni = nnickel.profile.x ng_ni = nnickel.profile.y ngcalc_ni = nnickel.profile.ycalc - ndiffzero_ni = -0.8 * max(ng_ni) * numpy.ones_like(ng_ni) + ndiffzero_ni = -0.8 * max(ng_ni) * numpy.ones_like(ng_ni) ndiff_ni = ng_ni - ngcalc_ni + ndiffzero_ni xsini = recipe.xsini xr_sini = xsini.profile.x xg_sini = xsini.profile.y xgcalc_sini = xsini.profile.ycalc - xdiffzero_sini = -0.8 * max(xg_sini) * numpy.ones_like(xg_sini) + xdiffzero_sini = -0.8 * max(xg_sini) * numpy.ones_like(xg_sini) xdiff_sini = xg_sini - xgcalc_sini + xdiffzero_sini - import pylab + pylab.subplot(2, 2, 1) - pylab.plot(xr_ni,xg_ni,'bo',label="G(r) x-ray nickel Data") - pylab.plot(xr_ni,xgcalc_ni,'r-',label="G(r) x-ray nickel Fit") - pylab.plot(xr_ni,xdiff_ni,'g-',label="G(r) x-ray nickel diff") - pylab.plot(xr_ni,xdiffzero_ni,'k-') + pylab.plot(xr_ni, xg_ni, "bo", label="G(r) x-ray nickel Data") + pylab.plot(xr_ni, xgcalc_ni, "r-", label="G(r) x-ray nickel Fit") + pylab.plot(xr_ni, xdiff_ni, "g-", label="G(r) x-ray nickel diff") + pylab.plot(xr_ni, xdiffzero_ni, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) pylab.subplot(2, 2, 2) - pylab.plot(xr_si,xg_si,'bo',label="G(r) x-ray silicon Data") - pylab.plot(xr_si,xgcalc_si,'r-',label="G(r) x-ray silicon Fit") - pylab.plot(xr_si,xdiff_si,'g-',label="G(r) x-ray silicon diff") - pylab.plot(xr_si,xdiffzero_si,'k-') + pylab.plot(xr_si, xg_si, "bo", label="G(r) x-ray silicon Data") + pylab.plot(xr_si, xgcalc_si, "r-", label="G(r) x-ray silicon Fit") + pylab.plot(xr_si, xdiff_si, "g-", label="G(r) x-ray silicon diff") + pylab.plot(xr_si, xdiffzero_si, "k-") pylab.legend(loc=1) pylab.subplot(2, 2, 3) - pylab.plot(nr_ni,ng_ni,'bo',label="G(r) neutron nickel Data") - pylab.plot(nr_ni,ngcalc_ni,'r-',label="G(r) neutron nickel Fit") - pylab.plot(nr_ni,ndiff_ni,'g-',label="G(r) neutron nickel diff") - pylab.plot(nr_ni,ndiffzero_ni,'k-') + pylab.plot(nr_ni, ng_ni, "bo", label="G(r) neutron nickel Data") + pylab.plot(nr_ni, ngcalc_ni, "r-", label="G(r) neutron nickel Fit") + pylab.plot(nr_ni, ndiff_ni, "g-", label="G(r) neutron nickel diff") + pylab.plot(nr_ni, ndiffzero_ni, "k-") pylab.legend(loc=1) pylab.subplot(2, 2, 4) - pylab.plot(xr_sini,xg_sini,'bo',label="G(r) x-ray sini Data") - pylab.plot(xr_sini,xgcalc_sini,'r-',label="G(r) x-ray sini Fit") - pylab.plot(xr_sini,xdiff_sini,'g-',label="G(r) x-ray sini diff") - pylab.plot(xr_sini,xdiffzero_sini,'k-') + pylab.plot(xr_sini, xg_sini, "bo", label="G(r) x-ray sini Data") + pylab.plot(xr_sini, xgcalc_sini, "r-", label="G(r) x-ray sini Fit") + pylab.plot(xr_sini, xdiff_sini, "g-", label="G(r) x-ray sini diff") + pylab.plot(xr_sini, xdiffzero_sini, "k-") pylab.legend(loc=1) pylab.show() return + if __name__ == "__main__": # Make the data and the recipe @@ -215,8 +222,9 @@ def plotResults(recipe): xdata_sini = "data/si90ni10-q27r60-xray.gr" # Make the recipe - recipe = makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, - xdata_sini) + recipe = makeRecipe( + ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, xdata_sini + ) # Optimize scipyOptimize(recipe) diff --git a/doc/examples/crystalpdfobjcryst.py b/doc/examples/crystalpdfobjcryst.py index c7201493..48f94e0e 100644 --- a/doc/examples/crystalpdfobjcryst.py +++ b/doc/examples/crystalpdfobjcryst.py @@ -12,26 +12,28 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement using pyobjcryst and PDFGenerator. This example is similar to crystalpdf.py, except that here we refine a -pyobjcryst crystal object. In this example we use internal constraints provided -by the ObjCrystCrystalParSet structure adapter. +pyobjcryst crystal object. In this example we use internal constraints +provided by the ObjCrystCrystalParSet structure adapter. """ +from crystalpdf import plotResults +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize -from crystalpdf import plotResults ####### Example Code + def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" @@ -46,7 +48,7 @@ def makeRecipe(ciffile, datname): parser = PDFParser() parser.parseFile(datname) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) ## The ProfileGenerator # This time we use the CreateCrystalFromCIF method of pyobjcryst.crystal to @@ -60,7 +62,7 @@ def makeRecipe(ciffile, datname): ## The FitContribution contribution = FitContribution("nickel") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() @@ -89,7 +91,7 @@ def makeRecipe(ciffile, datname): for par in phase.sgpars: recipe.addVar(par) # set the initial thermal factor to a non-zero value - assert hasattr(recipe, 'B11_0') + assert hasattr(recipe, "B11_0") recipe.B11_0 = 0.1 # We now select non-structural parameters to refine. @@ -103,6 +105,7 @@ def makeRecipe(ciffile, datname): # Give the recipe away so it can be used! return recipe + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/crystalpdftwodata.py b/doc/examples/crystalpdftwodata.py index a43da784..12e9e9f1 100644 --- a/doc/examples/crystalpdftwodata.py +++ b/doc/examples/crystalpdftwodata.py @@ -12,28 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement of two-phase structure. -This example uses PDFGenerator to refine a single structure two profiles. -This will require setting up two FitContribution, each with its own -PDFGenerator. However, the PDFGenerators will refer to the same underlying -ObjCrystCrystalParSet. +This example uses PDFGenerator to refine a single structure two +profiles. This will require setting up two FitContribution, each with +its own PDFGenerator. However, the PDFGenerators will refer to the same +underlying ObjCrystCrystalParSet. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeRecipe(ciffile, xdatname, ndatname): """Create a fitting recipe for crystalline PDF data.""" @@ -47,12 +48,12 @@ def makeRecipe(ciffile, xdatname, ndatname): parser = PDFParser() parser.parseFile(xdatname) xprofile.loadParsedData(parser) - xprofile.setCalculationRange(xmax = 20) + xprofile.setCalculationRange(xmax=20) parser = PDFParser() parser.parseFile(ndatname) nprofile.loadParsedData(parser) - nprofile.setCalculationRange(xmax = 20) + nprofile.setCalculationRange(xmax=20) ## The ProfileGenerators # We need one of these for the x-ray data. @@ -83,11 +84,11 @@ def makeRecipe(ciffile, xdatname, ndatname): # We associate the x-ray PDFGenerator and Profile in one FitContribution... xcontribution = FitContribution("xnickel") xcontribution.addProfileGenerator(xgenerator) - xcontribution.setProfile(xprofile, xname = "r") + xcontribution.setProfile(xprofile, xname="r") # and the neutron objects in another. ncontribution = FitContribution("nnickel") ncontribution.addProfileGenerator(ngenerator) - ncontribution.setProfile(nprofile, xname = "r") + ncontribution.setProfile(nprofile, xname="r") # This example is different than the previous ones in that we are composing # a residual function from other residuals (one for the x-ray contribution @@ -132,6 +133,7 @@ def makeRecipe(ciffile, xdatname, ndatname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -149,18 +151,19 @@ def plotResults(recipe): ndiff = ng - ngcalc + ndiffzero import pylab + pylab.subplot(2, 1, 1) - pylab.plot(xr,xg,'bo',label="G(r) x-ray Data") - pylab.plot(xr,xgcalc,'r-',label="G(r) x-ray Fit") - pylab.plot(xr,xdiff,'g-',label="G(r) x-ray diff") - pylab.plot(xr,xdiffzero,'k-') + pylab.plot(xr, xg, "bo", label="G(r) x-ray Data") + pylab.plot(xr, xgcalc, "r-", label="G(r) x-ray Fit") + pylab.plot(xr, xdiff, "g-", label="G(r) x-ray diff") + pylab.plot(xr, xdiffzero, "k-") pylab.legend(loc=1) pylab.subplot(2, 1, 2) - pylab.plot(nr,ng,'bo',label="G(r) neutron Data") - pylab.plot(nr,ngcalc,'r-',label="G(r) neutron Fit") - pylab.plot(nr,ndiff,'g-',label="G(r) neutron diff") - pylab.plot(nr,ndiffzero,'k-') + pylab.plot(nr, ng, "bo", label="G(r) neutron Data") + pylab.plot(nr, ngcalc, "r-", label="G(r) neutron Fit") + pylab.plot(nr, ndiff, "g-", label="G(r) neutron diff") + pylab.plot(nr, ndiffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -168,6 +171,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/crystalpdftwophase.py b/doc/examples/crystalpdftwophase.py index df7cce77..a5958554 100644 --- a/doc/examples/crystalpdftwophase.py +++ b/doc/examples/crystalpdftwophase.py @@ -12,28 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement of two-phase structure. -Like the ones before, this example uses PDFGenerator to refine a structure to -PDF data. However, for a multi-phase structure one must use multiple -PDFGenerators. This example refines a physical mixture of nickel and silicon to -find the structures and phase fractions. +Like the ones before, this example uses PDFGenerator to refine a +structure to PDF data. However, for a multi-phase structure one must use +multiple PDFGenerators. This example refines a physical mixture of +nickel and silicon to find the structures and phase fractions. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeRecipe(niciffile, siciffile, datname): """Create a fitting recipe for crystalline PDF data.""" @@ -44,7 +45,7 @@ def makeRecipe(niciffile, siciffile, datname): parser = PDFParser() parser.parseFile(datname) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) ## The ProfileGenerator # In order to fit two phases simultaneously, we must use two PDFGenerators. @@ -72,7 +73,7 @@ def makeRecipe(niciffile, siciffile, datname): contribution = FitContribution("nisi") contribution.addProfileGenerator(generator_ni) contribution.addProfileGenerator(generator_si) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") # Write the fitting equation. We want to sum the PDFs from each phase and # multiply it by a scaling factor. We also want a certain phase scaling @@ -105,13 +106,13 @@ def makeRecipe(niciffile, siciffile, datname): # First the nickel parameters phase_ni = generator_ni.phase for par in phase_ni.sgpars: - recipe.addVar(par, name = par.name + "_ni") - recipe.addVar(generator_ni.delta2, name = "delta2_ni") + recipe.addVar(par, name=par.name + "_ni") + recipe.addVar(generator_ni.delta2, name="delta2_ni") # Next the silicon parameters phase_si = generator_si.phase for par in phase_si.sgpars: - recipe.addVar(par, name = par.name + "_si") - recipe.addVar(generator_si.delta2, name = "delta2_si") + recipe.addVar(par, name=par.name + "_si") + recipe.addVar(generator_si.delta2, name="delta2_si") # We have prior information from the earlier examples so we'll use it here # in the form of restraints. @@ -121,22 +122,23 @@ def makeRecipe(niciffile, siciffile, datname): # derived has no uncertainty. Thus, we will tell the recipe to scale the # residual, which means that it will be weighted as much as the average # data point during the fit. - recipe.restrain("a_ni", lb = 3.527, ub = 3.527, scaled = True) + recipe.restrain("a_ni", lb=3.527, ub=3.527, scaled=True) # Now we do the same with the delta2 and Biso parameters (remember that # Biso = 8*pi**2*Uiso) - recipe.restrain("delta2_ni", lb = 2.22, ub = 2.22, scaled = True) - recipe.restrain("Biso_0_ni", lb = 0.454, ub = 0.454, scaled = True) + recipe.restrain("delta2_ni", lb=2.22, ub=2.22, scaled=True) + recipe.restrain("Biso_0_ni", lb=0.454, ub=0.454, scaled=True) # # We can do the same with the silicon values. We haven't done a thorough # job of measuring the uncertainties in the results, so we'll scale these # as well. - recipe.restrain("a_si", lb = 5.430, ub = 5.430, scaled = True) - recipe.restrain("delta2_si", lb = 3.54, ub = 3.54, scaled = True) - recipe.restrain("Biso_0_si", lb = 0.645, ub = 0.645, scaled = True) + recipe.restrain("a_si", lb=5.430, ub=5.430, scaled=True) + recipe.restrain("delta2_si", lb=3.54, ub=3.54, scaled=True) + recipe.restrain("Biso_0_si", lb=0.645, ub=0.645, scaled=True) # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -148,10 +150,11 @@ def plotResults(recipe): diff = g - gcalc + diffzero import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r,diff,'g-',label="G(r) diff") - pylab.plot(r,diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) diff --git a/doc/examples/data/ni.iq b/doc/examples/data/ni.iq index 917301a1..56594ba3 100644 --- a/doc/examples/data/ni.iq +++ b/doc/examples/data/ni.iq @@ -2462,4 +2462,3 @@ 26.123511 0 26.133371 0 26.143228 0 - diff --git a/doc/examples/debyemodel.py b/doc/examples/debyemodel.py index 79bf7add..a53d24f2 100644 --- a/doc/examples/debyemodel.py +++ b/doc/examples/debyemodel.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting the Debye model to experimental Debye-Waller factors. In this example, we build a fit recipe that uses an external function that can @@ -34,11 +33,15 @@ """ import numpy - -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults - from gaussianrecipe import scipyOptimize +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) + # The data data = """\ 015.0 0.00334 0.00013 @@ -56,28 +59,29 @@ ####### Example Code + def makeRecipe(): """Make the recipe for the fit. - The instructions for what we want to refine, and how to refine it will be - defined within a FitRecipe instance. The job of a FitRecipe is to collect - and associate all the data, the fitting equations, fitting variables, - constraints and restrations. We will demonstrate each of these within the - code. - - Data is held within a Profile object. The Profile is simply a container - that holds the data, and the theoretical profile once it has been - calculated. - - Data is associated with a fitting equation within a FitContribution. The - FitContribution defines the equation and parameters that will be adjusted - to fit the data. The fitting equation can be defined within a function or - optionally within the ProfileGenerator class. We won't need the - ProfileGenerator class in this example since the signature of the fitting - equation (the 'debye' function defined below) is so simple. The - FitContribution also defines the residual function to optimize for the - data/equation pair. This can be modified, but we won't do that here. - + The instructions for what we want to refine, and how to refine it + will be defined within a FitRecipe instance. The job of a FitRecipe + is to collect and associate all the data, the fitting equations, + fitting variables, constraints and restrations. We will demonstrate + each of these within the code. + + Data is held within a Profile object. The Profile is simply a + container that holds the data, and the theoretical profile once it + has been calculated. + + Data is associated with a fitting equation within a FitContribution. + The FitContribution defines the equation and parameters that will be + adjusted to fit the data. The fitting equation can be defined within + a function or optionally within the ProfileGenerator class. We won't + need the ProfileGenerator class in this example since the signature + of the fitting equation (the 'debye' function defined below) is so + simple. The FitContribution also defines the residual function to + optimize for the data/equation pair. This can be modified, but we + won't do that here. """ ## The Profile @@ -86,7 +90,7 @@ def makeRecipe(): # Load data and add it to the profile. It is our responsibility to get our # data into the profile. - xydy = numpy.array(map(float, data.split()), dtype=float).reshape(-1,3) + xydy = numpy.array(map(float, data.split()), dtype=float).reshape(-1, 3) x, y, dy = numpy.hsplit(xydy, 3) profile.setObservedProfile(x, y, dy) @@ -146,11 +150,12 @@ def makeRecipe(): # breaking the restraint by the point-average chi^2 value so that the # restraint is roughly as significant as any other data point throughout # the fit. - recipe.restrain(recipe.offset, lb = 0, scaled = True) + recipe.restrain(recipe.offset, lb=0, scaled=True) # We're done setting up the recipe. We can now do other things with it. return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -163,15 +168,17 @@ def plotResults(recipe): Ucalc = recipe.pb.profile.ycalc import pylab - pylab.plot(T,U,'o',label="Pb $U_{iso}$ Data") - pylab.plot(T,Ucalc) + + pylab.plot(T, U, "o", label="Pb $U_{iso}$ Data") + pylab.plot(T, Ucalc) pylab.xlabel("T (K)") pylab.ylabel(r"$U_{iso} (\AA^2)$") - pylab.legend(loc = (0.0,0.8)) + pylab.legend(loc=(0.0, 0.8)) pylab.show() return + def main(): """The workflow of creating, running and inspecting a fit.""" @@ -197,13 +204,15 @@ def main(): # as we treat them as if existing in some external library that we cannot # modify. + def debye(T, m, thetaD): """A wrapped version of 'adps' that can handle an array of T-values.""" y = numpy.array([adps(m, thetaD, x) for x in T]) return y -def adps(m,thetaD,T): - """Calculates atomic displacement factors within the Debye model + +def adps(m, thetaD, T): + """Calculates atomic displacement factors within the Debye model. = (3h^2/4 pi^2 m kB thetaD)(phi(thetaD/T)/(ThetaD/T) + 1/4) @@ -214,14 +223,13 @@ def adps(m,thetaD,T): return: Uiso -- float -- the thermal factor from the Debye recipe at temp T - """ - h = 6.6260755e-34 # Planck's constant. J.s of m^2.kg/s + h = 6.6260755e-34 # Planck's constant. J.s of m^2.kg/s kB = 1.3806503e-23 # Boltzmann's constant. J/K - amu = 1.66053886e-27 # Atomic mass unit. kg + amu = 1.66053886e-27 # Atomic mass unit. kg def __phi(x): - """evaluates the phi integral needed in Debye calculation + """Evaluates the phi integral needed in Debye calculation. phi(x) = (1/x) int_0^x xi/(exp(xi)-1) dxi @@ -230,28 +238,27 @@ def __phi(x): returns: phi -- float -- value of the phi function - """ - def __debyeKernel(xi): - """function needed by debye calculators - """ - y = xi/(numpy.exp(xi)-1) + def __debyeKernel(xi): + """Function needed by debye calculators.""" + y = xi / (numpy.exp(xi) - 1) return y import scipy.integrate int = scipy.integrate.quad(__debyeKernel, 0, x) - phi = (1/x) * int[0] + phi = (1 / x) * int[0] return phi - m = m * amu - u2 = (3*h**2 / (4 * numpy.pi**2 *m *kB *thetaD))*\ - (__phi(thetaD/T)/(thetaD/T) + 1./4.) + u2 = (3 * h**2 / (4 * numpy.pi**2 * m * kB * thetaD)) * ( + __phi(thetaD / T) / (thetaD / T) + 1.0 / 4.0 + ) + + return u2 * 1e20 - return u2*1e20 if __name__ == "__main__": diff --git a/doc/examples/debyemodelII.py b/doc/examples/debyemodelII.py index 0e5e3542..6ff8d1e6 100644 --- a/doc/examples/debyemodelII.py +++ b/doc/examples/debyemodelII.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting the Debye recipe to experimental Debye-Waller factors. This is an extension of example in debyemodel.py. The recipe we create will @@ -34,23 +33,25 @@ done. """ -from diffpy.srfit.fitbase import FitRecipe, FitResults - from debyemodel import makeRecipe, scipyOptimize +from diffpy.srfit.fitbase import FitRecipe, FitResults + ####### Example Code + def makeRecipeII(): """Make a recipe for fitting low and high temperature regions. We will fit the low and high temperature parts of Debye curve - simultaneously with the same Debye temperature, but different offsets. + simultaneously with the same Debye temperature, but different + offsets. We will make two FitRecipes using the makeRecipe function from - debyemodel.py and extract the configured FitContribution from each. We will - use different fitting ranges for each FitContribution and constrain the - Debye temperature in each FitContribution to be the same. - + debyemodel.py and extract the configured FitContribution from each. + We will use different fitting ranges for each FitContribution and + constrain the Debye temperature in each FitContribution to be the + same. """ # We'll throw these away. We just want the FitContributions that are @@ -81,8 +82,8 @@ def makeRecipeII(): # Vary the offset from each FitContribution separately, while keeping the # Debye temperatures the same. We give each offset variable a different # name in the recipe so it retains its identity. - recipe.addVar(recipe.lowT.offset, name = "lowToffset") - recipe.addVar(recipe.highT.offset, name = "highToffset") + recipe.addVar(recipe.lowT.offset, name="lowToffset") + recipe.addVar(recipe.highT.offset, name="highToffset") # We create a new Variable and use the recipe's "constrain" method to # associate the Debye temperature parameters with that variable. recipe.newVar("thetaD", 100) @@ -90,6 +91,7 @@ def makeRecipeII(): recipe.constrain(recipe.highT.thetaD, "thetaD") return recipe + def plotResults(recipe): """Display the results contained within a refined FitRecipe.""" @@ -99,8 +101,8 @@ def plotResults(recipe): # We want to extend the fitting range to its full extent so we can get a # nice full plot. - recipe.lowT.profile.setCalculationRange(xmin='obs', xmax='obs') - recipe.highT.profile.setCalculationRange(xmin='obs', xmax='obs') + recipe.lowT.profile.setCalculationRange(xmin="obs", xmax="obs") + recipe.highT.profile.setCalculationRange(xmin="obs", xmax="obs") T = recipe.lowT.profile.x U = recipe.lowT.profile.y # We can use a FitContribution's 'evaluateEquation' method to evaluate @@ -114,18 +116,23 @@ def plotResults(recipe): # Now we can plot this. import pylab - pylab.plot(T,U,'o',label="Pb $U_{iso}$ Data") - lbl1 = r"$T_d$=%3.1f K, lowToff=%1.5f $\AA^2$"% (abs(thetaD),lowToffset) - lbl2 = r"$T_d$=%3.1f K, highToff=%1.5f $\AA^2$"% (abs(thetaD),highToffset) - pylab.plot(T,lowU,label=lbl1) - pylab.plot(T,highU,label=lbl2) + + pylab.plot(T, U, "o", label="Pb $U_{iso}$ Data") + lbl1 = r"$T_d$=%3.1f K, lowToff=%1.5f $\AA^2$" % (abs(thetaD), lowToffset) + lbl2 = r"$T_d$=%3.1f K, highToff=%1.5f $\AA^2$" % ( + abs(thetaD), + highToffset, + ) + pylab.plot(T, lowU, label=lbl1) + pylab.plot(T, highU, label=lbl2) pylab.xlabel("T (K)") pylab.ylabel(r"$U_{iso} (\AA^2)$") - pylab.legend(loc = (0.0,0.8)) + pylab.legend(loc=(0.0, 0.8)) pylab.show() return + def main(): # Create the recipe diff --git a/doc/examples/ellipsoidsas.py b/doc/examples/ellipsoidsas.py index 87c4d016..ece4f73a 100644 --- a/doc/examples/ellipsoidsas.py +++ b/doc/examples/ellipsoidsas.py @@ -12,18 +12,21 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## +"""Example of a refinement of SAS I(Q) data to an ellipsoidal model.""" -"""Example of a refinement of SAS I(Q) data to an ellipsoidal model. -""" +from gaussianrecipe import scipyOptimize +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.sas import SASGenerator, SASParser -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults, Profile - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeRecipe(datname): """Create a fitting recipe for ellipsoidal SAS data.""" @@ -45,6 +48,7 @@ def makeRecipe(datname): # data. The documentation for the various sas models can be found at # http://www.sasview.org. from sas.models.EllipsoidModel import EllipsoidModel + model = EllipsoidModel() generator = SASGenerator("generator", model) @@ -53,7 +57,7 @@ def makeRecipe(datname): # before. contribution = FitContribution("ellipsoid") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "q") + contribution.setProfile(profile, xname="q") # We want to fit the log of the signal to the log of the data so that the # higher-Q information remains significant. There are no I(Q) uncertainty @@ -81,6 +85,7 @@ def makeRecipe(datname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -91,9 +96,10 @@ def plotResults(recipe): diff = y - ycalc + min(y) import pylab - pylab.loglog(r,y,'bo',label="I(Q) Data") - pylab.loglog(r, ycalc,'r-',label="I(Q) Fit") - pylab.loglog(r,diff,'g-',label="I(Q) diff") + + pylab.loglog(r, y, "bo", label="I(Q) Data") + pylab.loglog(r, ycalc, "r-", label="I(Q) Fit") + pylab.loglog(r, diff, "g-", label="I(Q) diff") pylab.xlabel(r"$Q (\AA^{-1})$") pylab.ylabel("$I (arb. units)$") pylab.legend(loc=1) @@ -101,6 +107,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/gaussiangenerator.py b/doc/examples/gaussiangenerator.py index 7b0818b8..6df50694 100644 --- a/doc/examples/gaussiangenerator.py +++ b/doc/examples/gaussiangenerator.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of using ProfileGenerators in FitContributions. This is an example of building a ProfileGenerator and using it in a @@ -41,28 +40,32 @@ from numpy import exp -from diffpy.srfit.fitbase import ProfileGenerator, Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + Profile, + ProfileGenerator, +) ####### Example Code + class GaussianGenerator(ProfileGenerator): """A class for calculating a Gaussian profile. - Generating a Gaussian is not difficult, as was shown in gaussianrecipe.py. - Here we create a class that encapsulates this functionality. Placing this - class in a python module would make it possible to import it and reuse it, - thereby saving future code writing and debugging. - - The purpose of a ProfileGenerator is to - 1) provide a function that generates a profile signal - 2) organize the Parameters required for the calculation - - Thus, this class overloads the __init__ method to create the necessary - Parameters for the calculation, and the __call__ method to generate the - signal. + Generating a Gaussian is not difficult, as was shown in + gaussianrecipe.py. Here we create a class that encapsulates this + functionality. Placing this class in a python module would make it + possible to import it and reuse it, thereby saving future code + writing and debugging. + The purpose of a ProfileGenerator is to 1) provide a function that + generates a profile signal 2) organize the Parameters required for + the calculation + Thus, this class overloads the __init__ method to create the + necessary Parameters for the calculation, and the __call__ method to + generate the signal. """ def __init__(self, name): @@ -77,7 +80,6 @@ def __init__(self, name): A -- The amplitude x0 -- The center sigma -- The width - """ # This initializes various parts of the generator ProfileGenerator.__init__(self, name) @@ -86,17 +88,16 @@ def __init__(self, name): # ProfileGenerator. The signature is # _newParameter(name, value). # See the API for full details. - self._newParameter('A', 1.0) - self._newParameter('x0', 0.0) - self._newParameter('sigma', 1.0) + self._newParameter("A", 1.0) + self._newParameter("x0", 0.0) + self._newParameter("sigma", 1.0) return def __call__(self, x): """Calculate the profile. - Here we calculate the Gaussian profile given the independent variable, - x. We will define it as we did in gaussianrecipe.py. - + Here we calculate the Gaussian profile given the independent + variable, x. We will define it as we did in gaussianrecipe.py. """ # First we must get the values of the Parameters. Since we used # _newParameter to create them, the Parameters are accessible as @@ -107,19 +108,20 @@ def __call__(self, x): # Now we can use them. Note that we imported exp from numpy at the top # of the module. - y = A * exp(-0.5*(x-x0)**2/sigma**2) + y = A * exp(-0.5 * (x - x0) ** 2 / sigma**2) # Now return the value. return y + # End class GaussianGenerator + def makeRecipe(): """Create a recipe that uses the GaussianGenerator. This will create a FitContribution that uses the GaussianGenerator, associate this with a Profile, and use this to define a FitRecipe. - """ ## The Profile @@ -159,7 +161,7 @@ def makeRecipe(): # gaussianrecipe.py so we can expect the same output. recipe.addVar(generator.A, 1) recipe.addVar(generator.x0, 5) - recipe.addVar(generator.sigma, name = "sig") + recipe.addVar(generator.sigma, name="sig") recipe.sig.value = 1 # Give the recipe away so it can be used! @@ -171,6 +173,7 @@ def makeRecipe(): # We can use main from gaussianrecipe.py, since this doesn't care if we use # a ProfileGenerator or not. from gaussianrecipe import main + main() # End of file diff --git a/doc/examples/gaussianrecipe.py b/doc/examples/gaussianrecipe.py index b4bb404d..d82ad4d0 100644 --- a/doc/examples/gaussianrecipe.py +++ b/doc/examples/gaussianrecipe.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting a Gaussian to simulated data. This is an example of building a fit recipe that can be driven by an optimizer @@ -46,10 +45,16 @@ from __future__ import print_function -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def main(): """The workflow of creating, running and inspecting a fit.""" @@ -78,17 +83,16 @@ def main(): def makeRecipe(): """Make a FitRecipe for fitting a Gaussian curve to data. - The instructions for what we want to refine, and how to refine it will be - defined within a FitRecipe instance. The job of a FitRecipe is to collect - and associate all the data, the fitting equations, fitting variables, - constraints and restraints. The configured recipe provides a 'residual' - function and the initial variable values that an optimizer can use to - refine the variables to minimize the disagreement between the calculated - profile and the data. - - Once we define the FitRecipe, we can send it an optimizer to be optimized. - See the 'scipyOptimize' function. + The instructions for what we want to refine, and how to refine it + will be defined within a FitRecipe instance. The job of a FitRecipe + is to collect and associate all the data, the fitting equations, + fitting variables, constraints and restraints. The configured recipe + provides a 'residual' function and the initial variable values that + an optimizer can use to refine the variables to minimize the + disagreement between the calculated profile and the data. + Once we define the FitRecipe, we can send it an optimizer to be + optimized. See the 'scipyOptimize' function. """ ## The Profile @@ -150,18 +154,18 @@ def makeRecipe(): # Here we create a Variable named 'sig', which is tied to the 'sigma' # Parameter of our FitContribution. We give it an initial value through the # FitRecipe instance. - recipe.addVar(contribution.sigma, name = "sig") + recipe.addVar(contribution.sigma, name="sig") recipe.sig.value = 1 return recipe + def scipyOptimize(recipe): """Optimize the recipe created above using scipy. - The FitRecipe we created in makeRecipe has a 'residual' method that we can - be minimized using a scipy optimizer. The details are described in the - source. - + The FitRecipe we created in makeRecipe has a 'residual' method that + we can be minimized using a scipy optimizer. The details are + described in the source. """ # We're going to use the least-squares (Levenberg-Marquardt) optimizer from @@ -169,11 +173,13 @@ def scipyOptimize(recipe): # (recipe.residual) and the starting values of the Variables # (recipe.getValues()). from scipy.optimize.minpack import leastsq + print("Fit using scipy's LM optimizer") leastsq(recipe.residual, recipe.getValues()) return + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -189,15 +195,17 @@ def plotResults(recipe): # This stuff is specific to pylab from the matplotlib distribution. import pylab - pylab.plot(x, y, 'b.', label = "observed Gaussian") - pylab.plot(x, ycalc, 'g-', label = "calculated Gaussian") - pylab.legend(loc = (0.0,0.8)) + + pylab.plot(x, y, "b.", label="observed Gaussian") + pylab.plot(x, ycalc, "g-", label="calculated Gaussian") + pylab.legend(loc=(0.0, 0.8)) pylab.xlabel("x") pylab.ylabel("y") pylab.show() return + if __name__ == "__main__": main() diff --git a/doc/examples/interface.py b/doc/examples/interface.py index 106af7a2..5b1a250e 100644 --- a/doc/examples/interface.py +++ b/doc/examples/interface.py @@ -12,17 +12,22 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting a Gaussian to simulated data. -This is like gaussianrecipe.py, but it uses a shorthand interface defined in -the diffpy.srfit.interface.interface.py module. +This is like gaussianrecipe.py, but it uses a shorthand interface +defined in the diffpy.srfit.interface.interface.py module. """ -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def main(): p = Profile() @@ -47,11 +52,12 @@ def main(): # loosely tying parameters to a value. r = FitRecipe() r |= c - r += (c.A, 0.5), (c.x0, 5), 'sig' - r *= c.sigma, 'sig' + r += (c.A, 0.5), (c.x0, 5), "sig" + r *= c.sigma, "sig" r %= c.A, 0.5, 0.5 from gaussianrecipe import scipyOptimize + scipyOptimize(r) res = FitResults(r) @@ -59,6 +65,7 @@ def main(): res.printResults() # Plot the results. from gaussianrecipe import plotResults + plotResults(r) return diff --git a/doc/examples/npintensity.py b/doc/examples/npintensity.py index 91a2b55d..5cad99a6 100644 --- a/doc/examples/npintensity.py +++ b/doc/examples/npintensity.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of using ProfileGenerators in FitContributions. This is an example of building a ProfileGenerator and using it in a @@ -45,44 +44,48 @@ from __future__ import print_function import numpy +from gaussianrecipe import scipyOptimize -from diffpy.srfit.fitbase import ProfileGenerator, Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, + ProfileGenerator, +) from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet -from gaussianrecipe import scipyOptimize - ####### Example Code + class IntensityGenerator(ProfileGenerator): """A class for calculating intensity using the Debye equation. - Calculating intensity from a structure is difficult in general. This class - takes a diffpy.structure.Structure instance and from that generates a - theoretical intensity signal. Unlike the example in gaussianrecipe.py, the - intensity generator is not simple. It must take a structure object and some - Parameters, and from that generate a signal. At the same time, the - structure itself (the lattice, atom positions, thermal parameters, etc.) - needs to be refinable. Thus we define this ProfileGenerator to help us - interface which exposes the Parameters required by the calculation and - provides a way for a FitContribution to perform that calculation. - - The purpose of a ProfileGenerator is to - 1) provide a function that generates a profile signal - 2) organize the Parameters required for the calculation - - This generator wraps the 'iofq' function defined below. Knowledge of this - function is not required for this example. - + Calculating intensity from a structure is difficult in general. This + class takes a diffpy.structure.Structure instance and from that + generates a theoretical intensity signal. Unlike the example in + gaussianrecipe.py, the intensity generator is not simple. It must + take a structure object and some Parameters, and from that generate + a signal. At the same time, the structure itself (the lattice, atom + positions, thermal parameters, etc.) needs to be refinable. Thus we + define this ProfileGenerator to help us interface which exposes the + Parameters required by the calculation and provides a way for a + FitContribution to perform that calculation. + + The purpose of a ProfileGenerator is to 1) provide a function that + generates a profile signal 2) organize the Parameters required for + the calculation + + This generator wraps the 'iofq' function defined below. Knowledge of + this function is not required for this example. """ def __init__(self, name): """Define our generator. - In this example we will keep count of how many times the calculation - gets performed. The 'count' attribute will be used to store the count. - + In this example we will keep count of how many times the + calculation gets performed. The 'count' attribute will be used + to store the count. """ ProfileGenerator.__init__(self, name) # Count the calls @@ -132,10 +135,10 @@ def setStructure(self, strufile): The diffpy.structure.Structure instance is held within the DiffpyStructureParSet as the 'stru' attribute. - """ # Load the structure from file from diffpy.structure import Structure + stru = Structure() stru.read(strufile) @@ -156,17 +159,18 @@ def setStructure(self, strufile): def __call__(self, q): """Calculate the intensity. - This ProfileGenerator will be used in a FitContribution that will be - optimized to fit some data. By the time this function is evaluated, - the diffpy.structure.Structure instance has been updated by the - optimizer via the DiffpyStructureParSet defined in setStructure. Thus, - we need only call iofq with the internal structure object. - + This ProfileGenerator will be used in a FitContribution that + will be optimized to fit some data. By the time this function + is evaluated, the diffpy.structure.Structure instance has been + updated by the optimizer via the DiffpyStructureParSet defined + in setStructure. Thus, we need only call iofq with the internal + structure object. """ self.count += 1 print("iofq called", self.count) return iofq(self.phase.stru, q) + # End class IntensityGenerator @@ -175,7 +179,6 @@ def makeRecipe(strufile, datname): This will create a FitContribution that uses the IntensityGenerator, associate this with a Profile, and use this to define a FitRecipe. - """ ## The Profile @@ -200,7 +203,7 @@ def makeRecipe(strufile, datname): # use it in equations with this name. contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "q") + contribution.setProfile(profile, xname="q") # Now we're ready to define the fitting equation for the FitContribution. # We need to modify the intensity calculation, and we'll do that from @@ -233,8 +236,13 @@ def makeRecipe(strufile, datname): # function and registering it with the FitContribution. pi = numpy.pi exp = numpy.exp + def gaussian(q, q0, width): - return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) + return ( + 1 + / (2 * pi * width**2) ** 0.5 + * exp(-0.5 * ((q - q0) / width) ** 2) + ) # This registers the python function and extracts the name and creates # Parameters from the arguments. @@ -294,6 +302,7 @@ def gaussian(q, q0, width): # Give the recipe away so it can be used! return recipe + def main(): # Make the data and the recipe @@ -315,14 +324,15 @@ def main(): # 'iofq' from the IntensityGenerator. rescount = recipe.fithooks[0].count calcount = recipe.bucky.I.count - footer = "iofq called %i%% of the time"%int(100.0*calcount/rescount) - res.printResults(footer = footer) + footer = "iofq called %i%% of the time" % int(100.0 * calcount / rescount) + res.printResults(footer=footer) # Plot! plotResults(recipe) return + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -335,10 +345,11 @@ def plotResults(recipe): diff = I - Icalc import pylab - pylab.plot(q,I,'ob',label="I(Q) Data") - pylab.plot(q,Icalc,'r-',label="I(Q) Fit") - pylab.plot(q,diff,'g-',label="I(Q) diff") - pylab.plot(q,bkgd,'c-',label="Bkgd. Fit") + + pylab.plot(q, I, "ob", label="I(Q) Data") + pylab.plot(q, Icalc, "r-", label="I(Q) Fit") + pylab.plot(q, diff, "g-", label="I(Q) diff") + pylab.plot(q, bkgd, "c-", label="Bkgd. Fit") pylab.xlabel(r"$Q (\AA^{-1})$") pylab.ylabel("Intensity (arb. units)") pylab.legend(loc=1) @@ -360,7 +371,6 @@ def iofq(S, q): This uses cctbx for the calculation of the f_i if it is available, otherwise f_i = 1. - """ # The functions we need sinc = numpy.sinc @@ -371,9 +381,9 @@ def iofq(S, q): # The precision of distance measurements deltad = 1e-6 - dmult = int(1/deltad) + dmult = int(1 / deltad) deltau = deltad**2 - umult = int(1/deltau) + umult = int(1 / deltau) pairdict = {} elcount = {} @@ -395,11 +405,11 @@ def iofq(S, q): # Get the distance to the desired precision d = S.distance(i, j) - D = int(d*dmult) + D = int(d * dmult) # Get the DW factor to the same precision ss = S[i].Uisoequiv + S[j].Uisoequiv - SS = int(ss*umult) + SS = int(ss * umult) # Record the multiplicity of this pair key = (els[0], els[1], D, SS) @@ -439,23 +449,25 @@ def iofq(S, q): return y + def getXScatteringFactor(el, q): """Get the x-ray scattering factor for an element over the q range. If cctbx is not available, f(q) = 1 is used. - """ try: import cctbx.eltbx.xray_scattering as xray + wk1995 = xray.wk1995(el) g = wk1995.fetch() # at_stol - at sin(theta)/lambda = Q/(4*pi) - f = numpy.asarray( map( g.at_stol, q/(4*numpy.pi) ) ) + f = numpy.asarray(map(g.at_stol, q / (4 * numpy.pi))) return f except ImportError: return 1 -def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): + +def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl=1): """Make some fake data and save it to file. Make some data to fit. This uses iofq to calculate an intensity curve, and @@ -470,10 +482,10 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): sig -- The broadening factor bkgc -- A parameter that gives minor control of the background. nl -- Noise level (0, inf), default 1, larger -> less noise. - """ from diffpy.structure import Structure + S = Structure() S.read(strufile) @@ -487,11 +499,11 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): # We want to broaden the peaks as well. This simulates instrument effects. q0 = q[len(q) // 2] - g = numpy.exp(-0.5*((q-q0)/sig)**2) - y = numpy.convolve(y, g, mode='same')/sum(g) + g = numpy.exp(-0.5 * ((q - q0) / sig) ** 2) + y = numpy.convolve(y, g, mode="same") / sum(g) # Add a polynomial background. - bkgd = (q + bkgc)**2 * (1.5*max(q) - q)**5 + bkgd = (q + bkgc) ** 2 * (1.5 * max(q) - q) ** 5 bkgd *= 0.2 * max(y) / max(bkgd) y += bkgd @@ -500,11 +512,11 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): y *= scale # Calculate the uncertainty - u = (y/nl)**0.5 + u = (y / nl) ** 0.5 # And apply the noise if nl > 0: - y = numpy.random.poisson(y*nl) / nl + y = numpy.random.poisson(y * nl) / nl # Now save it numpy.savetxt(datname, numpy.transpose([q, y, u])) diff --git a/doc/examples/npintensityII.py b/doc/examples/npintensityII.py index 7ab98dce..e4607f90 100644 --- a/doc/examples/npintensityII.py +++ b/doc/examples/npintensityII.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of extracting information from multiple data sets simultaneously. This example builds on npintensitygenerator.py, and uses IntensityGenerator @@ -35,28 +34,32 @@ """ import numpy - -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults -from npintensity import IntensityGenerator -from npintensity import makeData - from gaussianrecipe import scipyOptimize +from npintensity import IntensityGenerator, makeData + +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def makeRecipe(strufile, datname1, datname2): """Create a recipe that uses the IntensityGenerator. - We will create two FitContributions that use the IntensityGenerator from - npintensitygenerator.py and associate each of these with a Profile, and use - this to define a FitRecipe. - - Both simulated data sets come from the same structure. We're going to make - two FitContributions that are identical, except for the profile that is - held in each. We're going to assure that the structures are identical by - using the same DiffpyStructureParSet (which is generated by the - IntensityGenerator when we load the structure) in both generators. + We will create two FitContributions that use the IntensityGenerator + from npintensitygenerator.py and associate each of these with a + Profile, and use this to define a FitRecipe. + Both simulated data sets come from the same structure. We're going + to make two FitContributions that are identical, except for the + profile that is held in each. We're going to assure that the + structures are identical by using the same DiffpyStructureParSet + (which is generated by the IntensityGenerator when we load the + structure) in both generators. """ ## The Profiles @@ -85,10 +88,10 @@ def makeRecipe(strufile, datname1, datname2): # Create the FitContributions. contribution1 = FitContribution("bucky1") contribution1.addProfileGenerator(generator1) - contribution1.setProfile(profile1, xname = "q") + contribution1.setProfile(profile1, xname="q") contribution2 = FitContribution("bucky2") contribution2.addProfileGenerator(generator2) - contribution2.setProfile(profile2, xname = "q") + contribution2.setProfile(profile2, xname="q") # Now we're ready to define the fitting equation for each FitContribution. # The functions registered below will be independent, even though they take @@ -105,8 +108,13 @@ def makeRecipe(strufile, datname1, datname2): # We will create the broadening function by registering a python function. pi = numpy.pi exp = numpy.exp + def gaussian(q, q0, width): - return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) + return ( + 1 + / (2 * pi * width**2) ** 0.5 + * exp(-0.5 * ((q - q0) / width) ** 2) + ) contribution1.registerFunction(gaussian) contribution2.registerFunction(gaussian) @@ -128,32 +136,32 @@ def gaussian(q, q0, width): # background that we just defined in the FitContributions. We have to do # this separately for each FitContribution. We tag the variables so it is # easy to retrieve the background variables. - recipe.addVar(contribution1.b0, 0, name = "b1_0", tag = "bcoeffs1") - recipe.addVar(contribution1.b1, 0, name = "b1_1", tag = "bcoeffs1") - recipe.addVar(contribution1.b2, 0, name = "b1_2", tag = "bcoeffs1") - recipe.addVar(contribution1.b3, 0, name = "b1_3", tag = "bcoeffs1") - recipe.addVar(contribution1.b4, 0, name = "b1_4", tag = "bcoeffs1") - recipe.addVar(contribution1.b5, 0, name = "b1_5", tag = "bcoeffs1") - recipe.addVar(contribution1.b6, 0, name = "b1_6", tag = "bcoeffs1") - recipe.addVar(contribution1.b7, 0, name = "b1_7", tag = "bcoeffs1") - recipe.addVar(contribution1.b8, 0, name = "b1_8", tag = "bcoeffs1") - recipe.addVar(contribution1.b9, 0, name = "b1_9", tag = "bcoeffs1") - recipe.addVar(contribution2.b0, 0, name = "b2_0", tag = "bcoeffs2") - recipe.addVar(contribution2.b1, 0, name = "b2_1", tag = "bcoeffs2") - recipe.addVar(contribution2.b2, 0, name = "b2_2", tag = "bcoeffs2") - recipe.addVar(contribution2.b3, 0, name = "b2_3", tag = "bcoeffs2") - recipe.addVar(contribution2.b4, 0, name = "b2_4", tag = "bcoeffs2") - recipe.addVar(contribution2.b5, 0, name = "b2_5", tag = "bcoeffs2") - recipe.addVar(contribution2.b6, 0, name = "b2_6", tag = "bcoeffs2") - recipe.addVar(contribution2.b7, 0, name = "b2_7", tag = "bcoeffs2") - recipe.addVar(contribution2.b8, 0, name = "b2_8", tag = "bcoeffs2") - recipe.addVar(contribution2.b9, 0, name = "b2_9", tag = "bcoeffs2") + recipe.addVar(contribution1.b0, 0, name="b1_0", tag="bcoeffs1") + recipe.addVar(contribution1.b1, 0, name="b1_1", tag="bcoeffs1") + recipe.addVar(contribution1.b2, 0, name="b1_2", tag="bcoeffs1") + recipe.addVar(contribution1.b3, 0, name="b1_3", tag="bcoeffs1") + recipe.addVar(contribution1.b4, 0, name="b1_4", tag="bcoeffs1") + recipe.addVar(contribution1.b5, 0, name="b1_5", tag="bcoeffs1") + recipe.addVar(contribution1.b6, 0, name="b1_6", tag="bcoeffs1") + recipe.addVar(contribution1.b7, 0, name="b1_7", tag="bcoeffs1") + recipe.addVar(contribution1.b8, 0, name="b1_8", tag="bcoeffs1") + recipe.addVar(contribution1.b9, 0, name="b1_9", tag="bcoeffs1") + recipe.addVar(contribution2.b0, 0, name="b2_0", tag="bcoeffs2") + recipe.addVar(contribution2.b1, 0, name="b2_1", tag="bcoeffs2") + recipe.addVar(contribution2.b2, 0, name="b2_2", tag="bcoeffs2") + recipe.addVar(contribution2.b3, 0, name="b2_3", tag="bcoeffs2") + recipe.addVar(contribution2.b4, 0, name="b2_4", tag="bcoeffs2") + recipe.addVar(contribution2.b5, 0, name="b2_5", tag="bcoeffs2") + recipe.addVar(contribution2.b6, 0, name="b2_6", tag="bcoeffs2") + recipe.addVar(contribution2.b7, 0, name="b2_7", tag="bcoeffs2") + recipe.addVar(contribution2.b8, 0, name="b2_8", tag="bcoeffs2") + recipe.addVar(contribution2.b9, 0, name="b2_9", tag="bcoeffs2") # We also want to adjust the scale and the convolution width - recipe.addVar(contribution1.scale, 1, name = "scale1") - recipe.addVar(contribution1.width, 0.1, name = "width1") - recipe.addVar(contribution2.scale, 1, name = "scale2") - recipe.addVar(contribution2.width, 0.1, name = "width2") + recipe.addVar(contribution1.scale, 1, name="scale1") + recipe.addVar(contribution1.width, 0.1, name="width1") + recipe.addVar(contribution2.scale, 1, name="scale2") + recipe.addVar(contribution2.width, 0.1, name="width2") # We can also refine structural parameters. We only have to do this once, # since each generator holds the same DiffpyStructureParSet. @@ -175,6 +183,7 @@ def gaussian(q, q0, width): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -197,18 +206,19 @@ def plotResults(recipe): diff1 += offset import pylab + pylab.subplot(2, 1, 1) - pylab.plot(q,I1,'bo',label="I1(Q) Data") - pylab.plot(q,Icalc1,'r-',label="I1(Q) Fit") - pylab.plot(q,diff1,'g-',label="I1(Q) diff") - pylab.plot(q,bkgd1,'c-',label="Bkgd1 Fit") + pylab.plot(q, I1, "bo", label="I1(Q) Data") + pylab.plot(q, Icalc1, "r-", label="I1(Q) Fit") + pylab.plot(q, diff1, "g-", label="I1(Q) diff") + pylab.plot(q, bkgd1, "c-", label="Bkgd1 Fit") pylab.legend(loc=1) pylab.subplot(2, 1, 2) - pylab.plot(q,I2,'bo',label="I2(Q) Data") - pylab.plot(q,Icalc2,'r-',label="I2(Q) Fit") - pylab.plot(q,diff2,'g-',label="I2(Q) diff") - pylab.plot(q,bkgd2,'c-',label="Bkgd2 Fit") + pylab.plot(q, I2, "bo", label="I2(Q) Data") + pylab.plot(q, Icalc2, "r-", label="I2(Q) Fit") + pylab.plot(q, diff2, "g-", label="I2(Q) diff") + pylab.plot(q, bkgd2, "c-", label="Bkgd2 Fit") pylab.xlabel(r"$Q (\AA^{-1})$") pylab.ylabel("Intensity (arb. units)") pylab.legend(loc=1) @@ -216,6 +226,7 @@ def plotResults(recipe): pylab.show() return + def main(): # Make two different data sets, each from the same structure, but with @@ -258,6 +269,7 @@ def main(): return + if __name__ == "__main__": main() diff --git a/doc/examples/nppdfcrystal.py b/doc/examples/nppdfcrystal.py index 46ffb848..0c098efe 100644 --- a/doc/examples/nppdfcrystal.py +++ b/doc/examples/nppdfcrystal.py @@ -12,29 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting a crystal-like nanoparticle (nanocrystal) PDF. -This is an example of modeling the PDF from a nanocrystal as an attenuated bulk -PDF. This involves a crystal PDF calculation and a spherical nanoparticle -characteristic function. -The equation we model is -Gnano(r) = f(r) * Gbulk(r), -where f(r) is the nanoparticle characteristic function for the nanoparticle -shape. Functions for calculating the characteristic function in the +This is an example of modeling the PDF from a nanocrystal as an +attenuated bulk PDF. This involves a crystal PDF calculation and a +spherical nanoparticle characteristic function. The equation we model is +Gnano(r) = f(r) * Gbulk(r), where f(r) is the nanoparticle +characteristic function for the nanoparticle shape. Functions for +calculating the characteristic function in the diffpy.srfit.pdf.characteristicfunctions module. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults -from gaussianrecipe import scipyOptimize def makeRecipe(ciffile, grdata): """Make a recipe to model a crystal-like nanoparticle PDF.""" @@ -45,10 +45,10 @@ def makeRecipe(ciffile, grdata): pdfparser = PDFParser() pdfparser.parseFile(grdata) pdfprofile.loadParsedData(pdfparser) - pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20) + pdfprofile.setCalculationRange(xmin=0.1, xmax=20) pdfcontribution = FitContribution("pdf") - pdfcontribution.setProfile(pdfprofile, xname = "r") + pdfcontribution.setProfile(pdfprofile, xname="r") pdfgenerator = PDFGenerator("G") pdfgenerator.setQmax(30.0) @@ -58,7 +58,8 @@ def makeRecipe(ciffile, grdata): # Register the nanoparticle shape factor. from diffpy.srfit.pdf.characteristicfunctions import sphericalCF - pdfcontribution.registerFunction(sphericalCF, name = "f") + + pdfcontribution.registerFunction(sphericalCF, name="f") # Now we set up the fitting equation. pdfcontribution.setEquation("f * G") @@ -79,6 +80,7 @@ def makeRecipe(ciffile, grdata): return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -96,12 +98,13 @@ def plotResults(recipe): fr *= max(g) / fr[0] import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcryst,'y--',label="G(r) Crystal") - pylab.plot(r, fr,'k--',label="f(r) calculated (scaled)") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r,diff,'g-',label="G(r) diff") - pylab.plot(r, diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcryst, "y--", label="G(r) Crystal") + pylab.plot(r, fr, "k--", label="f(r) calculated (scaled)") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -109,6 +112,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": ciffile = "data/pb.cif" diff --git a/doc/examples/nppdfobjcryst.py b/doc/examples/nppdfobjcryst.py index bab460fc..ad420944 100644 --- a/doc/examples/nppdfobjcryst.py +++ b/doc/examples/nppdfobjcryst.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a nanoparticle PDF refinement using DebyePDFGenerator. This example is similar to crystalpdfobjcryst.py, except that it uses @@ -21,13 +20,17 @@ import numpy -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import DebyePDFGenerator ####### Example Code + def makeRecipe(molecule, datname): """Create a recipe that uses the DebyePDFGenerator.""" @@ -49,7 +52,7 @@ def makeRecipe(molecule, datname): ## The FitContribution contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") # Make a FitRecipe. recipe = FitRecipe() @@ -92,7 +95,7 @@ def makeRecipe(molecule, datname): # This creates a Parameter that moves the second atom according to the # bond length. Note that each Parameter needs a unique name. - par = c60.addBondLengthParameter("rad%i"%i, center, atom) + par = c60.addBondLengthParameter("rad%i" % i, center, atom) recipe.constrain(par, radius) # Add the correlation term, scale. The scale is too short to effectively @@ -103,6 +106,7 @@ def makeRecipe(molecule, datname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -110,14 +114,15 @@ def plotResults(recipe): r = recipe.bucky.profile.x g = recipe.bucky.profile.y gcalc = recipe.bucky.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) + diffzero = -0.8 * max(g) * numpy.ones_like(g) diff = g - gcalc + diffzero import pylab - pylab.plot(r,g,'ob',label="G(r) Data") - pylab.plot(r,gcalc,'-r',label="G(r) Fit") - pylab.plot(r,diff,'-g',label="G(r) diff") - pylab.plot(r,diffzero,'-k') + + pylab.plot(r, g, "ob", label="G(r) Data") + pylab.plot(r, gcalc, "-r", label="G(r) Fit") + pylab.plot(r, diff, "-g", label="G(r) diff") + pylab.plot(r, diffzero, "-k") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -125,6 +130,7 @@ def plotResults(recipe): pylab.show() return + def main(): molecule = makeC60() @@ -135,6 +141,7 @@ def main(): # Optimize from scipy.optimize import leastsq + leastsq(recipe.residual, recipe.getValues()) # Print results @@ -146,8 +153,8 @@ def main(): return -c60xyz = \ -""" + +c60xyz = """ 3.451266498 0.685000000 0.000000000 3.451266498 -0.685000000 0.000000000 -3.451266498 0.685000000 0.000000000 @@ -210,6 +217,7 @@ def main(): -2.279809890 -2.580456608 -0.724000000 """ + def makeC60(): """Make the C60 molecule using pyobjcryst.""" @@ -236,7 +244,7 @@ def makeC60(): # Add the other atoms. They will be named C1, C2, ..., C60. for i, l in enumerate(c60xyz.strip().splitlines()): x, y, z = map(float, l.split()) - m.AddAtom(x, y, z, sp, "C%i"%(i+1)) + m.AddAtom(x, y, z, sp, "C%i" % (i + 1)) return m diff --git a/doc/examples/nppdfsas.py b/doc/examples/nppdfsas.py index b5975345..0a9b8202 100644 --- a/doc/examples/nppdfsas.py +++ b/doc/examples/nppdfsas.py @@ -12,37 +12,36 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of combining PDF and SAS nanoparticles data. -This is an example of using both PDF and SAS data in the same fit. This fits a -crystal model to the PDF while fitting a shape model to both the SAS profile -and the PDF data. Using the same shape for the PDF and SAS provides a feedback -mechanism into the fit that allows the PDF and SAS portions of the fit to guide -one another, and in the end gives the shape of the nanoparticle that agrees -best with both the PDF and SAS data. +This is an example of using both PDF and SAS data in the same fit. This +fits a crystal model to the PDF while fitting a shape model to both the +SAS profile and the PDF data. Using the same shape for the PDF and SAS +provides a feedback mechanism into the fit that allows the PDF and SAS +portions of the fit to guide one another, and in the end gives the shape +of the nanoparticle that agrees best with both the PDF and SAS data. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.srfit.pdf.characteristicfunctions import SASCF -from diffpy.srfit.sas import SASParser, SASGenerator -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.sas import SASGenerator, SASParser -from gaussianrecipe import scipyOptimize def makeRecipe(ciffile, grdata, iqdata): - """Make complex-modeling recipe where I(q) and G(r) are fit - simultaneously. - - The fit I(q) is fed into the calculation of G(r), which provides feedback - for the fit parameters of both. + """Make complex-modeling recipe where I(q) and G(r) are fit simultaneously. + The fit I(q) is fed into the calculation of G(r), which provides + feedback for the fit parameters of both. """ # Create a PDF contribution as before @@ -50,10 +49,10 @@ def makeRecipe(ciffile, grdata, iqdata): pdfparser = PDFParser() pdfparser.parseFile(grdata) pdfprofile.loadParsedData(pdfparser) - pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20) + pdfprofile.setCalculationRange(xmin=0.1, xmax=20) pdfcontribution = FitContribution("pdf") - pdfcontribution.setProfile(pdfprofile, xname = "r") + pdfcontribution.setProfile(pdfprofile, xname="r") pdfgenerator = PDFGenerator("G") pdfgenerator.setQmax(30.0) @@ -75,6 +74,7 @@ def makeRecipe(ciffile, grdata, iqdata): sascontribution.setProfile(sasprofile) from sas.models.EllipsoidModel import EllipsoidModel + model = EllipsoidModel() sasgenerator = SASGenerator("generator", model) sascontribution.addProfileGenerator(sasgenerator) @@ -105,7 +105,7 @@ def makeRecipe(ciffile, grdata, iqdata): recipe.addVar(pdfgenerator.delta2, 0) # SAS - recipe.addVar(sasgenerator.scale, 1, name = "iqscale") + recipe.addVar(sasgenerator.scale, 1, name="iqscale") recipe.addVar(sasgenerator.radius_a, 10) recipe.addVar(sasgenerator.radius_b, 10) @@ -117,16 +117,17 @@ def makeRecipe(ciffile, grdata, iqdata): return recipe + def fitRecipe(recipe): """We refine in stages to help the refinement converge.""" # Tune SAS. recipe.setWeight(recipe.pdf, 0) recipe.fix("all") - recipe.free("radius_a", "radius_b", iqscale = 1e8) - recipe.constrain('radius_b', 'radius_a') + recipe.free("radius_a", "radius_b", iqscale=1e8) + recipe.constrain("radius_b", "radius_a") scipyOptimize(recipe) - recipe.unconstrain('radius_b') + recipe.unconstrain("radius_b") # Tune PDF recipe.setWeight(recipe.pdf, 1) @@ -143,6 +144,7 @@ def fitRecipe(recipe): return + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -160,12 +162,13 @@ def plotResults(recipe): fr *= max(g) / fr[0] import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcryst,'y--',label="G(r) Crystal") - pylab.plot(r, fr,'k--',label="f(r) calculated (scaled)") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r, diff,'g-',label="G(r) diff") - pylab.plot(r, diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcryst, "y--", label="G(r) Crystal") + pylab.plot(r, fr, "k--", label="f(r) calculated (scaled)") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) diff --git a/doc/examples/simplepdf.py b/doc/examples/simplepdf.py index df95bbd2..dc36862a 100644 --- a/doc/examples/simplepdf.py +++ b/doc/examples/simplepdf.py @@ -12,29 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF using the PDFContribution helper class. -This is example of fitting the fcc nickel structure to measured PDF data. -It uses the PDFContribution class to simplify fit setup. +This is example of fitting the fcc nickel structure to measured PDF +data. It uses the PDFContribution class to simplify fit setup. """ -from diffpy.structure import Structure -from diffpy.srfit.pdf import PDFContribution -from diffpy.srfit.fitbase import FitRecipe, FitResults - -from gaussianrecipe import scipyOptimize from crystalpdf import plotResults +from gaussianrecipe import scipyOptimize + +from diffpy.srfit.fitbase import FitRecipe, FitResults +from diffpy.srfit.pdf import PDFContribution +from diffpy.structure import Structure ####### Example Code + def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" # Work directly with a custom PDFContribution to load the data contribution = PDFContribution("nickel") contribution.loadData(datname) - contribution.setCalculationRange(xmin = 1, xmax = 20, dx = 0.1) + contribution.setCalculationRange(xmin=1, xmax=20, dx=0.1) # and the phase stru = Structure() @@ -49,6 +49,7 @@ def makeRecipe(ciffile, datname): phase = contribution.nickel.phase from diffpy.srfit.structure import constrainAsSpaceGroup + sgpars = constrainAsSpaceGroup(phase, "Fm-3m") for par in sgpars.latpars: @@ -57,12 +58,13 @@ def makeRecipe(ciffile, datname): recipe.addVar(par, 0.005) recipe.addVar(contribution.scale, 1) - recipe.addVar(contribution.qdamp, 0.03, fixed = True) + recipe.addVar(contribution.qdamp, 0.03, fixed=True) recipe.addVar(contribution.nickel.delta2, 5) # Give the recipe away so it can be used! return recipe + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/simplepdftwophase.py b/doc/examples/simplepdftwophase.py index f000926a..af1c1dae 100644 --- a/doc/examples/simplepdftwophase.py +++ b/doc/examples/simplepdftwophase.py @@ -12,26 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a simplified PDF refinement of two-phase structure.""" +from crystalpdftwophase import plotResults +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal -from diffpy.srfit.pdf import PDFContribution from diffpy.srfit.fitbase import FitRecipe, FitResults - -from gaussianrecipe import scipyOptimize -from crystalpdftwophase import plotResults +from diffpy.srfit.pdf import PDFContribution ####### Example Code + def makeRecipe(niciffile, siciffile, datname): """Create a fitting recipe for crystalline PDF data.""" # Load data and add it to the profile contribution = PDFContribution("nisi") contribution.loadData(datname) - contribution.setCalculationRange(xmax = 20) + contribution.setCalculationRange(xmax=20) stru = loadCrystal(niciffile) contribution.addStructure("ni", stru) @@ -66,13 +65,13 @@ def makeRecipe(niciffile, siciffile, datname): # above. phase_ni = contribution.ni.phase for par in phase_ni.sgpars: - recipe.addVar(par, name = par.name + "_ni") - recipe.addVar(contribution.ni.delta2, name = "delta2_ni") + recipe.addVar(par, name=par.name + "_ni") + recipe.addVar(contribution.ni.delta2, name="delta2_ni") # Next the silicon parameters phase_si = contribution.si.phase for par in phase_si.sgpars: - recipe.addVar(par, name = par.name + "_si") - recipe.addVar(contribution.si.delta2, name = "delta2_si") + recipe.addVar(par, name=par.name + "_si") + recipe.addVar(contribution.si.delta2, name="delta2_si") # We have prior information from the earlier examples so we'll use it here # in the form of restraints. @@ -82,18 +81,18 @@ def makeRecipe(niciffile, siciffile, datname): # derived has no uncertainty. Thus, we will tell the recipe to scale the # residual, which means that it will be weighted as much as the average # data point during the fit. - recipe.restrain("a_ni", lb = 3.527, ub = 3.527, scaled = True) + recipe.restrain("a_ni", lb=3.527, ub=3.527, scaled=True) # Now we do the same with the delta2 and Biso parameters (remember that # Biso = 8*pi**2*Uiso) - recipe.restrain("delta2_ni", lb = 2.22, ub = 2.22, scaled = True) - recipe.restrain("Biso_0_ni", lb = 0.454, ub = 0.454, scaled = True) + recipe.restrain("delta2_ni", lb=2.22, ub=2.22, scaled=True) + recipe.restrain("Biso_0_ni", lb=0.454, ub=0.454, scaled=True) # # We can do the same with the silicon values. We haven't done a thorough # job of measuring the uncertainties in the results, so we'll scale these # as well. - recipe.restrain("a_si", lb = 5.430, ub = 5.430, scaled = True) - recipe.restrain("delta2_si", lb = 3.54, ub = 3.54, scaled = True) - recipe.restrain("Biso_0_si", lb = 0.645, ub = 0.645, scaled = True) + recipe.restrain("a_si", lb=5.430, ub=5.430, scaled=True) + recipe.restrain("delta2_si", lb=3.54, ub=3.54, scaled=True) + recipe.restrain("Biso_0_si", lb=0.645, ub=0.645, scaled=True) # Give the recipe away so it can be used! return recipe diff --git a/doc/examples/simplerecipe.py b/doc/examples/simplerecipe.py index 45b8785f..92ed185e 100644 --- a/doc/examples/simplerecipe.py +++ b/doc/examples/simplerecipe.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of simplified fitting. This is like gaussianrecipe.py, but it uses the SimpleRecipe, which @@ -24,6 +23,7 @@ ####### Example Code + def main(): """Set up a simple recipe in a few lines.""" @@ -46,6 +46,7 @@ def main(): # We explicitly optimize the residual method of the SimpleRecipe from scipy.optimize import leastsq + leastsq(recipe.residual, recipe.values) # Print the results @@ -53,6 +54,7 @@ def main(): return + if __name__ == "__main__": main() diff --git a/doc/examples/threedoublepeaks.py b/doc/examples/threedoublepeaks.py index 35b7597b..75c3d9ef 100644 --- a/doc/examples/threedoublepeaks.py +++ b/doc/examples/threedoublepeaks.py @@ -12,33 +12,34 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - -"""Example of fitting a three double peaks to simulated data. -""" +"""Example of fitting a three double peaks to simulated data.""" from __future__ import print_function import numpy -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def makeRecipe(): """Make a FitRecipe for fitting three double-gaussian curves to data. - The separation and amplitude ratio of the double peaks follows a specific - relationship. The peaks are broadend according to their position and they - sit on top of a background. We are seeking the absolute locations of the - peaks as well as their amplitudes. + The separation and amplitude ratio of the double peaks follows a + specific relationship. The peaks are broadend according to their + position and they sit on top of a background. We are seeking the + absolute locations of the peaks as well as their amplitudes. The independent variable is t. The relationship between the double - peaks is - sin(t2) / l2 = sin(t1) / l1 - amplitude(peak2) = r * amplitude(peak1) - The values of l1, l2 and r come from experiment. For this example, we - use l1 = 1.012, l2 = 1.0 and r = 0.23. - + peaks is sin(t2) / l2 = sin(t1) / l1 amplitude(peak2) = r * + amplitude(peak1) The values of l1, l2 and r come from experiment. + For this example, we use l1 = 1.012, l2 = 1.0 and r = 0.23. """ ## The Profile @@ -48,22 +49,21 @@ def makeRecipe(): # Create the contribution contribution = FitContribution("peaks") - contribution.setProfile(profile, xname = "t") + contribution.setProfile(profile, xname="t") pi = numpy.pi exp = numpy.exp # This is a building-block of our profile function def gaussian(t, mu, sig): - return 1/(2*pi*sig**2)**0.5 * exp(-0.5 * ((t-mu)/sig)**2) + return 1 / (2 * pi * sig**2) ** 0.5 * exp(-0.5 * ((t - mu) / sig) ** 2) - contribution.registerFunction(gaussian, name = "peakshape") + contribution.registerFunction(gaussian, name="peakshape") def delta(t, mu): """Calculate a delta-function. - We don't have perfect precision, so we must make this a very thin - Gaussian. - + We don't have perfect precision, so we must make this a very + thin Gaussian. """ sig = t[1] - t[0] return gaussian(t, mu, sig) @@ -83,10 +83,11 @@ def delta(t, mu): + 0.23*convolve( delta(t, mu22), peakshape(t, c, sig22) ) ) + \ A3 * ( convolve( delta(t, mu31), peakshape(t, c, sig31) ) \ + 0.23*convolve( delta(t, mu32), peakshape(t, c, sig32) ) ) + \ - bkgd") + bkgd" + ) # c is the center of the gaussian. - contribution.c.value = x[len(x) // 2] + contribution.c.value = x[len(x) // 2] ## The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can @@ -109,12 +110,13 @@ def delta(t, mu): recipe.addVar(contribution.mu31, 33.0) # Constrain the position of the second double peak - from numpy import sin, arcsin + from numpy import arcsin, sin + def peakloc(mu): """Calculate the location of the second peak given the first.""" l1 = 1.012 l2 = 1.0 - return 180 / pi * arcsin( pi / 180 * l2 * sin(mu) / l1 ) + return 180 / pi * arcsin(pi / 180 * l2 * sin(mu) / l1) recipe.registerFunction(peakloc) recipe.constrain(contribution.mu12, "peakloc(mu11)") @@ -128,7 +130,7 @@ def peakloc(mu): def sig(sig0, dsig, mu): """Calculate the peak broadening with respect to position.""" - return sig0 * (1 - dsig * mu**2); + return sig0 * (1 - dsig * mu**2) recipe.registerFunction(sig) recipe.fix("mu") @@ -136,32 +138,41 @@ def sig(sig0, dsig, mu): recipe.sig0.value = 0.001 recipe.dsig.value = 4.0 recipe.constrain(contribution.sig11, "sig(sig0, dsig, mu11)") - recipe.constrain(contribution.sig12, "sig(sig0, dsig, mu12)", - ns = {"mu12" : contribution.mu12} ) + recipe.constrain( + contribution.sig12, + "sig(sig0, dsig, mu12)", + ns={"mu12": contribution.mu12}, + ) recipe.constrain(contribution.sig21, "sig(sig0, dsig, mu21)") - recipe.constrain(contribution.sig22, "sig(sig0, dsig, mu22)", - ns = {"mu22" : contribution.mu22} ) + recipe.constrain( + contribution.sig22, + "sig(sig0, dsig, mu22)", + ns={"mu22": contribution.mu22}, + ) recipe.constrain(contribution.sig31, "sig(sig0, dsig, mu31)") - recipe.constrain(contribution.sig32, "sig(sig0, dsig, mu32)", - ns = {"mu32" : contribution.mu32} ) + recipe.constrain( + contribution.sig32, + "sig(sig0, dsig, mu32)", + ns={"mu32": contribution.mu32}, + ) # Also the background - recipe.addVar(contribution.b0, 0, tag = "bkgd") - recipe.addVar(contribution.b1, 0, tag = "bkgd") - recipe.addVar(contribution.b2, 0, tag = "bkgd") - recipe.addVar(contribution.b3, 0, tag = "bkgd") - recipe.addVar(contribution.b4, 0, tag = "bkgd") - recipe.addVar(contribution.b5, 0, tag = "bkgd") - recipe.addVar(contribution.b6, 0, tag = "bkgd") + recipe.addVar(contribution.b0, 0, tag="bkgd") + recipe.addVar(contribution.b1, 0, tag="bkgd") + recipe.addVar(contribution.b2, 0, tag="bkgd") + recipe.addVar(contribution.b3, 0, tag="bkgd") + recipe.addVar(contribution.b4, 0, tag="bkgd") + recipe.addVar(contribution.b5, 0, tag="bkgd") + recipe.addVar(contribution.b6, 0, tag="bkgd") return recipe + def scipyOptimize(recipe): """Optimize the recipe created above using scipy. - The FitRecipe we created in makeRecipe has a 'residual' method that we can - be minimized using a scipy optimizer. The details are described in the - source. - + The FitRecipe we created in makeRecipe has a 'residual' method that + we can be minimized using a scipy optimizer. The details are + described in the source. """ # We're going to use the least-squares (Levenberg-Marquardt) optimizer from @@ -169,6 +180,7 @@ def scipyOptimize(recipe): # (recipe.residual) and the starting values of the Variables # (recipe.getValues()). from scipy.optimize.minpack import leastsq + print("Fit using scipy's LM optimizer") leastsq(recipe.residual, recipe.getValues()) @@ -190,16 +202,18 @@ def plotResults(recipe): # This stuff is specific to pylab from the matplotlib distribution. import pylab - pylab.plot(x, y, 'b.', label = "observed profile") - pylab.plot(x, ycalc, 'r-', label = "calculated profile") - pylab.plot(x, y - ycalc - 0.1 * max(y), 'g-', label = "difference") - pylab.legend(loc = (0.0,0.8)) + + pylab.plot(x, y, "b.", label="observed profile") + pylab.plot(x, ycalc, "r-", label="calculated profile") + pylab.plot(x, y - ycalc - 0.1 * max(y), "g-", label="difference") + pylab.legend(loc=(0.0, 0.8)) pylab.xlabel("x") pylab.ylabel("y") pylab.show() return + def steerFit(recipe): """Steer the fit for this problem. @@ -219,6 +233,7 @@ def steerFit(recipe): return + if __name__ == "__main__": # Create the recipe diff --git a/doc/source/conf.py b/doc/source/conf.py index b0a86f3c..8d285db5 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -32,7 +32,9 @@ sys.path.insert(0, str(Path("../../src").resolve())) # abbreviations -ab_authors = "Christopher Farrow, Pavol Juhas, and members of the Billinge Group" +ab_authors = ( + "Christopher Farrow, Pavol Juhas, and members of the Billinge Group" +) # -- General configuration ------------------------------------------------ diff --git a/src/diffpy/srfit/__init__.py b/src/diffpy/srfit/__init__.py index 203c1e15..79dc93c5 100644 --- a/src/diffpy/srfit/__init__.py +++ b/src/diffpy/srfit/__init__.py @@ -14,20 +14,21 @@ ############################################################################## """Complex modeling framework for structure refinement and solution. -SrFit is a tool for coherently combining known information about a material to -derive other properties, in particular material structure. SrFit allows the -customization and creation of structure representations, profile calculators, -constraints, restraints and file input parsers. The customized pieces can be -glued together within SrFit to optimize a structure, or other physically -relevant information from one or more experimental profiles. Other known -information about the system of interest can be included with arbitrarily -complex constraints and restraints. In this way, the end user creates a -customized fitting application that suits the problem to the available -information. +SrFit is a tool for coherently combining known information about a +material to derive other properties, in particular material structure. +SrFit allows the customization and creation of structure +representations, profile calculators, constraints, restraints and file +input parsers. The customized pieces can be glued together within SrFit +to optimize a structure, or other physically relevant information from +one or more experimental profiles. Other known information about the +system of interest can be included with arbitrarily complex constraints +and restraints. In this way, the end user creates a customized fitting +application that suits the problem to the available information. -The subpackages herein define various pieces of the SrFit framework. Developers -are encouraged to work through the examples described in the documentation to -learn how to use and customize the various parts of SrFit. +The subpackages herein define various pieces of the SrFit framework. +Developers are encouraged to work through the examples described in the +documentation to learn how to use and customize the various parts of +SrFit. """ # package version diff --git a/src/diffpy/srfit/equation/__init__.py b/src/diffpy/srfit/equation/__init__.py index 135a6af2..d662474a 100644 --- a/src/diffpy/srfit/equation/__init__.py +++ b/src/diffpy/srfit/equation/__init__.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The core equation evaluator for diffpy.srfit. -This package contains modules and subpackages that are used to create Equation -objects. The Equation class is an encapsulation of a lazy evaluation network -that is used throughout SrFit. The EquationsFactory class is used to create -Equation objects from strings and can incorporate user-defined functions as -well as default operations. +This package contains modules and subpackages that are used to create +Equation objects. The Equation class is an encapsulation of a lazy +evaluation network that is used throughout SrFit. The EquationsFactory +class is used to create Equation objects from strings and can +incorporate user-defined functions as well as default operations. The subpackages define various pieces of the evaluation network. """ @@ -29,5 +28,4 @@ from diffpy.srfit.equation.equationmod import Equation - # End of file diff --git a/src/diffpy/srfit/equation/builder.py b/src/diffpy/srfit/equation/builder.py index af1d12ff..8e76a105 100644 --- a/src/diffpy/srfit/equation/builder.py +++ b/src/diffpy/srfit/equation/builder.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Classes and utilities for creating equations. The EquationFactory class is used to create an equation (an Equation instance) @@ -77,9 +76,16 @@ > eq = beq.makeEquation() """ -__all__ = ["EquationFactory", "BaseBuilder", "ArgumentBuilder", - "OperatorBuilder", "wrapArgument", "wrapOperator", "wrapFunction", - "getBuilder"] +__all__ = [ + "EquationFactory", + "BaseBuilder", + "ArgumentBuilder", + "OperatorBuilder", + "wrapArgument", + "wrapOperator", + "wrapFunction", + "getBuilder", +] # NOTE - the builder cannot handle numpy arrays on the left of a binary # operation because the array will automatically loop the operator of the @@ -91,13 +97,13 @@ import inspect import numbers -import numpy +import numpy import six import diffpy.srfit.equation.literals as literals -from diffpy.srfit.equation.literals.literal import Literal from diffpy.srfit.equation.equationmod import Equation +from diffpy.srfit.equation.literals.literal import Literal class EquationFactory(object): @@ -125,8 +131,9 @@ def __init__(self): self.registerConstant("e", numpy.e) return - def makeEquation(self, eqstr, buildargs = True, argclass = - literals.Argument, argkw = {}): + def makeEquation( + self, eqstr, buildargs=True, argclass=literals.Argument, argkw={} + ): """Make an equation from an equation string. Arguments @@ -152,7 +159,7 @@ def makeEquation(self, eqstr, buildargs = True, argclass = # handle scalar numbers or numpy arrays if isinstance(beq, (numbers.Number, numpy.ndarray)): lit = literals.Argument(value=beq, const=True) - eq = Equation(name='', root=lit) + eq = Equation(name="", root=lit) else: eq = beq.getEquation() self.equations.add(eq) @@ -177,11 +184,12 @@ def registerArgument(self, name, arg): def registerOperator(self, name, op): """Register an Operator literal with the factory. - Operators can be used with or without arguments (or parentheses) in an - equation string. If used with arguments, then the Operator will use - the passed arguments as arguments for the operation. If used without - arguments, it is assumed that the operator is already populated with - arguments, and those will be used. + Operators can be used with or without arguments (or parentheses) + in an equation string. If used with arguments, then the + Operator will use the passed arguments as arguments for the + operation. If used without arguments, it is assumed that the + operator is already populated with arguments, and those will be + used. Returns the registered builder. """ @@ -215,13 +223,14 @@ def registerFunction(self, name, func, argnames): def registerBuilder(self, name, builder): """Register builder in this module so it can be used in makeEquation. - If an extant builder with the given name is already registered, this - will replace all instances of the old builder's literal in the - factory's equation set with the new builder's literal. Note that this - may lead to errors if one of the replacements causes a self-reference. + If an extant builder with the given name is already registered, + this will replace all instances of the old builder's literal in + the factory's equation set with the new builder's literal. Note + that this may lead to errors if one of the replacements causes a + self-reference. - Raises ValueError if the new builder's literal causes a self-reference - in an existing equation. + Raises ValueError if the new builder's literal causes a self- + reference in an existing equation. """ if not isinstance(name, six.string_types): raise TypeError("Name must be a string") @@ -248,21 +257,21 @@ def registerBuilder(self, name, builder): def deRegisterBuilder(self, name): """De-register a builder by name. - This does not change the equations that use the Literal wrapped by the - builder. + This does not change the equations that use the Literal wrapped + by the builder. """ if name in self.builders: del self.builders[name] return - def wipeout(self, eq): """Invalidate the specified equation and remove it from the factory. - This will remove the equation from the purview of the factory and also - change its formula to return NaN. This ensures that eq does not - observe any object in the factory and thus prevents its indirect - pickling with the factory because of observer callback function. + This will remove the equation from the purview of the factory + and also change its formula to return NaN. This ensures that eq + does not observe any object in the factory and thus prevents its + indirect pickling with the factory because of observer callback + function. No return value. """ @@ -272,11 +281,10 @@ def wipeout(self, eq): self.equations.discard(eq) # invalidate this equation to clean up any observer relations of # objects in the factory towards its literals tree. - nan = literals.Argument('nan', value=numpy.nan, const=True) + nan = literals.Argument("nan", value=numpy.nan, const=True) eq.setRoot(nan) return - def _prepareBuilders(self, eqstr, buildargs, argclass, argkw): """Prepare builders so that equation string can be evaluated. @@ -309,14 +317,14 @@ def _prepareBuilders(self, eqstr, buildargs, argclass, argkw): # this is disallowed. if not buildargs and eqargs: eqargsstr = ", ".join(eqargs) - msg = "The equation contains undefined arguments: %s"%eqargsstr + msg = "The equation contains undefined arguments: %s" % eqargsstr raise ValueError(msg) # Make the arguments newargs = set() for argname in eqargs: - arg = argclass(name = argname, **argkw) - argbuilder = ArgumentBuilder(name = argname, arg = arg) + arg = argclass(name=argname, **argkw) + argbuilder = ArgumentBuilder(name=argname, arg=arg) newargs.add(arg) self.registerBuilder(argname, argbuilder) @@ -327,14 +335,14 @@ def _prepareBuilders(self, eqstr, buildargs, argclass, argkw): def _getUndefinedArgs(self, eqstr): """Get the undefined arguments from eqstr. - This tokenizes eqstr and extracts undefined arguments. An undefined - argument is defined as any token that is not a special character that - does not correspond to a builder. + This tokenizes eqstr and extracts undefined arguments. An + undefined argument is defined as any token that is not a special + character that does not correspond to a builder. Raises SyntaxError if the equation string uses invalid syntax. """ - import tokenize import token + import tokenize interface = six.StringIO(eqstr).readline # output is an iterator. Each entry (token) is a 5-tuple @@ -353,7 +361,7 @@ def _getUndefinedArgs(self, eqstr): if tok[0] in (token.NAME, token.OP): args.add(tok[1]) except tokenize.TokenError: - m = "invalid syntax: '%s'"%eqstr + m = "invalid syntax: '%s'" % eqstr raise SyntaxError(m) # Scan the tokens for names that do not correspond to registered @@ -363,18 +371,22 @@ def _getUndefinedArgs(self, eqstr): # Move genuine varibles to the eqargs dictionary if ( # Check registered builders - tok in self.builders or + tok in self.builders + or # Check symbols - tok in EquationFactory.symbols or + tok in EquationFactory.symbols + or # Check ignored characters tok in EquationFactory.ignore - ): + ): args.remove(tok) return args + # End class EquationFactory + class BaseBuilder(object): """Class for building equations. @@ -392,23 +404,24 @@ def __init__(self): def __call__(self, *args): """Raises exception for easier debugging.""" - m = "%s (%s) cannot accept arguments"%\ - (self.literal.name, self.__class__.__name__) + m = "%s (%s) cannot accept arguments" % ( + self.literal.name, + self.__class__.__name__, + ) raise TypeError(m) - def getEquation(self): """Get the equation built by this object. - The equation will given the name "_eq_" where "" is the - name of the root node. + The equation will given the name "_eq_" where "" is + the name of the root node. """ # We need to make a name for this, so we name it after its root - name = "_eq_%s"%self.literal.name + name = "_eq_%s" % self.literal.name eq = Equation(name, self.literal) return eq - def __evalBinary(self, other, OperatorClass, onleft = True): + def __evalBinary(self, other, OperatorClass, onleft=True): """Evaluate a binary function. Other can be an BaseBuilder or a constant. @@ -498,8 +511,10 @@ def __rmod__(self, other): def __neg__(self): return self.__evalUnary(literals.NegationOperator) + ## These are used by the class. + class ArgumentBuilder(BaseBuilder): """BaseBuilder wrapper around an Argument literal. @@ -510,7 +525,7 @@ class ArgumentBuilder(BaseBuilder): literal -- The Argument wrapped by this instance. """ - def __init__(self, value = None, name = None, const = False, arg = None): + def __init__(self, value=None, name=None, const=False, arg=None): """Create an ArgumentBuilder instance, containing a new Argument. Arguments @@ -524,14 +539,17 @@ def __init__(self, value = None, name = None, const = False, arg = None): """ BaseBuilder.__init__(self) if arg is None: - self.literal = literals.Argument(value=value, name=name, - const=const) + self.literal = literals.Argument( + value=value, name=name, const=const + ) else: self.literal = arg return + # end class ArgumentBuilder + class OperatorBuilder(BaseBuilder): """BaseBuilder wrapper around an Operator literal. @@ -540,7 +558,7 @@ class OperatorBuilder(BaseBuilder): name -- The name of the operator to be wrapped """ - def __init__(self, name, op = None): + def __init__(self, name, op=None): """Wrap an Operator or a function by name. Arguments @@ -573,43 +591,51 @@ def __call__(self, *args): self.literal = literals.UFuncOperator(ufunc) # Here the Operator is already specified. We can copy its attributes # to a new Operator inside of the new OperatorBuilder. - op = literals.makeOperator(name=self.literal.name, - symbol=self.literal.symbol, - nin=self.literal.nin, - nout=self.literal.nout, - operation=self.literal.operation) + op = literals.makeOperator( + name=self.literal.name, + symbol=self.literal.symbol, + nin=self.literal.nin, + nout=self.literal.nout, + operation=self.literal.operation, + ) newobj.literal = op # Now that we have a literal, let's check our inputs literal = newobj.literal if literal.nin >= 0 and len(args) != literal.nin: - raise ValueError("%s takes %i arguments (%i given)"%\ - (self.literal, self.literal.nin, len(args))) + raise ValueError( + "%s takes %i arguments (%i given)" + % (self.literal, self.literal.nin, len(args)) + ) # Wrap scalar arguments for i, arg in enumerate(args): # Wrap the argument if it is not already if not isinstance(arg, BaseBuilder): - name = self.name + "_%i"%i - arg = ArgumentBuilder(value = arg, name = name, const = True) + name = self.name + "_%i" % i + arg = ArgumentBuilder(value=arg, name=name, const=True) newobj.literal.addLiteral(arg.literal) return newobj + # end class OperatorBuilder # Utility functions + def wrapArgument(name, arg): """Wrap an Argument as a builder.""" - argbuilder = ArgumentBuilder(arg = arg) + argbuilder = ArgumentBuilder(arg=arg) return argbuilder + def wrapOperator(name, op): """Wrap an Operator as a builder.""" opbuilder = OperatorBuilder(name, op) return opbuilder + def wrapFunction(name, func, nin=2, nout=1): """Wrap a function in an OperatorBuilder instance. @@ -620,19 +646,21 @@ def wrapFunction(name, func, nin=2, nout=1): Returns the OperatorBuilder instance that wraps the function. """ - op = literals.makeOperator(name=name, symbol=name, - nin=nin, nout=nout, - operation=func) + op = literals.makeOperator( + name=name, symbol=name, nin=nin, nout=nout, operation=func + ) # Create the OperatorBuilder opbuilder = OperatorBuilder(name, op) return opbuilder + def getBuilder(name): """Get an operator from the global builders dictionary.""" return _builders[name] + def __wrapNumpyOperators(): """Export all numpy operators as OperatorBuilder instances in the module namespace.""" @@ -641,8 +669,11 @@ def __wrapNumpyOperators(): if isinstance(op, numpy.ufunc): _builders[name] = OperatorBuilder(name) return + + __wrapNumpyOperators() + # Register other functions as well def __wrapSrFitOperators(): """Export all non-base operators from the @@ -651,16 +682,20 @@ def __wrapSrFitOperators(): opmod = literals.operators excluded_types = set((opmod.CustomOperator, opmod.UFuncOperator)) # check if opmod member should be wrapped as OperatorBuilder - is_exported_type = lambda cls : ( - inspect.isclass(cls) and issubclass(cls, opmod.Operator) and - not inspect.isabstract(cls) and - not cls in excluded_types) + is_exported_type = lambda cls: ( + inspect.isclass(cls) + and issubclass(cls, opmod.Operator) + and not inspect.isabstract(cls) + and not cls in excluded_types + ) # create OperatorBuilder objects for nm, opclass in inspect.getmembers(opmod, is_exported_type): op = opclass() assert op.name, "Unnamed Operator should never appear here." _builders[op.name] = OperatorBuilder(op.name, op) return + + __wrapSrFitOperators() # End of file diff --git a/src/diffpy/srfit/equation/equationmod.py b/src/diffpy/srfit/equation/equationmod.py index 7df4967e..80f6262d 100644 --- a/src/diffpy/srfit/equation/equationmod.py +++ b/src/diffpy/srfit/equation/equationmod.py @@ -12,26 +12,21 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The Equation class for holding and evaluating an equation. -Equation is a functor that holds a Literal tree that defines an equation. It's -__call__ method evaluates the equation at the most recent value of its -Arguments. The non-constant arguments are accessible as attributes of the -Equation instance and can be passed as arguments to __call__. - -Example -> # make a Literal tree. Here's a simple one -> add = AdditionOperator() -> a = Argument(name="a") # Don't forget to name these! -> b = Argument(name="b") -> add.addLiteral(a) -> add.addLiteral(b) -> # make an Equation instance and pass the root > eq = Equation(root = add) -> eq(a=3, b=4) # returns 7 > eq(a=2) # remembers b=4, returns 6 -> eq.a.setValue(-3) -> eq.b.setValue(3) -> eq() # uses last assignment of a and b, returns 0 +Equation is a functor that holds a Literal tree that defines an +equation. It's __call__ method evaluates the equation at the most recent +value of its Arguments. The non-constant arguments are accessible as +attributes of the Equation instance and can be passed as arguments to +__call__. + +Example > # make a Literal tree. Here's a simple one > add = +AdditionOperator() > a = Argument(name="a") # Don't forget to name +these! > b = Argument(name="b") > add.addLiteral(a) > add.addLiteral(b) +> # make an Equation instance and pass the root > eq = Equation(root = +add) > eq(a=3, b=4) # returns 7 > eq(a=2) # remembers b=4, returns 6 > +eq.a.setValue(-3) > eq.b.setValue(3) > eq() # uses last assignment of a +and b, returns 0 See the class documentation for more information. """ @@ -40,9 +35,10 @@ from collections import OrderedDict -from diffpy.srfit.equation.visitors import validate, getArgs, swap -from diffpy.srfit.equation.literals.operators import Operator from diffpy.srfit.equation.literals.literal import Literal +from diffpy.srfit.equation.literals.operators import Operator +from diffpy.srfit.equation.visitors import getArgs, swap, validate + class Equation(Operator): """Class for holding and evaluating a Literal tree. @@ -79,7 +75,7 @@ class Equation(Operator): nin = None nout = 1 - def __init__(self, name = None, root = None): + def __init__(self, name=None, root=None): """Initialize. name -- A name for this Equation. @@ -90,7 +86,7 @@ def __init__(self, name = None, root = None): # Operator stuff. We circumvent Operator.__init__ since we're using # args as a property. We cannot set it, as the Operator tries to do. if name is None and root is not None: - name = "eq_%s"%root.name + name = "eq_%s" % root.name Literal.__init__(self, name) self.root = None self.argdict = OrderedDict() @@ -98,23 +94,20 @@ def __init__(self, name = None, root = None): self.setRoot(root) return - @property def symbol(self): return self.name - def operation(self, *args, **kw): """Evaluate this Equation object. - Same as the __call__ method. This method is used via the Operator - interface. + Same as the __call__ method. This method is used via the + Operator interface. Return the result of __call__(*args, **kw). """ return self.__call__(*args, **kw) - def _getArgs(self): return list(self.argdict.values()) @@ -123,16 +116,15 @@ def _getArgs(self): def __getattr__(self, name): """Gives access to the Arguments as attributes.""" # Avoid infinite loop on argdict lookup. - argdict = object.__getattribute__(self, 'argdict') + argdict = object.__getattribute__(self, "argdict") if not name in argdict: raise AttributeError("No argument named '%s' here" % name) return argdict[name] - # Ensure there is no __dir__ override in the base class. - assert (getattr(Operator, '__dir__', None) is - getattr(object, '__dir__', None)) - + assert getattr(Operator, "__dir__", None) is getattr( + object, "__dir__", None + ) def __dir__(self): "Return sorted list of attributes for this object." @@ -141,7 +133,6 @@ def __dir__(self): rv = sorted(rv) return rv - def setRoot(self, root): """Set the root of the Literal tree. @@ -163,20 +154,20 @@ def setRoot(self, root): # Get the args args = getArgs(root, getconsts=False) - self.argdict = OrderedDict( [(arg.name, arg) for arg in args] ) + self.argdict = OrderedDict([(arg.name, arg) for arg in args]) # Set Operator attributes self.nin = len(self.args) return - def __call__(self, *args, **kw): """Call the equation. - New Argument values are acceped as arguments or keyword assignments (or - both). The order of accepted arguments is given by the args attribute. - The Equation will remember values set in this way. + New Argument values are acceped as arguments or keyword + assignments (or both). The order of accepted arguments is given + by the args attribute. The Equation will remember values set in + this way. Raises ValueError when a passed argument cannot be found """ @@ -191,7 +182,7 @@ def __call__(self, *args, **kw): for name, val in kw.items(): arg = self.argdict.get(name) if arg is None: - raise ValueError("No argument named '%s' here"%name) + raise ValueError("No argument named '%s' here" % name) arg.setValue(val) self._value = self.root.getValue() @@ -218,4 +209,5 @@ def identify(self, visitor): """Identify self to a visitor.""" return visitor.onEquation(self) + # End of file diff --git a/src/diffpy/srfit/equation/literals/__init__.py b/src/diffpy/srfit/equation/literals/__init__.py index 2101faba..862cac3b 100644 --- a/src/diffpy/srfit/equation/literals/__init__.py +++ b/src/diffpy/srfit/equation/literals/__init__.py @@ -12,45 +12,60 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Building blocks for defining a lazy evaluation network. -Literals are the building blocks of the evaluation network. An Argument holds -the name and value of an equation variable. Operators are used to compose other -Literals to produce a new value. +Literals are the building blocks of the evaluation network. An Argument +holds the name and value of an equation variable. Operators are used to +compose other Literals to produce a new value. + +Literal networks can be evaluated or have other actions performed on +them by Visitors (in diffpy.srfit.equation.visitors). The Literal- +Visitor relationship is that described by the Visitor pattern ( -Literal networks can be evaluated or have other actions performed on them by -Visitors (in diffpy.srfit.equation.visitors). The Literal-Visitor relationship -is that described by the Visitor pattern -(http://en.wikipedia.org/wiki/Visitor_pattern). +http://en.wikipedia.org/wiki/Visitor_pattern). """ -__all__ = ["Argument", "Operator", "BinaryOperator", "CustomOperator", - "AdditionOperator", "SubtractionOperator", - "MultiplicationOperator", "DivisionOperator", "ExponentiationOperator", - "RemainderOperator", "NegationOperator", "ConvolutionOperator", - "SumOperator", "UFuncOperator", "ArrayOperator", "PolyvalOperator", - "makeOperator"] +__all__ = [ + "Argument", + "Operator", + "BinaryOperator", + "CustomOperator", + "AdditionOperator", + "SubtractionOperator", + "MultiplicationOperator", + "DivisionOperator", + "ExponentiationOperator", + "RemainderOperator", + "NegationOperator", + "ConvolutionOperator", + "SumOperator", + "UFuncOperator", + "ArrayOperator", + "PolyvalOperator", + "makeOperator", +] # Import the operators from diffpy.srfit.equation.literals.argument import Argument -from diffpy.srfit.equation.literals.operators import Operator -from diffpy.srfit.equation.literals.operators import BinaryOperator -from diffpy.srfit.equation.literals.operators import CustomOperator -from diffpy.srfit.equation.literals.operators import AdditionOperator -from diffpy.srfit.equation.literals.operators import SubtractionOperator -from diffpy.srfit.equation.literals.operators import MultiplicationOperator -from diffpy.srfit.equation.literals.operators import DivisionOperator -from diffpy.srfit.equation.literals.operators import ExponentiationOperator -from diffpy.srfit.equation.literals.operators import RemainderOperator -from diffpy.srfit.equation.literals.operators import NegationOperator -from diffpy.srfit.equation.literals.operators import ConvolutionOperator -from diffpy.srfit.equation.literals.operators import UFuncOperator -from diffpy.srfit.equation.literals.operators import SumOperator -from diffpy.srfit.equation.literals.operators import ArrayOperator -from diffpy.srfit.equation.literals.operators import PolyvalOperator -from diffpy.srfit.equation.literals.operators import makeOperator +from diffpy.srfit.equation.literals.operators import ( + AdditionOperator, + ArrayOperator, + BinaryOperator, + ConvolutionOperator, + CustomOperator, + DivisionOperator, + ExponentiationOperator, + MultiplicationOperator, + NegationOperator, + Operator, + PolyvalOperator, + RemainderOperator, + SubtractionOperator, + SumOperator, + UFuncOperator, + makeOperator, +) # End of file diff --git a/src/diffpy/srfit/equation/literals/abcs.py b/src/diffpy/srfit/equation/literals/abcs.py index d57bf047..605bc53e 100644 --- a/src/diffpy/srfit/equation/literals/abcs.py +++ b/src/diffpy/srfit/equation/literals/abcs.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Abstract Base Classes for Literals.""" __all__ = ["LiteralABC", "ArgumentABC", "OperatorABC"] @@ -31,13 +30,16 @@ class LiteralABC(object): """ @abstractmethod - def identify(self, visitor): pass + def identify(self, visitor): + pass @abstractmethod - def getValue(self): pass + def getValue(self): + pass name = abstractproperty(None, None) + # End class LiteralABC @@ -48,11 +50,13 @@ class ArgumentABC(LiteralABC): """ @abstractmethod - def setValue(self, value): pass + def setValue(self, value): + pass const = abstractproperty(None, None) value = abstractproperty(None, None) + # End class ArgumentABC @@ -63,7 +67,8 @@ class OperatorABC(LiteralABC): """ @abstractmethod - def addLiteral(self, literal): pass + def addLiteral(self, literal): + pass args = abstractproperty(None, None) nin = abstractproperty(None, None) @@ -72,4 +77,5 @@ def addLiteral(self, literal): pass symbol = abstractproperty(None, None) value = abstractproperty(None, None) + # End class OperatorABC diff --git a/src/diffpy/srfit/equation/literals/argument.py b/src/diffpy/srfit/equation/literals/argument.py index 83564207..eed3803a 100644 --- a/src/diffpy/srfit/equation/literals/argument.py +++ b/src/diffpy/srfit/equation/literals/argument.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Argument class. Arguments are the leaves of an equation tree, in essense a variable or a @@ -21,10 +20,12 @@ __all__ = ["Argument"] -from numpy import ndarray, array_equal +from numpy import array_equal, ndarray + from diffpy.srfit.equation.literals.abcs import ArgumentABC from diffpy.srfit.equation.literals.literal import Literal + class Argument(Literal, ArgumentABC): """Argument class. @@ -38,7 +39,7 @@ class Argument(Literal, ArgumentABC): const = None - def __init__(self, name = None, value = None, const = False): + def __init__(self, name=None, value=None, const=False): """Initialization.""" Literal.__init__(self, name) self.const = const @@ -67,7 +68,9 @@ def setValue(self, val): self.notify() return - value = property( lambda self: self.getValue(), - lambda self, val: self.setValue(val)) + value = property( + lambda self: self.getValue(), lambda self, val: self.setValue(val) + ) + # End of file diff --git a/src/diffpy/srfit/equation/literals/literal.py b/src/diffpy/srfit/equation/literals/literal.py index 4a6f41b5..1bf7fdac 100644 --- a/src/diffpy/srfit/equation/literals/literal.py +++ b/src/diffpy/srfit/equation/literals/literal.py @@ -12,12 +12,11 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Literal base class used to construct equation trees. -Literals are base pieces of the equation hierarchy. The 'identify' method -identifies the Literal to a visitor by calling the identifying method of the -vistior. +Literals are base pieces of the equation hierarchy. The 'identify' +method identifies the Literal to a visitor by calling the identifying +method of the vistior. """ __all__ = ["Literal"] @@ -25,7 +24,8 @@ from diffpy.srfit.equation.literals.abcs import LiteralABC from diffpy.srfit.util.observable import Observable -class Literal(Observable,LiteralABC): + +class Literal(Observable, LiteralABC): """Abstract class for equation pieces, such as operators and arguments. Literal derives from Observable. See diffpy.srfit.util.observable. @@ -63,6 +63,7 @@ def _flush(self, other): return def __str__(self): - return "%s(%s)"%(self.__class__.__name__, self.name) + return "%s(%s)" % (self.__class__.__name__, self.name) + # End of file diff --git a/src/diffpy/srfit/equation/literals/operators.py b/src/diffpy/srfit/equation/literals/operators.py index 12ef1ff2..cfe612e9 100644 --- a/src/diffpy/srfit/equation/literals/operators.py +++ b/src/diffpy/srfit/equation/literals/operators.py @@ -12,24 +12,34 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Operator classes. -Operators are combined with other Literals to create an equation. Operators are -non-leaf nodes on a Literal tree. These trees can be evaluated by the Evaluator -visitor, or otherwise inspected. +Operators are combined with other Literals to create an equation. +Operators are non-leaf nodes on a Literal tree. These trees can be +evaluated by the Evaluator visitor, or otherwise inspected. -The Operator class contains all the information necessary to be identified and -evaluated by a Visitor. Thus, a single onOperator method exists in the Visitor -base class. Other Operators can be derived from Operator (see -AdditionOperator), but they all identify themselves with the Visitor.onOperator -method. +The Operator class contains all the information necessary to be +identified and evaluated by a Visitor. Thus, a single onOperator method +exists in the Visitor base class. Other Operators can be derived from +Operator (see AdditionOperator), but they all identify themselves with +the Visitor.onOperator method. """ -__all__ = ["Operator", "AdditionOperator", "SubtractionOperator", - "MultiplicationOperator", "DivisionOperator", "ExponentiationOperator", - "RemainderOperator", "NegationOperator", "ConvolutionOperator", - "SumOperator", "UFuncOperator", "ArrayOperator", "PolyvalOperator"] +__all__ = [ + "Operator", + "AdditionOperator", + "SubtractionOperator", + "MultiplicationOperator", + "DivisionOperator", + "ExponentiationOperator", + "RemainderOperator", + "NegationOperator", + "ConvolutionOperator", + "SumOperator", + "UFuncOperator", + "ArrayOperator", + "PolyvalOperator", +] import numpy @@ -72,13 +82,11 @@ class Operator(Literal, OperatorABC): # _value : float, numpy.ndarray or None # The last value of the operator or None. - # We must declare the abstract `args` here. args = None # default for the value _value = None - def __init__(self, name=None): """Initialize the operator object with the specified name. @@ -92,7 +100,6 @@ def __init__(self, name=None): self.args = [] return - def identify(self, visitor): """Identify self to a visitor.""" return visitor.onOperator(self) @@ -100,8 +107,8 @@ def identify(self, visitor): def addLiteral(self, literal): """Add a literal to this operator. - Note that order of operation matters. The first literal added is the - leftmost argument. The last is the rightmost. + Note that order of operation matters. The first literal added is + the leftmost argument. The last is the rightmost. Raises ValueError if the literal causes a self-reference. """ @@ -124,7 +131,7 @@ def getValue(self): def _loopCheck(self, literal): """Check if a literal causes self-reference.""" if literal is self: - raise ValueError("'%s' causes self-reference"%literal) + raise ValueError("'%s' causes self-reference" % literal) # Check to see if I am a dependency of the literal. if hasattr(literal, "args"): @@ -203,6 +210,7 @@ def makeOperator(name, symbol, operation, nin, nout): op.nout = nout return op + # Some specified operators @@ -275,10 +283,10 @@ def _conv(v1, v2): # Find the centroid of the first signal s1 = sum(v1) x1 = numpy.arange(len(v1), dtype=float) - c1idx = numpy.sum(v1 * x1)/s1 + c1idx = numpy.sum(v1 * x1) / s1 # Find the centroid of the convolution xc = numpy.arange(len(c), dtype=float) - ccidx = numpy.sum(c * xc)/sum(c) + ccidx = numpy.sum(c * xc) / sum(c) # Interpolate the convolution such that the centroids line up. This # uses linear interpolation. shift = ccidx - c1idx @@ -288,7 +296,7 @@ def _conv(v1, v2): # Normalize sc = sum(c) if sc > 0: - c *= s1/sc + c *= s1 / sc return c @@ -296,13 +304,14 @@ def _conv(v1, v2): class ConvolutionOperator(BinaryOperator): """Convolve two signals. - This convolves two signals such that centroid of the first array is not - altered by the convolution. Furthermore, the integrated amplitude of the - convolution is scaled to be that of the first signal. This is mean to act - as a convolution of a signal by a probability distribution. + This convolves two signals such that centroid of the first array is + not altered by the convolution. Furthermore, the integrated + amplitude of the convolution is scaled to be that of the first + signal. This is mean to act as a convolution of a signal by a + probability distribution. - Note that this is only possible when the signals are computed over the same - range. + Note that this is only possible when the signals are computed over + the same range. """ name = "convolve" @@ -367,4 +376,5 @@ class PolyvalOperator(BinaryOperator): operation = staticmethod(numpy.polyval) pass + # End of file diff --git a/src/diffpy/srfit/equation/visitors/__init__.py b/src/diffpy/srfit/equation/visitors/__init__.py index b4014c3e..f6cce3bd 100644 --- a/src/diffpy/srfit/equation/visitors/__init__.py +++ b/src/diffpy/srfit/equation/visitors/__init__.py @@ -12,26 +12,27 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Visitors that perform on Literal networks. -Visitors are designed to traverse and extract information from Literal networks -(diffpy.srfit.equation.literals). Visitors are used to validate, print and -extracting Arguments from Literal networks. +Visitors are designed to traverse and extract information from Literal +networks (diffpy.srfit.equation.literals). Visitors are used to +validate, print and extracting Arguments from Literal networks. + +The Literal-Visitor relationship is that described by the Visitor +pattern ( -The Literal-Visitor relationship is that described by the Visitor pattern -(http://en.wikipedia.org/wiki/Visitor_pattern). +http://en.wikipedia.org/wiki/Visitor_pattern). """ from __future__ import print_function from diffpy.srfit.equation.visitors.argfinder import ArgFinder from diffpy.srfit.equation.visitors.printer import Printer -from diffpy.srfit.equation.visitors.validator import Validator from diffpy.srfit.equation.visitors.swapper import Swapper +from diffpy.srfit.equation.visitors.validator import Validator -def getArgs(literal, getconsts = True): +def getArgs(literal, getconsts=True): """Get the Arguments of a Literal tree. getconsts -- If True (default), then Arguments designated as constant @@ -72,7 +73,7 @@ def validate(literal): v = Validator() errors = literal.identify(v) if errors: - m = "Errors found in Literal tree '%s'\n"%literal + m = "Errors found in Literal tree '%s'\n" % literal m += "\n".join(errors) raise ValueError(m) return @@ -81,8 +82,8 @@ def validate(literal): def swap(literal, oldlit, newlit): """Swap one literal for another in a Literal tree. - Corrections are done in-place unless literal is oldlit, in which case the - return value is newlit. + Corrections are done in-place unless literal is oldlit, in which + case the return value is newlit. Returns the literal tree with oldlit swapped for newlit. """ diff --git a/src/diffpy/srfit/equation/visitors/argfinder.py b/src/diffpy/srfit/equation/visitors/argfinder.py index 566955e8..b0338c92 100644 --- a/src/diffpy/srfit/equation/visitors/argfinder.py +++ b/src/diffpy/srfit/equation/visitors/argfinder.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Visitor for extracting the Argument entries in a Literal tree. ArgFinder extracts all Arguments from a literal true. @@ -31,7 +30,7 @@ class ArgFinder(Visitor): getconsts -- Flag indicating whether to grab constant arguments. """ - def __init__(self, getconsts = True): + def __init__(self, getconsts=True): """Initialize. Arguments @@ -59,4 +58,5 @@ def onOperator(self, op): arg.identify(self) return self.args + # End of file diff --git a/src/diffpy/srfit/equation/visitors/printer.py b/src/diffpy/srfit/equation/visitors/printer.py index 9b98414c..b3bc5d9d 100644 --- a/src/diffpy/srfit/equation/visitors/printer.py +++ b/src/diffpy/srfit/equation/visitors/printer.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Printer visitor for printing the equation represented by a Literal tree. -The Printer visitor creates a one-line representation of the Literal tree, -which is valid as a string equivalent of the equation. +The Printer visitor creates a one-line representation of the Literal +tree, which is valid as a string equivalent of the equation. """ __all__ = ["Printer"] @@ -25,6 +24,7 @@ from diffpy.srfit.equation.visitors.visitor import Visitor + class Printer(Visitor): """Printer for printing a Literal tree. @@ -44,13 +44,11 @@ def __init__(self): self.reset() return - def reset(self): """Reset the out put string.""" self.output = "" return - @property def eqskip(self): """Pattern for equation objects to be skipped. @@ -70,7 +68,6 @@ def eqskip(self, value): self._eqpat = re.compile(value) return - def onArgument(self, arg): """Process an Argument node. @@ -82,7 +79,6 @@ def onArgument(self, arg): self.output += str(arg.name) return self.output - def onOperator(self, op): """Process an Operator node.""" # We have to deal with infix operators @@ -93,31 +89,32 @@ def onOperator(self, op): self.output += str(op.name) + "(" for idx, literal in enumerate(op.args): - if idx != 0: self.output += ", " + if idx != 0: + self.output += ", " literal.identify(self) self.output += ")" return self.output - def onEquation(self, eq): """Process an Equation node.""" - skipthis = (self._eqpat is not None and eq.name and - self._eqpat.match(eq.name)) + skipthis = ( + self._eqpat is not None and eq.name and self._eqpat.match(eq.name) + ) if skipthis: self.onArgument(eq) else: eq.root.identify(self) return self.output - def _onInfix(self, op): """Process infix operators.""" self.output += "(" op.args[0].identify(self) - self.output += " %s "%op.symbol + self.output += " %s " % op.symbol op.args[1].identify(self) self.output += ")" return + # End of file diff --git a/src/diffpy/srfit/equation/visitors/swapper.py b/src/diffpy/srfit/equation/visitors/swapper.py index 85603911..756cdb68 100644 --- a/src/diffpy/srfit/equation/visitors/swapper.py +++ b/src/diffpy/srfit/equation/visitors/swapper.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Swapper for replacing a Literal in an equation with another Literals.""" __all__ = ["Swapper"] from diffpy.srfit.equation.visitors.visitor import Visitor + class Swapper(Visitor): """Swapper for swapping out one literal for another in a literal tree. @@ -48,7 +48,8 @@ class for how the replacement takes place. def onArgument(self, arg): """Process an Argument node. - Tell the parent to swap the old Argument with the replacement Literal. + Tell the parent to swap the old Argument with the replacement + Literal. """ if arg is self.oldlit: @@ -59,7 +60,8 @@ def onArgument(self, arg): def onOperator(self, op): """Process an Operator node. - Tell the parent to swap the old Operator with the replacement Literal. + Tell the parent to swap the old Operator with the replacement + Literal. """ # Check to see if we need to swap out this Operator. If so, then we @@ -108,7 +110,6 @@ def onOperator(self, op): newlit.addObserver(op._flush) op._flush(other=()) - self._swap = False return @@ -137,4 +138,5 @@ def onEquation(self, eq): return + # End of file diff --git a/src/diffpy/srfit/equation/visitors/validator.py b/src/diffpy/srfit/equation/visitors/validator.py index 318204e0..cdf6d542 100644 --- a/src/diffpy/srfit/equation/visitors/validator.py +++ b/src/diffpy/srfit/equation/visitors/validator.py @@ -12,16 +12,15 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Validator visitor for validating a tree of Literals. The Validator walks an equation tree composed of Literals and checks the validity of each equation as much as possible without evaluating it. It collects errors in a list. -The Validator checks that the input count of each Operator is equal to the -output count of its arguments. It also checks that each object has the proper -interface. +The Validator checks that the input count of each Operator is equal to +the output count of its arguments. It also checks that each object has +the proper interface. """ __all__ = ["Validator"] @@ -59,7 +58,7 @@ def onArgument(self, arg): diffpy.srfit.equation.literals.abcs """ if not isinstance(arg, ArgumentABC): - m = msg%(arg, ArgumentABC.__name__) + m = msg % (arg, ArgumentABC.__name__) self.errors.append(m) self._nin = 1 return self.errors @@ -72,24 +71,24 @@ def onOperator(self, op): """ if not isinstance(op, OperatorABC): - m = msg%(op, OperatorABC.__name__) + m = msg % (op, OperatorABC.__name__) self.errors.append(m) # Can only process single-valued functions if op.nout != 1: - m = "'%s' is not single-valued (nout != 1)"%op + m = "'%s' is not single-valued (nout != 1)" % op self.errors.append(m) # Check name if op.name is None: - m = "'%s' does not have a name"%op + m = "'%s' does not have a name" % op self.errors.append(m) # Check symbol if op.symbol is None: - m = "'%s' does not have a symbol"%op + m = "'%s' does not have a symbol" % op self.errors.append(m) # Check operation without evaluating it if op.operation is None: - m = "'%s' does not define and operation"%op + m = "'%s' does not define and operation" % op self.errors.append(m) localnin = 0 @@ -100,10 +99,15 @@ def onOperator(self, op): # Check the input/output balance if op.nin >= 0 and localnin != op.nin: - m = "'%s' requires %i inputs but receives %i"%(op, op.nin, localnin) + m = "'%s' requires %i inputs but receives %i" % ( + op, + op.nin, + localnin, + ) self.errors.append(m) self._nin = op.nout return self.errors + # End of file diff --git a/src/diffpy/srfit/equation/visitors/visitor.py b/src/diffpy/srfit/equation/visitors/visitor.py index 194ea72c..965c5a92 100644 --- a/src/diffpy/srfit/equation/visitors/visitor.py +++ b/src/diffpy/srfit/equation/visitors/visitor.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Base visitor class. Visitors work with Literal trees to perform a specified action. They are @@ -29,6 +28,7 @@ __all__ = ["Visitor"] + class Visitor(object): """Abstract class for all visitors to a Literal tree. @@ -46,15 +46,17 @@ def onOperator(self, op): def onEquation(self, eq): """Process an Equation node. - Equations are specialized Operators. They don't need to be specifically - supported by a Visitor. + Equations are specialized Operators. They don't need to be + specifically supported by a Visitor. """ return self.onOperator(eq) # throw an exception def _abstract(self, method): raise NotImplementedError( - "class '%s' should override method '%s'" % (self.__class__.__name__, method)) + "class '%s' should override method '%s'" + % (self.__class__.__name__, method) + ) # End of file diff --git a/src/diffpy/srfit/exceptions.py b/src/diffpy/srfit/exceptions.py index eed5a572..f61ada87 100644 --- a/src/diffpy/srfit/exceptions.py +++ b/src/diffpy/srfit/exceptions.py @@ -12,7 +12,6 @@ # See LICENSE.txt for license information. # ############################################################################## - """ Exceptions used for SrFit - specific errors. """ @@ -20,9 +19,11 @@ class SrFitError(Exception): """Generic error in SrFit expressions or recipe.""" + pass class ParseError(Exception): """Exception used by ProfileParsers.""" + pass diff --git a/src/diffpy/srfit/fitbase/__init__.py b/src/diffpy/srfit/fitbase/__init__.py index 48b96c79..bdbcb2d1 100644 --- a/src/diffpy/srfit/fitbase/__init__.py +++ b/src/diffpy/srfit/fitbase/__init__.py @@ -12,33 +12,42 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The base fitting classes for diffpy.srfit. -This package contains modules and subpackages that are used to define a fit -problem in SrFit. Unaltered, these classes will help set up a fit problem that -can be optimized within a fitting framework or with a standalone optimizer. -They provide the basic framework for defining a forward calculator, and from -that defining a fit problem with data, constraints and restraints. The classes -involved in this collaboration can be tied to a fitting framework at various -levels through inheritance. One can create a fitting problem using the -FitContribution, Profile and FitRecipe classes. +This package contains modules and subpackages that are used to define a +fit problem in SrFit. Unaltered, these classes will help set up a fit +problem that can be optimized within a fitting framework or with a +standalone optimizer. They provide the basic framework for defining a +forward calculator, and from that defining a fit problem with data, +constraints and restraints. The classes involved in this collaboration +can be tied to a fitting framework at various levels through +inheritance. One can create a fitting problem using the FitContribution, +Profile and FitRecipe classes. Various code and design taken from Paul Kienzle's PARK package. http://www.reflectometry.org/danse/park.html """ -__all__ = ['Calculator', 'FitContribution', 'FitHook', 'FitRecipe', - 'FitResults', 'initializeRecipe', 'PlotFitHook', 'Profile', - 'ProfileGenerator', 'SimpleRecipe'] +__all__ = [ + "Calculator", + "FitContribution", + "FitHook", + "FitRecipe", + "FitResults", + "initializeRecipe", + "PlotFitHook", + "Profile", + "ProfileGenerator", + "SimpleRecipe", +] from diffpy.srfit.fitbase.calculator import Calculator from diffpy.srfit.fitbase.fitcontribution import FitContribution from diffpy.srfit.fitbase.fithook import FitHook, PlotFitHook from diffpy.srfit.fitbase.fitrecipe import FitRecipe -from diffpy.srfit.fitbase.simplerecipe import SimpleRecipe from diffpy.srfit.fitbase.fitresults import FitResults, initializeRecipe from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator +from diffpy.srfit.fitbase.simplerecipe import SimpleRecipe # End of file diff --git a/src/diffpy/srfit/fitbase/calculator.py b/src/diffpy/srfit/fitbase/calculator.py index de6e5719..32e4c094 100644 --- a/src/diffpy/srfit/fitbase/calculator.py +++ b/src/diffpy/srfit/fitbase/calculator.py @@ -12,22 +12,21 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The Calculator for Parameter-aware functions. -Calculator is a functor class for producing a signal from embedded Parameters. -Calculators can store Parameters and ParameterSets, Constraints and Restraints. -Also, the __call__ function can be overloaded to accept external arguments. -Calculators are used to wrap registered functions so that the function's -Parameters are contained in an object specific to the function. A custom -Calculator can be added to another RecipeOrganizer with the -'registerCalculator' method. +Calculator is a functor class for producing a signal from embedded +Parameters. Calculators can store Parameters and ParameterSets, +Constraints and Restraints. Also, the __call__ function can be +overloaded to accept external arguments. Calculators are used to wrap +registered functions so that the function's Parameters are contained in +an object specific to the function. A custom Calculator can be added to +another RecipeOrganizer with the 'registerCalculator' method. """ __all__ = ["Calculator"] -from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.equation.literals.operators import Operator +from diffpy.srfit.fitbase.parameterset import ParameterSet class Calculator(Operator, ParameterSet): @@ -84,9 +83,9 @@ def symbol(self): def __call__(self, *args): """Calculate something. - This method must be overloaded. When overloading, you should specify - the arguments explicitly, otherwise the parameters must be specified - when adding the Calculator to a RecipeOrganizer. + This method must be overloaded. When overloading, you should + specify the arguments explicitly, otherwise the parameters must + be specified when adding the Calculator to a RecipeOrganizer. """ return 0 @@ -97,9 +96,9 @@ def operation(self, *args): def _validate(self): """Validate my state. - This performs ParameterSet validations. This does not validate the - operation, since this could be costly. The operation should be - validated with a containing equation. + This performs ParameterSet validations. This does not validate + the operation, since this could be costly. The operation should + be validated with a containing equation. Raises AttributeError if validation fails. """ @@ -107,4 +106,5 @@ def _validate(self): return + # End class Calculator diff --git a/src/diffpy/srfit/fitbase/configurable.py b/src/diffpy/srfit/fitbase/configurable.py index d0c185cf..8de392e1 100644 --- a/src/diffpy/srfit/fitbase/configurable.py +++ b/src/diffpy/srfit/fitbase/configurable.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Configurable class. A Configurable has state of which a FitRecipe must be aware. @@ -45,13 +44,14 @@ def _updateConfiguration(self): def _storeConfigurable(self, obj): """Store a Configurable. - The passed obj is only stored if it is a a Configurable, otherwise this - method quietly exits. + The passed obj is only stored if it is a a Configurable, + otherwise this method quietly exits. """ if isinstance(obj, Configurable): self._configobjs.add(obj) return + # End class Configurable # End of file diff --git a/src/diffpy/srfit/fitbase/constraint.py b/src/diffpy/srfit/fitbase/constraint.py index a0f06178..5cf1e2f3 100644 --- a/src/diffpy/srfit/fitbase/constraint.py +++ b/src/diffpy/srfit/fitbase/constraint.py @@ -12,13 +12,12 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Constraint class. -Constraints are used by a FitRecipe (and other RecipeOrganizers) to organize -constraint equations. They store a Parameter object and an Equation object that -is used to compute its value. The Constraint.constrain method is used to create -this association. +Constraints are used by a FitRecipe (and other RecipeOrganizers) to +organize constraint equations. They store a Parameter object and an +Equation object that is used to compute its value. The +Constraint.constrain method is used to create this association. """ __all__ = ["Constraint"] @@ -48,17 +47,17 @@ def __init__(self): def constrain(self, par, eq): """Constrain a Parameter according to an Equation. - The parameter will be set constant once it is constrained. This will - keep it from being constrained multiple times. + The parameter will be set constant once it is constrained. This + will keep it from being constrained multiple times. Raises a ValueError if par is const. """ if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) if par.constrained: - raise ValueError("The parameter '%s' is already constrained"%par) + raise ValueError("The parameter '%s' is already constrained" % par) par.constrained = True @@ -86,8 +85,7 @@ def update(self): def _validate(self): """Validate my state. - This validates that par is not None. - This validates eq. + This validates that par is not None. This validates eq. Raises SrFitError if validation fails. """ @@ -97,6 +95,7 @@ def _validate(self): raise SrFitError("eq is None") self.par._validate() from diffpy.srfit.equation.visitors import validate + try: validate(self.eq) except ValueError as e: @@ -114,4 +113,5 @@ def _validate(self): return + # End of file diff --git a/src/diffpy/srfit/fitbase/fitcontribution.py b/src/diffpy/srfit/fitbase/fitcontribution.py index 48640c19..3de608d4 100644 --- a/src/diffpy/srfit/fitbase/fitcontribution.py +++ b/src/diffpy/srfit/fitbase/fitcontribution.py @@ -12,24 +12,24 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """FitContribution class. FitContributions generate a residual function for a FitRecipe. A -FitContribution associates an Equation for generating a signal, optionally one -or more ProfileGenerators or Calculators that help in this, and a Profile that -holds the observed and calculated signals. +FitContribution associates an Equation for generating a signal, +optionally one or more ProfileGenerators or Calculators that help in +this, and a Profile that holds the observed and calculated signals. See the examples in the documention for how to use a FitContribution. """ __all__ = ["FitContribution"] -from diffpy.srfit.fitbase.parameterset import ParameterSet -from diffpy.srfit.fitbase.recipeorganizer import equationFromString +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.parameter import ParameterProxy +from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.fitbase.profile import Profile -from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.recipeorganizer import equationFromString + class FitContribution(ParameterSet): """FitContribution class. @@ -79,7 +79,7 @@ def __init__(self, name): self._manage(self._generators) return - def setProfile(self, profile, xname = None, yname = None, dyname = None): + def setProfile(self, profile, xname=None, yname=None, dyname=None): """Assign the Profile for this FitContribution. profile -- A Profile that specifies the calculation points and that @@ -119,9 +119,9 @@ def setProfile(self, profile, xname = None, yname = None, dyname = None): xpar = ParameterProxy(xname, self.profile.xpar) ypar = ParameterProxy(yname, self.profile.ypar) dypar = ParameterProxy(dyname, self.profile.dypar) - self.addParameter(xpar, check = False) - self.addParameter(ypar, check = False) - self.addParameter(dypar, check = False) + self.addParameter(xpar, check=False) + self.addParameter(ypar, check=False) + self.addParameter(dypar, check=False) # If we have ProfileGenerators, set their Profiles. for gen in self._generators.values(): @@ -129,12 +129,11 @@ def setProfile(self, profile, xname = None, yname = None, dyname = None): # If we have _eq, but not _reseq, set the residual if self._eq is not None and self._reseq is None: - self.setResidualEquation('chiv') + self.setResidualEquation("chiv") return - - def addProfileGenerator(self, gen, name = None): + def addProfileGenerator(self, gen, name=None): """Add a ProfileGenerator to be used by this FitContribution. The ProfileGenerator is given a name so that it can be used as part of @@ -173,7 +172,7 @@ def addProfileGenerator(self, gen, name = None): return - def setEquation(self, eqstr, ns = {}): + def setEquation(self, eqstr, ns={}): """Set the profile equation for the FitContribution. This sets the equation that will be used when generating the residual @@ -193,8 +192,7 @@ def setEquation(self, eqstr, ns = {}): variable. """ # Build the equation instance. - eq = equationFromString(eqstr, self._eqfactory, - buildargs=True, ns=ns) + eq = equationFromString(eqstr, self._eqfactory, buildargs=True, ns=ns) eq.name = "eq" # Register any new Parameters. @@ -208,11 +206,10 @@ def setEquation(self, eqstr, ns = {}): # Set the residual if we need to if self.profile is not None and self._reseq is None: - self.setResidualEquation('chiv') + self.setResidualEquation("chiv") return - def getEquation(self): """Get math expression string for the active profile equation. @@ -220,12 +217,12 @@ def getEquation(self): equation has not been set yet. """ from diffpy.srfit.equation.visitors import getExpression + rv = "" if self._eq is not None: rv = getExpression(self._eq) return rv - def setResidualEquation(self, eqstr): """Set the residual equation for the FitContribution. @@ -266,20 +263,19 @@ def setResidualEquation(self, eqstr): return - def getResidualEquation(self): """Get math expression string for the active residual equation. - Return normalized math formula or an empty string if residual equation - has not been configured yet. + Return normalized math formula or an empty string if residual + equation has not been configured yet. """ from diffpy.srfit.equation.visitors import getExpression + rv = "" if self._reseq is not None: - rv = getExpression(self._reseq, eqskip='eq$') + rv = getExpression(self._reseq, eqskip="eq$") return rv - def residual(self): """Calculate the residual for this fitcontribution. @@ -300,7 +296,6 @@ def residual(self): # the following will not recompute the equation. return self._reseq() - def evaluate(self): """Evaluate the contribution equation and update profile.ycalc.""" yc = self._eq() @@ -308,12 +303,12 @@ def evaluate(self): self.profile.ycalc = yc return yc - def _validate(self): """Validate my state. - This performs profile validations. This performs ProfileGenerator - validations. This validates _eq. This validates _reseq and residual. + This performs profile validations. This performs + ProfileGenerator validations. This validates _eq. This validates + _reseq and residual. Raises SrFitError if validation fails. """ @@ -322,6 +317,7 @@ def _validate(self): # Try to get the value of eq. from diffpy.srfit.equation.visitors import validate + try: validate(self._eq) except ValueError as e: @@ -333,7 +329,7 @@ def _validate(self): try: val = self._eq() except TypeError as e: - raise SrFitError("_eq cannot be evaluated: %s"%e) + raise SrFitError("_eq cannot be evaluated: %s" % e) if val is None: raise SrFitError("_eq evaluates to None") @@ -351,4 +347,5 @@ def _validate(self): raise SrFitError("residual evaluates to None") return + # End of file diff --git a/src/diffpy/srfit/fitbase/fithook.py b/src/diffpy/srfit/fitbase/fithook.py index dfa521b1..35e775f6 100644 --- a/src/diffpy/srfit/fitbase/fithook.py +++ b/src/diffpy/srfit/fitbase/fithook.py @@ -12,17 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The FitHook class for inspecting the progress of a FitRecipe refinement. -FitHooks are called by a FitRecipe during various times of the residual is -evaluation. The default FitHook simply counts the number of times the residual -is called, and reports that number every time the residual is calculated. -Depending on the verbosity, it will also report the residual and the current -variable values. +FitHooks are called by a FitRecipe during various times of the residual +is evaluation. The default FitHook simply counts the number of times the +residual is called, and reports that number every time the residual is +calculated. Depending on the verbosity, it will also report the residual +and the current variable values. -Custom FitHooks can be added to a FitRecipe with the FitRecipe.setFitHook -method. +Custom FitHooks can be added to a FitRecipe with the +FitRecipe.setFitHook method. """ from __future__ import print_function @@ -37,18 +36,20 @@ class FitHook(object): """Base class for inspecting the progress of a FitRecipe refinement. - Can serve as a fithook for the FitRecipe class (see FitRecipe.pushFitHook - method.) The methods in this class are called during the preparation of the - FitRecipe for refinement, and during the residual call. See the class - methods for a description of their purpose. + Can serve as a fithook for the FitRecipe class (see + FitRecipe.pushFitHook method.) The methods in this class are called + during the preparation of the FitRecipe for refinement, and during + the residual call. See the class methods for a description of their + purpose. """ def reset(self, recipe): """Reset the hook data. - This is called whenever FitRecipe._prepare is called, which is whenever - a configurational change to the fit hierarchy takes place, such as - adding a new ParameterSet, constraint or restraint. + This is called whenever FitRecipe._prepare is called, which is + whenever a configurational change to the fit hierarchy takes + place, such as adding a new ParameterSet, constraint or + restraint. """ return @@ -67,8 +68,10 @@ def postcall(self, recipe, chiv): """ return + # End class FitHook + class PrintFitHook(FitHook): """Base class for inspecting the progress of a FitRecipe refinement. @@ -94,9 +97,10 @@ def __init__(self): def reset(self, recipe): """Reset the hook data. - This is called whenever FitRecipe._prepare is called, which is whenever - a configurational change to the fit hierarchy takes place, such as - adding a new ParameterSet, constraint or restraint. + This is called whenever FitRecipe._prepare is called, which is + whenever a configurational change to the fit hierarchy takes + place, such as adding a new ParameterSet, constraint or + restraint. """ self.count = 0 return @@ -140,14 +144,16 @@ def postcall(self, recipe, chiv): print("Variables") vnames = recipe.getNames() vals = recipe.getValues() - byname = lambda nv : sortKeyForNumericString(nv[0]) + byname = lambda nv: sortKeyForNumericString(nv[0]) items = sorted(zip(vnames, vals), key=byname) for name, val in items: print(" %s = %f" % (name, val)) return + # End class PrintFitHook + # TODO - Display the chi^2 on the plot during refinement. class PlotFitHook(FitHook): """This FitHook has live plotting of whatever is being refined.""" @@ -177,20 +183,20 @@ def reset(self, recipe): # Create a subplot if nc > 1: - pylab.subplot(nrows, ncols, idx+1) - pdata = pylab.plot(p.x, p.y, 'bo')[0] - pfit = pylab.plot(p.x, p.y, 'r-')[0] + pylab.subplot(nrows, ncols, idx + 1) + pdata = pylab.plot(p.x, p.y, "bo")[0] + pfit = pylab.plot(p.x, p.y, "r-")[0] self._plots.append((pdata, pfit)) pylab.xlabel(xname) pylab.ylabel(yname) pylab.title(name) # Set up some event handling, so things behave nicely. - #def redraw(event): + # def redraw(event): # canvas = event.canvas # canvas.draw() # return - #pylab.connect('resize_event', redraw) + # pylab.connect('resize_event', redraw) return diff --git a/src/diffpy/srfit/fitbase/fitrecipe.py b/src/diffpy/srfit/fitbase/fitrecipe.py index 9e09c271..8539ab53 100644 --- a/src/diffpy/srfit/fitbase/fitrecipe.py +++ b/src/diffpy/srfit/fitbase/fitrecipe.py @@ -12,37 +12,38 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """FitRecipe class. -FitRecipes organize FitContributions, variables, Restraints and Constraints to -create a recipe of the system you wish to optimize. From the client's -perspective, the FitRecipe is a residual calculator. The residual method does -the work of updating variable values, which get propagated to the Parameters of -the underlying FitContributions via the variables and Constraints. This class -needs no special knowledge of the type of FitContribution or data being used. -Thus, it is suitable for combining residual equations from various types of -refinements into a single residual. - -Variables added to a FitRecipe can be tagged with string identifiers. Variables -can be later retrieved or manipulated by tag. The tag name "__fixed" is -reserved. - -See the examples in the documentation for how to create an optimization problem -using FitRecipe. +FitRecipes organize FitContributions, variables, Restraints and +Constraints to create a recipe of the system you wish to optimize. From +the client's perspective, the FitRecipe is a residual calculator. The +residual method does the work of updating variable values, which get +propagated to the Parameters of the underlying FitContributions via the +variables and Constraints. This class needs no special knowledge of the +type of FitContribution or data being used. Thus, it is suitable for +combining residual equations from various types of refinements into a +single residual. + +Variables added to a FitRecipe can be tagged with string identifiers. +Variables can be later retrieved or manipulated by tag. The tag name +"__fixed" is reserved. + +See the examples in the documentation for how to create an optimization +problem using FitRecipe. """ __all__ = ["FitRecipe"] from collections import OrderedDict -from numpy import array, concatenate, sqrt, dot + import six +from numpy import array, concatenate, dot, sqrt -from diffpy.srfit.interface import _fitrecipe_interface -from diffpy.srfit.util.tagmanager import TagManager +from diffpy.srfit.fitbase.fithook import PrintFitHook from diffpy.srfit.fitbase.parameter import ParameterProxy from diffpy.srfit.fitbase.recipeorganizer import RecipeOrganizer -from diffpy.srfit.fitbase.fithook import PrintFitHook +from diffpy.srfit.interface import _fitrecipe_interface +from diffpy.srfit.util.tagmanager import TagManager class FitRecipe(_fitrecipe_interface, RecipeOrganizer): @@ -87,18 +88,28 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): bounds2 -- Bounds on parameters (read only). See getBounds2. """ - fixednames = property(lambda self: - [v.name for v in self._parameters.values() - if not (self.isFree(v) or self.isConstrained(v))], - doc='names of the fixed refinable variables') - fixedvalues = property(lambda self: - array([v.value for v in self._parameters.values() - if not (self.isFree(v) or self.isConstrained(v))]), - doc='values of the fixed refinable variables') + fixednames = property( + lambda self: [ + v.name + for v in self._parameters.values() + if not (self.isFree(v) or self.isConstrained(v)) + ], + doc="names of the fixed refinable variables", + ) + fixedvalues = property( + lambda self: array( + [ + v.value + for v in self._parameters.values() + if not (self.isFree(v) or self.isConstrained(v)) + ] + ), + doc="values of the fixed refinable variables", + ) bounds = property(lambda self: self.getBounds()) bounds2 = property(lambda self: self.getBounds2()) - def __init__(self, name = "fit"): + def __init__(self, name="fit"): """Initialization.""" RecipeOrganizer.__init__(self, name) self.fithooks = [] @@ -119,7 +130,7 @@ def __init__(self, name = "fit"): return - def pushFitHook(self, fithook, index = None): + def pushFitHook(self, fithook, index=None): """Add a FitHook to be called within the residual method. The hook is an object for reporting updates, or more fundamentally, @@ -138,7 +149,7 @@ def pushFitHook(self, fithook, index = None): self._updateConfiguration() return - def popFitHook(self, fithook = None, index = -1): + def popFitHook(self, fithook=None, index=-1): """Remove a FitHook by index or reference. fithook -- FitHook instance to remove from the sequence. If this is @@ -164,7 +175,7 @@ def clearFitHooks(self): del self.fithooks[:] return - def addContribution(self, con, weight = 1.0): + def addContribution(self, con, weight=1.0): """Add a FitContribution to the FitRecipe. con -- The FitContribution to be stored. @@ -203,7 +214,7 @@ def removeParameterSet(self, parset): self._removeObject(parset, self._parsets) return - def residual(self, p = []): + def residual(self, p=[]): """Calculate the vector residual to be optimized. Arguments @@ -234,22 +245,25 @@ def residual(self, p = []): con.update() # Calculate the bare chiv - chiv = concatenate([ - wi * ci.residual().flatten() - for wi, ci in zip(self._weights, self._contributions.values())]) + chiv = concatenate( + [ + wi * ci.residual().flatten() + for wi, ci in zip(self._weights, self._contributions.values()) + ] + ) # Calculate the point-average chi^2 - w = dot(chiv, chiv)/len(chiv) + w = dot(chiv, chiv) / len(chiv) # Now we must append the restraints - penalties = [ sqrt(res.penalty(w)) for res in self._restraintlist ] - chiv = concatenate( [ chiv, penalties ] ) + penalties = [sqrt(res.penalty(w)) for res in self._restraintlist] + chiv = concatenate([chiv, penalties]) for fithook in self.fithooks: fithook.postcall(self, chiv) return chiv - def scalarResidual(self, p = []): + def scalarResidual(self, p=[]): """Calculate the scalar residual to be optimized. Arguments @@ -267,7 +281,7 @@ def scalarResidual(self, p = []): chiv = self.residual(p) return dot(chiv, chiv) - def __call__(self, p = []): + def __call__(self, p=[]): """Same as scalarResidual method.""" return self.scalarResidual(p) @@ -277,7 +291,8 @@ def _prepare(self): This will prepare the data attributes to be used in the residual calculation. - This updates the local restraints with those of the contributions. + This updates the local restraints with those of the + contributions. Raises AttributeError if there are variables without a value. """ @@ -318,14 +333,16 @@ def __verifyProfiles(self): # Check for profile values for con in self._contributions.values(): if con.profile is None: - m = "FitContribution '%s' does not have a Profile"%con.name + m = "FitContribution '%s' does not have a Profile" % con.name raise AttributeError(m) - if con.profile.x is None or\ - con.profile.y is None or\ - con.profile.dy is None: + if ( + con.profile.x is None + or con.profile.y is None + or con.profile.dy is None + ): - m = "Profile for '%s' is missing data"%con.name - raise AttributeError(m) + m = "Profile for '%s' is missing data" % con.name + raise AttributeError(m) return def __verifyParameters(self): @@ -344,7 +361,7 @@ def __verifyParameters(self): for par in badpars: objlist = self._locateManagedObject(par) names = [obj.name for obj in objlist] - badnames.append( ".".join(names) ) + badnames.append(".".join(names)) # Construct an error message, if necessary m = "" @@ -362,14 +379,15 @@ def __verifyParameters(self): def __collectConstraintsAndRestraints(self): """Collect the Constraints and Restraints from subobjects.""" - from itertools import chain from functools import cmp_to_key + from itertools import chain + rset = set(self._restraints) cdict = {} for org in chain(self._contributions.values(), self._parsets.values()): - rset.update( org._getRestraints() ) - cdict.update( org._getConstraints() ) + rset.update(org._getRestraints()) + cdict.update(org._getConstraints()) cdict.update(self._constraints) # The order of the restraint list does not matter @@ -386,7 +404,7 @@ def __collectConstraintsAndRestraints(self): # Now check the constraint's equation for constrained arguments for arg in con.eq.args: if arg in cdict: - depmap[con].add( cdict[arg] ) + depmap[con].add(cdict[arg]) # Turn the dependency map into multi-level map. def _extendDeps(con): @@ -422,8 +440,9 @@ def cmp(x, y): # Variable manipulation - def addVar(self, par, value = None, name = None, fixed = False, tag = None, - tags = []): + def addVar( + self, par, value=None, name=None, fixed=False, tag=None, tags=[] + ): """Add a variable to be refined. par -- A Parameter that will be varied during a fit. @@ -448,10 +467,10 @@ def addVar(self, par, value = None, name = None, fixed = False, tag = None, name = name or par.name if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) if par.constrained: - raise ValueError("The parameter '%s' is constrained"%par) + raise ValueError("The parameter '%s' is constrained" % par) var = ParameterProxy(name, par) if value is not None: @@ -487,13 +506,12 @@ def delVar(self, var): def __delattr__(self, name): if name in self._parameters: - self.delVar( self._parameters[name] ) + self.delVar(self._parameters[name]) return super(FitRecipe, self).__delattr__(name) return - - def newVar(self, name, value = None, fixed = False, tag = None, tags = []): + def newVar(self, name, value=None, fixed=False, tag=None, tags=[]): """Create a new variable of the fit. This method lets new variables be created that are not tied to a @@ -543,7 +561,6 @@ def _newParameter(self, name, value, check=True): self.fix(par.name) return par - def __getVarAndCheck(self, var): """Get the actual variable from var. @@ -563,30 +580,32 @@ def __getVarAndCheck(self, var): def __getVarsFromArgs(self, *args, **kw): """Get a list of variables from passed arguments. - This method accepts string or variable arguments. An argument of "all" - selects all variables. Keyword arguments must be parameter names, - followed by a value to assign to the fixed variable. This method is - used by the fix and free methods. + This method accepts string or variable arguments. An argument of + "all" selects all variables. Keyword arguments must be parameter + names, followed by a value to assign to the fixed variable. This + method is used by the fix and free methods. - Raises ValueError if an unknown variable, name or tag is passed, or if - a tag is passed in a keyword. + Raises ValueError if an unknown variable, name or tag is passed, + or if a tag is passed in a keyword. """ # Process args. Each variable is tagged with its name, so this is easy. - strargs = set([arg for arg in args if isinstance(arg, six.string_types)]) + strargs = set( + [arg for arg in args if isinstance(arg, six.string_types)] + ) varargs = set(args) - strargs # Check that the tags are valid alltags = set(self._tagmanager.alltags()) badtags = strargs - alltags if badtags: names = ",".join(badtags) - raise ValueError("Variables or tags cannot be found (%s)"% names) + raise ValueError("Variables or tags cannot be found (%s)" % names) # Check that variables are valid allvars = set(self._parameters.values()) badvars = varargs - allvars if badvars: names = ",".join(v.name for v in badvars) - raise ValueError("Variables cannot be found (%s)"% names) + raise ValueError("Variables cannot be found (%s)" % names) # Make sure that we only have parameters in kw kwnames = set(kw.keys()) @@ -594,7 +613,7 @@ def __getVarsFromArgs(self, *args, **kw): badkw = kwnames - allnames if badkw: names = ",".join(badkw) - raise ValueError("Tags cannot be passed as keywords (%s)"% names) + raise ValueError("Tags cannot be passed as keywords (%s)" % names) # Now get all the objects referred to in the arguments. varargs |= self._tagmanager.union(*strargs) @@ -606,17 +625,16 @@ def fix(self, *args, **kw): A fixed variable is not refined. Variables are free by default. - This method accepts string or variable arguments. An argument of "all" - selects all variables. Keyword arguments must be parameter names, - followed by a value to assign to the fixed variable. + This method accepts string or variable arguments. An argument of + "all" selects all variables. Keyword arguments must be parameter + names, followed by a value to assign to the fixed variable. - Raises ValueError if an unknown Parameter, name or tag is passed, or if - a tag is passed in a keyword. + Raises ValueError if an unknown Parameter, name or tag is + passed, or if a tag is passed in a keyword. """ # Check the inputs and get the variables from them varargs = self.__getVarsFromArgs(*args, **kw) - # Fix all of these for var in varargs: self._tagmanager.tag(var, self._fixedtag) @@ -630,15 +648,15 @@ def fix(self, *args, **kw): def free(self, *args, **kw): """Free a parameter by reference, name or tag. - A free variable is refined. Variables are free by default. Constrained - variables are not free. + A free variable is refined. Variables are free by default. + Constrained variables are not free. - This method accepts string or variable arguments. An argument of "all" - selects all variables. Keyword arguments must be parameter names, - followed by a value to assign to the fixed variable. + This method accepts string or variable arguments. An argument of + "all" selects all variables. Keyword arguments must be parameter + names, followed by a value to assign to the fixed variable. - Raises ValueError if an unknown Parameter, name or tag is passed, or if - a tag is passed in a keyword. + Raises ValueError if an unknown Parameter, name or tag is + passed, or if a tag is passed in a keyword. """ # Check the inputs and get the variables from them varargs = self.__getVarsFromArgs(*args, **kw) @@ -656,7 +674,7 @@ def free(self, *args, **kw): def isFree(self, var): """Check if a variable is fixed.""" - return (not self._tagmanager.hasTags(var, self._fixedtag)) + return not self._tagmanager.hasTags(var, self._fixedtag) def unconstrain(self, *pars): """Unconstrain a Parameter. @@ -692,7 +710,7 @@ def unconstrain(self, *pars): return - def constrain(self, par, con, ns = {}): + def constrain(self, par, con, ns={}): """Constrain a parameter to an equation. Note that only one constraint can exist on a Parameter at a time. @@ -722,13 +740,13 @@ def constrain(self, par, con, ns = {}): if par is None: par = ns.get(name) if par is None: - raise ValueError("The parameter '%s' cannot be found"%name) + raise ValueError("The parameter '%s' cannot be found" % name) if con in self._parameters.keys(): con = self._parameters[con] if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) # This will pass the value of a constrained parameter to the initial # value of a parameter constraint. @@ -744,11 +762,11 @@ def constrain(self, par, con, ns = {}): RecipeOrganizer.constrain(self, par, con, ns) return - def getValues(self): """Get the current values of the variables in a list.""" - return array([v.value for v in self._parameters.values() if - self.isFree(v)]) + return array( + [v.value for v in self._parameters.values() if self.isFree(v)] + ) def getNames(self): """Get the names of the variables in a list.""" @@ -757,8 +775,8 @@ def getNames(self): def getBounds(self): """Get the bounds on variables in a list. - Returns a list of (lb, ub) pairs, where lb is the lower bound and ub is - the upper bound. + Returns a list of (lb, ub) pairs, where lb is the lower bound + and ub is the upper bound. """ return [v.bounds for v in self._parameters.values() if self.isFree(v)] @@ -772,7 +790,7 @@ def getBounds2(self): ub = array([b[1] for b in bounds]) return lb, ub - def boundsToRestraints(self, sig = 1, scaled = False): + def boundsToRestraints(self, sig=1, scaled=False): """Turn all bounded parameters into restraints. The bounds become limits on the restraint. @@ -785,13 +803,15 @@ def boundsToRestraints(self, sig = 1, scaled = False): if not hasattr(sig, "__iter__"): sig = [sig] * len(pars) for par, x in zip(pars, sig): - self.restrain(par, par.bounds[0], par.bounds[1], sig = x, - scaled = scaled) + self.restrain( + par, par.bounds[0], par.bounds[1], sig=x, scaled=scaled + ) return def _applyValues(self, p): """Apply variable values to the variables.""" - if len(p) == 0: return + if len(p) == 0: + return vargen = (v for v in self._parameters.values() if self.isFree(v)) for var, pval in zip(vargen, p): var.setValue(pval) @@ -802,4 +822,5 @@ def _updateConfiguration(self): self._ready = False return + # End of file diff --git a/src/diffpy/srfit/fitbase/fitresults.py b/src/diffpy/srfit/fitbase/fitresults.py index e4c4f1e9..4a12974c 100644 --- a/src/diffpy/srfit/fitbase/fitresults.py +++ b/src/diffpy/srfit/fitbase/fitresults.py @@ -12,12 +12,11 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The FitResults and ContributionResults classes for storing results of a fit. -The FitResults class is used to display the current state of a FitRecipe. It -stores the state, and uses it to calculate useful statistics, which can be -displayed on screen or saved to file. +The FitResults class is used to display the current state of a +FitRecipe. It stores the state, and uses it to calculate useful +statistics, which can be displayed on screen or saved to file. """ from __future__ import print_function @@ -25,12 +24,13 @@ __all__ = ["FitResults", "ContributionResults", "initializeRecipe"] import re -import numpy from collections import OrderedDict -from diffpy.srfit.util.inpututils import inputToString +import numpy + from diffpy.srfit.util import _DASHEDLINE from diffpy.srfit.util import sortKeyForNumericString as numstr +from diffpy.srfit.util.inpututils import inputToString class FitResults(object): @@ -69,8 +69,7 @@ class FitResults(object): the update method is called. """ - def __init__(self, recipe, update = True, showfixed = True, showcon = - False): + def __init__(self, recipe, update=True, showfixed=True, showcon=False): """Initialize the attributes. recipe -- The recipe containing the results @@ -137,15 +136,18 @@ def update(self): self._calculateCovariance() # Get the variable uncertainties - self.varunc = [self.cov[i,i]**0.5 for i in \ - range(len(self.varnames))] + self.varunc = [ + self.cov[i, i] ** 0.5 for i in range(len(self.varnames)) + ] # Get the constraint uncertainties self._calculateConstraintUncertainties() # Store the fitting arrays and metrics for each FitContribution. self.conresults = OrderedDict() - for con, weight in zip(recipe._contributions.values(), recipe._weights): + for con, weight in zip( + recipe._contributions.values(), recipe._weights + ): self.conresults[con.name] = ContributionResults(con, weight, self) # Calculate the metrics @@ -167,8 +169,8 @@ def _calculateCovariance(self): """ try: J = self._calculateJacobian() - u,s,vh = numpy.linalg.svd(J,0) - self.cov = numpy.dot(vh.T.conj()/s**2,vh) + u, s, vh = numpy.linalg.svd(J, 0) + self.cov = numpy.dot(vh.T.conj() / s**2, vh) except numpy.linalg.LinAlgError: self.messages.append("Cannot compute covariance matrix.") l = len(self.varvals) @@ -178,14 +180,14 @@ def _calculateCovariance(self): def _calculateJacobian(self): """Calculate the Jacobian for the fitting. - Adapted from PARK. Returns the derivative wrt the fit variables at - point p. + Adapted from PARK. Returns the derivative wrt the fit variables + at point p. - This also calculates the derivatives of the constrained parameters - while we're at it. + This also calculates the derivatives of the constrained + parameters while we're at it. - Numeric derivatives are calculated based on step, where step is the - portion of variable value. E.g. step = dv/v. + Numeric derivatives are calculated based on step, where step is + the portion of variable value. E.g. step = dv/v. """ recipe = self.recipe step = self.derivstep @@ -203,7 +205,7 @@ def _calculateJacobian(self): # The list of constraint derivatives with respect to variables # The forward difference would be faster, but perhaps not as accurate. conr = [] - for k,v in enumerate(pvals): + for k, v in enumerate(pvals): h = delta[k] pvals[k] = v + h rk = self.recipe.residual(pvals) @@ -223,14 +225,14 @@ def _calculateJacobian(self): val = con.par.getValue() if numpy.isscalar(val): cond[i] -= con.par.getValue() - cond[i] /= 2*h + cond[i] /= 2 * h else: cond[i] = 0.0 conr.append(cond) pvals[k] = v - r.append(rk/(2*h)) + r.append(rk / (2 * h)) # Reset the constrained parameters to their original values for con in recipe._oconstraints: @@ -290,7 +292,7 @@ def _calculateConstraintUncertainties(self): self.conunc.append(sig2c**0.5) return - def formatResults(self, header = "", footer = "", update = False): + def formatResults(self, header="", footer="", update=False): """Format the results and return them in a string. This function is called by printResults and saveResults. Overloading @@ -308,7 +310,7 @@ def formatResults(self, header = "", footer = "", update = False): lines = [] corrmin = 0.25 p = self.precision - pe = "%-" + "%i.%ie" % (p+6, p) + pe = "%-" + "%i.%ie" % (p + 6, p) pet = "%" + ".%ie" % (p,) # Check to see if the uncertainty values are reliable. certain = True @@ -335,12 +337,14 @@ def formatResults(self, header = "", footer = "", update = False): lines.append(l) lines.append(_DASHEDLINE) formatstr = "%-14s %.8f" - lines.append(formatstr%("Residual",self.residual)) - lines.append(formatstr%("Contributions", self.residual - self.penalty)) - lines.append(formatstr%("Restraints", self.penalty)) - lines.append(formatstr%("Chi2",self.chi2)) - lines.append(formatstr%("Reduced Chi2",self.rchi2)) - lines.append(formatstr%("Rw",self.rw)) + lines.append(formatstr % ("Residual", self.residual)) + lines.append( + formatstr % ("Contributions", self.residual - self.penalty) + ) + lines.append(formatstr % ("Restraints", self.penalty)) + lines.append(formatstr % ("Chi2", self.chi2)) + lines.append(formatstr % ("Reduced Chi2", self.rchi2)) + lines.append(formatstr % ("Rw", self.rw)) ## Per-FitContribution results if len(self.conresults) > 1: @@ -357,12 +361,12 @@ def formatResults(self, header = "", footer = "", update = False): for name in keys: res = self.conresults[name] lines.append("") - namestr = name + " (%f)"%res.weight + namestr = name + " (%f)" % res.weight lines.append(namestr) - lines.append("-"*len(namestr)) - lines.append(formatstr%("Residual",res.residual)) - lines.append(formatstr%("Chi2",res.chi2)) - lines.append(formatstr%("Rw",res.rw)) + lines.append("-" * len(namestr)) + lines.append(formatstr % ("Residual", res.residual)) + lines.append(formatstr % ("Chi2", res.chi2)) + lines.append(formatstr % ("Rw", res.rw)) ## The variables if self.varnames: @@ -370,7 +374,7 @@ def formatResults(self, header = "", footer = "", update = False): l = "Variables" if not certain: m = "Uncertainties invalid" - l += " (%s)"%m + l += " (%s)" % m lines.append(l) lines.append(_DASHEDLINE) @@ -380,11 +384,11 @@ def formatResults(self, header = "", footer = "", update = False): varlines = [] w = max(map(len, varnames)) - w = str(w+1) + w = str(w + 1) # Format the lines - formatstr = "%-"+w+"s " + pe + " +/- " + pet + formatstr = "%-" + w + "s " + pe + " +/- " + pet for name, val, unc in zip(varnames, varvals, varunc): - varlines.append(formatstr%(name, val, unc)) + varlines.append(formatstr % (name, val, unc)) varlines.sort() lines.extend(varlines) @@ -396,14 +400,13 @@ def formatResults(self, header = "", footer = "", update = False): lines.append("Fixed Variables") lines.append(_DASHEDLINE) w = max(map(len, self.fixednames)) - w = str(w+1) - formatstr = "%-"+w+"s " + pet + w = str(w + 1) + formatstr = "%-" + w + "s " + pet for name, val in zip(self.fixednames, self.fixedvals): - varlines.append(formatstr%(name, val)) + varlines.append(formatstr % (name, val)) varlines.sort() lines.extend(varlines) - ## The constraints if self.connames and self.showcon: lines.append("") @@ -427,16 +430,16 @@ def formatResults(self, header = "", footer = "", update = False): vals[name] = (val, unc) keys.sort(key=numstr) - w = str(w+1) - formatstr = "%-"+w+"s %- 15f +/- %-15f" + w = str(w + 1) + formatstr = "%-" + w + "s %- 15f +/- %-15f" for name in keys: val, unc = vals[name] - lines.append(formatstr%(name, val, unc)) + lines.append(formatstr % (name, val, unc)) ## Variable correlations lines.append("") - corint = int(corrmin*100) - l = "Variable Correlations greater than %i%%"%corint + corint = int(corrmin * 100) + l = "Variable Correlations greater than %i%%" % corint if not certain: l += " (Correlations invalid)" lines.append(l) @@ -446,33 +449,32 @@ def formatResults(self, header = "", footer = "", update = False): n = len(self.varnames) for i in range(n): for j in range(i + 1, n): - name = "corr(%s, %s)"%(varnames[i], varnames[j]) - val = (self.cov[i,j]/(self.cov[i,i] * self.cov[j,j])**0.5) + name = "corr(%s, %s)" % (varnames[i], varnames[j]) + val = self.cov[i, j] / (self.cov[i, i] * self.cov[j, j]) ** 0.5 if abs(val) > corrmin: cornames.append(name) tup.append((val, name)) - tup.sort(key=lambda vn : abs(vn[0])) + tup.sort(key=lambda vn: abs(vn[0])) tup.reverse() if cornames: w = max(map(len, cornames)) w = str(w + 1) - formatstr = "%-"+w+"s %.4f" + formatstr = "%-" + w + "s %.4f" for val, name in tup: - lines.append(formatstr%(name, val)) + lines.append(formatstr % (name, val)) else: - lines.append("No correlations greater than %i%%"%corint) - + lines.append("No correlations greater than %i%%" % corint) # User-defined footer if footer: lines.append(footer) - out = "\n".join(lines) + '\n' + out = "\n".join(lines) + "\n" return out - def printResults(self, header = "", footer = "", update = False): + def printResults(self, header="", footer="", update=False): """Format and print the results. header -- A header to add to the output (default "") @@ -485,8 +487,7 @@ def printResults(self, header = "", footer = "", update = False): def __str__(self): return self.formatResults() - - def saveResults(self, filename, header = "", footer = "", update = False): + def saveResults(self, filename, header="", footer="", update=False): """Format and save the results. filename - Name of the save file. @@ -495,20 +496,23 @@ def saveResults(self, filename, header = "", footer = "", update = False): update -- Flag indicating whether to call update() (default False). """ # Save the time and user - from time import ctime from getpass import getuser + from time import ctime + myheader = "Results written: " + ctime() + "\n" myheader += "produced by " + getuser() + "\n" header = myheader + header res = self.formatResults(header, footer, update) - f = open(filename, 'w') + f = open(filename, "w") f.write(res) f.close() return + # End class FitResults + class ContributionResults(object): """Class for processing, storing FitContribution results. @@ -600,13 +604,14 @@ def _calculateMetrics(self): # We take absolute values in case the signal is complex num = numpy.abs(self.y - self.ycalc) y = numpy.abs(self.y) - chiv = num/self.dy + chiv = num / self.dy self.cumchi2 = numpy.cumsum(chiv**2) # avoid index error for empty array self.chi2 = self.cumchi2[-1:].sum() yw = y / self.dy yw2tot = numpy.dot(yw, yw) - if yw2tot == 0.0: yw2tot = 1.0 + if yw2tot == 0.0: + yw2tot = 1.0 self.cumrw = numpy.sqrt(self.cumchi2 / yw2tot) # avoid index error for empty array self.rw = self.cumrw[-1:].sum() @@ -615,6 +620,7 @@ def _calculateMetrics(self): # End class ContributionResults + def resultsDictionary(results): """Get dictionary of results from file. @@ -626,8 +632,10 @@ def resultsDictionary(results): """ resstr = inputToString(results) - rx = {'f' : r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", - 'n' : r'[a-zA-Z_]\w*'} + rx = { + "f": r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", + "n": r"[a-zA-Z_]\w*", + } pat = r"(%(n)s)\s+(%(f)s)" % rx matches = re.findall(pat, resstr) @@ -636,6 +644,7 @@ def resultsDictionary(results): mpairs = dict(matches) return mpairs + def initializeRecipe(recipe, results): """Initialize the variables of a recipe from a results file. diff --git a/src/diffpy/srfit/fitbase/parameter.py b/src/diffpy/srfit/fitbase/parameter.py index 6b703336..21625b6d 100644 --- a/src/diffpy/srfit/fitbase/parameter.py +++ b/src/diffpy/srfit/fitbase/parameter.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Parameter classes. Parameters encapsulate an adjustable parameter within SrFit. @@ -29,12 +28,12 @@ import numpy -from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.equation.literals import Argument -from diffpy.srfit.util.nameutils import validateName -from diffpy.srfit.util.argbinders import bind2nd -from diffpy.srfit.interface import _parameter_interface +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.validatable import Validatable +from diffpy.srfit.interface import _parameter_interface +from diffpy.srfit.util.argbinders import bind2nd +from diffpy.srfit.util.nameutils import validateName class Parameter(_parameter_interface, Argument, Validatable): @@ -52,7 +51,7 @@ class Parameter(_parameter_interface, Argument, Validatable): FitRecipe.getBounds and FitRecipe.boundsToRestraints. """ - def __init__(self, name, value = None, const = False): + def __init__(self, name, value=None, const=False): """Initialization. name -- The name of this Parameter (must be a valid attribute @@ -83,7 +82,7 @@ def setValue(self, val): Argument.setValue(self, val) return self - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. const -- Flag indicating if the parameter is constant (default @@ -99,7 +98,6 @@ def setConst(self, const = True, value = None): self.setValue(value) return self - def boundRange(self, lb=None, ub=None): """Set lower and upper bound of the Parameter. @@ -114,8 +112,7 @@ def boundRange(self, lb=None, ub=None): self.bounds[1] = ub return self - - def boundWindow(self, lr = 0, ur = None): + def boundWindow(self, lr=0, ur=None): """Create bounds centered on the current value of the Parameter. lr -- The radius of the lower bound (default 0). The lower bound is @@ -142,11 +139,13 @@ def _validate(self): Raises SrFitError if validation fails. """ if self.value is None: - raise SrFitError("value of '%s' is None"%self.name) + raise SrFitError("value of '%s' is None" % self.name) return + # End class Parameter + class ParameterProxy(Parameter): """A Parameter proxy for another parameter. @@ -158,7 +157,6 @@ class ParameterProxy(Parameter): par -- The Parameter this is a proxy for. """ - def __init__(self, name, par): """Initialization. @@ -185,13 +183,13 @@ def constrained(self, value): self.par.constrained = bool(value) return - @property def bounds(self): """List of lower and upper bounds of the proxied Parameter. - This can be used by some optimizers when the Parameter is varied. See - FitRecipe.getBounds and FitRecipe.boundsToRestraints. + This can be used by some optimizers when the Parameter is + varied. See FitRecipe.getBounds and + FitRecipe.boundsToRestraints. """ return self.par.bounds @@ -200,7 +198,6 @@ def bounds(self, value): self.par.bounds = value return - @property def _observers(self): return self.par._observers @@ -211,27 +208,22 @@ def _observers(self): def setValue(self, val): return self.par.setValue(val) - @wraps(Parameter.getValue) def getValue(self): return self.par.getValue() - @wraps(Parameter.setConst) def setConst(self, const=True, value=None): return self.par.setConst(const, value) - @wraps(Parameter.boundRange) def boundRange(self, lb=None, ub=None): return self.par.boundRange(lb, ub) - @wraps(Parameter.boundWindow) def boundWindow(self, lr=0, ur=None): return self.par.boundWindow(lr, ur) - def _validate(self): """Validate my state. @@ -244,6 +236,7 @@ def _validate(self): self.par._validate() return + # End class ParameterProxy @@ -254,7 +247,7 @@ class ParameterAdapter(Parameter): methods defer to the data of the wrapped object. """ - def __init__(self, name, obj, getter = None, setter = None, attr = None): + def __init__(self, name, obj, getter=None, setter=None, attr=None): """Wrap an object as a Parameter. name -- The name of this Parameter. @@ -314,6 +307,7 @@ def setValue(self, value): self.notify() return self + # End class ParameterAdapter # End of file diff --git a/src/diffpy/srfit/fitbase/parameterset.py b/src/diffpy/srfit/fitbase/parameterset.py index 0ae80a48..69efaa7c 100644 --- a/src/diffpy/srfit/fitbase/parameterset.py +++ b/src/diffpy/srfit/fitbase/parameterset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """ParameterSet class. ParameterSets organize Parameters, Constraints, Restraints and other @@ -94,7 +93,7 @@ def removeParameterSet(self, parset): self._removeObject(parset, self._parsets) return - def setConst(self, const = True): + def setConst(self, const=True): """Set every parameter within the set to a constant. const -- Flag indicating if the parameter is constant (default @@ -105,6 +104,7 @@ def setConst(self, const = True): return + # End class ParameterSet # End of file diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index 11c6ea57..df129e5b 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -12,24 +12,23 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The Profile class containing the physical and calculated data. -Profile holds the arrays representing an observed profile, a selected subset of -the observed profile and a calculated profile. Profiles are used by Calculators -to store a calculated signal, and by FitContributions to help calculate a -residual equation. +Profile holds the arrays representing an observed profile, a selected +subset of the observed profile and a calculated profile. Profiles are +used by Calculators to store a calculated signal, and by +FitContributions to help calculate a residual equation. """ __all__ = ["Parameter", "Profile"] -import six import numpy +import six -from diffpy.srfit.util.observable import Observable +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.fitbase.validatable import Validatable -from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.util.observable import Observable # This is the roundoff tolerance for selecting bounds on arrays. epsilon = 1e-8 @@ -87,19 +86,27 @@ def __init__(self): return # We want x, y, ycalc and dy to stay in-sync with xpar, ypar and dypar - x = property( lambda self : self.xpar.getValue(), - lambda self, val : self.xpar.setValue(val) ) - y = property( lambda self : self.ypar.getValue(), - lambda self, val : self.ypar.setValue(val) ) - dy = property( lambda self : self.dypar.getValue(), - lambda self, val : self.dypar.setValue(val) ) - ycalc = property( lambda self : self.ycpar.getValue(), - lambda self, val : self.ycpar.setValue(val) ) + x = property( + lambda self: self.xpar.getValue(), + lambda self, val: self.xpar.setValue(val), + ) + y = property( + lambda self: self.ypar.getValue(), + lambda self, val: self.ypar.setValue(val), + ) + dy = property( + lambda self: self.dypar.getValue(), + lambda self, val: self.dypar.setValue(val), + ) + ycalc = property( + lambda self: self.ycpar.getValue(), + lambda self, val: self.ycpar.setValue(val), + ) # We want xobs, yobs and dyobs to be read-only - xobs = property( lambda self: self._xobs ) - yobs = property( lambda self: self._yobs ) - dyobs = property( lambda self: self._dyobs ) + xobs = property(lambda self: self._xobs) + yobs = property(lambda self: self._yobs) + dyobs = property(lambda self: self._dyobs) def loadParsedData(self, parser): """Load parsed data from a ProfileParser. @@ -111,7 +118,7 @@ def loadParsedData(self, parser): self.setObservedProfile(x, y, dy) return - def setObservedProfile(self, xobs, yobs, dyobs = None): + def setObservedProfile(self, xobs, yobs, dyobs=None): """Set the observed profile. Arguments @@ -179,29 +186,37 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): """ if self.xobs is None: raise AttributeError("No observed profile") + # local helper function def _isobs(a): if not isinstance(a, six.string_types): return False - if a != 'obs': + if a != "obs": raise ValueError('Must be either float or "obs".') return True + # resolve new low and high bounds for x - lo = (self.x[0] if xmin is None else - self.xobs[0] if _isobs(xmin) else float(xmin)) + lo = ( + self.x[0] + if xmin is None + else self.xobs[0] if _isobs(xmin) else float(xmin) + ) lo = max(lo, self.xobs[0]) - hi = (self.x[-1] if xmax is None else - self.xobs[-1] if _isobs(xmax) else float(xmax)) + hi = ( + self.x[-1] + if xmax is None + else self.xobs[-1] if _isobs(xmax) else float(xmax) + ) hi = min(hi, self.xobs[-1]) # determine if we need to clip the original grid clip = True step = None ncur = len(self.x) - stepcur = (1 if ncur < 2 - else (self.x[-1] - self.x[0]) / (ncur - 1.0)) + stepcur = 1 if ncur < 2 else (self.x[-1] - self.x[0]) / (ncur - 1.0) nobs = len(self.xobs) - stepobs = (1 if nobs < 2 - else (self.xobs[-1] - self.xobs[0]) / (nobs - 1.0)) + stepobs = ( + 1 if nobs < 2 else (self.xobs[-1] - self.xobs[0]) / (nobs - 1.0) + ) if dx is None: # check if xobs overlaps with x i0 = numpy.fabs(self.xobs - self.x[0]).argmin() @@ -241,7 +256,6 @@ def _isobs(a): self.setCalculationPoints(x1) return - def setCalculationPoints(self, x): """Set the calculation points. @@ -254,8 +268,8 @@ def setCalculationPoints(self, x): """ x = numpy.asarray(x) if self.xobs is not None: - x = x[ x >= self.xobs[0] - epsilon ] - x = x[ x <= self.xobs[-1] + epsilon ] + x = x[x >= self.xobs[0] - epsilon] + x = x[x <= self.xobs[-1] + epsilon] self.x = x if self.yobs is not None: self.y = rebinArray(self.yobs, self.xobs, self.x) @@ -264,8 +278,8 @@ def setCalculationPoints(self, x): if (self.dyobs == 1).all(): self.dy = numpy.ones_like(self.x) else: - # FIXME - This does not follow error propogation rules and it - # introduces (more) correlation between the data points. + # FIXME - This does not follow error propogation rules and it + # introduces (more) correlation between the data points. self.dy = rebinArray(self.dyobs, self.xobs, self.x) return @@ -273,13 +287,14 @@ def setCalculationPoints(self, x): def loadtxt(self, *args, **kw): """Use numpy.loadtxt to load data. - Arguments are passed to numpy.loadtxt. unpack = True is enforced. - The first two arrays returned by numpy.loadtxt are assumed to be x and y. - If there is a third array, it is assumed to by dy. Any other arrays are - ignored. These are passed to setObservedProfile. + Arguments are passed to numpy.loadtxt. unpack = True is + enforced. The first two arrays returned by numpy.loadtxt are + assumed to be x and y. If there is a third array, it is assumed + to by dy. Any other arrays are ignored. These are passed to + setObservedProfile. - Raises ValueError if the call to numpy.loadtxt returns fewer than 2 - arrays. + Raises ValueError if the call to numpy.loadtxt returns fewer + than 2 arrays. Returns the x, y and dy arrays loaded from the file """ @@ -303,7 +318,6 @@ def loadtxt(self, *args, **kw): self.setObservedProfile(x, y, dy) return x, y, dy - def savetxt(self, fname, **kwargs): """Call `numpy.savetxt` with x, ycalc, y, dy. @@ -331,12 +345,11 @@ def savetxt(self, fname, **kwargs): raise SrFitError("ycalc is None") y = self.y dy = self.dy - kwargs.setdefault('header', 'x ycalc y dy') + kwargs.setdefault("header", "x ycalc y dy") data = numpy.transpose([x, ycalc, y, dy]) numpy.savetxt(fname, data, **kwargs) return - def _flush(self, other): """Invalidate cached state. @@ -349,13 +362,22 @@ def _flush(self, other): def _validate(self): """Validate my state. - This validates that x, y, dy, xobx, yobs and dyobs are not None. This - validates that x, y, and dy are the same length. + This validates that x, y, dy, xobx, yobs and dyobs are not None. + This validates that x, y, and dy are the same length. Raises SrFitError if validation fails. """ - datanotset = any(v is None for v in - [self.x, self.y, self.dy, self.xobs, self.yobs, self.dyobs]) + datanotset = any( + v is None + for v in [ + self.x, + self.y, + self.dy, + self.xobs, + self.yobs, + self.dyobs, + ] + ) if datanotset: raise SrFitError("Missing data") if len(self.x) != len(self.y) or len(self.x) != len(self.dy): @@ -365,6 +387,7 @@ def _validate(self): # End class Profile + def rebinArray(A, xold, xnew): """Rebin the an array by interpolating over the new x range. diff --git a/src/diffpy/srfit/fitbase/profilegenerator.py b/src/diffpy/srfit/fitbase/profilegenerator.py index 46d7fe35..c1f0d2e7 100644 --- a/src/diffpy/srfit/fitbase/profilegenerator.py +++ b/src/diffpy/srfit/fitbase/profilegenerator.py @@ -12,41 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The ProfileGenerator class for generating a profile. ProfileGenerators encapsulate the evaluation and required Parameters and -ParameterSets of a profile calculator. The ProfileGenerator class can be -associated with a FitContribution to help calculate a profile. - -To define a ProfileGenerator, one must implement the required Parameters and -ParameterSets as well as overload the __call__ method with the calculation. A -very simple example is -> class Gaussian(ProfileGenerator): -> -> def __init__(self): -> # Initialize and give this a name -> ProfileGenerator.__init__(self, "g") -> # Add amplitude, center and width parameters -> self.newParameter("amp", 0) -> self.newParameter("center", 0) -> self.newParameter("width", 0) -> -> def __call__(self, x): -> a = self.amp.getValue() -> x0 = self.center.getValue() -> w = self.width.getValue() -> return a * exp(-0.5*((x-x0)/w)**2) - -More examples can be found in the example directory of the documentation. +ParameterSets of a profile calculator. The ProfileGenerator class can +be associated with a FitContribution to help calculate a profile. + +To define a ProfileGenerator, one must implement the required Parameters +and ParameterSets as well as overload the __call__ method with the +calculation. A very simple example is > class +Gaussian(ProfileGenerator): > > def __init__(self): > # +Initialize and give this a name > ProfileGenerator.__init__(self, +"g") > # Add amplitude, center and width parameters > +self.newParameter("amp", 0) > self.newParameter("center", 0) > +self.newParameter("width", 0) > > def __call__(self, x): > a = +self.amp.getValue() > x0 = self.center.getValue() > w = +self.width.getValue() > return a * exp(-0.5*((x-x0)/w)**2) + +More examples can be found in the example directory of the +documentation. """ __all__ = ["ProfileGenerator"] from diffpy.srfit.equation.literals.operators import Operator -from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.parameterset import ParameterSet class ProfileGenerator(Operator, ParameterSet): @@ -95,7 +87,6 @@ class ProfileGenerator(Operator, ParameterSet): nin = 0 nout = 1 - def __init__(self, name): """Initialize the attributes.""" Operator.__init__(self) @@ -104,7 +95,6 @@ def __init__(self, name): self.meta = {} return - @property def symbol(self): return self.name @@ -116,7 +106,8 @@ def __call__(self, x): This method must be overloaded. - This method only takes the independent variables to calculate over. + This method only takes the independent variables to calculate + over. """ return x @@ -130,7 +121,6 @@ def operation(self): y = self.__call__(self.profile.x) return y - def setProfile(self, profile): """Assign the profile. @@ -145,15 +135,16 @@ def setProfile(self, profile): self._flush(other=(self,)) # Merge the profiles metadata with our own - self.meta.update( self.profile.meta ) + self.meta.update(self.profile.meta) self.processMetaData() return def processMetaData(self): """Process the metadata. - This can be used to configure a ProfileGenerator upon a change in the - metadata. This method gets called whenever the Profile is set. + This can be used to configure a ProfileGenerator upon a change + in the metadata. This method gets called whenever the Profile is + set. """ return @@ -161,8 +152,9 @@ def _validate(self): """Validate my state. This performs profile validations. This performs ParameterSet - validations. This does not validate the operation, since this could be - costly. The operation should be validated with a containing equation. + validations. This does not validate the operation, since this + could be costly. The operation should be validated with a + containing equation. Raises SrFitError if validation fails. """ diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index bd17d4de..36ae244f 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -12,12 +12,12 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains classes for parsing profiles from files. -ProfileParser is a base class for parsing data. It can interact with a Profile -object to automatically set the Profile's data and metadata. Each specific file -format must be encapsulated in a ProfileParser subclass. +ProfileParser is a base class for parsing data. It can interact with a +Profile object to automatically set the Profile's data and metadata. +Each specific file format must be encapsulated in a ProfileParser +subclass. See the class documentation for more information. """ @@ -102,7 +102,7 @@ def parseFile(self, filename): Raises IOError if the file cannot be read Raises ParseError if the file cannot be parsed """ - infile = open(filename, 'r') + infile = open(filename, "r") self._banks = [] self._meta = {} filestring = infile.read() @@ -151,7 +151,7 @@ def selectBank(self, index): self._x, self._y, self._dx, self._dy = self._banks[index] return - def getData(self, index = None): + def getData(self, index=None): """Get the data. This method should only be called after the data has been parsed. The @@ -174,4 +174,5 @@ def getMetaData(self): """Get the parsed metadata.""" return self._meta + # End of ProfileParser diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index b31df9b4..a39041f1 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -12,37 +12,35 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Base classes and tools for constructing a FitRecipe. RecipeContainer is the base class for organizing Parameters, and other RecipeContainers. RecipeOrganizer is an extended RecipeContainer that -incorporates equation building, constraints and Restraints. equationFromString -creates an Equation instance from a string. +incorporates equation building, constraints and Restraints. +equationFromString creates an Equation instance from a string. """ __all__ = ["RecipeContainer", "RecipeOrganizer", "equationFromString"] -from numpy import inf +import re from collections import OrderedDict from itertools import chain, groupby -import re import six +from numpy import inf +from diffpy.srfit.equation import Equation +from diffpy.srfit.equation.builder import EquationFactory +from diffpy.srfit.fitbase.configurable import Configurable from diffpy.srfit.fitbase.constraint import Constraint -from diffpy.srfit.fitbase.restraint import Restraint from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.configurable import Configurable +from diffpy.srfit.fitbase.restraint import Restraint from diffpy.srfit.fitbase.validatable import Validatable - -from diffpy.srfit.util.observable import Observable -from diffpy.srfit.equation import Equation -from diffpy.srfit.equation.builder import EquationFactory -from diffpy.srfit.util.nameutils import validateName from diffpy.srfit.interface import _recipeorganizer_interface from diffpy.srfit.util import _DASHEDLINE from diffpy.srfit.util import sortKeyForNumericString as numstr +from diffpy.srfit.util.nameutils import validateName +from diffpy.srfit.util.observable import Observable class RecipeContainer(Observable, Configurable, Validatable): @@ -109,7 +107,6 @@ def _iterManaged(self): """Get iterator over managed objects.""" return chain(*(d.values() for d in self.__managed)) - def iterPars(self, pattern="", recurse=True): """Iterate over the Parameters contained in this object. @@ -137,7 +134,6 @@ def iterPars(self, pattern="", recurse=True): yield par return - def __iter__(self): """Iterate over top-level parameters.""" return iter(self._parameters.values()) @@ -158,12 +154,13 @@ def __getattr__(self, name): raise AttributeError(name) return arg - # Ensure there is no __dir__ override in the base class. - assert (getattr(Observable, '__dir__', None) is - getattr(Configurable, '__dir__', None) is - getattr(Validatable, '__dir__', None) is - getattr(object, '__dir__', None)) + assert ( + getattr(Observable, "__dir__", None) + is getattr(Configurable, "__dir__", None) + is getattr(Validatable, "__dir__", None) + is getattr(object, "__dir__", None) + ) def __dir__(self): "Return sorted list of attributes for this object." @@ -175,7 +172,6 @@ def __dir__(self): rv = sorted(rv) return rv - # Needed by __setattr__ _parameters = OrderedDict() __managed = [] @@ -192,30 +188,30 @@ def __setattr__(self, name, value): m = self.get(name) if m is not None: - raise AttributeError("Cannot set '%s'"%name) + raise AttributeError("Cannot set '%s'" % name) super(RecipeContainer, self).__setattr__(name, value) return - def __delattr__(self, name): """Delete parameters with del. - This does not allow deletion of non-parameters, as this may require - configuration changes that are not yet handled in a general way. + This does not allow deletion of non-parameters, as this may + require configuration changes that are not yet handled in a + general way. """ if name in self._parameters: - self._removeParameter( self._parameters[name] ) + self._removeParameter(self._parameters[name]) return m = self.get(name) if m is not None: - raise AttributeError("Cannot delete '%s'"%name) + raise AttributeError("Cannot delete '%s'" % name) super(RecipeContainer, self).__delattr__(name) return - def get(self, name, default = None): + def get(self, name, default=None): """Get a managed object.""" for d in self.__managed: arg = d.get(name) @@ -232,7 +228,7 @@ def getValues(self): """Get the values of managed parameters.""" return [p.value for p in self._parameters.values()] - def _addObject(self, obj, d, check = True): + def _addObject(self, obj, d, check=True): """Add an object to a managed dictionary. obj -- The object to be stored. @@ -253,14 +249,18 @@ def _addObject(self, obj, d, check = True): # Check for extant object in d with same name oldobj = d.get(obj.name) if check and oldobj is not None: - message = "%s with name '%s' already exists"%\ - (obj.__class__.__name__, obj.name) + message = "%s with name '%s' already exists" % ( + obj.__class__.__name__, + obj.name, + ) raise ValueError(message) # Check for object with same name in other dictionary. if oldobj is None and self.get(obj.name) is not None: - message = "Non-%s with name '%s' already exists"%\ - (obj.__class__.__name__, obj.name) + message = "Non-%s with name '%s' already exists" % ( + obj.__class__.__name__, + obj.name, + ) raise ValueError(message) # Detach the old object, if there is one @@ -326,8 +326,8 @@ def _locateManagedObject(self, obj): def _flush(self, other): """Invalidate cached state. - This will force any observer to invalidate its state. By default this - does nothing. + This will force any observer to invalidate its state. By default + this does nothing. """ self.notify(other) return @@ -335,7 +335,8 @@ def _flush(self, other): def _validate(self): """Validate my state. - This validates that contained Parameters and managed objects are valid. + This validates that contained Parameters and managed objects are + valid. Raises AttributeError if validation fails. """ @@ -346,6 +347,7 @@ def _validate(self): # End class RecipeContainer + class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer): """Extended base class for organizing pieces of a FitRecipe. @@ -391,8 +393,8 @@ def __init__(self, name): def _newParameter(self, name, value, check=True): """Add a new Parameter to the container. - This creates a new Parameter and adds it to the container using the - _addParameter method. + This creates a new Parameter and adds it to the container using + the _addParameter method. Returns the Parameter. """ @@ -424,11 +426,11 @@ def _addParameter(self, par, check=True): def _removeParameter(self, par): """Remove a parameter. - This de-registers the Parameter with the _eqfactory. The Parameter will - remain part of built equations. + This de-registers the Parameter with the _eqfactory. The + Parameter will remain part of built equations. - Note that constraints and restraints involving the Parameter are not - modified. + Note that constraints and restraints involving the Parameter are + not modified. Raises ValueError if par is not part of the RecipeOrganizer. """ @@ -436,7 +438,7 @@ def _removeParameter(self, par): self._eqfactory.deRegisterBuilder(par.name) return - def registerCalculator(self, f, argnames = None): + def registerCalculator(self, f, argnames=None): """Register a Calculator so it can be used within equation strings. A Calculator is an elaborate function that can organize Parameters. @@ -456,7 +458,7 @@ def registerCalculator(self, f, argnames = None): if argnames is None: fncode = f.__call__.__func__.__code__ argnames = list(fncode.co_varnames) - argnames = argnames[1:fncode.co_argcount] + argnames = argnames[1 : fncode.co_argcount] for pname in argnames: if pname not in self._eqfactory.builders: @@ -469,7 +471,7 @@ def registerCalculator(self, f, argnames = None): eq = self._eqfactory.makeEquation(f.name) return eq - def registerFunction(self, f, name = None, argnames = None): + def registerFunction(self, f, name=None, argnames=None): """Register a function so it can be used within equation strings. This creates a function with this class that can be used within string @@ -523,12 +525,12 @@ def registerFunction(self, f, name = None, argnames = None): fncode = f.__code__ # check class method elif inspect.ismethod(f): - fncode = f.__func__.__code__ - offset = 1 + fncode = f.__func__.__code__ + offset = 1 # check functor - elif hasattr(f, "__call__") and hasattr(f.__call__, '__func__'): - fncode = f.__call__.__func__.__code__ - offset = 1 + elif hasattr(f, "__call__") and hasattr(f.__call__, "__func__"): + fncode = f.__call__.__func__.__code__ + offset = 1 else: m = "Cannot extract name or argnames" raise ValueError(m) @@ -536,14 +538,14 @@ def registerFunction(self, f, name = None, argnames = None): # Extract the name if name is None: name = fncode.co_name - if name == '': + if name == "": m = "You must supply a name name for a lambda function" raise ValueError(m) # Extract the arguments if argnames is None: argnames = list(fncode.co_varnames) - argnames = argnames[offset:fncode.co_argcount] + argnames = argnames[offset : fncode.co_argcount] #### End introspection code @@ -554,6 +556,7 @@ def registerFunction(self, f, name = None, argnames = None): # Initialize and register from diffpy.srfit.fitbase.calculator import Calculator + if isinstance(f, Calculator): for pname in argnames: par = self.get(pname) @@ -567,7 +570,7 @@ def registerFunction(self, f, name = None, argnames = None): return eq - def registerStringFunction(self, fstr, name, ns = {}): + def registerStringFunction(self, fstr, name, ns={}): """Register a string function. This creates a function with this class that can be used within string @@ -589,8 +592,7 @@ def registerStringFunction(self, fstr, name, ns = {}): """ # Build the equation instance. - eq = equationFromString(fstr, self._eqfactory, ns = ns, buildargs = - True) + eq = equationFromString(fstr, self._eqfactory, ns=ns, buildargs=True) eq.name = name # Register any new Parameters. @@ -601,8 +603,7 @@ def registerStringFunction(self, fstr, name, ns = {}): argnames = eq.argdict.keys() return self.registerFunction(eq, name, argnames) - - def evaluateEquation(self, eqstr, ns = {}): + def evaluateEquation(self, eqstr, ns={}): """Evaluate a string equation. eqstr -- A string equation to evaluate. The equation is evaluated at @@ -620,8 +621,7 @@ def evaluateEquation(self, eqstr, ns = {}): self._eqfactory.wipeout(eq) return rv - - def constrain(self, par, con, ns = {}): + def constrain(self, par, con, ns={}): """Constrain a parameter to an equation. Note that only one constraint can exist on a Parameter at a time. @@ -651,16 +651,16 @@ def constrain(self, par, con, ns = {}): raise ValueError("The parameter cannot be found") if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) if isinstance(con, six.string_types): eqstr = con eq = equationFromString(con, self._eqfactory, ns) else: - eq = Equation(root = con) + eq = Equation(root=con) eqstr = con.name - eq.name = "_constraint_%s"%par.name + eq.name = "_constraint_%s" % par.name # Make and store the constraint con = Constraint() @@ -683,7 +683,7 @@ def isConstrained(self, par): name = par par = self.get(name) - return (par in self._constraints) + return par in self._constraints def unconstrain(self, *pars): """Unconstrain a Parameter. @@ -719,7 +719,7 @@ def unconstrain(self, *pars): return - def getConstrainedPars(self, recurse = False): + def getConstrainedPars(self, recurse=False): """Get a list of constrained managed Parameters in this object. recurse -- Recurse into managed objects and retrive their constrained @@ -728,7 +728,7 @@ def getConstrainedPars(self, recurse = False): const = self._getConstraints(recurse) return const.keys() - def clearConstraints(self, recurse = False): + def clearConstraints(self, recurse=False): """Clear all constraints managed by this organizer. recurse -- Recurse into managed objects and clear all constraints @@ -741,13 +741,12 @@ def clearConstraints(self, recurse = False): self.unconstrain(*self._constraints) if recurse: - f = lambda m : hasattr(m, "clearConstraints") + f = lambda m: hasattr(m, "clearConstraints") for m in filter(f, self._iterManaged()): m.clearConstraints(recurse) return - def restrain(self, res, lb = -inf, ub = inf, sig = 1, scaled = False, ns = - {}): + def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}): """Restrain an expression to specified bounds. res -- An equation string or Parameter to restrain. @@ -778,7 +777,7 @@ def restrain(self, res, lb = -inf, ub = inf, sig = 1, scaled = False, ns = eqstr = res eq = equationFromString(res, self._eqfactory, ns) else: - eq = Equation(root = res) + eq = Equation(root=res) eqstr = res.name # Make and store the restraint @@ -816,7 +815,7 @@ def unrestrain(self, *ress): return - def clearRestraints(self, recurse = False): + def clearRestraints(self, recurse=False): """Clear all restraints. recurse -- Recurse into managed objects and clear all restraints @@ -825,41 +824,41 @@ def clearRestraints(self, recurse = False): self.unrestrain(*self._restraints) if recurse: - f = lambda m : hasattr(m, "clearRestraints") + f = lambda m: hasattr(m, "clearRestraints") for m in filter(f, self._iterManaged()): m.clearRestraints(recurse) return - def _getConstraints(self, recurse = True): + def _getConstraints(self, recurse=True): """Get the constrained Parameters for this and managed sub-objects.""" constraints = {} if recurse: - f = lambda m : hasattr(m, "_getConstraints") + f = lambda m: hasattr(m, "_getConstraints") for m in filter(f, self._iterManaged()): - constraints.update( m._getConstraints(recurse) ) + constraints.update(m._getConstraints(recurse)) - constraints.update( self._constraints) + constraints.update(self._constraints) return constraints - def _getRestraints(self, recurse = True): + def _getRestraints(self, recurse=True): """Get the Restraints for this and embedded ParameterSets. This returns a set of Restraint objects. """ restraints = set(self._restraints) if recurse: - f = lambda m : hasattr(m, "_getRestraints") + f = lambda m: hasattr(m, "_getRestraints") for m in filter(f, self._iterManaged()): - restraints.update( m._getRestraints(recurse) ) + restraints.update(m._getRestraints(recurse)) return restraints def _validate(self): """Validate my state. - This performs RecipeContainer validations. This validates contained - Restraints and Constraints. + This performs RecipeContainer validations. This validates + contained Restraints and Constraints. Raises AttributeError if validation fails. """ @@ -889,9 +888,11 @@ def _formatManaged(self, prefix=""): if self._parameters: w0 = max(len(n) for n in self._parameters) w1 = ((w0 + len(prefix) + 1) // 4 + 1) * 4 - fmt = formatstr.replace('W', str(w1)) - lines.extend(fmt.format(prefix + n, p.value) - for n, p in self._parameters.items()) + fmt = formatstr.replace("W", str(w1)) + lines.extend( + fmt.format(prefix + n, p.value) + for n, p in self._parameters.items() + ) # Recurse into managed objects. for obj in self._iterManaged(): if hasattr(obj, "_formatManaged"): @@ -901,7 +902,6 @@ def _formatManaged(self, prefix=""): lines.extend(tlines) return lines - def _formatConstraints(self): """Format constraints for showing. @@ -927,7 +927,6 @@ def _formatConstraints(self): clines.sort(key=numstr) return clines - def _formatRestraints(self): """Format restraints for showing. @@ -943,13 +942,17 @@ def _formatRestraints(self): rset = self._getRestraints() rlines = [] for res in rset: - line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s"%\ - (res.eqstr, res.lb, res.ub, res.sig, res.scaled) + line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s" % ( + res.eqstr, + res.lb, + res.ub, + res.sig, + res.scaled, + ) rlines.append(line) rlines.sort(key=numstr) return rlines - def show(self, pattern="", textwidth=78): """Show the configuration hierarchy on the screen. @@ -965,8 +968,9 @@ def show(self, pattern="", textwidth=78): the screen width. Do not trim when negative or 0. """ regexp = re.compile(pattern) - pmatch = lambda s : (len(s.split(None, 1)) < 2 or - regexp.search(s.split(None, 1)[0])) + pmatch = lambda s: ( + len(s.split(None, 1)) < 2 or regexp.search(s.split(None, 1)[0]) + ) # Show sub objects and their parameters lines = [] tlines = self._formatManaged() @@ -1007,10 +1011,13 @@ def show(self, pattern="", textwidth=78): print("\n".join(s[:tw] for s in lines)) return + # End RecipeOrganizer -def equationFromString(eqstr, factory, ns = {}, buildargs = False, - argclass = Parameter, argkw = {}): + +def equationFromString( + eqstr, factory, ns={}, buildargs=False, argclass=Parameter, argkw={} +): """Make an equation from a string. eqstr -- A string representation of the equation. The equation must diff --git a/src/diffpy/srfit/fitbase/restraint.py b/src/diffpy/srfit/fitbase/restraint.py index 76e0e7ff..a80486d2 100644 --- a/src/diffpy/srfit/fitbase/restraint.py +++ b/src/diffpy/srfit/fitbase/restraint.py @@ -12,21 +12,20 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Restraints class. Restraints are used by RecipeOrganizers to organize restraint equations. -Restraints store an Equation, bounds on its value, and the form of the penalty -function for breaking a restraint. This penalty is added to the residual -equation calculated by a FitRecipe. +Restraints store an Equation, bounds on its value, and the form of the +penalty function for breaking a restraint. This penalty is added to the +residual equation calculated by a FitRecipe. """ __all__ = ["Restraint"] from numpy import inf -from diffpy.srfit.fitbase.validatable import Validatable from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.validatable import Validatable class Restraint(Validatable): @@ -48,7 +47,7 @@ class Restraint(Validatable): average chi^2 if scaled is True. """ - def __init__(self, eq, lb = -inf, ub = inf, sig = 1, scaled = False): + def __init__(self, eq, lb=-inf, ub=inf, sig=1, scaled=False): """Restrain an equation to specified bounds. eq -- An equation whose evaluation is compared against the @@ -69,7 +68,7 @@ def __init__(self, eq, lb = -inf, ub = inf, sig = 1, scaled = False): self.scaled = bool(scaled) return - def penalty(self, w = 1.0): + def penalty(self, w=1.0): """Calculate the penalty of the restraint. w -- The point-average chi^2 which is optionally used to scale the @@ -78,7 +77,7 @@ def penalty(self, w = 1.0): Returns the penalty as a float """ val = self.eq() - penalty = (max(0, self.lb - val, val - self.ub) / self.sig)**2 + penalty = (max(0, self.lb - val, val - self.ub) / self.sig) ** 2 if self.scaled: penalty *= w @@ -95,6 +94,7 @@ def _validate(self): if self.eq is None: raise SrFitError("eq is None") from diffpy.srfit.equation.visitors import validate + try: validate(self.eq) except ValueError as e: @@ -111,6 +111,7 @@ def _validate(self): return + # End class Restraint # End of file diff --git a/src/diffpy/srfit/fitbase/simplerecipe.py b/src/diffpy/srfit/fitbase/simplerecipe.py index 27dec0c6..2f454fd2 100644 --- a/src/diffpy/srfit/fitbase/simplerecipe.py +++ b/src/diffpy/srfit/fitbase/simplerecipe.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Simple FitRecipe class that includes a FitContribution and Profile.""" -from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitcontribution import FitContribution +from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitresults import FitResults from diffpy.srfit.fitbase.profile import Profile @@ -66,7 +65,7 @@ class SimpleRecipe(FitRecipe): values -- Variable values (read only). See getValues. """ - def __init__(self, name = "fit", conclass = FitContribution): + def __init__(self, name="fit", conclass=FitContribution): """Initialization.""" FitRecipe.__init__(self, name) self.fithooks[0].verbose = 3 @@ -74,11 +73,14 @@ def __init__(self, name = "fit", conclass = FitContribution): self.profile = Profile() contribution.setProfile(self.profile) self.addContribution(contribution) - self.results = FitResults(self, update = False) + self.results = FitResults(self, update=False) # Adopt all the FitContribution methods - public = [aname for aname in dir(contribution) if aname not in - dir(self) and not aname.startswith("_")] + public = [ + aname + for aname in dir(contribution) + if aname not in dir(self) and not aname.startswith("_") + ] for mname in public: method = getattr(contribution, mname) setattr(self, mname, method) @@ -92,7 +94,7 @@ def loadParsedData(self, parser): """ return self.profile.loadParsedData(parser) - def setObservedProfile(self, xobs, yobs, dyobs = None): + def setObservedProfile(self, xobs, yobs, dyobs=None): """Set the observed profile. Arguments @@ -107,7 +109,6 @@ def setObservedProfile(self, xobs, yobs, dyobs = None): """ return self.profile.setObservedProfile(xobs, yobs, dyobs) - def setCalculationRange(self, xmin=None, xmax=None, dx=None): """Set epsilon-inclusive calculation range. @@ -142,7 +143,6 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): """ return self.profile.setCalculationRange(xmin, xmax, dx) - def setCalculationPoints(self, x): """Set the calculation points. @@ -158,20 +158,21 @@ def setCalculationPoints(self, x): def loadtxt(self, *args, **kw): """Use numpy.loadtxt to load data. - Arguments are passed to numpy.loadtxt. unpack = True is enforced. - The first two arrays returned by numpy.loadtxt are assumed to be x and y. - If there is a third array, it is assumed to by dy. Any other arrays are - ignored. These are passed to setObservedProfile. + Arguments are passed to numpy.loadtxt. unpack = True is + enforced. The first two arrays returned by numpy.loadtxt are + assumed to be x and y. If there is a third array, it is assumed + to by dy. Any other arrays are ignored. These are passed to + setObservedProfile. - Raises ValueError if the call to numpy.loadtxt returns fewer than 2 - arrays. + Raises ValueError if the call to numpy.loadtxt returns fewer + than 2 arrays. Returns the x, y and dy arrays loaded from the file """ return self.profile.loadtxt(*args, **kw) # FitContribution - def setEquation(self, eqstr, ns = {}): + def setEquation(self, eqstr, ns={}): """Set the profile equation for the FitContribution. This sets the equation that will be used when generating the residual. @@ -187,11 +188,12 @@ def setEquation(self, eqstr, ns = {}): Raises ValueError if ns uses a name that is already used for a variable. """ - self.contribution.setEquation(eqstr, ns = {}) + self.contribution.setEquation(eqstr, ns={}) # Extract variables for par in self.contribution: # Skip Profile Parameters - if par.name in ("x", "y", "dy"): continue + if par.name in ("x", "y", "dy"): + continue if par.value is None: par.value = 0 if par.name not in self._parameters: @@ -204,7 +206,7 @@ def __call__(self): # FitResults methods - def printResults(self, header = "", footer = ""): + def printResults(self, header="", footer=""): """Format and print the results. header -- A header to add to the output (default "") @@ -213,7 +215,7 @@ def printResults(self, header = "", footer = ""): self.results.printResults(header, footer, True) return - def saveResults(self, filename, header = "", footer = ""): + def saveResults(self, filename, header="", footer=""): """Format and save the results. filename - Name of the save file. @@ -222,6 +224,7 @@ def saveResults(self, filename, header = "", footer = ""): """ self.results.saveResults(filename, header, footer, True) + # End class SimpleRecipe # End of file diff --git a/src/diffpy/srfit/fitbase/validatable.py b/src/diffpy/srfit/fitbase/validatable.py index e7216b1d..a3aeac66 100644 --- a/src/diffpy/srfit/fitbase/validatable.py +++ b/src/diffpy/srfit/fitbase/validatable.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Validatable class. -A Validatable has state that must be validated before a FitRecipe can first -calculate the residual. +A Validatable has state that must be validated before a FitRecipe can +first calculate the residual. """ __all__ = ["Validatable"] @@ -31,12 +30,13 @@ class Validatable(object): def _validateOthers(self, iterable): """Method to validate configuration of Validatables in iterable. - This is provided as a convenience for derived classes. No need to - overload this. Call this method from overloaded _validate with an - iterable of other Validatables. + This is provided as a convenience for derived classes. No need + to overload this. Call this method from overloaded _validate + with an iterable of other Validatables. """ for obj in iterable: - if obj is self: continue + if obj is self: + continue if isinstance(obj, Validatable): obj._validate() @@ -53,6 +53,7 @@ def _validate(self): # Then validate others. return + # End class Validatable # End of file diff --git a/src/diffpy/srfit/interface/__init__.py b/src/diffpy/srfit/interface/__init__.py index 8f6bd3a7..a611f118 100644 --- a/src/diffpy/srfit/interface/__init__.py +++ b/src/diffpy/srfit/interface/__init__.py @@ -12,22 +12,24 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Usability interface for SrFit. -The classes and functions in this package are designed to unobtrusively mix -with base SrFit objects and provide them with interface enhancements for -scripting. +The classes and functions in this package are designed to unobtrusively +mix with base SrFit objects and provide them with interface enhancements +for scripting. """ from diffpy.srfit.interface.interface import ParameterInterface + _parameter_interface = ParameterInterface from diffpy.srfit.interface.interface import RecipeOrganizerInterface + _recipeorganizer_interface = RecipeOrganizerInterface from diffpy.srfit.interface.interface import FitRecipeInterface + _fitrecipe_interface = FitRecipeInterface # End of file diff --git a/src/diffpy/srfit/interface/interface.py b/src/diffpy/srfit/interface/interface.py index b4f912e7..c9f67c57 100644 --- a/src/diffpy/srfit/interface/interface.py +++ b/src/diffpy/srfit/interface/interface.py @@ -12,17 +12,20 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Interface enhancements for Parameter-type classes. -Most interface additions can thought of by considering the classes in SrFit to -be sets of parameters. "+=" adds a new parameter, when that makes sense. "|=" -is the 'union' of these sets, and in general is used to combine different -objects. See individual interface classes for specifics. +Most interface additions can thought of by considering the classes in +SrFit to be sets of parameters. "+=" adds a new parameter, when that +makes sense. "|=" is the 'union' of these sets, and in general is used +to combine different objects. See individual interface classes for +specifics. """ -__all__ = ["ParameterInterface", "FitRecipeInterface", - "RecipeOrganizerInterface"] +__all__ = [ + "ParameterInterface", + "FitRecipeInterface", + "RecipeOrganizerInterface", +] import six @@ -45,10 +48,12 @@ def __lshift__(self, v): self.value = v return self + # End class ParameterInterface # ---------------------------------------------------------------------------- + class RecipeOrganizerInterface(object): """Mix-in class for enhancing the RecipeOrganizer interface.""" @@ -79,6 +84,7 @@ def __iadd__(self, args): This accepts arguments for a single function call. """ + # Want to detect _addParameter or _newParameter def f(*args): if isinstance(args[0], six.string_types): @@ -90,10 +96,12 @@ def f(*args): _applyargs(args, f) return self + # End class RecipeOrganizerInterface # ---------------------------------------------------------------------------- + class FitRecipeInterface(object): """Mix-in class for enhancing the FitRecipe interface.""" @@ -112,9 +120,10 @@ def __iadd__(self, args): Think of "+" as addition of a variable. - This accepts a single argument or an iterable of single arguments or - argument tuples. + This accepts a single argument or an iterable of single + arguments or argument tuples. """ + # Want to detect addVar or newVar def f(*args): if isinstance(args[0], six.string_types): @@ -126,10 +135,12 @@ def f(*args): _applymanyargs(args, f) return self + # End class FitRecipeInterface # Local helper functions ----------------------------------------------------- + def _applymanyargs(args, f): """Apply arguments to a function. @@ -138,18 +149,19 @@ def _applymanyargs(args, f): (arg1, arg2, ...) ((arg1a, arg1b, ...), ...) """ - if not hasattr(args, '__iter__'): + if not hasattr(args, "__iter__"): f(args) return for arg in args: - if hasattr(arg, '__iter__'): + if hasattr(arg, "__iter__"): f(*arg) else: f(arg) return + def _applyargs(args, f): """Apply arguments to a function. @@ -158,10 +170,11 @@ def _applyargs(args, f): (arg1, arg2, ...) ((arg1a, arg1b, ...), ...) """ - if not hasattr(args, '__iter__'): + if not hasattr(args, "__iter__"): f(args) else: f(*args) return + # End of file diff --git a/src/diffpy/srfit/pdf/__init__.py b/src/diffpy/srfit/pdf/__init__.py index eceba859..cde4d163 100644 --- a/src/diffpy/srfit/pdf/__init__.py +++ b/src/diffpy/srfit/pdf/__init__.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF calculation tools.""" __all__ = ["PDFGenerator", "DebyePDFGenerator", "PDFContribution", "PDFParser"] -from diffpy.srfit.pdf.pdfgenerator import PDFGenerator from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator from diffpy.srfit.pdf.pdfcontribution import PDFContribution +from diffpy.srfit.pdf.pdfgenerator import PDFGenerator from diffpy.srfit.pdf.pdfparser import PDFParser # End of file diff --git a/src/diffpy/srfit/pdf/basepdfgenerator.py b/src/diffpy/srfit/pdf/basepdfgenerator.py index 16e009e8..8eeb58da 100644 --- a/src/diffpy/srfit/pdf/basepdfgenerator.py +++ b/src/diffpy/srfit/pdf/basepdfgenerator.py @@ -12,26 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF profile generator base class. -The BasePDFGenerator class interfaces with SrReal PDF calculators and is used -as a base for the PDFGenerator and DebyePDFGenerator classes. +The BasePDFGenerator class interfaces with SrReal PDF calculators and is +used as a base for the PDFGenerator and DebyePDFGenerator classes. """ __all__ = ["BasePDFGenerator"] import numpy +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase import ProfileGenerator from diffpy.srfit.fitbase.parameter import ParameterAdapter from diffpy.srfit.structure import struToParameterSet -from diffpy.srfit.exceptions import SrFitError - # FIXME - Parameter creation will have to be smarter once deeper calculator # configuration is enabled. + class BasePDFGenerator(ProfileGenerator): """Base class for calculating PDF profiles using SrReal. @@ -73,7 +72,7 @@ class BasePDFGenerator(ProfileGenerator): qdamp -- See Managed Parameters. """ - def __init__(self, name = "pdf"): + def __init__(self, name="pdf"): """Initialize the generator.""" ProfileGenerator.__init__(self, name) @@ -87,23 +86,21 @@ def __init__(self, name = "pdf"): return - _parnames = ['delta1', 'delta2', 'qbroad', 'scale', 'qdamp'] + _parnames = ["delta1", "delta2", "qbroad", "scale", "qdamp"] def _setCalculator(self, calc): """Set the SrReal calulator instance. - Setting the calculator creates Parameters from the variable attributes - of the SrReal calculator. + Setting the calculator creates Parameters from the variable + attributes of the SrReal calculator. """ self._calc = calc for pname in self.__class__._parnames: - self.addParameter( - ParameterAdapter(pname, self._calc, attr = pname) - ) + self.addParameter(ParameterAdapter(pname, self._calc, attr=pname)) self.processMetaData() return - def parallel(self, ncpu, mapfunc = None): + def parallel(self, ncpu, mapfunc=None): """Run calculation in parallel. ncpu -- Number of parallel processes. Revert to serial mode when 1. @@ -113,8 +110,9 @@ def parallel(self, ncpu, mapfunc = None): No return value. """ from diffpy.srreal.parallel import createParallelCalculator + calc_serial = self._calc - if hasattr(calc_serial, 'pqobj'): + if hasattr(calc_serial, "pqobj"): calc_serial = calc_serial.pqobj # revert to serial calculator for ncpu <= 1 if ncpu <= 1: @@ -125,6 +123,7 @@ def parallel(self, ncpu, mapfunc = None): # ncpu = min(ncpu, multiprocessing.cpu_count()) if mapfunc is None: import multiprocessing + self._pool = multiprocessing.Pool(ncpu) mapfunc = self._pool.imap_unordered @@ -155,7 +154,7 @@ def processMetaData(self): return - def setScatteringType(self, stype = "X"): + def setScatteringType(self, stype="X"): """Set the scattering type. stype -- "X" for x-ray, "N" for neutron, "E" for electrons, @@ -196,7 +195,7 @@ def getQmin(self): """Get the qmin value.""" return self._calc.qmin - def setStructure(self, stru, name = "phase", periodic = True): + def setStructure(self, stru, name="phase", periodic=True): """Set the structure that will be used to calculate the PDF. This creates a DiffpyStructureParSet, ObjCrystCrystalParSet or @@ -221,8 +220,7 @@ def setStructure(self, stru, name = "phase", periodic = True): self.setPhase(parset, periodic) return - - def setPhase(self, parset, periodic = True): + def setPhase(self, parset, periodic=True): """Set the phase that will be used to calculate the PDF. Set the phase directly with a DiffpyStructureParSet, @@ -256,7 +254,7 @@ def _prepare(self, r): ndiv = max(len(r) - 1, 1) self._calc.rstep = (hi - lo) / ndiv self._calc.rmin = lo - self._calc.rmax = hi + 0.5*self._calc.rstep + self._calc.rmax = hi + 0.5 * self._calc.rstep return def _validate(self): @@ -277,11 +275,11 @@ def _validate(self): def __call__(self, r): """Calculate the PDF. - This ProfileGenerator will be used in a fit equation that will be - optimized to fit some data. By the time this function is evaluated, - the crystal has been updated by the optimizer via the ObjCrystParSet - created in setCrystal. Thus, we need only call pdf with the internal - structure object. + This ProfileGenerator will be used in a fit equation that will + be optimized to fit some data. By the time this function is + evaluated, the crystal has been updated by the optimizer via the + ObjCrystParSet created in setCrystal. Thus, we need only call + pdf with the internal structure object. """ if not numpy.array_equal(r, self._lastr): self._prepare(r) @@ -294,4 +292,5 @@ def __call__(self, r): y = numpy.interp(r, rcalc, y) return y + # End class BasePDFGenerator diff --git a/src/diffpy/srfit/pdf/characteristicfunctions.py b/src/diffpy/srfit/pdf/characteristicfunctions.py index 24d8a824..febdf269 100644 --- a/src/diffpy/srfit/pdf/characteristicfunctions.py +++ b/src/diffpy/srfit/pdf/characteristicfunctions.py @@ -12,26 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Form factors (characteristic functions) used in PDF nanoshape fitting. -These are used to calculate the attenuation of the PDF due to a finite size. -For a crystal-like nanoparticle, one can calculate the PDF via Gnano(r) = f(r) -Gcryst(r), where f(r) is the nanoparticle characteristic function and Gcryst(f) -is the crystal PDF. +These are used to calculate the attenuation of the PDF due to a finite +size. For a crystal-like nanoparticle, one can calculate the PDF via +Gnano(r) = f(r) Gcryst(r), where f(r) is the nanoparticle characteristic +function and Gcryst(f) is the crystal PDF. -These functions are meant to be imported and added to a FitContribution using -the 'registerFunction' method of that class. +These functions are meant to be imported and added to a FitContribution +using the 'registerFunction' method of that class. """ -__all__ = ["sphericalCF", "spheroidalCF", "spheroidalCF2", - "lognormalSphericalCF", "sheetCF", "shellCF", "shellCF2", "SASCF"] +__all__ = [ + "sphericalCF", + "spheroidalCF", + "spheroidalCF2", + "lognormalSphericalCF", + "sheetCF", + "shellCF", + "shellCF2", + "SASCF", +] import numpy -from numpy import pi, sqrt, log, exp, log2, ceil, sign from numpy import arctan as atan from numpy import arctanh as atanh -from numpy.fft import ifft, fftfreq +from numpy import ceil, exp, log, log2, pi, sign, sqrt +from numpy.fft import fftfreq, ifft from scipy.special import erf from diffpy.srfit.fitbase.calculator import Calculator @@ -49,11 +56,12 @@ def sphericalCF(r, psize): f = numpy.zeros(numpy.shape(r), dtype=float) if psize > 0: x = numpy.array(r, dtype=float) / psize - inside = (x < 1.0) + inside = x < 1.0 xin = x[inside] - f[inside] = 1.0 - 1.5*xin + 0.5*xin*xin*xin + f[inside] = 1.0 - 1.5 * xin + 0.5 * xin * xin * xin return f + def spheroidalCF(r, erad, prad): """Spheroidal characteristic function specified using radii. @@ -70,6 +78,7 @@ def spheroidalCF(r, erad, prad): pelpt = 1.0 * prad / erad return spheroidalCF2(r, psize, pelpt) + def spheroidalCF2(r, psize, axrat): """Spheroidal nanoparticle characteristic function. @@ -89,8 +98,8 @@ def spheroidalCF2(r, psize, axrat): # to simplify the equations v = pelpt d = 1.0 * psize - d2 = d*d - v2 = v*v + d2 = d * d + v2 = v * v if v == 1: return sphericalCF(r, psize) @@ -98,40 +107,80 @@ def spheroidalCF2(r, psize, axrat): rx = r if v < 1: - r = rx[rx <= v*psize] - r2 = r*r - f1 = 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) \ - - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(1-v2)*atanh(sqrt(1-v2)) - - r = rx[numpy.logical_and(rx > v*psize, rx <= psize)] - r2 = r*r - f2 = (3*d/(8*r)*(1+r2/(2*d2))*sqrt(1-r2/d2) \ - - 3*r/(4*d)*(1-r2/(4*d2))*atanh(sqrt(1-r2/d2)) \ - ) * v/sqrt(1-v2) + r = rx[rx <= v * psize] + r2 = r * r + f1 = ( + 1 + - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * v + / sqrt(1 - v2) + * atanh(sqrt(1 - v2)) + ) + + r = rx[numpy.logical_and(rx > v * psize, rx <= psize)] + r2 = r * r + f2 = ( + ( + 3 * d / (8 * r) * (1 + r2 / (2 * d2)) * sqrt(1 - r2 / d2) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * atanh(sqrt(1 - r2 / d2)) + ) + * v + / sqrt(1 - v2) + ) r = rx[rx > psize] f3 = numpy.zeros_like(r) - f = numpy.concatenate((f1,f2,f3)) + f = numpy.concatenate((f1, f2, f3)) elif v > 1: r = rx[rx <= psize] - r2 = r*r - f1 = 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) \ - - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(v2-1)*atan(sqrt(v2-1)) - - r = rx[numpy.logical_and(rx > psize, rx <= v*psize)] - r2 = r*r - f2 = 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) \ - - 3.0/8*(1+r2/(2*d2))*sqrt(1-d2/r2)*v/sqrt(v2-1) \ - - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(v2-1) \ - * (atan(sqrt(v2-1)) - atan(sqrt(r2/d2-1))) - - r = rx[rx > v*psize] + r2 = r * r + f1 = ( + 1 + - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * v + / sqrt(v2 - 1) + * atan(sqrt(v2 - 1)) + ) + + r = rx[numpy.logical_and(rx > psize, rx <= v * psize)] + r2 = r * r + f2 = ( + 1 + - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) + - 3.0 + / 8 + * (1 + r2 / (2 * d2)) + * sqrt(1 - d2 / r2) + * v + / sqrt(v2 - 1) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * v + / sqrt(v2 - 1) + * (atan(sqrt(v2 - 1)) - atan(sqrt(r2 / d2 - 1))) + ) + + r = rx[rx > v * psize] f3 = numpy.zeros_like(r) - f = numpy.concatenate((f1,f2,f3)) + f = numpy.concatenate((f1, f2, f3)) return f @@ -162,19 +211,33 @@ def lognormalSphericalCF(r, psize, psig): Source unknown """ - if psize <= 0: return numpy.zeros_like(r) - if psig <= 0: return sphericalCF(r, psize) + if psize <= 0: + return numpy.zeros_like(r) + if psig <= 0: + return sphericalCF(r, psize) - erfc = lambda x: 1.0-erf(x) + erfc = lambda x: 1.0 - erf(x) sqrt2 = sqrt(2.0) - s = sqrt(log(psig*psig/(1.0*psize*psize) + 1)) - mu = log(psize) - s*s/2; - if mu < 0: return numpy.zeros_like(r) + s = sqrt(log(psig * psig / (1.0 * psize * psize) + 1)) + mu = log(psize) - s * s / 2 + if mu < 0: + return numpy.zeros_like(r) + + return ( + 0.5 * erfc((-mu - 3 * s * s + log(r)) / (sqrt2 * s)) + + 0.25 + * r + * r + * r + * erfc((-mu + log(r)) / (sqrt2 * s)) + * exp(-3 * mu - 4.5 * s * s) + - 0.75 + * r + * erfc((-mu - 2 * s * s + log(r)) / (sqrt2 * s)) + * exp(-mu - 2.5 * s * s) + ) - return 0.5*erfc((-mu-3*s*s+log(r))/(sqrt2*s)) \ - + 0.25*r*r*r*erfc((-mu+log(r))/(sqrt2*s))*exp(-3*mu-4.5*s*s) \ - - 0.75*r*erfc((-mu-2*s*s+log(r))/(sqrt2*s))*exp(-mu-2.5*s*s) def sheetCF(r, sthick): """Nanosheet characteristic function. @@ -211,10 +274,11 @@ def shellCF(r, radius, thickness): From Lei et al., Phys. Rev. B, 80, 024118 (2009) """ - d = 1.0*thickness - a = 1.0*radius + d/2.0 + d = 1.0 * thickness + a = 1.0 * radius + d / 2.0 return shellCF2(r, a, d) + def shellCF2(r, a, delta): """Spherical shell characteristic function. @@ -225,22 +289,30 @@ def shellCF2(r, a, delta): From Lei et al., Phys. Rev. B, 80, 024118 (2009) """ - a = 1.0*a - d = 1.0*delta + a = 1.0 * a + d = 1.0 * delta a2 = a**2 d2 = d**2 - dmr = d-r + dmr = d - r dmr2 = dmr**2 - f = r * (16*a*a2 + 12*a*d*dmr + 36*a2*(2*d-r) + 3*dmr2*(2*d+r)) \ - + 2*dmr2 * (r*(2*d+r)-12*a2) * sign(dmr) \ - - 2*(2*a-r)**2 * (r*(4*a+r)-3*d2) * sign(2*a-r) \ - + r*(4*a-2*d+r)*(2*a-d-r)**2*sign(2*a-d-r) - - f[r > 2*a+d] = 0 - - den = 8.0*r*d*(12*a2+d2) - zmask = (den == 0.0) + f = ( + r + * ( + 16 * a * a2 + + 12 * a * d * dmr + + 36 * a2 * (2 * d - r) + + 3 * dmr2 * (2 * d + r) + ) + + 2 * dmr2 * (r * (2 * d + r) - 12 * a2) * sign(dmr) + - 2 * (2 * a - r) ** 2 * (r * (4 * a + r) - 3 * d2) * sign(2 * a - r) + + r * (4 * a - 2 * d + r) * (2 * a - d - r) ** 2 * sign(2 * a - d - r) + ) + + f[r > 2 * a + d] = 0 + + den = 8.0 * r * d * (12 * a2 + d2) + zmask = den == 0.0 vmask = ~zmask f[vmask] /= den[vmask] f[zmask] = 1 @@ -277,6 +349,7 @@ def __init__(self, name, model): self._model = model from diffpy.srfit.sas.sasparameter import SASParameter + # Wrap normal parameters for parname in model.params: par = SASParameter(parname, model) @@ -319,11 +392,11 @@ def __call__(self, r): rmax = max(ed, 2 * r[-1]) dq = pi / rmax qmax = pi / dr - numpoints = int(2**(ceil(log2(qmax/dq)))) + numpoints = int(2 ** (ceil(log2(qmax / dq)))) qmax = dq * numpoints # Calculate F(q) = q * I(q) from model - q = fftfreq(int(qmax/dq)) * qmax + q = fftfreq(int(qmax / dq)) * qmax fq = q * self._model.evalDistribution(q) # Calculate g(r) and the effective r-points @@ -332,20 +405,20 @@ def __call__(self, r): gr = ifft(fq).imag # Calculate full-fr for normalization - assert (rp[0] == 0.0) + assert rp[0] == 0.0 frp = numpy.zeros_like(gr) frp[1:] = gr[1:] / rp[1:] # Inerpolate onto requested grid, do not use data after jump in rp - assert (numpoints % 2 == 0) + assert numpoints % 2 == 0 nhalf = numpoints / 2 fr = numpy.interp(r, rp[:nhalf], gr[:nhalf]) - vmask = (r != 0) + vmask = r != 0 fr[vmask] /= r[vmask] # Normalize. We approximate fr[0] by using the fact that f(r) is linear # at low r. By definition, fr[0] should equal 1. - fr0 = 2*frp[2] - frp[1] + fr0 = 2 * frp[2] - frp[1] fr /= fr0 # Fix potential divide-by-zero issue, fr is 1 at r == 0 diff --git a/src/diffpy/srfit/pdf/debyepdfgenerator.py b/src/diffpy/srfit/pdf/debyepdfgenerator.py index 1c86bf6a..e83e2026 100644 --- a/src/diffpy/srfit/pdf/debyepdfgenerator.py +++ b/src/diffpy/srfit/pdf/debyepdfgenerator.py @@ -12,13 +12,12 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF profile generator using the Debye equation. The DebyePDFGenerator class can take a diffpy.structure, -pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule object and calculate -the PDF from it. This generator is especially appropriate for isolated -scatterers, such as nanoparticles and molecules. +pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule object and +calculate the PDF from it. This generator is especially appropriate for +isolated scatterers, such as nanoparticles and molecules. """ __all__ = ["DebyePDFGenerator"] @@ -65,7 +64,7 @@ class DebyePDFGenerator(BasePDFGenerator): qdamp -- See Managed Parameters. """ - def setStructure(self, stru, name = "phase", periodic = False): + def setStructure(self, stru, name="phase", periodic=False): """Set the structure that will be used to calculate the PDF. This creates a DiffpyStructureParSet, ObjCrystCrystalParSet or @@ -84,8 +83,7 @@ def setStructure(self, stru, name = "phase", periodic = False): """ return BasePDFGenerator.setStructure(self, stru, name, periodic) - - def setPhase(self, parset, periodic = False): + def setPhase(self, parset, periodic=False): """Set the phase that will be used to calculate the PDF. Set the phase directly with a DiffpyStructureParSet, @@ -103,14 +101,15 @@ def setPhase(self, parset, periodic = False): """ return BasePDFGenerator.setPhase(self, parset, periodic) - - def __init__(self, name = "pdf"): + def __init__(self, name="pdf"): """Initialize the generator.""" from diffpy.srreal.pdfcalculator import DebyePDFCalculator + BasePDFGenerator.__init__(self, name) self._setCalculator(DebyePDFCalculator()) return + # End class DebyePDFGenerator # End of file diff --git a/src/diffpy/srfit/pdf/pdfcontribution.py b/src/diffpy/srfit/pdf/pdfcontribution.py index 4abc9af4..981cb91f 100644 --- a/src/diffpy/srfit/pdf/pdfcontribution.py +++ b/src/diffpy/srfit/pdf/pdfcontribution.py @@ -12,16 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDFContribution class. -This is a custom FitContribution that simplifies the creation of PDF fits. +This is a custom FitContribution that simplifies the creation of PDF +fits. """ __all__ = ["PDFContribution"] -from diffpy.srfit.fitbase import FitContribution -from diffpy.srfit.fitbase import Profile +from diffpy.srfit.fitbase import FitContribution, Profile + class PDFContribution(FitContribution): """PDFContribution class. @@ -70,7 +70,7 @@ def __init__(self, name): self._meta = {} # Add the profile profile = Profile() - self.setProfile(profile, xname = "r") + self.setProfile(profile, xname="r") # Need a parameter for the overall scale, in the case that this is a # multi-phase fit. @@ -93,10 +93,12 @@ def loadData(self, data): """ # Get the data into a string from diffpy.srfit.util.inpututils import inputToString + datstr = inputToString(data) # Load data with a PDFParser from diffpy.srfit.pdf.pdfparser import PDFParser + parser = PDFParser() parser.parseString(datstr) @@ -104,7 +106,6 @@ def loadData(self, data): self.profile.loadParsedData(parser) return - def setCalculationRange(self, xmin=None, xmax=None, dx=None): """Set epsilon-inclusive calculation range. @@ -139,7 +140,6 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): """ return self.profile.setCalculationRange(xmin, xmax, dx) - def savetxt(self, fname, **kwargs): """Call numpy.savetxt with x, ycalc, y, dy. @@ -151,7 +151,7 @@ def savetxt(self, fname, **kwargs): # Phase methods - def addStructure(self, name, stru, periodic = True): + def addStructure(self, name, stru, periodic=True): """Add a phase that goes into the PDF calculation. name -- A name to give the generator that will manage the PDF @@ -176,9 +176,11 @@ def addStructure(self, name, stru, periodic = True): # Based on periodic, create the proper generator. if periodic: from diffpy.srfit.pdf.pdfgenerator import PDFGenerator + gen = PDFGenerator(name) else: from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator + gen = DebyePDFGenerator(name) # Set up the generator @@ -187,7 +189,7 @@ def addStructure(self, name, stru, periodic = True): return gen.phase - def addPhase(self, name, parset, periodic = True): + def addPhase(self, name, parset, periodic=True): """Add a phase that goes into the PDF calculation. name -- A name to give the generator that will manage the PDF @@ -213,9 +215,11 @@ def addPhase(self, name, parset, periodic = True): # Based on periodic, create the proper generator. if periodic: from diffpy.srfit.pdf.pdfgenerator import PDFGenerator + gen = PDFGenerator(name) else: from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator + gen = DebyePDFGenerator(name) # Set up the generator @@ -227,8 +231,8 @@ def addPhase(self, name, parset, periodic = True): def _setupGenerator(self, gen): """Setup a generator. - The generator must already have a managed SrRealParSet, added with - setStructure or setPhase. + The generator must already have a managed SrRealParSet, added + with setStructure or setPhase. """ # Add the generator to this FitContribution self.addProfileGenerator(gen) @@ -262,8 +266,7 @@ def _getMetaValue(self, kwd): val = self.profile.meta.get(kwd) return val - - def setScatteringType(self, type = "X"): + def setScatteringType(self, type="X"): """Set the scattering type. type -- "X" for x-ray or "N" for neutron @@ -304,4 +307,5 @@ def getQmin(self): """Get the qmin value.""" return self._getMetaValue("qmin") + # End of file diff --git a/src/diffpy/srfit/pdf/pdfgenerator.py b/src/diffpy/srfit/pdf/pdfgenerator.py index 819017a5..9b5b1608 100644 --- a/src/diffpy/srfit/pdf/pdfgenerator.py +++ b/src/diffpy/srfit/pdf/pdfgenerator.py @@ -12,14 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF profile generator. -The PDFGenerator class can take a diffpy.structure, pyobjcryst.crystal.Crystal -or pyobjcryst.molecule.Molecule object and calculate the crystal PDF from it. -The passed structure object is wrapped in a StructureParameter set, which makes -its attributes refinable. See the class definition for more details and the -examples for its use. +The PDFGenerator class can take a diffpy.structure, +pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule object and +calculate the crystal PDF from it. The passed structure object is +wrapped in a StructureParameter set, which makes its attributes +refinable. See the class definition for more details and the examples +for its use. """ __all__ = ["PDFGenerator"] @@ -65,11 +65,13 @@ class PDFGenerator(BasePDFGenerator): qdamp -- See Managed Parameters. """ - def __init__(self, name = "pdf"): + def __init__(self, name="pdf"): """Initialize the generator.""" from diffpy.srreal.pdfcalculator import PDFCalculator + BasePDFGenerator.__init__(self, name) self._setCalculator(PDFCalculator()) return + # End class PDFGenerator diff --git a/src/diffpy/srfit/pdf/pdfparser.py b/src/diffpy/srfit/pdf/pdfparser.py index ae87a191..6211211b 100644 --- a/src/diffpy/srfit/pdf/pdfparser.py +++ b/src/diffpy/srfit/pdf/pdfparser.py @@ -12,10 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains parsers for PDF data. -PDFParser is suitable for parsing data generated from PDFGetN and PDFGetX. +PDFParser is suitable for parsing data generated from PDFGetN and +PDFGetX. See the class documentation for more information. """ @@ -23,11 +23,13 @@ __all__ = ["PDFParser"] import re + import numpy from diffpy.srfit.exceptions import ParseError from diffpy.srfit.fitbase.profileparser import ProfileParser + class PDFParser(ProfileParser): """Class for holding a diffraction pattern. @@ -89,15 +91,15 @@ def parseString(self, patstring): Raises ParseError if the string cannot be parsed """ # useful regex patterns: - rx = { 'f' : r'[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?' } + rx = {"f": r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?"} # find where does the data start - res = re.search(r'^#+ start data\s*(?:#.*\s+)*', patstring, re.M) + res = re.search(r"^#+ start data\s*(?:#.*\s+)*", patstring, re.M) # start_data is position where the first data line starts if res: start_data = res.end() else: # find line that starts with a floating point number - regexp = r'^\s*%(f)s' % rx + regexp = r"^\s*%(f)s" % rx res = re.search(regexp, patstring, re.M) if res: start_data = res.start() @@ -107,19 +109,19 @@ def parseString(self, patstring): databody = patstring[start_data:].strip() # find where the metadata starts - metadata = '' - res = re.search(r'^#+\ +metadata\b\n', header, re.M) + metadata = "" + res = re.search(r"^#+\ +metadata\b\n", header, re.M) if res: - metadata = header[res.end():] - header = header[:res.start()] + metadata = header[res.end() :] + header = header[: res.start()] # parse header meta = self._meta # stype - if re.search('(x-?ray|PDFgetX)', header, re.I): - meta["stype"] = 'X' - elif re.search('(neutron|PDFgetN)', header, re.I): - meta["stype"] = 'N' + if re.search("(x-?ray|PDFgetX)", header, re.I): + meta["stype"] = "X" + elif re.search("(neutron|PDFgetN)", header, re.I): + meta["stype"] = "N" # qmin regexp = r"\bqmin *= *(%(f)s)\b" % rx res = re.search(regexp, header, re.I) @@ -154,12 +156,12 @@ def parseString(self, patstring): regexp = r"\b(?:temp|temperature|T)\ *=\ *(%(f)s)\b" % rx res = re.search(regexp, header) if res: - meta['temperature'] = float(res.groups()[0]) + meta["temperature"] = float(res.groups()[0]) # doping regexp = r"\b(?:x|doping)\ *=\ *(%(f)s)\b" % rx res = re.search(regexp, header) if res: - meta['doping'] = float(res.groups()[0]) + meta["doping"] = float(res.groups()[0]) # parsing gerneral metadata if metadata: @@ -168,12 +170,12 @@ def parseString(self, patstring): res = re.search(regexp, metadata, re.M) if res: meta[res.groups()[0]] = float(res.groups()[1]) - metadata = metadata[res.end():] + metadata = metadata[res.end() :] else: break # read actual data - robs, Gobs, drobs, dGobs - inf_or_nan = re.compile('(?i)^[+-]?(NaN|Inf)\\b') + inf_or_nan = re.compile("(?i)^[+-]?(NaN|Inf)\\b") has_drobs = True has_dGobs = True # raise ParseError if something goes wrong @@ -188,15 +190,17 @@ def parseString(self, patstring): robs.append(float(v[0])) Gobs.append(float(v[1])) # drobs is valid if all values are defined and positive - has_drobs = (has_drobs and - len(v) > 2 and not inf_or_nan.match(v[2])) + has_drobs = ( + has_drobs and len(v) > 2 and not inf_or_nan.match(v[2]) + ) if has_drobs: v2 = float(v[2]) has_drobs = v2 > 0.0 drobs.append(v2) # dGobs is valid if all values are defined and positive - has_dGobs = (has_dGobs and - len(v) > 3 and not inf_or_nan.match(v[3])) + has_dGobs = ( + has_dGobs and len(v) > 3 and not inf_or_nan.match(v[3]) + ) if has_dGobs: v3 = float(v[3]) has_dGobs = v3 > 0.0 @@ -218,4 +222,5 @@ def parseString(self, patstring): self._banks.append([robs, Gobs, drobs, dGobs]) return + # End of PDFParser diff --git a/src/diffpy/srfit/sas/__init__.py b/src/diffpy/srfit/sas/__init__.py index 42e918de..bd7024a9 100644 --- a/src/diffpy/srfit/sas/__init__.py +++ b/src/diffpy/srfit/sas/__init__.py @@ -12,15 +12,19 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """SAS calculation tools.""" -__all__ = ["SASGenerator", "SASParser", "SASProfile", "PrCalculator", - "CFCalculator"] +__all__ = [ + "SASGenerator", + "SASParser", + "SASProfile", + "PrCalculator", + "CFCalculator", +] +from .prcalculator import CFCalculator, PrCalculator from .sasgenerator import SASGenerator from .sasparser import SASParser from .sasprofile import SASProfile -from .prcalculator import PrCalculator, CFCalculator # End of file diff --git a/src/diffpy/srfit/sas/prcalculator.py b/src/diffpy/srfit/sas/prcalculator.py index 9ea540da..0af67260 100644 --- a/src/diffpy/srfit/sas/prcalculator.py +++ b/src/diffpy/srfit/sas/prcalculator.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Nanoparticle form factor P(r) calculator. -The PrCalculator class wraps a sas.pr.invertor.Invertor object as a Calculator. -This is not wrapped as a ProfileGenerator because it will be used to share -information between SAS I(Q) to PDF G(r), but it does not use the same profile -as the PDF, which is where the calculator will be applied. +The PrCalculator class wraps a sas.pr.invertor.Invertor object as a +Calculator. This is not wrapped as a ProfileGenerator because it will be +used to share information between SAS I(Q) to PDF G(r), but it does not +use the same profile as the PDF, which is where the calculator will be +applied. """ __all__ = ["PrCalculator", "CFCalculator"] @@ -65,7 +65,8 @@ def __init__(self, name): global Invertor if Invertor is None: from diffpy.srfit.sas.sasimport import sasimport - Invertor = sasimport('sas.pr.invertor').Invertor + + Invertor = sasimport("sas.pr.invertor").Invertor self._invertor = Invertor() @@ -95,8 +96,10 @@ def __call__(self, r): pr = numpy.array(pr) return self.scale.value * pr + # End class PrCalculator + class CFCalculator(PrCalculator): """A class for calculating the characteristic function (CF) from data. @@ -127,4 +130,5 @@ def __call__(self, r): fr[0] = 1 return fr + # End class CFCalculator diff --git a/src/diffpy/srfit/sas/sasgenerator.py b/src/diffpy/srfit/sas/sasgenerator.py index 11b20c86..bb3de8cf 100644 --- a/src/diffpy/srfit/sas/sasgenerator.py +++ b/src/diffpy/srfit/sas/sasgenerator.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """SAS profile generator. The SASGenerator class wraps a sas.models.BaseModel object as a @@ -67,4 +66,5 @@ def __call__(self, q): """Calculate I(Q) for the BaseModel.""" return self._model.evalDistribution(q) + # End class SASGenerator diff --git a/src/diffpy/srfit/sas/sasimport.py b/src/diffpy/srfit/sas/sasimport.py index 8c303af1..093a67b3 100644 --- a/src/diffpy/srfit/sas/sasimport.py +++ b/src/diffpy/srfit/sas/sasimport.py @@ -12,9 +12,9 @@ # See LICENSE.txt for license information. # ############################################################################## - """Universal import functions for volatile SasView/SansViews API-s.""" + def sasimport(modname): """Import specified module from the SasView sas package. @@ -23,41 +23,44 @@ def sasimport(modname): When specified import does not work directly, try older API-s and raise DeprecationWarning. Raise ImportError if nothing works. """ - if not modname.startswith('sas.'): + if not modname.startswith("sas."): emsg = 'Module name must start with "sas."' raise ValueError(emsg) mobj = None # standard import try: - exec('import %s as mobj' % modname) + exec("import %s as mobj" % modname) except ImportError: pass else: return mobj # revert to the old sans namespace, sas --> sans - modsans = 'sans' + modname[3:] + modsans = "sans" + modname[3:] import warnings - wfmt = ("Using obsolete package %r instead of %r. Please install " - "SasView 3.1 or the srfit-sasview package from Anaconda.") + + wfmt = ( + "Using obsolete package %r instead of %r. Please install " + "SasView 3.1 or the srfit-sasview package from Anaconda." + ) wmsg = wfmt % (modsans, modname) try: - exec('import %s as mobj' % modsans) + exec("import %s as mobj" % modsans) warnings.warn(wmsg, DeprecationWarning) except ImportError: pass else: return mobj # finally check the oldest DataLoader API for sas.dataloader - if modname.startswith('sas.dataloader'): - modloader = 'DataLoader' + modname[14:] + if modname.startswith("sas.dataloader"): + modloader = "DataLoader" + modname[14:] wmsg = wfmt % (modloader, modname) try: - exec('import %s as mobj' % modloader) + exec("import %s as mobj" % modloader) warnings.warn(wmsg, DeprecationWarning) except ImportError: pass else: return mobj # Obsolete API-s failed here. Import again and let it raise ImportError. - exec('import %s as mobj' % modname) + exec("import %s as mobj" % modname) raise AssertionError("The import above was supposed to fail.") diff --git a/src/diffpy/srfit/sas/sasparameter.py b/src/diffpy/srfit/sas/sasparameter.py index b00be925..0f042772 100644 --- a/src/diffpy/srfit/sas/sasparameter.py +++ b/src/diffpy/srfit/sas/sasparameter.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """SAS profile generator. The SASGenerator class wraps a sas.models.BaseModel object as a @@ -41,7 +40,7 @@ class SASParameter(Parameter): _parname -- The name of the underlying BaseModel parameter. """ - def __init__(self, name, model, parname = None): + def __init__(self, name, model, parname=None): """Create the Parameter. name -- Name of the Parameter @@ -57,7 +56,7 @@ def __init__(self, name, model, parname = None): def getValue(self): """Get the value of the Parameter.""" - value = self._model.getParam(self._parname) + value = self._model.getParam(self._parname) return value def setValue(self, value): @@ -67,4 +66,5 @@ def setValue(self, value): self.notify() return self + # End of class SASParameter diff --git a/src/diffpy/srfit/sas/sasparser.py b/src/diffpy/srfit/sas/sasparser.py index e20807b9..f5bd5680 100644 --- a/src/diffpy/srfit/sas/sasparser.py +++ b/src/diffpy/srfit/sas/sasparser.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains parsers for SAS data. SASParser uses the sas DataLoader class to load data. @@ -81,7 +80,7 @@ def parseFile(self, filename): Raises ParseError if the file cannot be parsed """ - Loader = sasimport('sas.dataloader.loader').Loader + Loader = sasimport("sas.dataloader.loader").Loader loader = Loader() try: @@ -114,8 +113,9 @@ def parseString(self, patstring): """ # This calls on parseFile, as that is how the sas data loader works. import tempfile + fh, fn = tempfile.mkstemp() - outfile = open(fn, 'w') + outfile = open(fn, "w") fn.write(patstring) outfile.close() self.parseFile(fn) @@ -124,6 +124,7 @@ def parseString(self, patstring): # Close the temporary file and delete it import os + os.close(fh) os.remove(fn) return diff --git a/src/diffpy/srfit/sas/sasprofile.py b/src/diffpy/srfit/sas/sasprofile.py index 9c410a06..b18dfa76 100644 --- a/src/diffpy/srfit/sas/sasprofile.py +++ b/src/diffpy/srfit/sas/sasprofile.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Class for adapting a sas DataInfo objects to the Profile interface.""" __all__ = ["SASProfile"] @@ -72,7 +71,7 @@ def __init__(self, datainfo): self._dyobs = self._datainfo.dy return - def setObservedProfile(self, xobs, yobs, dyobs = None): + def setObservedProfile(self, xobs, yobs, dyobs=None): """Set the observed profile. This is overloaded to change the value within the datainfo object. diff --git a/src/diffpy/srfit/structure/__init__.py b/src/diffpy/srfit/structure/__init__.py index 3296f0dd..d1a1ecd1 100644 --- a/src/diffpy/srfit/structure/__init__.py +++ b/src/diffpy/srfit/structure/__init__.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Modules and classes that adapt structure representations to the ParameterSet interface and automatic structure constraint generation from space group information.""" @@ -30,18 +29,22 @@ def struToParameterSet(name, stru): Raises TypeError if stru cannot be adapted """ from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + if DiffpyStructureParSet.canAdapt(stru): return DiffpyStructureParSet(name, stru) from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + if ObjCrystCrystalParSet.canAdapt(stru): return ObjCrystCrystalParSet(name, stru) from diffpy.srfit.structure.objcrystparset import ObjCrystMoleculeParSet + if ObjCrystMoleculeParSet.canAdapt(stru): return ObjCrystMoleculeParSet(name, stru) from diffpy.srfit.structure.cctbxparset import CCTBXCrystalParSet + if CCTBXCrystalParSet.canAdapt(stru): return CCTBXCrystalParSet(name, stru) diff --git a/src/diffpy/srfit/structure/basestructureparset.py b/src/diffpy/srfit/structure/basestructureparset.py index 7fa44c6d..31c619c9 100644 --- a/src/diffpy/srfit/structure/basestructureparset.py +++ b/src/diffpy/srfit/structure/basestructureparset.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Base class for adapting structures to a ParameterSet interface. -The BaseStructureParSet is a ParameterSet with functionality required by all -structure adapters. +The BaseStructureParSet is a ParameterSet with functionality required by +all structure adapters. """ __all__ = ["BaseStructureParSet"] @@ -43,21 +42,21 @@ def canAdapt(self, stru): def getLattice(self): """Get a ParameterSet containing the lattice Parameters. - The returned ParameterSet may contain other Parameters than the lattice - Parameters. It is assumed that the lattice parameters are named "a", - "b", "c", "alpha", "beta", "gamma". + The returned ParameterSet may contain other Parameters than the + lattice Parameters. It is assumed that the lattice parameters + are named "a", "b", "c", "alpha", "beta", "gamma". - Lattice must also have the "angunits" attribute, which is either "deg" - or "rad", to signify degrees or radians. + Lattice must also have the "angunits" attribute, which is either + "deg" or "rad", to signify degrees or radians. """ raise NotImplementedError("The must be overloaded") def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ raise NotImplementedError("The must be overloaded") diff --git a/src/diffpy/srfit/structure/bvsrestraint.py b/src/diffpy/srfit/structure/bvsrestraint.py index 28294045..b9c9226c 100644 --- a/src/diffpy/srfit/structure/bvsrestraint.py +++ b/src/diffpy/srfit/structure/bvsrestraint.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Bond-valence sum calculator from SrReal wrapped as a Restraint. This can be used as an addition to a cost function during a structure @@ -21,8 +20,8 @@ __all__ = ["BVSRestraint"] -from diffpy.srfit.fitbase.restraint import Restraint from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.restraint import Restraint class BVSRestraint(Restraint): @@ -40,7 +39,7 @@ class BVSRestraint(Restraint): (default False). """ - def __init__(self, parset, sig = 1, scaled = False): + def __init__(self, parset, sig=1, scaled=False): """Initialize the Restraint. parset -- SrRealParSet that creates this BVSRestraint. @@ -50,13 +49,14 @@ def __init__(self, parset, sig = 1, scaled = False): (chi^2/numpoints) (bool, default False). """ from diffpy.srreal.bvscalculator import BVSCalculator + self._calc = BVSCalculator() self._parset = parset self.sig = float(sig) self.scaled = bool(scaled) return - def penalty(self, w = 1.0): + def penalty(self, w=1.0): """Calculate the penalty of the restraint. w -- The point-average chi^2 which is optionally used to scale the @@ -71,7 +71,8 @@ def penalty(self, w = 1.0): penalty /= self.sig**2 # Optionally scale by w - if self.scaled: penalty *= w + if self.scaled: + penalty *= w return penalty @@ -81,16 +82,20 @@ def _validate(self): Raises SrFitError if validation fails. """ from numpy import nan + p = self.penalty() if p is None or p is nan: raise SrFitError("Cannot evaluate penalty") v = self._calc.value if len(v) > 1 and not v.any(): - emsg = ("Bond valence sums are all zero. Check atom symbols in " - "the structure or define custom bond-valence parameters.") + emsg = ( + "Bond valence sums are all zero. Check atom symbols in " + "the structure or define custom bond-valence parameters." + ) raise SrFitError(emsg) return # End of class BVSRestraint + # End of file diff --git a/src/diffpy/srfit/structure/cctbxparset.py b/src/diffpy/srfit/structure/cctbxparset.py index 2f6e9640..a61a1d50 100644 --- a/src/diffpy/srfit/structure/cctbxparset.py +++ b/src/diffpy/srfit/structure/cctbxparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Wrappers for interfacing cctbx crystal with SrFit. This wraps a cctbx.crystal as a ParameterSet with a similar hierarchy, which @@ -32,8 +31,7 @@ from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.structure.basestructureparset import BaseStructureParSet -__all__ = ["CCTBXScattererParSet", "CCTBXUnitCellParSet", - "CCTBXCrystalParSet"] +__all__ = ["CCTBXScattererParSet", "CCTBXUnitCellParSet", "CCTBXCrystalParSet"] class CCTBXScattererParSet(ParameterSet): @@ -63,16 +61,21 @@ def __init__(self, name, strups, idx): self.idx = idx # x, y, z, occupancy - self.addParameter(ParameterAdapter("x", None, self._xyzgetter(0), - self._xyzsetter(0))) - self.addParameter(ParameterAdapter("y", None, self._xyzgetter(1), - self._xyzsetter(1))) - self.addParameter(ParameterAdapter("z", None, self._xyzgetter(2), - self._xyzsetter(2))) - self.addParameter(ParameterAdapter("occupancy", None, self._getocc, - self._setocc)) - self.addParameter(ParameterAdapter("Uiso", None, self._getuiso, - self._setuiso)) + self.addParameter( + ParameterAdapter("x", None, self._xyzgetter(0), self._xyzsetter(0)) + ) + self.addParameter( + ParameterAdapter("y", None, self._xyzgetter(1), self._xyzsetter(1)) + ) + self.addParameter( + ParameterAdapter("z", None, self._xyzgetter(2), self._xyzsetter(2)) + ) + self.addParameter( + ParameterAdapter("occupancy", None, self._getocc, self._setocc) + ) + self.addParameter( + ParameterAdapter("Uiso", None, self._getuiso, self._setuiso) + ) return # Getters and setters @@ -113,8 +116,10 @@ def _getElem(self): element = property(_getElem) + # End class CCTBXScattererParSet + class CCTBXUnitCellParSet(ParameterSet): """A wrapper for cctbx unit_cell object. @@ -133,18 +138,30 @@ def __init__(self, strups): self.strups = strups self._latpars = list(self.strups.stru.unit_cell().parameters()) - self.addParameter(ParameterAdapter("a", None, self._latgetter(0), - self._latsetter(0))) - self.addParameter(ParameterAdapter("b", None, self._latgetter(1), - self._latsetter(1))) - self.addParameter(ParameterAdapter("c", None, self._latgetter(2), - self._latsetter(2))) - self.addParameter(ParameterAdapter("alpha", None, self._latgetter(3), - self._latsetter(3))) - self.addParameter(ParameterAdapter("beta", None, self._latgetter(4), - self._latsetter(4))) - self.addParameter(ParameterAdapter("gamma", None, self._latgetter(5), - self._latsetter(5))) + self.addParameter( + ParameterAdapter("a", None, self._latgetter(0), self._latsetter(0)) + ) + self.addParameter( + ParameterAdapter("b", None, self._latgetter(1), self._latsetter(1)) + ) + self.addParameter( + ParameterAdapter("c", None, self._latgetter(2), self._latsetter(2)) + ) + self.addParameter( + ParameterAdapter( + "alpha", None, self._latgetter(3), self._latsetter(3) + ) + ) + self.addParameter( + ParameterAdapter( + "beta", None, self._latgetter(4), self._latsetter(4) + ) + ) + self.addParameter( + ParameterAdapter( + "gamma", None, self._latgetter(5), self._latsetter(5) + ) + ) return @@ -169,6 +186,7 @@ def f(dummy, value): # FIXME - Special positions should be constant. + class CCTBXCrystalParSet(BaseStructureParSet): """A wrapper for CCTBX structure. @@ -195,14 +213,15 @@ def __init__(self, name, stru): for s in stru.scatterers(): el = s.element_symbol() i = cdict.get(el, 0) - sname = "%s%i"%(el,i) - cdict[el] = i+1 + sname = "%s%i" % (el, i) + cdict[el] = i + 1 scatterer = CCTBXScattererParSet(sname, self, i) self.addParameterSet(scatterer) self.scatterers.append(scatterer) # Constrain the lattice from diffpy.srfit.structure.sgconstraints import _constrainSpaceGroup + symbol = self.getSpaceGroup() _constrainSpaceGroup(self, symbol) @@ -212,8 +231,9 @@ def update(self): """Update the unit_cell to a change in lattice parameters. This remakes the unit cell according to a change in the lattice - parameters. Call this function before using the CCTBXCrystalParSet. The - unit_cell will only be remade if necessary. + parameters. Call this function before using the + CCTBXCrystalParSet. The unit_cell will only be remade if + necessary. """ if not self._update: return @@ -224,16 +244,15 @@ def update(self): # Create the symmetry object from cctbx.crystal import symmetry + symm = symmetry( - unit_cell = self.unitcell._latpars, - space_group_symbol = sgn - ) + unit_cell=self.unitcell._latpars, space_group_symbol=sgn + ) # Now the new structure newstru = stru.__class__( - crystal_symmetry = symm, - scatterers = stru.scatterers() - ) + crystal_symmetry=symm, scatterers=stru.scatterers() + ) self.unitcell._latpars = list(newstru.unit_cell().parameters()) @@ -256,10 +275,10 @@ def getLattice(self): def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.scatterers @@ -270,5 +289,4 @@ def getSpaceGroup(self): return t.lookup_symbol() - # End class CCTBXCrystalParSet diff --git a/src/diffpy/srfit/structure/diffpyparset.py b/src/diffpy/srfit/structure/diffpyparset.py index 5d6239b9..3c0f7a41 100644 --- a/src/diffpy/srfit/structure/diffpyparset.py +++ b/src/diffpy/srfit/structure/diffpyparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Adapters for interfacing a diffpy.structure.Structure with SrFit. A diffpy.structure.Structure object is meant to be passed to a @@ -30,8 +29,7 @@ __all__ = ["DiffpyStructureParSet"] -from diffpy.srfit.fitbase.parameter import ParameterProxy -from diffpy.srfit.fitbase.parameter import ParameterAdapter +from diffpy.srfit.fitbase.parameter import ParameterAdapter, ParameterProxy from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.structure.srrealparset import SrRealParSet from diffpy.srfit.util.argbinders import bind2nd @@ -92,12 +90,15 @@ def __init__(self, name, atom): self.atom = atom a = atom # x, y, z, occupancy - self.addParameter(ParameterAdapter("x", a, - _xyzgetter(0), _xyzsetter(0))) - self.addParameter(ParameterAdapter("y", a, - _xyzgetter(1), _xyzsetter(1))) - self.addParameter(ParameterAdapter("z", a, - _xyzgetter(2), _xyzsetter(2))) + self.addParameter( + ParameterAdapter("x", a, _xyzgetter(0), _xyzsetter(0)) + ) + self.addParameter( + ParameterAdapter("y", a, _xyzgetter(1), _xyzsetter(1)) + ) + self.addParameter( + ParameterAdapter("z", a, _xyzgetter(2), _xyzsetter(2)) + ) occupancy = ParameterAdapter("occupancy", a, attr="occupancy") self.addParameter(occupancy) self.addParameter(ParameterProxy("occ", occupancy)) @@ -148,6 +149,7 @@ def _setElem(self, el): element = property(_getElem, _setElem, "type of atom") + # End class DiffpyAtomParSet @@ -183,23 +185,36 @@ def __init__(self, lattice): self.angunits = "deg" self.lattice = lattice lat = lattice - self.addParameter(ParameterAdapter("a", lat, - _latgetter("a"), _latsetter("a"))) - self.addParameter(ParameterAdapter("b", lat, - _latgetter("b"), _latsetter("b"))) - self.addParameter(ParameterAdapter("c", lat, - _latgetter("c"), _latsetter("c"))) - self.addParameter(ParameterAdapter("alpha", lat, _latgetter("alpha"), - _latsetter("alpha"))) - self.addParameter(ParameterAdapter("beta", lat, _latgetter("beta"), - _latsetter("beta"))) - self.addParameter(ParameterAdapter("gamma", lat, _latgetter("gamma"), - _latsetter("gamma"))) + self.addParameter( + ParameterAdapter("a", lat, _latgetter("a"), _latsetter("a")) + ) + self.addParameter( + ParameterAdapter("b", lat, _latgetter("b"), _latsetter("b")) + ) + self.addParameter( + ParameterAdapter("c", lat, _latgetter("c"), _latsetter("c")) + ) + self.addParameter( + ParameterAdapter( + "alpha", lat, _latgetter("alpha"), _latsetter("alpha") + ) + ) + self.addParameter( + ParameterAdapter( + "beta", lat, _latgetter("beta"), _latsetter("beta") + ) + ) + self.addParameter( + ParameterAdapter( + "gamma", lat, _latgetter("gamma"), _latsetter("gamma") + ) + ) return def __repr__(self): return repr(self.lattice) + # End class DiffpyLatticeParSet @@ -241,7 +256,7 @@ def __init__(self, name, stru): el = el.replace("-", "m") i = cdict.get(el, 0) aname = "%s%i" % (el, i) - cdict[el] = i+1 + cdict[el] = i + 1 atom = DiffpyAtomParSet(aname, a) self.addParameterSet(atom) self.atoms.append(atom) @@ -259,27 +274,30 @@ def getLattice(self): def canAdapt(self, stru): """Return whether the structure can be adapted by this class.""" from diffpy.structure import Structure + return isinstance(stru, Structure) def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.atoms def _getSrRealStructure(self): """Get the structure object for use with SrReal calculators. - If this is periodic, then return the structure, otherwise, pass it - inside of a nosymmetry wrapper. This takes the extra step of wrapping - the structure in a nometa wrapper. + If this is periodic, then return the structure, otherwise, pass + it inside of a nosymmetry wrapper. This takes the extra step of + wrapping the structure in a nometa wrapper. """ from diffpy.srreal.structureadapter import nometa + stru = SrRealParSet._getSrRealStructure(self) return nometa(stru) + # End class DiffpyStructureParSet diff --git a/src/diffpy/srfit/structure/objcrystparset.py b/src/diffpy/srfit/structure/objcrystparset.py index daaa17f5..24ff151f 100644 --- a/src/diffpy/srfit/structure/objcrystparset.py +++ b/src/diffpy/srfit/structure/objcrystparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Wrappers for adapting pyobjcryst.crystal.Crystal to a srfit ParameterSet. This will adapt a Crystal or Molecule object from pyobjcryst into the @@ -40,13 +39,20 @@ __all__ = ["ObjCrystMoleculeParSet", "ObjCrystCrystalParSet"] import numpy - -from pyobjcryst.molecule import GetBondLength, GetBondAngle, GetDihedralAngle -from pyobjcryst.molecule import StretchModeBondLength, StretchModeBondAngle -from pyobjcryst.molecule import StretchModeTorsion - -from diffpy.srfit.fitbase.parameter import Parameter, ParameterAdapter -from diffpy.srfit.fitbase.parameter import ParameterProxy +from pyobjcryst.molecule import ( + GetBondAngle, + GetBondLength, + GetDihedralAngle, + StretchModeBondAngle, + StretchModeBondLength, + StretchModeTorsion, +) + +from diffpy.srfit.fitbase.parameter import ( + Parameter, + ParameterAdapter, + ParameterProxy, +) from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.structure.srrealparset import SrRealParSet @@ -81,11 +87,10 @@ def __init__(self, name, scat, parent): self.parent = parent # x, y, z, occ - self.addParameter(ParameterAdapter("x", self.scat, attr = "X")) - self.addParameter(ParameterAdapter("y", self.scat, attr = "Y")) - self.addParameter(ParameterAdapter("z", self.scat, attr = "Z")) - self.addParameter(ParameterAdapter("occ", self.scat, attr = - "Occupancy")) + self.addParameter(ParameterAdapter("x", self.scat, attr="X")) + self.addParameter(ParameterAdapter("y", self.scat, attr="Y")) + self.addParameter(ParameterAdapter("z", self.scat, attr="Z")) + self.addParameter(ParameterAdapter("occ", self.scat, attr="Occupancy")) return def isDummy(self): @@ -99,6 +104,7 @@ def hasScatterers(self): # End class ObjCrystScattererParSet + class ObjCrystAtomParSet(ObjCrystScattererParSet): """A adaptor for a pyobjcryst.Atom. @@ -131,15 +137,15 @@ def __init__(self, name, atom, parent): sp = atom.GetScatteringPower() # The B-parameters - self.addParameter(ParameterAdapter("Biso", sp, attr = "Biso")) - self.addParameter(ParameterAdapter("B11", sp, attr = "B11")) - self.addParameter(ParameterAdapter("B22", sp, attr = "B22")) - self.addParameter(ParameterAdapter("B33", sp, attr = "B33")) - B12 = ParameterAdapter("B12", sp, attr = "B12") + self.addParameter(ParameterAdapter("Biso", sp, attr="Biso")) + self.addParameter(ParameterAdapter("B11", sp, attr="B11")) + self.addParameter(ParameterAdapter("B22", sp, attr="B22")) + self.addParameter(ParameterAdapter("B33", sp, attr="B33")) + B12 = ParameterAdapter("B12", sp, attr="B12") B21 = ParameterProxy("B21", B12) - B13 = ParameterAdapter("B13", sp, attr = "B13") + B13 = ParameterAdapter("B13", sp, attr="B13") B31 = ParameterProxy("B31", B13) - B23 = ParameterAdapter("B23", sp, attr = "B23") + B23 = ParameterAdapter("B23", sp, attr="B23") B32 = ParameterProxy("B32", B23) self.addParameter(B12) self.addParameter(B21) @@ -159,8 +165,10 @@ def _getElem(self): element = property(_getElem) + # End class ObjCrystAtomParSet + class ObjCrystMoleculeParSet(ObjCrystScattererParSet): """A adaptor for a pyobjcryst.Molecule. @@ -183,7 +191,7 @@ class ObjCrystMoleculeParSet(ObjCrystScattererParSet): diffpy.srfit.fitbase.parameterset.ParameterSet """ - def __init__(self, name, molecule, parent = None): + def __init__(self, name, molecule, parent=None): """Initialize. name -- The name of the scatterer @@ -194,10 +202,10 @@ def __init__(self, name, molecule, parent = None): self.stru = molecule # Add orientiation quaternion - self.addParameter(ParameterAdapter("q0", self.scat, attr = "Q0")) - self.addParameter(ParameterAdapter("q1", self.scat, attr = "Q1")) - self.addParameter(ParameterAdapter("q2", self.scat, attr = "Q2")) - self.addParameter(ParameterAdapter("q3", self.scat, attr = "Q3")) + self.addParameter(ParameterAdapter("q0", self.scat, attr="Q0")) + self.addParameter(ParameterAdapter("q1", self.scat, attr="Q1")) + self.addParameter(ParameterAdapter("q2", self.scat, attr="Q2")) + self.addParameter(ParameterAdapter("q3", self.scat, attr="Q3")) # Wrap the MolAtoms within the molecule self.atoms = [] @@ -209,7 +217,7 @@ def __init__(self, name, molecule, parent = None): if not name: raise AttributeError("Each MolAtom must have a name") if name in anames: - raise AttributeError("MolAtom name '%s' is duplicated"%name) + raise AttributeError("MolAtom name '%s' is duplicated" % name) atom = ObjCrystMolAtomParSet(name, a, self) atom.molecule = self @@ -223,10 +231,11 @@ def __init__(self, name, molecule, parent = None): def canAdapt(self, stru): """Return whether the structure can be adapted by this class.""" from pyobjcryst.molecule import Molecule + return isinstance(stru, Molecule) # Part of SrRealParSet interface - def useSymmetry(self, use = True): + def useSymmetry(self, use=True): """Set this structure to use symmetry. This structure object does not support symmetry. @@ -245,8 +254,8 @@ def usingSymmetry(self): def _getSrRealStructure(self): """Get the structure object for use with SrReal calculators. - Molecule objects are never periodic. Return the object and let the - SrReal adapters do the proper thing. + Molecule objects are never periodic. Return the object and let + the SrReal adapters do the proper thing. """ return self.stru @@ -265,18 +274,18 @@ def getLattice(self): def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.atoms def wrapRestraints(self): """Wrap the restraints implicit to the molecule. - This will wrap MolBonds, MolBondAngles and MolDihedralAngles of the - Molecule as ObjCrystMoleculeRestraint objects. + This will wrap MolBonds, MolBondAngles and MolDihedralAngles of + the Molecule as ObjCrystMoleculeRestraint objects. """ # Wrap restraints. Restraints wrapped in this way cannot be modified # from within this class. @@ -316,18 +325,17 @@ def wrapStretchModeParameters(self): atom1 = getattr(self, name1) atom2 = getattr(self, name2) - par = ObjCrystBondLengthParameter(name, atom1, atom2, mode = mode) + par = ObjCrystBondLengthParameter(name, atom1, atom2, mode=mode) atoms = [] for a in mode.GetAtoms(): name = a.GetName() - atoms.append( getattr(self, name) ) + atoms.append(getattr(self, name)) par.AddAtoms(atoms) self.addParameter(par) - for mode in self.scat.GetStretchModeBondAngleList(): name1 = mode.mpAtom0.GetName() name2 = mode.mpAtom1.GetName() @@ -339,21 +347,23 @@ def wrapStretchModeParameters(self): atom2 = getattr(self, name2) atom3 = getattr(self, name3) - par = ObjCrystBondAngleParameter(name, atom1, atom2, atom3, mode = - mode) + par = ObjCrystBondAngleParameter( + name, atom1, atom2, atom3, mode=mode + ) atoms = [] for a in mode.GetAtoms(): name = a.GetName() - atoms.append( getattr(self, name) ) + atoms.append(getattr(self, name)) par.AddAtoms(atoms) self.addParameter(par) return - def restrainBondLength(self, atom1, atom2, length, sigma, delta, scaled = - False): + def restrainBondLength( + self, atom1, atom2, length, sigma, delta, scaled=False + ): """Add a bond length restraint. This creates an instance of ObjCrystBondLengthRestraint and adds it to @@ -371,13 +381,16 @@ def restrainBondLength(self, atom1, atom2, length, sigma, delta, scaled = Returns the ObjCrystBondLengthRestraint object for use with the 'unrestrain' method. """ - res = ObjCrystBondLengthRestraint(atom1, atom2, length, sigma, delta, scaled) + res = ObjCrystBondLengthRestraint( + atom1, atom2, length, sigma, delta, scaled + ) self._restraints.add(res) return res - def restrainBondLengthParameter(self, par, length, sigma, delta, scaled = - False): + def restrainBondLengthParameter( + self, par, length, sigma, delta, scaled=False + ): """Add a bond length restraint. This creates an instance of ObjCrystBondLengthRestraint and adds it to @@ -394,11 +407,13 @@ def restrainBondLengthParameter(self, par, length, sigma, delta, scaled = Returns the ObjCrystBondLengthRestraint object for use with the 'unrestrain' method. """ - return self.restrainBondLength(par.atom1, par.atom2, length, sigma, - delta, scaled) + return self.restrainBondLength( + par.atom1, par.atom2, length, sigma, delta, scaled + ) - def restrainBondAngle(self, atom1, atom2, atom3, angle, sigma, delta, - scaled = False): + def restrainBondAngle( + self, atom1, atom2, atom3, angle, sigma, delta, scaled=False + ): """Add a bond angle restraint. This creates an instance of ObjCrystBondAngleRestraint and adds it to @@ -418,14 +433,16 @@ def restrainBondAngle(self, atom1, atom2, atom3, angle, sigma, delta, Returns the ObjCrystBondAngleRestraint object for use with the 'unrestrain' method. """ - res = ObjCrystBondAngleRestraint(atom1, atom2, atom3, angle, sigma, - delta, scaled) + res = ObjCrystBondAngleRestraint( + atom1, atom2, atom3, angle, sigma, delta, scaled + ) self._restraints.add(res) return res - def restrainBondAngleParameter(self, par, angle, sigma, delta, - scaled = False): + def restrainBondAngleParameter( + self, par, angle, sigma, delta, scaled=False + ): """Add a bond angle restraint. This creates an instance of ObjCrystBondAngleRestraint and adds it to @@ -442,11 +459,13 @@ def restrainBondAngleParameter(self, par, angle, sigma, delta, Returns the ObjCrystBondAngleRestraint object for use with the 'unrestrain' method. """ - return self.restrainBondAngle(par.atom1, par.atom2, par.atom3, angle, - sigma, delta, scaled) + return self.restrainBondAngle( + par.atom1, par.atom2, par.atom3, angle, sigma, delta, scaled + ) - def restrainDihedralAngle(self, atom1, atom2, atom3, atom4, angle, sigma, - delta, scaled = False): + def restrainDihedralAngle( + self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False + ): """Add a dihedral angle restraint. This creates an instance of ObjCrystDihedralAngleRestraint and adds it @@ -466,14 +485,16 @@ def restrainDihedralAngle(self, atom1, atom2, atom3, atom4, angle, sigma, Returns the ObjCrystDihedralAngleRestraint object for use with the 'unrestrain' method. """ - res = ObjCrystDihedralAngleRestraint(atom1, atom2, atom3, atom4, angle, - sigma, delta, scaled) + res = ObjCrystDihedralAngleRestraint( + atom1, atom2, atom3, atom4, angle, sigma, delta, scaled + ) self._restraints.add(res) return res - def restrainDihedralAngleParameter(self, par, angle, sigma, delta, - scaled = False): + def restrainDihedralAngleParameter( + self, par, angle, sigma, delta, scaled=False + ): """Add a dihedral angle restraint. This creates an instance of ObjCrystDihedralAngleRestraint and adds it @@ -491,11 +512,20 @@ def restrainDihedralAngleParameter(self, par, angle, sigma, delta, Returns the ObjCrystDihedralAngleRestraint object for use with the 'unrestrain' method. """ - return self.restrainDihedralAngle(par.atom1, par.atom2, par.atom3, - par.atom4, angle, sigma, delta, scaled) - - def addBondLengthParameter(self, name, atom1, atom2, value = None, const = - False): + return self.restrainDihedralAngle( + par.atom1, + par.atom2, + par.atom3, + par.atom4, + angle, + sigma, + delta, + scaled, + ) + + def addBondLengthParameter( + self, name, atom1, atom2, value=None, const=False + ): """Add a bond length to the Molecule. This creates a ObjCrystBondLengthParameter to the @@ -518,8 +548,9 @@ def addBondLengthParameter(self, name, atom1, atom2, value = None, const = return par - def addBondAngleParameter(self, name, atom1, atom2, atom3, value = None, - const = False): + def addBondAngleParameter( + self, name, atom1, atom2, atom3, value=None, const=False + ): """Add a bond angle to the Molecule. This creates a ObjCrystBondAngleParameter to the ObjCrystMoleculeParSet @@ -539,14 +570,16 @@ def addBondAngleParameter(self, name, atom1, atom2, atom3, value = None, Returns the new ObjCrystBondAngleParameter. """ - par = ObjCrystBondAngleParameter(name, atom1, atom2, atom3, value, - const) + par = ObjCrystBondAngleParameter( + name, atom1, atom2, atom3, value, const + ) self.addParameter(par) return par - def addDihedralAngleParameter(self, name, atom1, atom2, atom3, atom4, value - = None, const = False): + def addDihedralAngleParameter( + self, name, atom1, atom2, atom3, atom4, value=None, const=False + ): """Add a dihedral angle to the Molecule. This creates a ObjCrystDihedralAngleParameter to the @@ -568,14 +601,17 @@ def addDihedralAngleParameter(self, name, atom1, atom2, atom3, atom4, value Returns the new ObjCrystDihedralAngleParameter. """ - par = ObjCrystDihedralAngleParameter(name, atom1, atom2, atom3, atom4, - value, const) + par = ObjCrystDihedralAngleParameter( + name, atom1, atom2, atom3, atom4, value, const + ) self.addParameter(par) return par + # End class ObjCrystMoleculeParSet + class ObjCrystMolAtomParSet(ObjCrystScattererParSet): """A adaptor for an pyobjcryst.molecule.MolAtom. @@ -612,15 +648,15 @@ def __init__(self, name, scat, parent): # Only wrap this if there is a scattering power if sp is not None: - self.addParameter(ParameterAdapter("Biso", sp, attr = "Biso")) - self.addParameter(ParameterAdapter("B11", sp, attr = "B11")) - self.addParameter(ParameterAdapter("B22", sp, attr = "B22")) - self.addParameter(ParameterAdapter("B33", sp, attr = "B33")) - B12 = ParameterAdapter("B12", sp, attr = "B12") + self.addParameter(ParameterAdapter("Biso", sp, attr="Biso")) + self.addParameter(ParameterAdapter("B11", sp, attr="B11")) + self.addParameter(ParameterAdapter("B22", sp, attr="B22")) + self.addParameter(ParameterAdapter("B33", sp, attr="B33")) + B12 = ParameterAdapter("B12", sp, attr="B12") B21 = ParameterProxy("B21", B12) - B13 = ParameterAdapter("B13", sp, attr = "B13") + B13 = ParameterAdapter("B13", sp, attr="B13") B31 = ParameterProxy("B31", B13) - B23 = ParameterAdapter("B23", sp, attr = "B23") + B23 = ParameterAdapter("B23", sp, attr="B23") B32 = ParameterProxy("B32", B23) self.addParameter(B12) self.addParameter(B21) @@ -645,8 +681,10 @@ def isDummy(self): """Indicate whether this atom is a dummy atom.""" return self.scat.IsDummy() + # End class ObjCrystMolAtomParSet + class ObjCrystMoleculeRestraint(object): """Base class for adapting pyobjcryst Molecule restraints to srfit. @@ -662,7 +700,7 @@ class ObjCrystMoleculeRestraint(object): False). """ - def __init__(self, res, scaled = False): + def __init__(self, res, scaled=False): """Create a Restraint-like from a pyobjcryst Molecule restraint. res -- The pyobjcryst Molecule restraint. @@ -674,7 +712,7 @@ def __init__(self, res, scaled = False): self.scaled = scaled return - def penalty(self, w = 1.0): + def penalty(self, w=1.0): """Calculate the penalty of the restraint. w -- The point-average chi^2 which is optionally used to scale the @@ -685,8 +723,10 @@ def penalty(self, w = 1.0): penalty *= w return penalty + # End class ObjCrystMoleculeRestraint + class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): """Restrain the distance between two atoms. @@ -702,7 +742,7 @@ class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): False) """ - def __init__(self, atom1, atom2, length, sigma, delta, scaled = False): + def __init__(self, atom1, atom2, length, sigma, delta, scaled=False): """Create a bond length restraint. atom1 -- First atom (ObjCrystMolAtomParSet) in the bond @@ -724,15 +764,23 @@ def __init__(self, atom1, atom2, length, sigma, delta, scaled = False): return # Give access to the parameters of the restraint - length = property( lambda self: self.res.GetLength0(), - lambda self, val: self.res.SetLength0(val)) - sigma = property( lambda self: self.res.GetLengthSigma(), - lambda self, val: self.res.SetLengthSigma(val)) - delta = property( lambda self: self.res.GetLengthDelta(), - lambda self, val: self.res.SetLengthDelta(val)) + length = property( + lambda self: self.res.GetLength0(), + lambda self, val: self.res.SetLength0(val), + ) + sigma = property( + lambda self: self.res.GetLengthSigma(), + lambda self, val: self.res.SetLengthSigma(val), + ) + delta = property( + lambda self: self.res.GetLengthDelta(), + lambda self, val: self.res.SetLengthDelta(val), + ) + # End class ObjCrystBondLengthRestraint + class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): """Restrain the angle defined by three atoms. @@ -749,8 +797,7 @@ class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): False) """ - def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled = - False): + def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False): """Create a bond angle restraint. atom1 -- First atom (ObjCrystMolAtomParSet) in the bond angle @@ -769,22 +816,31 @@ def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled = self.atom3 = atom3 m = self.atom1.scat.GetMolecule() - res = m.AddBondAngle(atom1.scat, atom2.scat, atom3.scat, angle, - sigma, delta) + res = m.AddBondAngle( + atom1.scat, atom2.scat, atom3.scat, angle, sigma, delta + ) ObjCrystMoleculeRestraint.__init__(self, res, scaled) return # Give access to the parameters of the restraint - angle = property( lambda self: self.res.GetAngle0(), - lambda self, val: self.res.SetAngle0(val)) - sigma = property( lambda self: self.res.GetAngleSigma(), - lambda self, val: self.res.SetAngleSigma(val)) - delta = property( lambda self: self.res.GetAngleDelta(), - lambda self, val: self.res.SetAngleDelta(val)) + angle = property( + lambda self: self.res.GetAngle0(), + lambda self, val: self.res.SetAngle0(val), + ) + sigma = property( + lambda self: self.res.GetAngleSigma(), + lambda self, val: self.res.SetAngleSigma(val), + ) + delta = property( + lambda self: self.res.GetAngleDelta(), + lambda self, val: self.res.SetAngleDelta(val), + ) + # End class ObjCrystBondAngleRestraint + class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): """Restrain the dihedral (torsion) angle defined by four atoms. @@ -802,8 +858,9 @@ class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): False) """ - def __init__(self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled - = False): + def __init__( + self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False + ): """Create a dihedral angle restraint. atom1 -- First atom (ObjCrystMolAtomParSet) in the angle @@ -823,22 +880,31 @@ def __init__(self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled self.atom4 = atom4 m = self.atom1.scat.GetMolecule() - res = m.AddDihedralAngle(atom1.scat, atom2.scat, atom3.scat, - atom4.scat, angle, sigma, delta) + res = m.AddDihedralAngle( + atom1.scat, atom2.scat, atom3.scat, atom4.scat, angle, sigma, delta + ) ObjCrystMoleculeRestraint.__init__(self, res, scaled) return # Give access to the parameters of the restraint - angle = property( lambda self: self.res.GetAngle0(), - lambda self, val: self.res.SetAngle0(val)) - sigma = property( lambda self: self.res.GetAngleSigma(), - lambda self, val: self.res.SetAngleSigma(val)) - delta = property( lambda self: self.res.GetAngleDelta(), - lambda self, val: self.res.SetAngleDelta(val)) + angle = property( + lambda self: self.res.GetAngle0(), + lambda self, val: self.res.SetAngle0(val), + ) + sigma = property( + lambda self: self.res.GetAngleSigma(), + lambda self, val: self.res.SetAngleSigma(val), + ) + delta = property( + lambda self: self.res.GetAngleDelta(), + lambda self, val: self.res.SetAngleDelta(val), + ) + # End class ObjCrystDihedralAngleRestraint + class StretchModeParameter(Parameter): """Partial Parameter class encapsulating pyobjcryst stretch modes. @@ -855,7 +921,7 @@ class StretchModeParameter(Parameter): value of the parameter (bool, default True). """ - def __init__(self, name, value = None, const = False): + def __init__(self, name, value=None, const=False): """Initialization. name -- The name of this Parameter (must be a valid attribute @@ -890,9 +956,10 @@ def addAtoms(self, atomlist): """Associate ObjCrystMolAtomParSets with the Parameter. This will associate additional ObjCrystMolAtomParSets with the - Parameter. These will be mutated in the exact same way as the primary - mutated ObjCrystMolAtomParSet. This is useful when a group of atoms - should move rigidly in response to a change in a bond property. + Parameter. These will be mutated in the exact same way as the + primary mutated ObjCrystMolAtomParSet. This is useful when a + group of atoms should move rigidly in response to a change in a + bond property. """ if not hasattr(atomlist, "__iter__"): atomlist = [atomlist] @@ -912,9 +979,9 @@ def addAtoms(self, atomlist): def notify(self, other=()): """Notify all mutated Parameters and observers. - Some of the mutated parameters will be observing us. At the same time - we need to observe them. Observable won't let us do both, so we notify - the Parameters that we mutate directly. + Some of the mutated parameters will be observing us. At the same + time we need to observe them. Observable won't let us do both, + so we notify the Parameters that we mutate directly. """ noneother = () # Notify the atoms that have moved @@ -931,8 +998,10 @@ def notify(self, other=()): Parameter.notify(self, other) return + # End class StretchModeParameter + class ObjCrystBondLengthParameter(StretchModeParameter): """Class for abstracting a bond length in a Molecule to a Parameter. @@ -980,8 +1049,7 @@ class ObjCrystBondLengthParameter(StretchModeParameter): used by some optimizers when the Parameter is varied. """ - def __init__(self, name, atom1, atom2, value = None, const = False, mode = - None): + def __init__(self, name, atom1, atom2, value=None, const=False, mode=None): """Create a ObjCrystBondLengthParameter. name -- The name of the ObjCrystBondLengthParameter @@ -1024,7 +1092,7 @@ def __init__(self, name, atom1, atom2, value = None, const = False, mode = return - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. This sets the underlying ObjCrystMolAtomParSet positions const as well. @@ -1046,9 +1114,9 @@ def setConst(self, const = True, value = None): def getValue(self): """This calculates the value if it might have been changed. - There is no guarantee that the ObjCrystMolAtomParSets underlying the - bond won't change, so the bond length is calculated if necessary each - time this is called. + There is no guarantee that the ObjCrystMolAtomParSets underlying + the bond won't change, so the bond length is calculated if + necessary each time this is called. """ if self._value is None: val = GetBondLength(self.atom1.scat, self.atom2.scat) @@ -1059,6 +1127,7 @@ def getValue(self): # End class ObjCrystBondLengthParameter + class ObjCrystBondAngleParameter(StretchModeParameter): """Class for abstracting a bond angle in a Molecule to a Parameter. @@ -1092,8 +1161,9 @@ class ObjCrystBondAngleParameter(StretchModeParameter): used by some optimizers when the Parameter is varied. """ - def __init__(self, name, atom1, atom2, atom3, value = None, const = False, - mode = None): + def __init__( + self, name, atom1, atom2, atom3, value=None, const=False, mode=None + ): """Create a ObjCrystBondAngleParameter. name -- The name of the ObjCrystBondAngleParameter. @@ -1114,8 +1184,9 @@ def __init__(self, name, atom1, atom2, atom3, value = None, const = False, # Create the stretch mode self.mode = mode if mode is None: - self.mode = StretchModeBondAngle(atom1.scat, atom2.scat, - atom3.scat, None) + self.mode = StretchModeBondAngle( + atom1.scat, atom2.scat, atom3.scat, None + ) # We only add the last atom. This is the one that will move self.mode.AddAtom(atom3.scat) self.matoms = set([atom3]) @@ -1139,7 +1210,7 @@ def __init__(self, name, atom1, atom2, atom3, value = None, const = False, return - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. This sets the underlying ObjCrystMolAtomParSet positions const as well. @@ -1160,19 +1231,22 @@ def setConst(self, const = True, value = None): def getValue(self): """This calculates the value if it might have been changed. - There is no guarantee that the MolAtoms underlying the bond angle won't - change, so the bond angle is calculated if necessary each time this is - called. + There is no guarantee that the MolAtoms underlying the bond + angle won't change, so the bond angle is calculated if necessary + each time this is called. """ if self._value is None: - val = GetBondAngle(self.atom1.scat, self.atom2.scat, - self.atom3.scat) + val = GetBondAngle( + self.atom1.scat, self.atom2.scat, self.atom3.scat + ) Parameter.setValue(self, val) return self._value + # End class ObjCrystBondAngleParameter + class ObjCrystDihedralAngleParameter(StretchModeParameter): """Class for abstracting a dihedral angle in a Molecule to a Parameter. @@ -1209,8 +1283,17 @@ class ObjCrystDihedralAngleParameter(StretchModeParameter): used by some optimizers when the Parameter is varied. """ - def __init__(self, name, atom1, atom2, atom3, atom4, value = None, const = - False, mode = None): + def __init__( + self, + name, + atom1, + atom2, + atom3, + atom4, + value=None, + const=False, + mode=None, + ): """Create a ObjCrystDihedralAngleParameter. name -- The name of the ObjCrystDihedralAngleParameter @@ -1253,14 +1336,15 @@ def __init__(self, name, atom1, atom2, atom3, atom4, value = None, const = # We do this last so the atoms are defined before we set any values. if value is None: - value = GetDihedralAngle(atom1.scat, atom2.scat, atom3.scat, - atom4.scat) + value = GetDihedralAngle( + atom1.scat, atom2.scat, atom3.scat, atom4.scat + ) StretchModeParameter.__init__(self, name, value, const) self.setConst(const) return - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. This sets the underlying ObjCrystMolAtomParSet positions const as well. @@ -1281,19 +1365,25 @@ def setConst(self, const = True, value = None): def getValue(self): """This calculates the value if it might have been changed. - There is no guarantee that the ObjCrystMolAtomParSets underlying the - dihedral angle won't change from some other Parameter, so the value is - recalculated each time. + There is no guarantee that the ObjCrystMolAtomParSets underlying + the dihedral angle won't change from some other Parameter, so + the value is recalculated each time. """ if self._value is None: - val = GetDihedralAngle(self.atom1.scat, self.atom2.scat, - self.atom3.scat, self.atom4.scat) + val = GetDihedralAngle( + self.atom1.scat, + self.atom2.scat, + self.atom3.scat, + self.atom4.scat, + ) Parameter.setValue(self, val) return self._value + # End class ObjCrystDihedralAngleParameter + class ObjCrystCrystalParSet(SrRealParSet): """A adaptor for pyobjcryst.crystal.Crystal instance. @@ -1332,14 +1422,12 @@ def __init__(self, name, cryst): self.stru = cryst self._sgpars = None - self.addParameter(ParameterAdapter("a", self.stru, attr = "a")) - self.addParameter(ParameterAdapter("b", self.stru, attr = "b")) - self.addParameter(ParameterAdapter("c", self.stru, attr = "c")) - self.addParameter(ParameterAdapter("alpha", self.stru, attr = - "alpha")) - self.addParameter(ParameterAdapter("beta", self.stru, attr = "beta")) - self.addParameter(ParameterAdapter("gamma", self.stru, attr = - "gamma")) + self.addParameter(ParameterAdapter("a", self.stru, attr="a")) + self.addParameter(ParameterAdapter("b", self.stru, attr="b")) + self.addParameter(ParameterAdapter("c", self.stru, attr="c")) + self.addParameter(ParameterAdapter("alpha", self.stru, attr="alpha")) + self.addParameter(ParameterAdapter("beta", self.stru, attr="beta")) + self.addParameter(ParameterAdapter("gamma", self.stru, attr="gamma")) # Now we must loop over the scatterers and create parameter sets from # them. @@ -1352,7 +1440,7 @@ def __init__(self, name, cryst): if not name: raise ValueError("Each Scatterer must have a name") if name in snames: - raise ValueError("Scatterer name '%s' is duplicated"%name) + raise ValueError("Scatterer name '%s' is duplicated" % name) # Now create the proper object cname = s.GetClassName() @@ -1361,7 +1449,7 @@ def __init__(self, name, cryst): elif cname == "Molecule": parset = ObjCrystMoleculeParSet(name, s, self) else: - raise TypeError("Unrecognized scatterer '%s'"%cname) + raise TypeError("Unrecognized scatterer '%s'" % cname) self.addParameterSet(parset) self.scatterers.append(parset) @@ -1375,11 +1463,18 @@ def _constrainSpaceGroup(self): return self._sgpars sg = self._createSpaceGroup(self.stru.GetSpaceGroup()) from diffpy.srfit.structure.sgconstraints import _constrainAsSpaceGroup + adpsymbols = ["B11", "B22", "B33", "B12", "B13", "B23"] isosymbol = "Biso" sgoffset = [0, 0, 0] - self._sgpars = _constrainAsSpaceGroup(self, sg, self.scatterers, - sgoffset, adpsymbols = adpsymbols, isosymbol = isosymbol) + self._sgpars = _constrainAsSpaceGroup( + self, + sg, + self.scatterers, + sgoffset, + adpsymbols=adpsymbols, + isosymbol=isosymbol, + ) return self._sgpars sgpars = property(_constrainSpaceGroup) @@ -1395,11 +1490,13 @@ def _createSpaceGroup(sgobjcryst): the actual space group. """ import copy + from diffpy.structure.spacegroups import GetSpaceGroup, SymOp + name = sgobjcryst.GetName() extnstr = ":%s" % sgobjcryst.GetExtension() if name.endswith(extnstr): - name = name[:-len(extnstr)] + name = name[: -len(extnstr)] # Get whatever spacegroup we can get by name. This will set the proper # crystal system. Creating a copy of the singleton from GetSpaceGroup, @@ -1415,7 +1512,7 @@ def _createSpaceGroup(sgobjcryst): for shift, rot in symops: tv = trans + shift tv -= numpy.floor(tv) - sg.symop_list.append( SymOp(rot, tv) ) + sg.symop_list.append(SymOp(rot, tv)) if sgobjcryst.IsCentrosymmetric(): center = sgobjcryst.GetInversionCenter() @@ -1423,7 +1520,7 @@ def _createSpaceGroup(sgobjcryst): for shift, rot in symops: tv = center - trans - shift tv -= numpy.floor(tv) - sg.symop_list.append( SymOp(-rot , tv) ) + sg.symop_list.append(SymOp(-rot, tv)) return sg @@ -1431,6 +1528,7 @@ def _createSpaceGroup(sgobjcryst): def canAdapt(self, stru): """Return whether the structure can be adapted by this class.""" from pyobjcryst.crystal import Crystal + return isinstance(stru, Crystal) def getLattice(self): @@ -1440,11 +1538,12 @@ def getLattice(self): def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.scatterers + # End class ObjCrystCrystalParSet diff --git a/src/diffpy/srfit/structure/sgconstraints.py b/src/diffpy/srfit/structure/sgconstraints.py index fe1edc15..21bb8378 100644 --- a/src/diffpy/srfit/structure/sgconstraints.py +++ b/src/diffpy/srfit/structure/sgconstraints.py @@ -12,22 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Code to set space group constraints for a crystal structure.""" import re + import numpy -from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer from diffpy.srfit.fitbase.parameter import ParameterProxy +from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer __all__ = ["constrainAsSpaceGroup"] -def constrainAsSpaceGroup(phase, spacegroup, scatterers = None, - sgoffset = [0, 0, 0], constrainlat = True, constrainadps = True, - adpsymbols = None, isosymbol = "Uiso"): +def constrainAsSpaceGroup( + phase, + spacegroup, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): """Constrain the structure to the space group. This applies space group constraints to a StructureParSet with P1 @@ -84,14 +91,30 @@ def constrainAsSpaceGroup(phase, spacegroup, scatterers = None, sg = spacegroup if not isinstance(spacegroup, SpaceGroup): sg = GetSpaceGroup(spacegroup) - sgp = _constrainAsSpaceGroup(phase, sg, scatterers, sgoffset, - constrainlat, constrainadps, adpsymbols, isosymbol) + sgp = _constrainAsSpaceGroup( + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) return sgp -def _constrainAsSpaceGroup(phase, sg, scatterers = None, - sgoffset = [0, 0, 0], constrainlat = True, constrainadps = True, - adpsymbols = None, isosymbol = "Uiso"): + +def _constrainAsSpaceGroup( + phase, + sg, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): """Restricted interface to constrainAsSpaceGroup. Arguments: As constrainAsSpaceGroup, except @@ -105,13 +128,23 @@ def _constrainAsSpaceGroup(phase, sg, scatterers = None, if adpsymbols is None: adpsymbols = stdUsymbols - sgp = SpaceGroupParameters(phase, sg, scatterers, sgoffset, - constrainlat, constrainadps, adpsymbols, isosymbol) + sgp = SpaceGroupParameters( + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) return sgp + # End constrainAsSpaceGroup + class BaseSpaceGroupParameters(RecipeContainer): """Base class for holding space group Parameters. @@ -125,7 +158,7 @@ class is to make it easy to access the free variables of a structure for name -- "sgpars" """ - def __init__(self, name = "sgpars"): + def __init__(self, name="sgpars"): """Create the BaseSpaceGroupParameters object. This initializes the attributes. @@ -133,7 +166,7 @@ def __init__(self, name = "sgpars"): RecipeContainer.__init__(self, name) return - def addParameter(self, par, check = True): + def addParameter(self, par, check=True): """Store a Parameter. par -- The Parameter to be stored. @@ -146,8 +179,10 @@ def addParameter(self, par, check = True): RecipeContainer._addObject(self, par, self._parameters, check) return + # End class BaseSpaceGroupParameters + class SpaceGroupParameters(BaseSpaceGroupParameters): """Class for holding and creating space group Parameters. @@ -177,8 +212,17 @@ class SpaceGroupParameters(BaseSpaceGroupParameters): adppars -- Property that populates _adppars. """ - def __init__(self, phase, sg, scatterers, sgoffset, constrainlat, - constrainadps, adpsymbols, isosymbol): + def __init__( + self, + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ): """Create the SpaceGroupParameters object. Arguments: @@ -219,13 +263,16 @@ def __init__(self, phase, sg, scatterers, sgoffset, constrainlat, def __iter__(self): """Iterate over top-level parameters.""" - if self._latpars is None or\ - self._xyzpars is None or\ - self._adppars is None: - self._makeConstraints() + if ( + self._latpars is None + or self._xyzpars is None + or self._adppars is None + ): + self._makeConstraints() return RecipeContainer.__iter__(self) latpars = property(lambda self: self._getLatPars()) + def _getLatPars(self): """Accessor for _latpars.""" if self._latpars is None: @@ -233,9 +280,10 @@ def _getLatPars(self): return self._latpars xyzpars = property(lambda self: self._getXYZPars()) + def _getXYZPars(self): """Accessor for _xyzpars.""" - positions = [] + positions = [] for scatterer in self.scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) @@ -244,9 +292,10 @@ def _getXYZPars(self): return self._xyzpars adppars = property(lambda self: self._getADPPars()) + def _getADPPars(self): """Accessor for _adppars.""" - positions = [] + positions = [] for scatterer in self.scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) @@ -266,7 +315,7 @@ def _makeConstraints(self): scatterers = self.scatterers # Prepare positions - positions = [] + positions = [] for scatterer in scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) @@ -277,11 +326,11 @@ def _makeConstraints(self): return - def _clearConstraints(self): """Clear old constraints. - This only clears constraints where new ones are going to be applied. + This only clears constraints where new ones are going to be + applied. """ phase = self.phase scatterers = self.scatterers @@ -300,8 +349,14 @@ def _clearConstraints(self): if self.constrainlat: lattice = phase.getLattice() - latpars = [lattice.a, lattice.b, lattice.c, lattice.alpha, - lattice.beta, lattice.gamma] + latpars = [ + lattice.a, + lattice.b, + lattice.c, + lattice.alpha, + lattice.beta, + lattice.gamma, + ] for par in latpars: if lattice.isConstrained(par): lattice.unconstrain(par) @@ -329,7 +384,8 @@ def _clearConstraints(self): def _constrainLattice(self): """Constrain the lattice parameters.""" - if not self.constrainlat: return + if not self.constrainlat: + return phase = self.phase sg = self.sg @@ -345,8 +401,14 @@ def _constrainLattice(self): # Now get the unconstrained, non-constant lattice pars and store them. self._latpars = BaseSpaceGroupParameters("latpars") - latpars = [lattice.a, lattice.b, lattice.c, lattice.alpha, - lattice.beta, lattice.gamma] + latpars = [ + lattice.a, + lattice.b, + lattice.c, + lattice.alpha, + lattice.beta, + lattice.gamma, + ] pars = [p for p in latpars if not p.const and not p.constrained] for par in pars: # FIXME - the original parameter will still appear as @@ -375,9 +437,9 @@ def _constrainXYZs(self, positions): self._xyzpars = BaseSpaceGroupParameters("xyzpars") # Make proxies to the free xyz parameters - xyznames = [name[:1]+"_"+name[1:] for name, val in g.pospars] + xyznames = [name[:1] + "_" + name[1:] for name, val in g.pospars] for pname in xyznames: - name, idx = pname.rsplit('_', 1) + name, idx = pname.rsplit("_", 1) idx = int(idx) par = scatterers[idx].get(name) newpar = self.__addPar(pname, par) @@ -390,8 +452,9 @@ def _constrainXYZs(self, positions): # Extract the constraint equation from the formula for parname, formula in fp.items(): - _makeconstraint(parname, formula, scatterer, idx, - self._parameters) + _makeconstraint( + parname, formula, scatterer, idx, self._parameters + ) return @@ -401,10 +464,13 @@ def _constrainADPs(self, positions): positions -- The coordinates of the scatterers. """ - from diffpy.structure.symmetryutilities import stdUsymbols - from diffpy.structure.symmetryutilities import SymmetryConstraints + from diffpy.structure.symmetryutilities import ( + SymmetryConstraints, + stdUsymbols, + ) - if not self.constrainadps: return + if not self.constrainadps: + return sg = self.sg sgoffset = self.sgoffset @@ -427,10 +493,10 @@ def _constrainADPs(self, positions): nonadps.append(sidx) continue - Uij = numpy.zeros((3,3), dtype=float) + Uij = numpy.zeros((3, 3), dtype=float) for idx, par in enumerate(pars): i, j = _idxtoij[idx] - Uij[i,j] = Uij[j,i] = par.getValue() + Uij[i, j] = Uij[j, i] = par.getValue() Uijs.append(Uij) @@ -450,7 +516,7 @@ def _constrainADPs(self, positions): isoidx = [] isonames = [] for pname in adpnames: - name, idx = pname.rsplit('_', 1) + name, idx = pname.rsplit("_", 1) idx = int(idx) # Check for isotropic ADPs scatterer = scatterers[idx] @@ -471,24 +537,26 @@ def _constrainADPs(self, positions): # Constrain dependent isotropics for idx, isoname in zip(isoidx[:], isonames): for j in g.coremap[idx]: - if j == idx: continue + if j == idx: + continue isoidx.append(j) scatterer = scatterers[j] - scatterer.constrain(isosymbol, isoname, ns = self._parameters) + scatterer.constrain(isosymbol, isoname, ns=self._parameters) fadp = g.UFormulas(adpnames) # Constrain dependent anisotropics. We use the fact that an # anisotropic cannot be dependent on an isotropic. for idx, tmp in enumerate(zip(scatterers, fadp)): - if idx in isoidx: continue + if idx in isoidx: + continue scatterer, fa = tmp # Extract the constraint equation from the formula for stdparname, formula in fa.items(): pname = adpmap[stdparname] - _makeconstraint(pname, formula, scatterer, idx, - self._parameters) - + _makeconstraint( + pname, formula, scatterer, idx, self._parameters + ) def __addPar(self, parname, par): """Constrain a parameter via proxy with a specified name. @@ -500,24 +568,28 @@ def __addPar(self, parname, par): self.addParameter(newpar) return newpar + # End class SpaceGroupParameters # crystal system rules # ref: Benjamin, W. A., Introduction to crystallography, # New York (1969), p.60 + def _constrainTriclinic(lattice): """Make constraints for Triclinic systems.""" return + def _constrainMonoclinic(lattice): """Make constraints for Monoclinic systems. - alpha and beta are fixed to 90 unless alpha != beta and alpha == gamma, in - which case alpha and gamma are constrained to 90. + alpha and beta are fixed to 90 unless alpha != beta and alpha == + gamma, in which case alpha and gamma are constrained to 90. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.setConst(True, ang90) beta = lattice.beta.getValue() @@ -529,26 +601,31 @@ def _constrainMonoclinic(lattice): lattice.beta.setConst(True, ang90) return + def _constrainOrthorhombic(lattice): """Make constraints for Orthorhombic systems. alpha, beta and gamma are constrained to 90 """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.setConst(True, ang90) lattice.beta.setConst(True, ang90) lattice.gamma.setConst(True, ang90) return + def _constrainTetragonal(lattice): """Make constraints for Tetragonal systems. - b is constrained to a and alpha, beta and gamma are constrained to 90. + b is constrained to a and alpha, beta and gamma are constrained to + 90. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.setConst(True, ang90) lattice.beta.setConst(True, ang90) @@ -556,15 +633,17 @@ def _constrainTetragonal(lattice): lattice.constrain(lattice.b, lattice.a) return + def _constrainTrigonal(lattice): """Make constraints for Trigonal systems. - If gamma == 120, then b is constrained to a, alpha and beta are constrained - to 90 and gamma is constrained to 120. Otherwise, b and c are constrained - to a, beta and gamma are constrained to alpha. + If gamma == 120, then b is constrained to a, alpha and beta are + constrained to 90 and gamma is constrained to 120. Otherwise, b and + c are constrained to a, beta and gamma are constrained to alpha. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor ang120 = 120.0 * afactor if lattice.gamma.getValue() == ang120: @@ -579,14 +658,16 @@ def _constrainTrigonal(lattice): lattice.constrain(lattice.gamma, lattice.alpha) return + def _constrainHexagonal(lattice): """Make constraints for Hexagonal systems. - b is constrained to a, alpha and beta are constrained to 90 and gamma is - constrained to 120. + b is constrained to a, alpha and beta are constrained to 90 and + gamma is constrained to 120. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor ang120 = 120.0 * afactor lattice.constrain(lattice.b, lattice.a) @@ -595,13 +676,16 @@ def _constrainHexagonal(lattice): lattice.gamma.setConst(True, ang120) return + def _constrainCubic(lattice): """Make constraints for Cubic systems. - b and c are constrained to a, alpha, beta and gamma are constrained to 90. + b and c are constrained to a, alpha, beta and gamma are constrained + to 90. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.constrain(lattice.b, lattice.a) lattice.constrain(lattice.c, lattice.a) @@ -610,19 +694,21 @@ def _constrainCubic(lattice): lattice.gamma.setConst(True, ang90) return + # This is used to map the correct crystal system to the proper constraint # function. _constraintMap = { - "Triclinic" : _constrainTriclinic, - "Monoclinic" : _constrainMonoclinic, - "Orthorhombic" : _constrainOrthorhombic, - "Tetragonal" : _constrainTetragonal, - "Trigonal" : _constrainTrigonal, - "Hexagonal" : _constrainHexagonal, - "Cubic" : _constrainCubic + "Triclinic": _constrainTriclinic, + "Monoclinic": _constrainMonoclinic, + "Orthorhombic": _constrainOrthorhombic, + "Tetragonal": _constrainTetragonal, + "Trigonal": _constrainTrigonal, + "Hexagonal": _constrainHexagonal, + "Cubic": _constrainCubic, } -def _makeconstraint(parname, formula, scatterer, idx, ns = {}): + +def _makeconstraint(parname, formula, scatterer, idx, ns={}): """Constrain a parameter according to a formula. parname -- Name of parameter @@ -638,10 +724,10 @@ def _makeconstraint(parname, formula, scatterer, idx, ns = {}): if par is None: return - compname = "%s_%i"%(parname, idx) + compname = "%s_%i" % (parname, idx) # Check to see if this parameter is free - pat = r'%s *([+-] *\d+)?$' % compname + pat = r"%s *([+-] *\d+)?$" % compname if re.match(pat, formula): return par @@ -654,9 +740,10 @@ def _makeconstraint(parname, formula, scatterer, idx, ns = {}): # If we got here, then we have a constraint equation # Fix any division issues formula = formula.replace("/", "*1.0/") - scatterer.constrain(par, formula, ns = ns) + scatterer.constrain(par, formula, ns=ns) return + def _getFloat(formula): """Get a float from a formula string, or None if this is not possible.""" try: @@ -664,6 +751,7 @@ def _getFloat(formula): except NameError: return None + # Constants needed above _idxtoij = [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)] deg2rad = numpy.pi / 180 diff --git a/src/diffpy/srfit/structure/srrealparset.py b/src/diffpy/srfit/structure/srrealparset.py index 6ea9a09e..4bd8b877 100644 --- a/src/diffpy/srfit/structure/srrealparset.py +++ b/src/diffpy/srfit/structure/srrealparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Structure wrapper class for structures compatible with SrReal.""" __all__ = ["SrRealParSet"] @@ -40,7 +39,7 @@ def __init__(self, *args, **kw): self.stru = None return - def restrainBVS(self, sig = 1, scaled = False): + def restrainBVS(self, sig=1, scaled=False): """Restrain the bond-valence sum to zero. This adds a penalty to the cost function equal to @@ -67,10 +66,11 @@ def restrainBVS(self, sig = 1, scaled = False): # Return the Restraint object return res - def useSymmetry(self, use = True): + def useSymmetry(self, use=True): """Set this structure to use symmetry. - This determines how the structure is treated by SrReal calculators. + This determines how the structure is treated by SrReal + calculators. """ self._usesymmetry = bool(use) return @@ -82,10 +82,11 @@ def usingSymmetry(self): def _getSrRealStructure(self): """Get the structure object for use with SrReal calculators. - If this is periodic, then return the structure, otherwise, pass it - inside of a nosymmetry wrapper. + If this is periodic, then return the structure, otherwise, pass + it inside of a nosymmetry wrapper. """ from diffpy.srreal.structureadapter import nosymmetry + if self._usesymmetry: return self.stru return nosymmetry(self.stru) diff --git a/src/diffpy/srfit/util/__init__.py b/src/diffpy/srfit/util/__init__.py index ed39b3b6..023b4256 100644 --- a/src/diffpy/srfit/util/__init__.py +++ b/src/diffpy/srfit/util/__init__.py @@ -12,10 +12,9 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Utilities and constants used throughout SrFit.""" -_DASHEDLINE = 78 * '-' +_DASHEDLINE = 78 * "-" def sortKeyForNumericString(s): @@ -39,10 +38,10 @@ def sortKeyForNumericString(s): """ if sortKeyForNumericString._rx is None: import re - sortKeyForNumericString._rx = re.compile(r'(\d+)') + + sortKeyForNumericString._rx = re.compile(r"(\d+)") rx = sortKeyForNumericString._rx - rv = tuple((int(w) if i % 2 else w) - for i, w in enumerate(rx.split(s))) + rv = tuple((int(w) if i % 2 else w) for i, w in enumerate(rx.split(s))) return rv diff --git a/src/diffpy/srfit/util/argbinders.py b/src/diffpy/srfit/util/argbinders.py index 5c659fa4..28cd5144 100644 --- a/src/diffpy/srfit/util/argbinders.py +++ b/src/diffpy/srfit/util/argbinders.py @@ -12,12 +12,10 @@ # See LICENSE.txt for license information. # ############################################################################## - """Functions for binding arguments of callable objects.""" class bind2nd(object): - """Freeze second argument of a callable object to a given constant.""" def __init__(self, func, arg1): @@ -27,5 +25,5 @@ def __init__(self, func, arg1): return def __call__(self, *args, **kwargs): - boundargs = ((args[0], self.arg1) + args[1:]) + boundargs = (args[0], self.arg1) + args[1:] return self.func(*boundargs, **kwargs) diff --git a/src/diffpy/srfit/util/inpututils.py b/src/diffpy/srfit/util/inpututils.py index 12372eb3..4117de0b 100644 --- a/src/diffpy/srfit/util/inpututils.py +++ b/src/diffpy/srfit/util/inpututils.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Input utilities.""" __all__ = ["inputToString"] @@ -40,11 +39,12 @@ def inputToString(inpt): # TODO remove handling of string input accept only file or filename # FIXME check for typos in the file name elif os.path.exists(inpt) or (len(inpt) < 80 and inpt.count("\n") == 0): - with open(inpt, 'r') as infile: + with open(inpt, "r") as infile: inptstr = infile.read() else: inptstr = inpt return inptstr + # End of file diff --git a/src/diffpy/srfit/util/nameutils.py b/src/diffpy/srfit/util/nameutils.py index c9b0d85e..e11820be 100644 --- a/src/diffpy/srfit/util/nameutils.py +++ b/src/diffpy/srfit/util/nameutils.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Name utilities.""" __all__ = ["isIdentifier", "validateName"] import re -reident = re.compile(r'^[a-zA-Z_]\w*$') +reident = re.compile(r"^[a-zA-Z_]\w*$") def isIdentifier(s): @@ -42,4 +41,5 @@ def validateName(name): raise ValueError(f"Name {name} is not a valid identifier") return + # End of file diff --git a/src/diffpy/srfit/util/observable.py b/src/diffpy/srfit/util/observable.py index 9cd24d41..d638af47 100644 --- a/src/diffpy/srfit/util/observable.py +++ b/src/diffpy/srfit/util/observable.py @@ -38,6 +38,7 @@ class Observable(object): the list of handlers to invoke notify: invoke the registered handlers in the order in which they were registered """ + def notify(self, other=()): """Notify all observers.""" # build a list before notification, just in case the observer's @@ -74,6 +75,7 @@ def __init__(self, **kwds): self._observers = set() return + # end of class Observable # Local helpers -------------------------------------------------------------- @@ -87,4 +89,5 @@ def _fbRemoveObserver(fobs, semaphors): observable.removeObserver(fobs) return + # end of file diff --git a/src/diffpy/srfit/util/tagmanager.py b/src/diffpy/srfit/util/tagmanager.py index 30c5bcdc..65e4ab9e 100644 --- a/src/diffpy/srfit/util/tagmanager.py +++ b/src/diffpy/srfit/util/tagmanager.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """TagManager class. -The TagManager class takes hashable objects and assigns tags to them. Objects -can then be easily referenced via their assigned tags. +The TagManager class takes hashable objects and assigns tags to them. +Objects can then be easily referenced via their assigned tags. """ __all__ = ["TagManager"] @@ -135,7 +134,8 @@ def verifyTags(self, *tags): def __getObjectSet(self, tag): """Helper function for getting an object set with given tag. - Raises KeyError if a passed tag does not exist and self.silent is False + Raises KeyError if a passed tag does not exist and self.silent + is False """ oset = self._tagdict.get(str(tag)) if oset is None: @@ -144,6 +144,7 @@ def __getObjectSet(self, tag): oset = set() return oset + # End class TagManager # End of file diff --git a/src/diffpy/srfit/util/weakrefcallable.py b/src/diffpy/srfit/util/weakrefcallable.py index 0803330f..287d2e47 100644 --- a/src/diffpy/srfit/util/weakrefcallable.py +++ b/src/diffpy/srfit/util/weakrefcallable.py @@ -12,12 +12,11 @@ # See LICENSE.txt for license information. # ############################################################################## - """Picklable storage of callable objects using weak references.""" -import weakref import types +import weakref import six @@ -46,7 +45,7 @@ class WeakBoundMethod(object): This is only used for pickling. """ - __slots__ = ('function', 'fallback', '_wref', '_class') + __slots__ = ("function", "fallback", "_wref", "_class") def __init__(self, f, fallback=None): """Create a weak reference wrapper to bound method. @@ -100,9 +99,9 @@ def __hash__(self): return hash((self.function, self._wref)) def __eq__(self, other): - rv = (self.function == other.function and - (self._wref == other._wref or - None is self._wref() is other._wref())) + rv = self.function == other.function and ( + self._wref == other._wref or None is self._wref() is other._wref() + ) return rv def __ne__(self, other): @@ -138,6 +137,7 @@ def __setstate__(self, state): def __mimic_empty_ref(): return None + # end of class WeakBoundMethod diff --git a/tests/__init__.py b/tests/__init__.py index 70359f35..7f498f76 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for diffpy.srfit.""" -import unittest import logging +import unittest # create logger instance for the tests subpackage logging.basicConfig() @@ -24,7 +23,7 @@ del logging -def testsuite(pattern=''): +def testsuite(pattern=""): """Create a unit tests suite for diffpy.srfit package. Parameters @@ -40,12 +39,14 @@ def testsuite(pattern=''): The TestSuite object containing the matching tests. """ import re - from os.path import dirname from itertools import chain + from os.path import dirname + from pkg_resources import resource_filename + loader = unittest.defaultTestLoader - thisdir = resource_filename(__name__, '') - depth = __name__.count('.') + 1 + thisdir = resource_filename(__name__, "") + depth = __name__.count(".") + 1 topdir = thisdir for i in range(depth): topdir = dirname(topdir) @@ -55,12 +56,12 @@ def testsuite(pattern=''): rx = re.compile(pattern) tsuites = list(chain.from_iterable(suite_all)) tsok = all(isinstance(ts, unittest.TestSuite) for ts in tsuites) - if not tsok: # pragma: no cover + if not tsok: # pragma: no cover return suite_all tcases = chain.from_iterable(tsuites) for tc in tcases: - tcwords = tc.id().split('.') - shortname = '.'.join(tcwords[-3:]) + tcwords = tc.id().split(".") + shortname = ".".join(tcwords[-3:]) if rx.search(shortname): suite.addTest(tc) # verify all tests are found for an empty pattern. diff --git a/tests/debug.py b/tests/debug.py index fa1caf2f..8ebfc4ad 100644 --- a/tests/debug.py +++ b/tests/debug.py @@ -12,7 +12,6 @@ # See LICENSE.txt for license information. # ############################################################################## - """Convenience module for debugging the unit tests using. python -m diffpy.srfit.tests.debug @@ -21,10 +20,12 @@ """ -if __name__ == '__main__': +if __name__ == "__main__": import sys + from diffpy.srfit.tests import testsuite - pattern = sys.argv[1] if len(sys.argv) > 1 else '' + + pattern = sys.argv[1] if len(sys.argv) > 1 else "" suite = testsuite(pattern) suite.debug() diff --git a/tests/run.py b/tests/run.py index 5e337417..a375045e 100644 --- a/tests/run.py +++ b/tests/run.py @@ -12,22 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Convenience module for executing all unit tests with. python -m diffpy.srfit.tests.run """ -if __name__ == '__main__': +if __name__ == "__main__": import sys + # show warnings by default if not sys.warnoptions: - import os, warnings + import os + import warnings + warnings.simplefilter("default") # also affect subprocesses os.environ["PYTHONWARNINGS"] = "default" from diffpy.srfit.tests import test + # produce zero exit code for a successful test sys.exit(not test().wasSuccessful()) diff --git a/tests/test_builder.py b/tests/test_builder.py index 7e61834f..d453a122 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest @@ -21,8 +20,8 @@ import diffpy.srfit.equation.builder as builder import diffpy.srfit.equation.literals as literals -from .utils import _makeArgs -from .utils import noObserversInGlobalBuilders + +from .utils import _makeArgs, noObserversInGlobalBuilders class TestBuilder(unittest.TestCase): @@ -52,7 +51,6 @@ def testRegisterArg(self): self.assertTrue(noObserversInGlobalBuilders()) return - def testRegisterOperator(self): """Try to use an operator without arguments in an equation.""" @@ -86,13 +84,13 @@ def testRegisterOperator(self): self.assertTrue(noObserversInGlobalBuilders()) return - def testSwapping(self): def g1(v1, v2, v3, v4): return (v1 + v2) * (v3 + v4) + def g2(v1): - return 0.5*v1 + return 0.5 * v1 factory = builder.EquationFactory() v1, v2, v3, v4, v5 = _makeArgs(5) @@ -149,7 +147,7 @@ def g2(v1): def testParseEquation(self): - from numpy import sin, divide, sqrt, array_equal, e + from numpy import array_equal, divide, e, sin, sqrt factory = builder.EquationFactory() @@ -163,8 +161,8 @@ def testParseEquation(self): eq.x.setValue(x) eq.B.setValue(B) eq.C.setValue(C) - f = lambda A, x, B, C: A*sin(0.5*x)+divide(B,C) - self.assertTrue(array_equal(eq(), f(A,x,B,C))) + f = lambda A, x, B, C: A * sin(0.5 * x) + divide(B, C) + self.assertTrue(array_equal(eq(), f(A, x, B, C))) # Make sure that the arguments of eq are listed in the order in which # they appear in the equations. @@ -176,8 +174,8 @@ def testParseEquation(self): sigma = 0.1 eq.x.setValue(x) eq.sigma.setValue(sigma) - f = lambda x, sigma : sqrt(e**(-0.5*(x/sigma)**2)) - self.assertTrue(numpy.allclose(eq(), f(x,sigma))) + f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) + self.assertTrue(numpy.allclose(eq(), f(x, sigma))) self.assertEqual(eq.args, [eq.x, eq.sigma]) @@ -186,14 +184,14 @@ def testParseEquation(self): eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") self.assertTrue("sigma" in eq.argdict) self.assertTrue("x" not in eq.argdict) - self.assertTrue(numpy.allclose(eq(sigma=sigma), f(x,sigma))) + self.assertTrue(numpy.allclose(eq(sigma=sigma), f(x, sigma))) self.assertEqual(eq.args, [eq.sigma]) # Equation with user-defined functions factory.registerFunction("myfunc", eq, ["sigma"]) eq2 = factory.makeEquation("c*myfunc(sigma)") - self.assertTrue(numpy.allclose(eq2(c=2, sigma=sigma), 2*f(x,sigma))) + self.assertTrue(numpy.allclose(eq2(c=2, sigma=sigma), 2 * f(x, sigma))) self.assertTrue("sigma" in eq2.argdict) self.assertTrue("c" in eq2.argdict) self.assertEqual(eq2.args, [eq2.c, eq2.sigma]) @@ -201,34 +199,32 @@ def testParseEquation(self): self.assertTrue(noObserversInGlobalBuilders()) return - def test_parse_constant(self): """Verify parsing of constant numeric expressions.""" factory = builder.EquationFactory() - eq = factory.makeEquation('3.12 + 2') + eq = factory.makeEquation("3.12 + 2") self.assertTrue(isinstance(eq, builder.Equation)) self.assertEqual(set(), factory.equations) self.assertEqual(5.12, eq()) self.assertRaises(ValueError, eq, 3) return - def testBuildEquation(self): from numpy import array_equal # simple equation sin = builder.getBuilder("sin") - a = builder.ArgumentBuilder(name="a", value = 1) - A = builder.ArgumentBuilder(name="A", value = 2) + a = builder.ArgumentBuilder(name="a", value=1) + A = builder.ArgumentBuilder(name="A", value=2) x = numpy.arange(0, numpy.pi, 0.1) - beq = A*sin(a*x) + beq = A * sin(a * x) eq = beq.getEquation() self.assertTrue("a" in eq.argdict) self.assertTrue("A" in eq.argdict) - self.assertTrue(array_equal(eq(), 2*numpy.sin(x))) + self.assertTrue(array_equal(eq(), 2 * numpy.sin(x))) self.assertEqual(eq.args, [eq.A, eq.a]) @@ -237,13 +233,13 @@ def testBuildEquation(self): # custom function def _f(a, b): - return (a-b)*1.0/(a+b) + return (a - b) * 1.0 / (a + b) f = builder.wrapFunction("f", _f, 2, 1) - a = builder.ArgumentBuilder(name="a", value = 2) - b = builder.ArgumentBuilder(name="b", value = 1) + a = builder.ArgumentBuilder(name="a", value=2) + b = builder.ArgumentBuilder(name="b", value=1) - beq = sin(f(a,b)) + beq = sin(f(a, b)) eq = beq.getEquation() self.assertEqual(eq(), numpy.sin(_f(2, 1))) @@ -251,32 +247,34 @@ def _f(a, b): sqrt = builder.getBuilder("sqrt") e = numpy.e _x = numpy.arange(0, 1, 0.05) - x = builder.ArgumentBuilder(name="x", value = _x, const = True) - sigma = builder.ArgumentBuilder(name="sigma", value = 0.1) - beq = sqrt(e**(-0.5*(x/sigma)**2)) + x = builder.ArgumentBuilder(name="x", value=_x, const=True) + sigma = builder.ArgumentBuilder(name="sigma", value=0.1) + beq = sqrt(e ** (-0.5 * (x / sigma) ** 2)) eq = beq.getEquation() - f = lambda x, sigma : sqrt(e**(-0.5*(x/sigma)**2)) - self.assertTrue(numpy.allclose(eq(), numpy.sqrt(e**(-0.5*(_x/0.1)**2)))) + f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) + self.assertTrue( + numpy.allclose(eq(), numpy.sqrt(e ** (-0.5 * (_x / 0.1) ** 2))) + ) # Equation with Equation - A = builder.ArgumentBuilder(name="A", value = 2) - B = builder.ArgumentBuilder(name="B", value = 4) + A = builder.ArgumentBuilder(name="A", value=2) + B = builder.ArgumentBuilder(name="B", value=4) beq = A + B eq = beq.getEquation() E = builder.wrapOperator("eq", eq) - eq2 = (2*E).getEquation() + eq2 = (2 * E).getEquation() # Make sure these evaulate to the same thing self.assertEqual(eq.args, [A.literal, B.literal]) - self.assertEqual(2*eq(), eq2()) + self.assertEqual(2 * eq(), eq2()) # Pass new arguments to the equation - C = builder.ArgumentBuilder(name="C", value = 5) - D = builder.ArgumentBuilder(name="D", value = 6) - eq3 = (E(C, D)+1).getEquation() + C = builder.ArgumentBuilder(name="C", value=5) + D = builder.ArgumentBuilder(name="D", value=6) + eq3 = (E(C, D) + 1).getEquation() self.assertEqual(12, eq3()) # Pass old and new arguments to the equation # If things work right, A has been given the value of C in the last # evaluation (5) - eq4 = (3*E(A, D)-1).getEquation() + eq4 = (3 * E(A, D) - 1).getEquation() self.assertEqual(32, eq4()) # Try to pass the wrong number of arguments self.assertRaises(ValueError, E, A) diff --git a/tests/test_characteristicfunctions.py b/tests/test_characteristicfunctions.py index c7d01035..ddd02c32 100644 --- a/tests/test_characteristicfunctions.py +++ b/tests/test_characteristicfunctions.py @@ -12,59 +12,59 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for sas package.""" import unittest import numpy -from .utils import has_sas, _msg_nosas from diffpy.srfit.sas.sasimport import sasimport +from .utils import _msg_nosas, has_sas + # Global variables to be assigned in setUp cf = None # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_sas, _msg_nosas) class TestSASCF(unittest.TestCase): def setUp(self): global cf import diffpy.srfit.pdf.characteristicfunctions as cf - return + return def testSphere(self): radius = 25 # Calculate sphere cf from SphereModel - SphereModel = sasimport('sas.models.SphereModel').SphereModel + SphereModel = sasimport("sas.models.SphereModel").SphereModel model = SphereModel() model.setParam("radius", radius) ff = cf.SASCF("sphere", model) - r = numpy.arange(1, 60, 0.1, dtype = float) + r = numpy.arange(1, 60, 0.1, dtype=float) fr1 = ff(r) # Calculate sphere cf analytically - fr2 = cf.sphericalCF(r, 2*radius) + fr2 = cf.sphericalCF(r, 2 * radius) diff = fr1 - fr2 res = numpy.dot(diff, diff) res /= numpy.dot(fr2, fr2) self.assertAlmostEqual(0, res, 4) return - def testSpheroid(self): prad = 20.9 erad = 33.114 # Calculate cf from EllipsoidModel - EllipsoidModel = sasimport('sas.models.EllipsoidModel').EllipsoidModel + EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel model = EllipsoidModel() model.setParam("radius_a", prad) model.setParam("radius_b", erad) ff = cf.SASCF("spheroid", model) - r = numpy.arange(0, 100, 1/numpy.pi, dtype = float) + r = numpy.arange(0, 100, 1 / numpy.pi, dtype=float) fr1 = ff(r) # Calculate cf analytically @@ -75,17 +75,16 @@ def testSpheroid(self): self.assertAlmostEqual(0, res, 4) return - def testShell(self): radius = 19.2 thickness = 7.8 # Calculate cf from VesicleModel - VesicleModel = sasimport('sas.models.VesicleModel').VesicleModel + VesicleModel = sasimport("sas.models.VesicleModel").VesicleModel model = VesicleModel() model.setParam("radius", radius) model.setParam("thickness", thickness) ff = cf.SASCF("vesicle", model) - r = numpy.arange(0, 99.45, 0.1, dtype = float) + r = numpy.arange(0, 99.45, 0.1, dtype=float) fr1 = ff(r) # Calculate sphere cf analytically @@ -96,23 +95,22 @@ def testShell(self): self.assertAlmostEqual(0, res, 4) return - def testCylinder(self): """Make sure cylinder works over different r-ranges.""" radius = 100 length = 30 - CylinderModel = sasimport('sas.models.CylinderModel').CylinderModel + CylinderModel = sasimport("sas.models.CylinderModel").CylinderModel model = CylinderModel() model.setParam("radius", radius) model.setParam("length", length) ff = cf.SASCF("cylinder", model) - r1 = numpy.arange(0, 10, 0.1, dtype = float) - r2 = numpy.arange(0, 50, 0.1, dtype = float) - r3 = numpy.arange(0, 100, 0.1, dtype = float) - r4 = numpy.arange(0, 500, 0.1, dtype = float) + r1 = numpy.arange(0, 10, 0.1, dtype=float) + r2 = numpy.arange(0, 50, 0.1, dtype=float) + r3 = numpy.arange(0, 100, 0.1, dtype=float) + r4 = numpy.arange(0, 500, 0.1, dtype=float) fr1 = ff(r1) fr2 = ff(r2) @@ -120,36 +118,37 @@ def testCylinder(self): fr4 = ff(r4) d = fr1 - numpy.interp(r1, r2, fr2) - res12 = numpy.dot(d,d) + res12 = numpy.dot(d, d) res12 /= numpy.dot(fr1, fr1) self.assertAlmostEqual(0, res12, 4) d = fr1 - numpy.interp(r1, r3, fr3) - res13 = numpy.dot(d,d) + res13 = numpy.dot(d, d) res13 /= numpy.dot(fr1, fr1) self.assertAlmostEqual(0, res13, 4) d = fr1 - numpy.interp(r1, r4, fr4) - res14 = numpy.dot(d,d) + res14 = numpy.dot(d, d) res14 /= numpy.dot(fr1, fr1) self.assertAlmostEqual(0, res14, 4) d = fr2 - numpy.interp(r2, r3, fr3) - res23 = numpy.dot(d,d) + res23 = numpy.dot(d, d) res23 /= numpy.dot(fr2, fr2) self.assertAlmostEqual(0, res23, 4) d = fr2 - numpy.interp(r2, r4, fr4) - res24 = numpy.dot(d,d) + res24 = numpy.dot(d, d) res24 /= numpy.dot(fr2, fr2) self.assertAlmostEqual(0, res24, 4) d = fr3 - numpy.interp(r3, r4, fr4) - res34 = numpy.dot(d,d) + res34 = numpy.dot(d, d) res34 /= numpy.dot(fr3, fr3) self.assertAlmostEqual(0, res34, 4) return + # End of class TestSASCF if __name__ == "__main__": diff --git a/tests/test_constraint.py b/tests/test_constraint.py index c2501f14..5c41689f 100644 --- a/tests/test_constraint.py +++ b/tests/test_constraint.py @@ -12,15 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest +from diffpy.srfit.equation.builder import EquationFactory from diffpy.srfit.fitbase.constraint import Constraint -from diffpy.srfit.fitbase.recipeorganizer import equationFromString from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.equation.builder import EquationFactory +from diffpy.srfit.fitbase.recipeorganizer import equationFromString class TestConstraint(unittest.TestCase): diff --git a/tests/test_contribution.py b/tests/test_contribution.py index 8e7cf177..60616a8b 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -12,18 +12,18 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from numpy import arange, dot, array_equal, sin +from numpy import arange, array_equal, dot, sin +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.fitcontribution import FitContribution -from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator -from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.profile import Profile +from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator + from .utils import noObserversInGlobalBuilders @@ -47,16 +47,15 @@ def testSetProfile(self): self.assertTrue(fc._eq is None) self.assertTrue(fc._reseq is None) # check type checking - fc1 = FitContribution('test1') - self.assertRaises(TypeError, fc1.setProfile, 'invalid') + fc1 = FitContribution("test1") + self.assertRaises(TypeError, fc1.setProfile, "invalid") # check if residual equation is set up when possible - fc2 = FitContribution('test2') - fc2.setEquation('A * x') + fc2 = FitContribution("test2") + fc2.setEquation("A * x") fc2.setProfile(profile) self.assertFalse(fc2._reseq is None) return - def testAddProfileGenerator(self): fc = self.fitcontribution gen = self.gen @@ -115,7 +114,7 @@ def testReplacements(self): profile = self.profile profile.setObservedProfile(xobs, yobs) xobs2 = arange(0, 10, 0.8) - yobs2 = 0.5*xobs2 + yobs2 = 0.5 * xobs2 profile2 = Profile() profile2.setObservedProfile(xobs2, yobs2) gen = self.gen @@ -145,7 +144,6 @@ def testReplacements(self): self.assertEqual(len(xobs2), len(fc.residual())) return - def testResidual(self): """Test the residual, which requires all other methods.""" fc = self.fitcontribution @@ -195,7 +193,7 @@ def testResidual(self): self.assertTrue(fc._eq._value is None) self.assertTrue(fc._reseq._value is None) xobs = arange(0, 10, 0.5) - yobs = 9*sin(xobs) + yobs = 9 * sin(xobs) profile.setObservedProfile(xobs, yobs) self.assertTrue(fc._eq._value is None) self.assertTrue(fc._reseq._value is None) @@ -207,25 +205,25 @@ def testResidual(self): fc.setEquation("2*I") fc.setResidualEquation("resv") chiv = fc.residual() - self.assertAlmostEqual(sum((2*xobs-yobs)**2)/sum(yobs**2), - dot(chiv, chiv)) + self.assertAlmostEqual( + sum((2 * xobs - yobs) ** 2) / sum(yobs**2), dot(chiv, chiv) + ) # Make a custom residual. fc.setResidualEquation("abs(eq-y)**0.5") chiv = fc.residual() - self.assertAlmostEqual(sum(abs(2*xobs-yobs)), dot(chiv, chiv)) + self.assertAlmostEqual(sum(abs(2 * xobs - yobs)), dot(chiv, chiv)) # Test configuration checks - fc1 = FitContribution('test1') - self.assertRaises(SrFitError, fc1.setResidualEquation, 'chiv') + fc1 = FitContribution("test1") + self.assertRaises(SrFitError, fc1.setResidualEquation, "chiv") fc1.setProfile(self.profile) - self.assertRaises(SrFitError, fc1.setResidualEquation, 'chiv') - fc1.setEquation('A * x') - fc1.setResidualEquation('chiv') + self.assertRaises(SrFitError, fc1.setResidualEquation, "chiv") + fc1.setEquation("A * x") + fc1.setResidualEquation("chiv") self.assertTrue(noObserversInGlobalBuilders()) return - def test_setEquation(self): """Check replacement of removed parameters.""" fc = self.fitcontribution @@ -234,59 +232,55 @@ def test_setEquation(self): self.assertEqual(7, fc.evaluate()) fc.removeParameter(fc.x) x = arange(0, 10, 0.5) - fc.newParameter('x', x) + fc.newParameter("x", x) self.assertTrue(array_equal(5 + x, fc.evaluate())) self.assertTrue(noObserversInGlobalBuilders()) return - def test_getEquation(self): """Check getting the current profile simulation formula.""" fc = self.fitcontribution - self.assertEqual('', fc.getEquation()) + self.assertEqual("", fc.getEquation()) fc.setEquation("A * sin(x + 5)") - self.assertEqual('(A * sin((x + 5)))', fc.getEquation()) + self.assertEqual("(A * sin((x + 5)))", fc.getEquation()) self.assertTrue(noObserversInGlobalBuilders()) return - def test_getResidualEquation(self): """Check getting the current formula for residual equation.""" fc = self.fitcontribution - self.assertEqual('', fc.getResidualEquation()) + self.assertEqual("", fc.getResidualEquation()) fc.setProfile(self.profile) - fc.setEquation('A * x + B') - self.assertEqual('((eq - y) / dy)', fc.getResidualEquation()) - fc.setResidualEquation('2 * (eq - y)') - self.assertEqual('(2 * (eq - y))', fc.getResidualEquation()) + fc.setEquation("A * x + B") + self.assertEqual("((eq - y) / dy)", fc.getResidualEquation()) + fc.setResidualEquation("2 * (eq - y)") + self.assertEqual("(2 * (eq - y))", fc.getResidualEquation()) return - def test_releaseOldEquations(self): """Ensure EquationFactory does not hold to obsolete Equations.""" fc = self.fitcontribution self.assertEqual(0, len(fc._eqfactory.equations)) for i in range(5): - fc.setEquation('A * x + B') + fc.setEquation("A * x + B") self.assertEqual(1, len(fc._eqfactory.equations)) fc.setProfile(self.profile) for i in range(5): - fc.setResidualEquation('chiv') + fc.setResidualEquation("chiv") self.assertEqual(2, len(fc._eqfactory.equations)) return - def test_registerFunction(self): """Ensure registered function works after second setEquation call.""" fc = self.fitcontribution - fsquare = lambda x : x**2 - fc.registerFunction(fsquare, name='fsquare') - fc.setEquation('fsquare') + fsquare = lambda x: x**2 + fc.registerFunction(fsquare, name="fsquare") + fc.setEquation("fsquare") fc.x.setValue(5) self.assertEqual(25, fc.evaluate()) fc.x << 6 self.assertEqual(36, fc.evaluate()) - fc.setEquation('fsquare + 5') + fc.setEquation("fsquare + 5") self.assertEqual(41, fc.evaluate()) fc.x << -1 self.assertEqual(6, fc.evaluate()) diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index fafb1619..b4868de8 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -12,39 +12,39 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for diffpy.srfit.structure package.""" -import unittest import pickle +import unittest import numpy -from .utils import has_structure, _msg_nostructure +from .utils import _msg_nostructure, has_structure # Global variables to be assigned in setUp Atom = Lattice = Structure = DiffpyStructureParSet = None # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_structure, _msg_nostructure) class TestParameterAdapter(unittest.TestCase): def setUp(self): global Atom, Lattice, Structure, DiffpyStructureParSet - from diffpy.structure import Atom, Lattice, Structure from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - return + from diffpy.structure import Atom, Lattice, Structure + return def testDiffpyStructureParSet(self): """Test the structure conversion.""" - a1 = Atom("Cu", xyz = numpy.array([.0, .1, .2]), Uisoequiv = 0.003) - a2 = Atom("Ag", xyz = numpy.array([.3, .4, .5]), Uisoequiv = 0.002) + a1 = Atom("Cu", xyz=numpy.array([0.0, 0.1, 0.2]), Uisoequiv=0.003) + a2 = Atom("Ag", xyz=numpy.array([0.3, 0.4, 0.5]), Uisoequiv=0.002) l = Lattice(2.5, 2.5, 2.5, 90, 90, 90) - dsstru = Structure([a1,a2], l) + dsstru = Structure([a1, a2], l) # Structure makes copies a1 = dsstru[0] a2 = dsstru[1] @@ -63,14 +63,14 @@ def _testAtoms(): self.assertEqual(a2.Bisoequiv, s.Ag0.Biso.getValue()) for i in range(1, 4): for j in range(i, 4): - uijstru = getattr(a1, "U%i%i"%(i,j)) - uij = getattr(s.Cu0, "U%i%i"%(i,j)).getValue() - uji = getattr(s.Cu0, "U%i%i"%(j,i)).getValue() + uijstru = getattr(a1, "U%i%i" % (i, j)) + uij = getattr(s.Cu0, "U%i%i" % (i, j)).getValue() + uji = getattr(s.Cu0, "U%i%i" % (j, i)).getValue() self.assertEqual(uijstru, uij) self.assertEqual(uijstru, uji) - bijstru = getattr(a1, "B%i%i"%(i,j)) - bij = getattr(s.Cu0, "B%i%i"%(i,j)).getValue() - bji = getattr(s.Cu0, "B%i%i"%(j,i)).getValue() + bijstru = getattr(a1, "B%i%i" % (i, j)) + bij = getattr(s.Cu0, "B%i%i" % (i, j)).getValue() + bji = getattr(s.Cu0, "B%i%i" % (j, i)).getValue() self.assertEqual(bijstru, bij) self.assertEqual(bijstru, bji) @@ -79,7 +79,6 @@ def _testAtoms(): self.assertEqual(a1.xyz[2], s.Cu0.z.getValue()) return - def _testLattice(): # Test the lattice @@ -114,7 +113,6 @@ def _testLattice(): self.assertNotEqual(d, dsstru.lattice.dist(a1.xyz, a2.xyz)) return - def test___repr__(self): """Test representation of DiffpyStructureParSet objects.""" lat = Lattice(3, 3, 2, 90, 90, 90) @@ -126,7 +124,6 @@ def test___repr__(self): self.assertEqual(repr(atom), repr(dsps.atoms[0])) return - def test_pickling(self): """Test pickling of DiffpyStructureParSet.""" stru = Structure([Atom("C", [0, 0.2, 0.5])]) @@ -137,6 +134,7 @@ def test_pickling(self): self.assertEqual(0.2, dsps2.atoms[0].y.value) return + # End of class TestParameterAdapter if __name__ == "__main__": diff --git a/tests/test_equation.py b/tests/test_equation.py index 6877de8f..1c9fe6b9 100644 --- a/tests/test_equation.py +++ b/tests/test_equation.py @@ -12,15 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest import diffpy.srfit.equation.literals as literals from diffpy.srfit.equation import Equation -from .utils import _makeArgs -from .utils import noObserversInGlobalBuilders + +from .utils import _makeArgs, noObserversInGlobalBuilders class TestEquation(unittest.TestCase): @@ -74,18 +73,18 @@ def testSimpleFunction(self): self.assertTrue(v3 is eq.v3) self.assertTrue(v4 is eq.v4) - self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) - self.assertEqual(20, eq.getValue()) # same as above - self.assertEqual(20, eq.value) # same as above - self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) - self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) - self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) - self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) + self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) + self.assertEqual(20, eq.getValue()) # same as above + self.assertEqual(20, eq.value) # same as above + self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) + self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) + self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) + self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) # Try some swapping eq.swap(v4, v1) self.assertTrue(eq._value is None) - self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) + self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) args = eq.args self.assertTrue(v4 not in args) @@ -160,18 +159,18 @@ def testEmbeddedEquation(self): self.assertTrue(eq._value is None) v1.value = 1 - self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) - self.assertEqual(20, eq.getValue()) # same as above - self.assertEqual(20, eq.value) # same as above - self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) - self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) - self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) - self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) + self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) + self.assertEqual(20, eq.getValue()) # same as above + self.assertEqual(20, eq.value) # same as above + self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) + self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) + self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) + self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) # Try some swapping. eq.swap(v4, v1) self.assertTrue(eq._value is None) - self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) + self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) args = eq.args self.assertTrue(v4 not in args) diff --git a/tests/test_fitrecipe.py b/tests/test_fitrecipe.py index 490753ac..1945e063 100644 --- a/tests/test_fitrecipe.py +++ b/tests/test_fitrecipe.py @@ -12,17 +12,17 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from numpy import linspace, array_equal, pi, sin, dot +from numpy import array_equal, dot, linspace, pi, sin -from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitcontribution import FitContribution -from diffpy.srfit.fitbase.profile import Profile +from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.parameter import Parameter +from diffpy.srfit.fitbase.profile import Profile + from .utils import capturestdout @@ -152,16 +152,17 @@ def testResidual(self): # Change the c value to 1 so that the equation evaluates as sin(x+1) x = self.profile.x - y = sin(x+1) + y = sin(x + 1) self.recipe.cont.c.setValue(1) res = self.recipe.residual() - self.assertTrue(array_equal(y-self.profile.y, res)) + self.assertTrue(array_equal(y - self.profile.y, res)) # Try some constraints # Make c = 2*A, A = Avar var = self.recipe.newVar("Avar") - self.recipe.constrain(self.fitcontribution.c, - "2*A", {"A": self.fitcontribution.A}) + self.recipe.constrain( + self.fitcontribution.c, "2*A", {"A": self.fitcontribution.A} + ) self.assertEqual(2, self.fitcontribution.c.value) self.recipe.constrain(self.fitcontribution.A, var) self.assertEqual(1, var.getValue()) @@ -170,9 +171,9 @@ def testResidual(self): self.assertEqual(2, self.fitcontribution.c.value) # The equation should evaluate to sin(x+2) x = self.profile.x - y = sin(x+2) + y = sin(x + 2) res = self.recipe.residual() - self.assertTrue(array_equal(y-self.profile.y, res)) + self.assertTrue(array_equal(y - self.profile.y, res)) # Now try some restraints. We want c to be exactly zero. It should give # a penalty of (c-0)**2, which is 4 in this case @@ -202,9 +203,9 @@ def testResidual(self): self.fitcontribution.constrain(self.fitcontribution.c, "2*A") # This should evaluate to sin(x+2) x = self.profile.x - y = sin(x+2) + y = sin(x + 2) res = self.recipe.residual() - self.assertTrue(array_equal(y-self.profile.y, res)) + self.assertTrue(array_equal(y - self.profile.y, res)) # Add a restraint at the fitcontribution level. r1 = self.fitcontribution.restrain(self.fitcontribution.c, 0, 0, 1) @@ -212,7 +213,7 @@ def testResidual(self): # The chi2 is the same as above, plus 4 res = self.recipe.residual() x = self.profile.x - y = sin(x+2) + y = sin(x + 2) chi2 = 4 + dot(y - self.profile.y, y - self.profile.y) self.assertAlmostEqual(chi2, dot(res, res)) @@ -241,25 +242,26 @@ def testResidual(self): def testPrintFitHook(self): "check output from default PrintFitHook." self.recipe.addVar(self.fitcontribution.c) - self.recipe.restrain('c', lb=5) - pfh, = self.recipe.getFitHooks() + self.recipe.restrain("c", lb=5) + (pfh,) = self.recipe.getFitHooks() out = capturestdout(self.recipe.scalarResidual) - self.assertEqual('', out) + self.assertEqual("", out) pfh.verbose = 1 out = capturestdout(self.recipe.scalarResidual) self.assertTrue(out.strip().isdigit()) - self.assertFalse('\nRestraints:' in out) + self.assertFalse("\nRestraints:" in out) pfh.verbose = 2 out = capturestdout(self.recipe.scalarResidual) - self.assertTrue('\nResidual:' in out) - self.assertTrue('\nRestraints:' in out) - self.assertFalse('\nVariables' in out) + self.assertTrue("\nResidual:" in out) + self.assertTrue("\nRestraints:" in out) + self.assertFalse("\nVariables" in out) pfh.verbose = 3 out = capturestdout(self.recipe.scalarResidual) - self.assertTrue('\nVariables' in out) - self.assertTrue('c = ' in out) + self.assertTrue("\nVariables" in out) + self.assertTrue("c = " in out) return + # End of class TestFitRecipe # ---------------------------------------------------------------------------- diff --git a/tests/test_fitresults.py b/tests/test_fitresults.py index 43deb3a8..841c7a73 100644 --- a/tests/test_fitresults.py +++ b/tests/test_fitresults.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for fitresults module.""" import unittest from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitresults import initializeRecipe + from .utils import datafile @@ -33,7 +33,7 @@ def setUp(self): self.Aval = 5.77619823e-01 self.sigval = -9.22758690e-01 - self.x0val = 6.12422115e+00 + self.x0val = 6.12422115e00 return def testInitializeFromFileName(self): @@ -52,7 +52,7 @@ def testInitializeFromFileObj(self): self.assertEqual(0, recipe.A.value) self.assertEqual(0, recipe.sig.value) self.assertEqual(0, recipe.x0.value) - infile = open(self.filename, 'r') + infile = open(self.filename, "r") initializeRecipe(recipe, infile) self.assertFalse(infile.closed) infile.close() @@ -61,13 +61,12 @@ def testInitializeFromFileObj(self): self.assertAlmostEqual(self.x0val, recipe.x0.value) return - def testInitializeFromString(self): recipe = self.recipe self.assertEqual(0, recipe.A.value) self.assertEqual(0, recipe.sig.value) self.assertEqual(0, recipe.x0.value) - infile = open(self.filename, 'r') + infile = open(self.filename, "r") resstr = infile.read() infile.close() initializeRecipe(recipe, resstr) @@ -76,6 +75,7 @@ def testInitializeFromString(self): self.assertAlmostEqual(self.x0val, recipe.x0.value) return + if __name__ == "__main__": unittest.main() diff --git a/tests/test_literals.py b/tests/test_literals.py index 4bc52c5b..d131b6f8 100644 --- a/tests/test_literals.py +++ b/tests/test_literals.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for the diffpy.srfit.equation.literals module.""" import unittest @@ -24,6 +23,7 @@ # ---------------------------------------------------------------------------- + class TestArgument(unittest.TestCase): def testInit(self): @@ -34,7 +34,6 @@ def testInit(self): self.assertTrue(None is a.name) return - def testIdentity(self): """Make sure an Argument is an Argument.""" a = literals.Argument() @@ -42,7 +41,6 @@ def testIdentity(self): self.assertTrue(isinstance(a, abcs.ArgumentABC)) return - def testValue(self): """Test value setting.""" @@ -59,16 +57,18 @@ def testValue(self): self.assertAlmostEqual(3.14, a.getValue()) return + # ---------------------------------------------------------------------------- + class TestCustomOperator(unittest.TestCase): def setUp(self): self.op = literals.makeOperator( - name="add", symbol="+", operation=numpy.add, nin=2, nout=1) + name="add", symbol="+", operation=numpy.add, nin=2, nout=1 + ) return - def testInit(self): """Test that everthing initializes as expected.""" op = self.op @@ -80,7 +80,6 @@ def testInit(self): self.assertEqual([], op.args) return - def testIdentity(self): """Make sure an Argument is an Argument.""" op = self.op @@ -88,13 +87,12 @@ def testIdentity(self): self.assertTrue(isinstance(op, abcs.OperatorABC)) return - def testValue(self): """Test value.""" # Test addition and operations op = self.op - a = literals.Argument(value = 0) - b = literals.Argument(value = 0) + a = literals.Argument(value=0) + b = literals.Argument(value=0) op.addLiteral(a) op.addLiteral(b) @@ -113,7 +111,6 @@ def testValue(self): return - def testAddLiteral(self): """Test adding a literal to an operator node.""" op = self.op @@ -123,8 +120,8 @@ def testAddLiteral(self): self.assertEqual(op.getValue(), 1) # Test addition and operations - a = literals.Argument(name = "a", value = 0) - b = literals.Argument(name = "b", value = 0) + a = literals.Argument(name="a", value=0) + b = literals.Argument(name="b", value=0) op.addLiteral(a) self.assertRaises(TypeError, op.getValue) @@ -140,21 +137,25 @@ def testAddLiteral(self): # Test for self-references # Try to add self - op1 = literals.makeOperator(name="add", symbol="+", - operation=numpy.add, nin=2, nout=1) + op1 = literals.makeOperator( + name="add", symbol="+", operation=numpy.add, nin=2, nout=1 + ) op1.addLiteral(a) self.assertRaises(ValueError, op1.addLiteral, op1) # Try to add argument that contains self op2 = literals.makeOperator( - name="sub", symbol="-", operation=numpy.subtract, nin=2, nout=1) + name="sub", symbol="-", operation=numpy.subtract, nin=2, nout=1 + ) op2.addLiteral(op1) self.assertRaises(ValueError, op1.addLiteral, op2) return + # ---------------------------------------------------------------------------- + class TestConvolutionOperator(unittest.TestCase): def testValue(self): @@ -169,10 +170,10 @@ def testValue(self): mu2 = 2.5 sig2 = 0.4 - g1 = exp(-0.5*((x-mu1)/sig1)**2) - a1 = literals.Argument(name = "g1", value = g1) - g2 = exp(-0.5*((x-mu2)/sig2)**2) - a2 = literals.Argument(name = "g2", value = g2) + g1 = exp(-0.5 * ((x - mu1) / sig1) ** 2) + a1 = literals.Argument(name="g1", value=g1) + g2 = exp(-0.5 * ((x - mu2) / sig2) ** 2) + a2 = literals.Argument(name="g2", value=g2) op = literals.ConvolutionOperator() op.addLiteral(a1) @@ -181,23 +182,25 @@ def testValue(self): g3c = op.value mu3 = mu1 - sig3 = (sig1**2 + sig2**2)**0.5 - g3 = exp(-0.5*((x-mu3)/sig3)**2) - g3 *= sum(g1)/sum(g3) + sig3 = (sig1**2 + sig2**2) ** 0.5 + g3 = exp(-0.5 * ((x - mu3) / sig3) ** 2) + g3 *= sum(g1) / sum(g3) self.assertAlmostEqual(sum(g3c), sum(g3)) - self.assertAlmostEqual(0, sum((g3-g3c)**2)) + self.assertAlmostEqual(0, sum((g3 - g3c) ** 2)) return + # ---------------------------------------------------------------------------- + class TestArrayOperator(unittest.TestCase): def test_value(self): """Check ArrayOperator.value.""" - x = literals.Argument('x', 1.0) - y = literals.Argument('y', 2.0) - z = literals.Argument('z', 3.0) + x = literals.Argument("x", 1.0) + y = literals.Argument("y", 2.0) + z = literals.Argument("z", 3.0) # check empty array op = literals.ArrayOperator() self.assertEqual(0, len(op.value)) @@ -212,6 +215,7 @@ def test_value(self): self.assertTrue(numpy.array_equal([1, 2, 7], op.value)) return + # ---------------------------------------------------------------------------- if __name__ == "__main__": diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index 8af66b0c..2e516136 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for diffpy.srfit.structure package.""" import unittest import numpy -from .utils import has_pyobjcryst, _msg_nopyobjcryst +from .utils import _msg_nopyobjcryst, has_pyobjcryst # Global variables to be assigned in setUp ObjCrystCrystalParSet = spacegroups = None @@ -89,6 +88,7 @@ -2.279809890 -2.580456608 -0.724000000 """ + def makeC60(): """Make a crystal containing the C60 molecule using pyobjcryst.""" pi = numpy.pi @@ -99,28 +99,32 @@ def makeC60(): c.AddScatterer(m) sp = ScatteringPowerAtom("C", "C") - sp.SetBiso(8*pi*pi*0.003) - #c.AddScatteringPower(sp) + sp.SetBiso(8 * pi * pi * 0.003) + # c.AddScatteringPower(sp) for i, l in enumerate(c60xyz.strip().splitlines()): x, y, z = map(float, l.split()) - m.AddAtom(x, y, z, sp, "C%i"%i) + m.AddAtom(x, y, z, sp, "C%i" % i) return c + # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) class TestParameterAdapter(unittest.TestCase): def setUp(self): global ObjCrystCrystalParSet, Crystal, Atom, Molecule global ScatteringPowerAtom - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet - from pyobjcryst.crystal import Crystal from pyobjcryst.atom import Atom + from pyobjcryst.crystal import Crystal from pyobjcryst.molecule import Molecule from pyobjcryst.scatteringpower import ScatteringPowerAtom + + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + self.occryst = makeC60() self.ocmol = self.occryst.GetScatterer("c60") return @@ -130,7 +134,6 @@ def tearDown(self): del self.ocmol return - def testObjCrystParSet(self): """Test the structure conversion.""" @@ -181,7 +184,6 @@ def _testMolecule(): self.assertAlmostEqual(ocsp.Biso, a.Biso.getValue()) return - _testCrystal() _testMolecule() @@ -196,17 +198,16 @@ def _testMolecule(): _testMolecule() ## Now change values from the srfit StructureParSet - cryst.c60.C44.x.setValue( 1.1 ) - cryst.c60.C44.occ.setValue( 1.1 ) - cryst.c60.C44.Biso.setValue( 1.1 ) - cryst.c60.q3.setValue( 1.1 ) + cryst.c60.C44.x.setValue(1.1) + cryst.c60.C44.occ.setValue(1.1) + cryst.c60.C44.Biso.setValue(1.1) + cryst.c60.q3.setValue(1.1) cryst.a.setValue(1.1) _testCrystal() _testMolecule() return - def testImplicitBondLengthRestraints(self): """Test the structure with implicit bond lengths.""" occryst = self.occryst @@ -233,7 +234,6 @@ def testImplicitBondLengthRestraints(self): return - def testImplicitBondAngleRestraints(self): """Test the structure with implicit bond angles.""" occryst = self.occryst @@ -260,17 +260,18 @@ def testImplicitBondAngleRestraints(self): return - def testImplicitDihedralAngleRestraints(self): """Test the structure with implicit dihedral angles.""" occryst = self.occryst ocmol = self.ocmol # Add some bond angles to the molecule - ocmol.AddDihedralAngle(ocmol[0], ocmol[5], ocmol[8], ocmol[41], 1.1, - 0.1, 0.1) - ocmol.AddDihedralAngle(ocmol[0], ocmol[7], ocmol[44], ocmol[2], 1.3, - 0.1, 0.1) + ocmol.AddDihedralAngle( + ocmol[0], ocmol[5], ocmol[8], ocmol[41], 1.1, 0.1, 0.1 + ) + ocmol.AddDihedralAngle( + ocmol[0], ocmol[7], ocmol[44], ocmol[2], 1.3, 0.1, 0.1 + ) # make our crystal cryst = ObjCrystCrystalParSet("bucky", occryst) @@ -289,13 +290,11 @@ def testImplicitDihedralAngleRestraints(self): return - def testImplicitStretchModes(self): """Test the molecule with implicit stretch modes.""" # Not sure how to make this happen. pass - def testExplicitBondLengthRestraints(self): """Test the structure with explicit bond lengths.""" occryst = self.occryst @@ -321,12 +320,11 @@ def testExplicitBondLengthRestraints(self): return - def testExplicitBondAngleRestraints(self): """Test the structure with explicit bond angles. - Note that this cannot work with co-linear points as the direction of - rotation cannot be defined in this case. + Note that this cannot work with co-linear points as the + direction of rotation cannot be defined in this case. """ occryst = self.occryst ocmol = self.ocmol @@ -336,10 +334,12 @@ def testExplicitBondAngleRestraints(self): m = cryst.c60 # restrain some bond angles - res0 = m.restrainBondAngle(m.atoms[0], m.atoms[5], m.atoms[8], 3.3, - 0.1, 0.1) - res1 = m.restrainBondAngle(m.atoms[0], m.atoms[7], m.atoms[44], 3.3, - 0.1, 0.1) + res0 = m.restrainBondAngle( + m.atoms[0], m.atoms[5], m.atoms[8], 3.3, 0.1, 0.1 + ) + res1 = m.restrainBondAngle( + m.atoms[0], m.atoms[7], m.atoms[44], 3.3, 0.1, 0.1 + ) # make sure that we have some restraints in the molecule self.assertTrue(2, len(m._restraints)) @@ -352,7 +352,6 @@ def testExplicitBondAngleRestraints(self): return - def testExplicitDihedralAngleRestraints(self): """Test the structure with explicit dihedral angles.""" occryst = self.occryst @@ -363,11 +362,12 @@ def testExplicitDihedralAngleRestraints(self): m = cryst.c60 # Restrain some dihedral angles. - res0 = m.restrainDihedralAngle(m.atoms[0], m.atoms[5], m.atoms[8], - m.atoms[41], 1.1, 0.1, 0.1) - res1 = m.restrainDihedralAngle(m.atoms[0], m.atoms[7], m.atoms[44], - m.atoms[2], 1.1, 0.1, 0.1) - + res0 = m.restrainDihedralAngle( + m.atoms[0], m.atoms[5], m.atoms[8], m.atoms[41], 1.1, 0.1, 0.1 + ) + res1 = m.restrainDihedralAngle( + m.atoms[0], m.atoms[7], m.atoms[44], m.atoms[2], 1.1, 0.1, 0.1 + ) # make sure that we have some restraints in the molecule self.assertTrue(2, len(m._restraints)) @@ -380,7 +380,6 @@ def testExplicitDihedralAngleRestraints(self): return - def testExplicitBondLengthParameter(self): """Test adding bond length parameters to the molecule.""" occryst = self.occryst @@ -400,48 +399,53 @@ def testExplicitBondLengthParameter(self): xyz0 = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) xyz7 = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20 = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) + xyz20 = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) dd = xyz0 - xyz7 - d0 = numpy.dot(dd, dd)**0.5 + d0 = numpy.dot(dd, dd) ** 0.5 self.assertAlmostEqual(d0, p1.getValue(), 6) # Record the unit direction of change for later - u = dd/d0 + u = dd / d0 # Change the value scale = 1.05 - p1.setValue(scale*d0) + p1.setValue(scale * d0) # Verify that it has changed. - self.assertAlmostEqual(scale*d0, p1.getValue()) - - xyz0a = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) - xyz7a = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20a = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) + self.assertAlmostEqual(scale * d0, p1.getValue()) + + xyz0a = numpy.array( + [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] + ) + xyz7a = numpy.array( + [a7.x.getValue(), a7.y.getValue(), a7.z.getValue()] + ) + xyz20a = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) dda = xyz0a - xyz7a - d1 = numpy.dot(dda, dda)**0.5 + d1 = numpy.dot(dda, dda) ** 0.5 - self.assertAlmostEqual(scale*d0, d1) + self.assertAlmostEqual(scale * d0, d1) # Verify that only the second and third atoms have moved. self.assertTrue(numpy.array_equal(xyz0, xyz0a)) - xyz7calc = xyz7 + (1-scale)*d0*u + xyz7calc = xyz7 + (1 - scale) * d0 * u for i in range(3): self.assertAlmostEqual(xyz7a[i], xyz7calc[i], 6) - xyz20calc = xyz20 + (1-scale)*d0*u + xyz20calc = xyz20 + (1 - scale) * d0 * u for i in range(3): self.assertAlmostEqual(xyz20a[i], xyz20calc[i], 6) return - def testExplicitBondAngleParameter(self): """Test adding bond angle parameters to the molecule.""" occryst = self.occryst @@ -457,18 +461,19 @@ def testExplicitBondAngleParameter(self): xyz0 = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) xyz7 = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20 = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25 = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) - + xyz20 = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25 = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) v1 = xyz7 - xyz0 - d1 = numpy.dot(v1, v1)**0.5 + d1 = numpy.dot(v1, v1) ** 0.5 v2 = xyz7 - xyz20 - d2 = numpy.dot(v2, v2)**0.5 + d2 = numpy.dot(v2, v2) ** 0.5 - angle0 = numpy.arccos(numpy.dot(v1, v2)/(d1*d2)) + angle0 = numpy.arccos(numpy.dot(v1, v2) / (d1 * d2)) # Add a parameter p1 = m.addBondAngleParameter("C0720", a0, a7, a20) @@ -479,26 +484,32 @@ def testExplicitBondAngleParameter(self): # Change the value scale = 1.05 - p1.setValue(scale*angle0) + p1.setValue(scale * angle0) # Verify that it has changed. - self.assertAlmostEqual(scale*angle0, p1.getValue(), 6) - - xyz0a = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) - xyz7a = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20a = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25a = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) + self.assertAlmostEqual(scale * angle0, p1.getValue(), 6) + + xyz0a = numpy.array( + [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] + ) + xyz7a = numpy.array( + [a7.x.getValue(), a7.y.getValue(), a7.z.getValue()] + ) + xyz20a = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25a = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) v1a = xyz7a - xyz0a - d1a = numpy.dot(v1a, v1a)**0.5 + d1a = numpy.dot(v1a, v1a) ** 0.5 v2a = xyz7a - xyz20a - d2a = numpy.dot(v2a, v2a)**0.5 + d2a = numpy.dot(v2a, v2a) ** 0.5 - angle1 = numpy.arccos(numpy.dot(v1a, v2a)/(d1a*d2a)) + angle1 = numpy.arccos(numpy.dot(v1a, v2a) / (d1a * d2a)) - self.assertAlmostEqual(scale*angle0, angle1) + self.assertAlmostEqual(scale * angle0, angle1) # Verify that only the last two atoms have moved. @@ -509,7 +520,6 @@ def testExplicitBondAngleParameter(self): return - def testExplicitDihedralAngleParameter(self): """Test adding dihedral angle parameters to the molecule.""" occryst = self.occryst @@ -526,13 +536,15 @@ def testExplicitDihedralAngleParameter(self): xyz0 = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) xyz7 = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20 = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25 = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) - xyz33 = numpy.array([a33.x.getValue(), a33.y.getValue(), - a33.z.getValue()]) - + xyz20 = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25 = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) + xyz33 = numpy.array( + [a33.x.getValue(), a33.y.getValue(), a33.z.getValue()] + ) v12 = xyz0 - xyz7 v23 = xyz7 - xyz20 @@ -540,9 +552,9 @@ def testExplicitDihedralAngleParameter(self): v123 = numpy.cross(v12, v23) v234 = numpy.cross(v23, v34) - d123 = numpy.dot(v123, v123)**0.5 - d234 = numpy.dot(v234, v234)**0.5 - angle0 = -numpy.arccos(numpy.dot(v123, v234)/(d123*d234)) + d123 = numpy.dot(v123, v123) ** 0.5 + d234 = numpy.dot(v234, v234) ** 0.5 + angle0 = -numpy.arccos(numpy.dot(v123, v234) / (d123 * d234)) # Add a parameter p1 = m.addDihedralAngleParameter("C072025", a0, a7, a20, a25) @@ -553,19 +565,26 @@ def testExplicitDihedralAngleParameter(self): # Change the value scale = 1.05 - p1.setValue(scale*angle0) + p1.setValue(scale * angle0) # Verify that it has changed. - self.assertAlmostEqual(scale*angle0, p1.getValue(), 6) - - xyz0a = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) - xyz7a = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20a = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25a = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) - xyz33a = numpy.array([a33.x.getValue(), a33.y.getValue(), - a33.z.getValue()]) + self.assertAlmostEqual(scale * angle0, p1.getValue(), 6) + + xyz0a = numpy.array( + [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] + ) + xyz7a = numpy.array( + [a7.x.getValue(), a7.y.getValue(), a7.z.getValue()] + ) + xyz20a = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25a = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) + xyz33a = numpy.array( + [a33.x.getValue(), a33.y.getValue(), a33.z.getValue()] + ) v12a = xyz0a - xyz7a v23a = xyz7a - xyz20a @@ -573,11 +592,11 @@ def testExplicitDihedralAngleParameter(self): v123a = numpy.cross(v12a, v23a) v234a = numpy.cross(v23a, v34a) - d123a = numpy.dot(v123a, v123a)**0.5 - d234a = numpy.dot(v234a, v234a)**0.5 - angle1 = -numpy.arccos(numpy.dot(v123a, v234a)/(d123a*d234a)) + d123a = numpy.dot(v123a, v123a) ** 0.5 + d234a = numpy.dot(v234a, v234a) ** 0.5 + angle1 = -numpy.arccos(numpy.dot(v123a, v234a) / (d123a * d234a)) - self.assertAlmostEqual(scale*angle0, angle1) + self.assertAlmostEqual(scale * angle0, angle1) # Verify that only the last two atoms have moved. @@ -589,16 +608,18 @@ def testExplicitDihedralAngleParameter(self): return + # End of class TestParameterAdapter # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) class TestCreateSpaceGroup(unittest.TestCase): """Test space group creation from pyobjcryst structures. - This makes sure that the space groups created by the structure parameter - set are correct. + This makes sure that the space groups created by the structure + parameter set are correct. """ def setUp(self): @@ -610,6 +631,7 @@ def setUp(self): def getObjCrystParSetSpaceGroup(sg): """Make an ObjCrystCrystalParSet with the proper space group.""" from pyobjcryst.spacegroup import SpaceGroup + sgobjcryst = SpaceGroup(sg.short_name) sgnew = ObjCrystCrystalParSet._createSpaceGroup(sgobjcryst) return sgnew @@ -617,7 +639,7 @@ def getObjCrystParSetSpaceGroup(sg): @staticmethod def hashDiffPySpaceGroup(sg): lines = [str(sg.number % 1000)] + sorted(map(str, sg.iter_symops())) - s = '\n'.join(lines) + s = "\n".join(lines) return s def sgsEquivalent(self, sg1, sg2): @@ -638,7 +660,7 @@ def xtestCreateSpaceGroup(self): for smbls in sgtbx.space_group_symbol_iterator(): shn = smbls.hermann_mauguin() - short_name = shn.replace(' ', '') + short_name = shn.replace(" ", "") if spacegroups.IsSpaceGroupIdentifier(short_name): sg = spacegroups.GetSpaceGroup(shn) sgnew = self.getObjCrystParSetSpaceGroup(sg) @@ -646,6 +668,7 @@ def xtestCreateSpaceGroup(self): self.assertTrue(self.sgsEquivalent(sg, sgnew)) return + # End of class TestCreateSpaceGroup if __name__ == "__main__": diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 76eaee3b..56e00d4f 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -12,13 +12,15 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.parameter import ParameterAdapter, ParameterProxy +from diffpy.srfit.fitbase.parameter import ( + Parameter, + ParameterAdapter, + ParameterProxy, +) class TestParameter(unittest.TestCase): @@ -32,16 +34,17 @@ def testSetValue(self): # Try array import numpy + x = numpy.arange(0, 10, 0.1) l.setValue(x) - self.assertTrue( l.getValue() is x ) - self.assertTrue( l.value is x ) + self.assertTrue(l.getValue() is x) + self.assertTrue(l.value is x) # Change the array y = numpy.arange(0, 10, 0.5) l.value = y - self.assertTrue( l.getValue() is y ) - self.assertTrue( l.value is y ) + self.assertTrue(l.getValue() is y) + self.assertTrue(l.value is y) # Back to scalar l.setValue(1.01) @@ -49,6 +52,7 @@ def testSetValue(self): self.assertAlmostEqual(1.01, l.value) return + class TestParameterProxy(unittest.TestCase): def testProxy(self): @@ -73,6 +77,7 @@ def testProxy(self): return + class TestParameterAdapter(unittest.TestCase): def testWrapper(self): @@ -83,8 +88,9 @@ def testWrapper(self): l = Parameter("l", 3.14) # Try Accessor adaptation - la = ParameterAdapter("l", l, getter = Parameter.getValue, setter = - Parameter.setValue) + la = ParameterAdapter( + "l", l, getter=Parameter.getValue, setter=Parameter.setValue + ) self.assertEqual(l.name, la.name) self.assertEqual(l.getValue(), la.getValue()) @@ -98,7 +104,7 @@ def testWrapper(self): self.assertEqual(l.getValue(), la.getValue()) # Try Attribute adaptation - la = ParameterAdapter("l", l, attr = "value") + la = ParameterAdapter("l", l, attr="value") self.assertEqual(l.name, la.name) self.assertEqual("value", la.attr) diff --git a/tests/test_parameterset.py b/tests/test_parameterset.py index abbad180..51ffa14c 100644 --- a/tests/test_parameterset.py +++ b/tests/test_parameterset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest diff --git a/tests/test_pdf.py b/tests/test_pdf.py index fb67158e..f9ad04bc 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -12,29 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for pdf package.""" -import unittest -import pickle import io +import pickle +import unittest import numpy -from .utils import datafile -from .utils import has_srreal, _msg_nosrreal -from .utils import has_structure, _msg_nostructure -from diffpy.srfit.pdf import PDFGenerator, PDFParser, PDFContribution from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser + +from .utils import ( + _msg_nosrreal, + _msg_nostructure, + datafile, + has_srreal, + has_structure, +) # ---------------------------------------------------------------------------- + class TestPDFParset(unittest.TestCase): def setUp(self): return - def testParser1(self): data = datafile("ni-q27r100-neutron.gr") parser = PDFParser() @@ -42,16 +46,16 @@ def testParser1(self): meta = parser._meta - self.assertEqual(data, meta['filename']) - self.assertEqual(1, meta['nbanks']) - self.assertEqual('N', meta['stype']) - self.assertEqual(27, meta['qmax']) - self.assertEqual(300, meta.get('temperature')) - self.assertEqual(None, meta.get('qdamp')) - self.assertEqual(None, meta.get('qbroad')) - self.assertEqual(None, meta.get('spdiameter')) - self.assertEqual(None, meta.get('scale')) - self.assertEqual(None, meta.get('doping')) + self.assertEqual(data, meta["filename"]) + self.assertEqual(1, meta["nbanks"]) + self.assertEqual("N", meta["stype"]) + self.assertEqual(27, meta["qmax"]) + self.assertEqual(300, meta.get("temperature")) + self.assertEqual(None, meta.get("qdamp")) + self.assertEqual(None, meta.get("qbroad")) + self.assertEqual(None, meta.get("spdiameter")) + self.assertEqual(None, meta.get("scale")) + self.assertEqual(None, meta.get("doping")) x, y, dx, dy = parser.getData() self.assertTrue(dx is None) @@ -62,15 +66,26 @@ def testParser1(self): res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testy = numpy.array([1.144, 2.258, 3.312, 4.279, 5.135, 5.862, 6.445, - 6.875, 7.150, 7.272]) + testy = numpy.array( + [ + 1.144, + 2.258, + 3.312, + 4.279, + 5.135, + 5.862, + 6.445, + 6.875, + 7.150, + 7.272, + ] + ) diff = testy - y[:10] res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) return - def testParser2(self): data = datafile("si-q27r60-xray.gr") parser = PDFParser() @@ -78,16 +93,16 @@ def testParser2(self): meta = parser._meta - self.assertEqual(data, meta['filename']) - self.assertEqual(1, meta['nbanks']) - self.assertEqual('X', meta['stype']) - self.assertEqual(27, meta['qmax']) - self.assertEqual(300, meta.get('temperature')) - self.assertEqual(None, meta.get('qdamp')) - self.assertEqual(None, meta.get('qbroad')) - self.assertEqual(None, meta.get('spdiameter')) - self.assertEqual(None, meta.get('scale')) - self.assertEqual(None, meta.get('doping')) + self.assertEqual(data, meta["filename"]) + self.assertEqual(1, meta["nbanks"]) + self.assertEqual("X", meta["stype"]) + self.assertEqual(27, meta["qmax"]) + self.assertEqual(300, meta.get("temperature")) + self.assertEqual(None, meta.get("qdamp")) + self.assertEqual(None, meta.get("qbroad")) + self.assertEqual(None, meta.get("spdiameter")) + self.assertEqual(None, meta.get("scale")) + self.assertEqual(None, meta.get("doping")) x, y, dx, dy = parser.getData() testx = numpy.linspace(0.01, 60, 5999, endpoint=False) @@ -95,15 +110,38 @@ def testParser2(self): res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testy = numpy.array([0.1105784, 0.2199684, 0.3270088, 0.4305913, - 0.5296853, 0.6233606, 0.7108060, 0.7913456, 0.8644501, 0.9297440]) + testy = numpy.array( + [ + 0.1105784, + 0.2199684, + 0.3270088, + 0.4305913, + 0.5296853, + 0.6233606, + 0.7108060, + 0.7913456, + 0.8644501, + 0.9297440, + ] + ) diff = testy - y[:10] res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testdy = numpy.array([0.001802192, 0.003521449, 0.005079115, - 0.006404892, 0.007440527, 0.008142955, 0.008486813, 0.008466340, - 0.008096858, 0.007416456]) + testdy = numpy.array( + [ + 0.001802192, + 0.003521449, + 0.005079115, + 0.006404892, + 0.007440527, + 0.008142955, + 0.008486813, + 0.008466340, + 0.008096858, + 0.007416456, + ] + ) diff = testdy - dy[:10] res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) @@ -111,10 +149,12 @@ def testParser2(self): self.assertTrue(dx is None) return + # End of class TestPDFParset # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_srreal, _msg_nosrreal) @unittest.skipUnless(has_structure, _msg_nostructure) class TestPDFGenerator(unittest.TestCase): @@ -123,15 +163,15 @@ def setUp(self): self.gen = PDFGenerator() return - def testGenerator(self): qmax = 27.0 gen = self.gen - gen.setScatteringType('N') - self.assertEqual('N', gen.getScatteringType()) + gen.setScatteringType("N") + self.assertEqual("N", gen.getScatteringType()) gen.setQmax(qmax) self.assertAlmostEqual(qmax, gen.getQmax()) from diffpy.structure import PDFFitStructure + stru = PDFFitStructure() ciffile = datafile("ni.cif") stru.read(ciffile) @@ -158,12 +198,13 @@ def testGenerator(self): # output, we just have to make sure we can calculate from the # PDFGenerator interface. from diffpy.srreal.pdfcalculator import PDFCalculator + calc = PDFCalculator() calc.rstep = r[1] - r[0] calc.rmin = r[0] calc.rmax = r[-1] + 0.5 * calc.rstep calc.qmax = qmax - calc.setScatteringFactorTableByType('N') + calc.setScatteringFactorTableByType("N") calc.eval(stru) yref = calc.pdf @@ -172,7 +213,6 @@ def testGenerator(self): self.assertAlmostEqual(0, res) return - def test_setQmin(self): """Verify qmin is propagated to the calculator object.""" gen = self.gen @@ -183,35 +223,37 @@ def test_setQmin(self): self.assertEqual(0.93, gen._calc.qmin) return + # End of class TestPDFGenerator # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_srreal, _msg_nosrreal) @unittest.skipUnless(has_structure, _msg_nostructure) class TestPDFContribution(unittest.TestCase): def setUp(self): - self.pc = PDFContribution('pdf') + self.pc = PDFContribution("pdf") return - def test_setQmax(self): """Check PDFContribution.setQmax()""" from diffpy.structure import Structure + pc = self.pc pc.setQmax(21) - pc.addStructure('empty', Structure()) + pc.addStructure("empty", Structure()) self.assertEqual(21, pc.empty.getQmax()) pc.setQmax(22) self.assertEqual(22, pc.getQmax()) self.assertEqual(22, pc.empty.getQmax()) return - def test_getQmax(self): """Check PDFContribution.getQmax()""" from diffpy.structure import Structure + # cover all code branches in PDFContribution._getMetaValue # (1) contribution metadata pc1 = self.pc @@ -219,54 +261,56 @@ def test_getQmax(self): pc1.setQmax(17) self.assertEqual(17, pc1.getQmax()) # (2) contribution metadata - pc2 = PDFContribution('pdf') - pc2.addStructure('empty', Structure()) + pc2 = PDFContribution("pdf") + pc2.addStructure("empty", Structure()) pc2.empty.setQmax(18) self.assertEqual(18, pc2.getQmax()) # (3) profile metadata - pc3 = PDFContribution('pdf') - pc3.profile.meta['qmax'] = 19 + pc3 = PDFContribution("pdf") + pc3.profile.meta["qmax"] = 19 self.assertEqual(19, pc3.getQmax()) return - def test_savetxt(self): "check PDFContribution.savetxt()" from diffpy.structure import Structure + pc = self.pc pc.loadData(datafile("si-q27r60-xray.gr")) pc.setCalculationRange(0, 10) - pc.addStructure('empty', Structure()) + pc.addStructure("empty", Structure()) fp = io.BytesIO() self.assertRaises(SrFitError, pc.savetxt, fp) pc.evaluate() pc.savetxt(fp) txt = fp.getvalue().decode() - nlines = len(txt.strip().split('\n')) + nlines = len(txt.strip().split("\n")) self.assertEqual(1001, nlines) return - def test_pickling(self): "validate PDFContribution.residual() after pickling." from itertools import chain + from diffpy.structure import loadStructure + pc = self.pc pc.loadData(datafile("ni-q27r100-neutron.gr")) ni = loadStructure(datafile("ni.cif")) ni.Uisoequiv = 0.003 - pc.addStructure('ni', ni) + pc.addStructure("ni", ni) pc.setCalculationRange(0, 10) pc2 = pickle.loads(pickle.dumps(pc)) res0 = pc.residual() self.assertTrue(numpy.array_equal(res0, pc2.residual())) - for p in chain(pc.iterPars('Uiso'), pc2.iterPars('Uiso')): + for p in chain(pc.iterPars("Uiso"), pc2.iterPars("Uiso")): p.value = 0.004 res1 = pc.residual() self.assertFalse(numpy.allclose(res0, res1)) self.assertTrue(numpy.array_equal(res1, pc2.residual())) return + # End of class TestPDFContribution # ---------------------------------------------------------------------------- diff --git a/tests/test_profile.py b/tests/test_profile.py index ded0f38d..f19adef6 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -12,17 +12,17 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" -import unittest -import re import io +import re +import unittest -from numpy import array, arange, array_equal, ones_like, allclose +from numpy import allclose, arange, array, array_equal, ones_like -from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.profile import Profile + from .utils import datafile @@ -55,9 +55,9 @@ def testSetObservedProfile(self): prof = self.profile prof.setObservedProfile(x, y, dy) - self.assertTrue( array_equal(x, prof.xobs) ) - self.assertTrue( array_equal(y, prof.yobs) ) - self.assertTrue( array_equal(dy, prof.dyobs) ) + self.assertTrue(array_equal(x, prof.xobs)) + self.assertTrue(array_equal(y, prof.yobs)) + self.assertTrue(array_equal(dy, prof.dyobs)) # Make a profile with undefined dy x = arange(0, 10, 0.1) @@ -66,18 +66,17 @@ def testSetObservedProfile(self): self.profile.setObservedProfile(x, y, dy) - self.assertTrue( array_equal(x, prof.xobs) ) - self.assertTrue( array_equal(y, prof.yobs) ) - self.assertTrue( array_equal(ones_like(prof.xobs), prof.dyobs)) + self.assertTrue(array_equal(x, prof.xobs)) + self.assertTrue(array_equal(y, prof.yobs)) + self.assertTrue(array_equal(ones_like(prof.xobs), prof.dyobs)) # Get the ranged profile to make sure its the same - self.assertTrue( array_equal(x, prof.x) ) - self.assertTrue( array_equal(y, prof.y) ) - self.assertTrue( array_equal(ones_like(prof.xobs), prof.dy)) + self.assertTrue(array_equal(x, prof.x)) + self.assertTrue(array_equal(y, prof.y)) + self.assertTrue(array_equal(ones_like(prof.xobs), prof.dy)) return - def testSetCalculationRange(self): """Test the setCalculationRange method.""" x = arange(2, 9.6, 0.5) @@ -110,26 +109,28 @@ def testSetCalculationRange(self): self.assertTrue(array_equal(y, prof.y)) self.assertTrue(array_equal(dy, prof.dy)) # Test xmin > xmax - self.assertRaises(ValueError, prof.setCalculationRange, - xmin=10, xmax=3) + self.assertRaises( + ValueError, prof.setCalculationRange, xmin=10, xmax=3 + ) # Test xmax - xmin < dx - self.assertRaises(ValueError, prof.setCalculationRange, - xmin=3, xmax=3.9, dx=1.0) + self.assertRaises( + ValueError, prof.setCalculationRange, xmin=3, xmax=3.9, dx=1.0 + ) # Test dx <= 0 self.assertRaises(ValueError, prof.setCalculationRange, dx=0) self.assertRaises(ValueError, prof.setCalculationRange, dx=-0.000001) # using string other than 'obs' - self.assertRaises(ValueError, prof.setCalculationRange, xmin='oobs') - self.assertRaises(ValueError, prof.setCalculationRange, xmax='oobs') - self.assertRaises(ValueError, prof.setCalculationRange, dx='oobs') + self.assertRaises(ValueError, prof.setCalculationRange, xmin="oobs") + self.assertRaises(ValueError, prof.setCalculationRange, xmax="oobs") + self.assertRaises(ValueError, prof.setCalculationRange, dx="oobs") # This should be alright prof.setCalculationRange(3, 5) - prof.setCalculationRange(xmin='obs', xmax=7, dx=0.001) + prof.setCalculationRange(xmin="obs", xmax=7, dx=0.001) self.assertEqual(5001, len(prof.x)) self.assertEqual(len(prof.x), len(prof.y)) self.assertEqual(len(prof.x), len(prof.dy)) # Test an internal bound - prof.setCalculationRange(4, 7, dx='obs') + prof.setCalculationRange(4, 7, dx="obs") self.assertTrue(array_equal(prof.x, arange(4, 7.1, 0.5))) self.assertTrue(array_equal(prof.y, arange(4, 7.1, 0.5))) self.assertTrue(array_equal(prof.y, arange(4, 7.1, 0.5))) @@ -158,7 +159,6 @@ def testSetCalculationRange(self): self.assertTrue(array_equal(prof.x, arange(4.5, 6.1, 0.5))) return - def testSetCalculationPoints(self): """Test the setCalculationPoints method.""" prof = self.profile @@ -170,11 +170,11 @@ def testSetCalculationPoints(self): # Test without data xcalc = arange(3, 12.2, 0.2) prof.setCalculationPoints(xcalc) - self.assertTrue( array_equal(xcalc, prof.x) ) + self.assertTrue(array_equal(xcalc, prof.x)) # Add the data. This should change the bounds of the calculation array. prof.setObservedProfile(x, y, dy) - self.assertTrue( array_equal(arange(3, 10.1, 0.2), prof.x ) ) + self.assertTrue(array_equal(arange(3, 10.1, 0.2), prof.x)) return @@ -190,17 +190,17 @@ def _test(p): self.assertAlmostEqual(1.802192e-3, p.dy[0]) # Test normal load - prof.loadtxt(data, usecols=(0,1,3)) + prof.loadtxt(data, usecols=(0, 1, 3)) _test(prof) # Test trying to not set unpack - prof.loadtxt(data, usecols=(0,1,3), unpack = False) + prof.loadtxt(data, usecols=(0, 1, 3), unpack=False) _test(prof) - prof.loadtxt(data, float, '#', None, None, 0, (0,1,3), False) + prof.loadtxt(data, float, "#", None, None, 0, (0, 1, 3), False) _test(prof) # Try not including dy - prof.loadtxt(data, usecols=(0,1)) + prof.loadtxt(data, usecols=(0, 1)) self.assertAlmostEqual(1e-2, prof.x[0]) self.assertAlmostEqual(1.105784e-1, prof.y[0]) self.assertAlmostEqual(1, prof.dy[0]) @@ -209,23 +209,23 @@ def _test(p): self.assertRaises(ValueError, prof.loadtxt, data, usecols=(0,)) return - def test_savetxt(self): "Check the savetxt method." prof = self.profile - self.assertRaises(SrFitError, prof.savetxt, 'foo') + self.assertRaises(SrFitError, prof.savetxt, "foo") xobs = arange(-2, 3.01, 0.25) - yobs = xobs ** 2 + yobs = xobs**2 prof.setObservedProfile(xobs, yobs) prof.ycalc = yobs.copy() fp = io.BytesIO() prof.savetxt(fp) txt = fp.getvalue().decode() - self.assertTrue(re.match(r'^# x +ycalc +y +dy\b', txt)) - nlines = len(txt.strip().split('\n')) + self.assertTrue(re.match(r"^# x +ycalc +y +dy\b", txt)) + nlines = len(txt.strip().split("\n")) self.assertEqual(22, nlines) return + # End of class TestProfile # ---------------------------------------------------------------------------- diff --git a/tests/test_profilegenerator.py b/tests/test_profilegenerator.py index 8e67ea29..6e42e397 100644 --- a/tests/test_profilegenerator.py +++ b/tests/test_profilegenerator.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for profilegenerator module.""" import pickle @@ -20,8 +19,8 @@ from numpy import arange, array_equal -from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator from diffpy.srfit.fitbase.profile import Profile +from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator class TestProfileGenerator(unittest.TestCase): @@ -34,7 +33,6 @@ def setUp(self): self.gen.setProfile(self.profile) return - def testOperation(self): """Test the operation method.""" gen = self.gen @@ -49,7 +47,6 @@ def testOperation(self): self.assertTrue(array_equal(2 * prof.x, val)) return - def testUpdate(self): """Update and change the profile to make sure generator is flushed.""" gen = self.gen @@ -77,17 +74,17 @@ def testUpdate(self): self.assertTrue(array_equal(x, gen.value)) return - def test_pickling(self): """Test pickling of ProfileGenerator.""" data = pickle.dumps(self.gen) gen2 = pickle.loads(data) - self.assertEqual('test', gen2.name) + self.assertEqual("test", gen2.name) x = self.profile.x self.assertTrue(array_equal(x, gen2.operation())) self.assertTrue(array_equal(3 * x, gen2(3 * x))) return + # End of class TestProfileGenerator if __name__ == "__main__": diff --git a/tests/test_recipeorganizer.py b/tests/test_recipeorganizer.py index 35746a79..60b2e4da 100644 --- a/tests/test_recipeorganizer.py +++ b/tests/test_recipeorganizer.py @@ -12,23 +12,26 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest +import numpy + from diffpy.srfit.equation.builder import EquationFactory from diffpy.srfit.fitbase.calculator import Calculator from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.recipeorganizer import equationFromString -from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer -from diffpy.srfit.fitbase.recipeorganizer import RecipeOrganizer -from .utils import capturestdout +from diffpy.srfit.fitbase.recipeorganizer import ( + RecipeContainer, + RecipeOrganizer, + equationFromString, +) -import numpy +from .utils import capturestdout # ---------------------------------------------------------------------------- + class TestEquationFromString(unittest.TestCase): def testEquationFromString(self): @@ -56,7 +59,7 @@ def testEquationFromString(self): self.assertRaises(ValueError, equationFromString, "p1+p2+p3", factory) # Pass that argument in the ns dictionary - eq = equationFromString("p1+p2+p3", factory, {"p3":p3}) + eq = equationFromString("p1+p2+p3", factory, {"p3": p3}) self.assertEqual(3, len(eq.args)) self.assertTrue(p1 in eq.args) self.assertTrue(p2 in eq.args) @@ -67,17 +70,21 @@ def testEquationFromString(self): self.assertTrue("p3" not in factory.builders) # Pass and use an unregistered parameter - self.assertRaises(ValueError, equationFromString, "p1+p2+p3+p4", - factory, {"p3":p3}) + self.assertRaises( + ValueError, equationFromString, "p1+p2+p3+p4", factory, {"p3": p3} + ) # Try to overload a registered parameter - self.assertRaises(ValueError, equationFromString, "p1+p2", - factory, {"p2":p4}) + self.assertRaises( + ValueError, equationFromString, "p1+p2", factory, {"p2": p4} + ) return + # ---------------------------------------------------------------------------- + class TestRecipeContainer(unittest.TestCase): def setUp(self): @@ -107,14 +114,18 @@ def testAccessors(self): self.assertTrue(m1.m2.p2 is p2) self.assertTrue(m1[0] is p1) - self.assertTrue(m1[0:] == [p1,]) + self.assertTrue( + m1[0:] + == [ + p1, + ] + ) self.assertTrue(m2[0] is p2) self.assertEqual(1, len(m1)) self.assertEqual(1, len(m2)) return - def testLocateManagedObject(self): """Test the locateManagedObject method.""" m1 = self.m @@ -155,8 +166,10 @@ def testLocateManagedObject(self): return + # ---------------------------------------------------------------------------- + class TestRecipeOrganizer(unittest.TestCase): def setUp(self): @@ -169,7 +182,6 @@ def setUp(self): def tearDown(self): return - def testNewParameter(self): """Test the addParameter method.""" @@ -186,7 +198,6 @@ def testNewParameter(self): self.assertTrue(p2 is m.p2) return - def testAddParameter(self): """Test the addParameter method.""" @@ -261,7 +272,6 @@ def testConstrain(self): self.assertEqual(0, len(self.m._constraints)) self.m.constrain(p1, "2*p2") - self.assertTrue(p1.constrained) self.assertTrue(p1 in self.m._constraints) self.assertEqual(1, len(self.m._constraints)) @@ -273,7 +283,7 @@ def testConstrain(self): # Check errors on unregistered parameters self.assertRaises(ValueError, self.m.constrain, p1, "2*p3") - self.assertRaises(ValueError, self.m.constrain, p1, "2*p2", {"p2":p3}) + self.assertRaises(ValueError, self.m.constrain, p1, "2*p2", {"p2": p3}) # Remove the constraint self.m.unconstrain(p1) @@ -298,21 +308,21 @@ def testRestrain(self): self.m._eqfactory.registerArgument("p2", p2) self.assertEqual(0, len(self.m._restraints)) - r = self.m.restrain("p1+p2", ub = 10) + r = self.m.restrain("p1+p2", ub=10) self.assertEqual(1, len(self.m._restraints)) p2.setValue(10) self.assertEqual(1, r.penalty()) self.m.unrestrain(r) self.assertEqual(0, len(self.m._restraints)) - r = self.m.restrain(p1, ub = 10) + r = self.m.restrain(p1, ub=10) self.assertEqual(1, len(self.m._restraints)) p1.setValue(11) self.assertEqual(1, r.penalty()) # Check errors on unregistered parameters self.assertRaises(ValueError, self.m.restrain, "2*p3") - self.assertRaises(ValueError, self.m.restrain, "2*p2", ns = {"p2":p3}) + self.assertRaises(ValueError, self.m.restrain, "2*p2", ns={"p2": p3}) return def testGetConstraints(self): @@ -384,7 +394,7 @@ def __call__(self, x): A = self.A.getValue() c = self.center.getValue() w = self.width.getValue() - return A * numpy.exp(-0.5*((x-c)/w)**2) + return A * numpy.exp(-0.5 * ((x - c) / w) ** 2) # End class GCalc @@ -397,27 +407,31 @@ def __call__(self, x): self.m.g.center.setValue(3.0) - self.assertTrue(numpy.array_equal(numpy.exp(-0.5*((x-3.0)/0.1)**2), - g(x))) + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 3.0) / 0.1) ** 2), g(x)) + ) self.m.g.center.setValue(5.0) - self.assertTrue(numpy.array_equal(numpy.exp(-0.5*((x-5.0)/0.1)**2), - g(x))) + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 5.0) / 0.1) ** 2), g(x)) + ) # Use this in another equation eq = self.m.registerStringFunction("g/x - 1", "pdf") - self.assertTrue(numpy.array_equal(g(x)/x - 1, eq())) + self.assertTrue(numpy.array_equal(g(x) / x - 1, eq())) return def testRegisterFunction(self): """Test registering various functions.""" + def g1(A, c, w, x): - return A * numpy.exp(-0.5*((x-c)/w)**2) + return A * numpy.exp(-0.5 * ((x - c) / w) ** 2) + def g2(A): - return A+1 + return A + 1 eq = self.m.registerFunction(g1, "g") @@ -430,12 +444,13 @@ def g2(A): self.m.c.setValue(3.0) self.m.w.setValue(0.1) - self.assertTrue(numpy.array_equal(numpy.exp(-0.5*((x-3.0)/0.1)**2), - eq())) + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 3.0) / 0.1) ** 2), eq()) + ) # Use this in another equation eq2 = self.m.registerStringFunction("g/x - 1", "pdf") - self.assertTrue(numpy.array_equal(eq()/x - 1, eq2())) + self.assertTrue(numpy.array_equal(eq() / x - 1, eq2())) # Make sure we can swap out "g". self.m.registerFunction(g2, "g") @@ -443,8 +458,11 @@ def g2(A): # Try a bound method class temp(object): - def eval(self): return 1.23 - def __call__(self): return 4.56 + def eval(self): + return 1.23 + + def __call__(self): + return 4.56 t = temp() eq = self.m.registerFunction(t.eval, "eval") @@ -494,48 +512,48 @@ def testRegisterStringFunction(self): return - def test_releaseOldEquations(self): """Verify EquationFactory does not hold temporary equations.""" - self.m._newParameter('x', 12) - self.assertEqual(36, self.m.evaluateEquation('3 * x')) + self.m._newParameter("x", 12) + self.assertEqual(36, self.m.evaluateEquation("3 * x")) self.assertEqual(0, len(self.m._eqfactory.equations)) return - def test_show(self): """Verify output from the show function.""" + def capture_show(*args, **kwargs): rv = capturestdout(self.m.show, *args, **kwargs) return rv - self.assertEqual('', capture_show()) - self.m._newParameter('x', 1) - self.m._newParameter('y', 2) + + self.assertEqual("", capture_show()) + self.m._newParameter("x", 1) + self.m._newParameter("y", 2) out1 = capture_show() - lines1 = out1.strip().split('\n') + lines1 = out1.strip().split("\n") self.assertEqual(4, len(lines1)) - self.assertTrue('Parameters' in lines1) - self.assertFalse('Constraints' in lines1) - self.assertFalse('Restraints' in lines1) - self.m._newParameter('z', 7) - self.m.constrain('y', '3 * z') + self.assertTrue("Parameters" in lines1) + self.assertFalse("Constraints" in lines1) + self.assertFalse("Restraints" in lines1) + self.m._newParameter("z", 7) + self.m.constrain("y", "3 * z") out2 = capture_show() - lines2 = out2.strip().split('\n') + lines2 = out2.strip().split("\n") self.assertEqual(9, len(lines2)) - self.assertTrue('Parameters' in lines2) - self.assertTrue('Constraints' in lines2) - self.assertFalse('Restraints' in lines2) - self.m.restrain('z', lb=2, ub=3, sig=0.001) + self.assertTrue("Parameters" in lines2) + self.assertTrue("Constraints" in lines2) + self.assertFalse("Restraints" in lines2) + self.m.restrain("z", lb=2, ub=3, sig=0.001) out3 = capture_show() - lines3 = out3.strip().split('\n') + lines3 = out3.strip().split("\n") self.assertEqual(13, len(lines3)) - self.assertTrue('Parameters' in lines3) - self.assertTrue('Constraints' in lines3) - self.assertTrue('Restraints' in lines3) - out4 = capture_show(pattern='x') - lines4 = out4.strip().split('\n') + self.assertTrue("Parameters" in lines3) + self.assertTrue("Constraints" in lines3) + self.assertTrue("Restraints" in lines3) + out4 = capture_show(pattern="x") + lines4 = out4.strip().split("\n") self.assertEqual(9, len(lines4)) - out5 = capture_show(pattern='^') + out5 = capture_show(pattern="^") self.assertEqual(out3, out5) # check output with another level of hierarchy self.m._addObject(RecipeOrganizer("foo"), self.m._containers) @@ -543,11 +561,12 @@ def capture_show(*args, **kwargs): out6 = capture_show() self.assertTrue("foo.bar" in out6) # filter out foo.bar - out7 = capture_show('^(?!foo).') + out7 = capture_show("^(?!foo).") self.assertFalse("foo.bar" in out7) self.assertEqual(out3, out7) return + # ---------------------------------------------------------------------------- if __name__ == "__main__": diff --git a/tests/test_restraint.py b/tests/test_restraint.py index 2968dd32..68673679 100644 --- a/tests/test_restraint.py +++ b/tests/test_restraint.py @@ -12,15 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from diffpy.srfit.fitbase.restraint import Restraint -from diffpy.srfit.fitbase.recipeorganizer import equationFromString -from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.equation.builder import EquationFactory +from diffpy.srfit.fitbase.parameter import Parameter +from diffpy.srfit.fitbase.recipeorganizer import equationFromString +from diffpy.srfit.fitbase.restraint import Restraint class TestRestraint(unittest.TestCase): @@ -63,6 +62,7 @@ def testRestraint(self): # Make a really large number to check the upper bound import numpy + r.ub = numpy.inf p1.setValue(1e100) self.assertEqual(0, r.penalty()) diff --git a/tests/test_sas.py b/tests/test_sas.py index 02bb61eb..110c863e 100644 --- a/tests/test_sas.py +++ b/tests/test_sas.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for sas package.""" import unittest @@ -20,12 +19,13 @@ import numpy from diffpy.srfit.sas import SASGenerator, SASParser, SASProfile -from .utils import datafile -from .utils import has_sas, _msg_nosas from diffpy.srfit.sas.sasimport import sasimport +from .utils import _msg_nosas, datafile, has_sas + # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_sas, _msg_nosas) class TestSASParser(unittest.TestCase): @@ -36,43 +36,93 @@ def testParser(self): x, y, dx, dy = parser.getData() - testx = numpy.array([0.002618, 0.007854, 0.01309, 0.01832, 0.02356, - 0.02879, 0.03402, 0.03925, 0.04448, 0.0497]) + testx = numpy.array( + [ + 0.002618, + 0.007854, + 0.01309, + 0.01832, + 0.02356, + 0.02879, + 0.03402, + 0.03925, + 0.04448, + 0.0497, + ] + ) diff = testx - x res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testy = numpy.array([ 0.02198, 0.02201, 0.02695, 0.02645, 0.03024, - 0.3927, 7.305, 17.43, 13.43, 8.346]) + testy = numpy.array( + [ + 0.02198, + 0.02201, + 0.02695, + 0.02645, + 0.03024, + 0.3927, + 7.305, + 17.43, + 13.43, + 8.346, + ] + ) diff = testy - y res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testdy = numpy.array([ 0.002704, 0.001643, 0.002452, 0.001769, - 0.001531, 0.1697, 1.006, 0.5351, 0.3677, 0.191]) + testdy = numpy.array( + [ + 0.002704, + 0.001643, + 0.002452, + 0.001769, + 0.001531, + 0.1697, + 1.006, + 0.5351, + 0.3677, + 0.191, + ] + ) diff = testdy - dy res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testdx = numpy.array([0.0004091, 0.005587, 0.005598, 0.005624, - 0.005707, 0.005975, 0.006264, 0.006344, 0.006424, 0.006516]) + testdx = numpy.array( + [ + 0.0004091, + 0.005587, + 0.005598, + 0.005624, + 0.005707, + 0.005975, + 0.006264, + 0.006344, + 0.006424, + 0.006516, + ] + ) diff = testdx - dx res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) return + # End of class TestSASParser # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_sas, _msg_nosas) class TestSASGenerator(unittest.TestCase): def testGenerator(self): # Test generator output - SphereModel = sasimport('sas.models.SphereModel').SphereModel + SphereModel = sasimport("sas.models.SphereModel").SphereModel model = SphereModel() gen = SASGenerator("sphere", model) @@ -88,8 +138,7 @@ def testGenerator(self): self.assertEqual(defval, par.getValue()) self.assertEqual(defval, model.getParam(pname)) - - r = numpy.arange(1, 10, 0.1, dtype = float) + r = numpy.arange(1, 10, 0.1, dtype=float) y = gen(r) refy = model.evalDistribution(r) diff = y - refy @@ -98,16 +147,15 @@ def testGenerator(self): return - def testGenerator2(self): # Test generator with a profile - EllipsoidModel = sasimport('sas.models.EllipsoidModel').EllipsoidModel + EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel model = EllipsoidModel() gen = SASGenerator("ellipsoid", model) # Load the data using SAS tools - Loader = sasimport('sas.dataloader.loader').Loader + Loader = sasimport("sas.dataloader.loader").Loader loader = Loader() data = datafile("sas_ellipsoid_testdata.txt") datainfo = loader.load(data) @@ -125,6 +173,7 @@ def testGenerator2(self): self.assertAlmostEqual(0, res) return + # End of class TestSASGenerator if __name__ == "__main__": diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index c6a29212..f057ab5d 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -12,19 +12,23 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests space group constraints.""" import unittest import numpy -from .utils import datafile -from .utils import has_pyobjcryst, _msg_nopyobjcryst -from .utils import has_structure, _msg_nostructure +from .utils import ( + _msg_nopyobjcryst, + _msg_nostructure, + datafile, + has_pyobjcryst, + has_structure, +) # ---------------------------------------------------------------------------- + class TestSGConstraints(unittest.TestCase): @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) @@ -32,8 +36,8 @@ def test_ObjCryst_constrainSpaceGroup(self): """Make sure that all Parameters are constrained properly. This tests constrainSpaceGroup from - diffpy.srfit.structure.sgconstraints, which is performed automatically - when an ObjCrystCrystalParSet is created. + diffpy.srfit.structure.sgconstraints, which is performed + automatically when an ObjCrystCrystalParSet is created. """ from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet @@ -50,16 +54,16 @@ def test_ObjCryst_constrainSpaceGroup(self): # Check the orthorhombic lattice l = stru.getLattice() - self.assertTrue( l.alpha.const ) - self.assertTrue( l.beta.const ) - self.assertTrue( l.gamma.const ) - self.assertEqual(pi/2, l.alpha.getValue()) - self.assertEqual(pi/2, l.beta.getValue()) - self.assertEqual(pi/2, l.gamma.getValue()) - - self.assertFalse( l.a.const ) - self.assertFalse( l.b.const ) - self.assertFalse( l.c.const ) + self.assertTrue(l.alpha.const) + self.assertTrue(l.beta.const) + self.assertTrue(l.gamma.const) + self.assertEqual(pi / 2, l.alpha.getValue()) + self.assertEqual(pi / 2, l.beta.getValue()) + self.assertEqual(pi / 2, l.gamma.getValue()) + + self.assertFalse(l.a.const) + self.assertFalse(l.b.const) + self.assertFalse(l.c.const) self.assertEqual(0, len(l._constraints)) # Now make sure the scatterers are constrained properly @@ -95,12 +99,12 @@ def test_ObjCryst_constrainSpaceGroup(self): # Nor can we make them into variables from diffpy.srfit.fitbase.fitrecipe import FitRecipe + f = FitRecipe() self.assertRaises(ValueError, f.addVar, mn.x) return - @unittest.skipUnless(has_structure, _msg_nostructure) def test_DiffPy_constrainAsSpaceGroup(self): """Test the constrainAsSpaceGroup function.""" @@ -110,14 +114,17 @@ def test_DiffPy_constrainAsSpaceGroup(self): stru = makeLaMnO3_P1() parset = DiffpyStructureParSet("LaMnO3", stru) - sgpars = constrainAsSpaceGroup(parset, "P b n m", - scatterers = parset.getScatterers()[::2], - constrainadps = True) + sgpars = constrainAsSpaceGroup( + parset, + "P b n m", + scatterers=parset.getScatterers()[::2], + constrainadps=True, + ) # Make sure that the new parameters were created for par in sgpars: self.assertNotEqual(None, par) - self.assertNotEqual(None, par.getValue() ) + self.assertNotEqual(None, par.getValue()) # Test the unconstrained atoms for scatterer in parset.getScatterers()[1::2]: @@ -136,10 +143,13 @@ def test_DiffPy_constrainAsSpaceGroup(self): def _consttest(par): return par.const + def _constrainedtest(par): return par.constrained + def _proxytest(par): return par in proxied + def _alltests(par): return _consttest(par) or _constrainedtest(par) or _proxytest(par) @@ -151,25 +161,31 @@ def _alltests(par): self.assertTrue(test) test = False - for par in [scatterer.U11, scatterer.U22, scatterer.U33, - scatterer.U12, scatterer.U13, scatterer.U23]: + for par in [ + scatterer.U11, + scatterer.U22, + scatterer.U33, + scatterer.U12, + scatterer.U13, + scatterer.U23, + ]: test |= _alltests(par) self.assertTrue(test) return - @unittest.skipUnless(has_structure, _msg_nostructure) def test_ConstrainAsSpaceGroup_args(self): """Test the arguments processing of constrainAsSpaceGroup function.""" from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup from diffpy.structure.spacegroups import GetSpaceGroup + stru = makeLaMnO3_P1() parset = DiffpyStructureParSet("LaMnO3", stru) sgpars = constrainAsSpaceGroup(parset, "P b n m") - sg = GetSpaceGroup('P b n m') + sg = GetSpaceGroup("P b n m") parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1()) sgpars2 = constrainAsSpaceGroup(parset2, sg) list(sgpars) @@ -177,20 +193,23 @@ def test_ConstrainAsSpaceGroup_args(self): self.assertEqual(sgpars.names, sgpars2.names) return + # End of class TestSGConstraints # Local helper functions ----------------------------------------------------- + def makeLaMnO3_P1(): from diffpy.structure import Structure + stru = Structure() - stru.read(datafile('LaMnO3.stru')) + stru.read(datafile("LaMnO3.stru")) return stru def makeLaMnO3(): - from pyobjcryst.crystal import Crystal from pyobjcryst.atom import Atom + from pyobjcryst.crystal import Crystal from pyobjcryst.scatteringpower import ScatteringPowerAtom pi = numpy.pi @@ -199,31 +218,32 @@ def makeLaMnO3(): crystal.SetName("LaMnO3") # La1 sp = ScatteringPowerAtom("La1", "La") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0.996096, 0.0321494, 0.25, "La1", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) # Mn1 sp = ScatteringPowerAtom("Mn1", "Mn") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0, 0.5, 0, "Mn1", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) # O1 sp = ScatteringPowerAtom("O1", "O") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0.0595746, 0.496164, 0.25, "O1", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) # O2 sp = ScatteringPowerAtom("O2", "O") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0.720052, 0.289387, 0.0311126, "O2", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) return crystal + # ---------------------------------------------------------------------------- if __name__ == "__main__": diff --git a/tests/test_speed.py b/tests/test_speed.py index 7329c2f6..19af219b 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -12,16 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" from __future__ import print_function import random + import numpy -import diffpy.srfit.equation.visitors as visitors import diffpy.srfit.equation.literals as literals +import diffpy.srfit.equation.visitors as visitors from .utils import _makeArgs @@ -57,7 +57,7 @@ def makeLazyEquation(): mult2.addLiteral(exp) v2.setValue(x) - v3.setValue(50*x) + v3.setValue(50 * x) v5.setValue(2.11) v6.setValue(numpy.e) @@ -75,23 +75,27 @@ def _f(a, b, c, d, e): return _f + def makeEquation1(): """Make the same equation as the lazy one.""" - y = 50*x + y = 50 * x def _f(a, b, c, d, e): - return ((a+x)*(y-b))**c * d**e + return ((a + x) * (y - b)) ** c * d**e return _f + def timeFunction(f, *args, **kw): """Time a function in ms.""" import time + t1 = time.time() f(*args, **kw) t2 = time.time() - return (t2-t1)*1000 + return (t2 - t1) * 1000 + def speedTest1(): f1 = makeLazyEquation() @@ -102,7 +106,7 @@ def speedTest1(): total1 = 0 total2 = 0 for i in range(len(args)): - args[i] = 10*random.random() + args[i] = 10 * random.random() print("Changing argument %i" % (i + 1)) t1 = timeFunction(f1, *args) t2 = timeFunction(f2, *args) @@ -114,11 +118,13 @@ def speedTest1(): print("Totals:") print("lazy", total1) print("regular", total2) - print("Ratio (lazy/regular)", total1/total2) + print("Ratio (lazy/regular)", total1 / total2) + -def speedTest2(mutate = 2): +def speedTest2(mutate=2): from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 20, 0.05) @@ -144,17 +150,20 @@ def speedTest2(mutate = 2): eq.b7.setValue(2.0) eq.b8.setValue(2.0) - from numpy import exp - from numpy import polyval + from numpy import exp, polyval + def f(A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8): - return A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2)) + polyval([b8, b7, b6, b5,b4,b3,b2,b1],x) + return A0 * exp(-((x * qsig) ** 2)) * ( + exp(-(((x - 1.0) / sigma1) ** 2)) + + exp(-(((x - 2.0) / sigma2) ** 2)) + ) + polyval([b8, b7, b6, b5, b4, b3, b2, b1], x) tnpy = 0 teq = 0 # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [0.0]*(len(eq.args)) + args = [0.0] * (len(eq.args)) # The call-loop random.seed() @@ -174,15 +183,17 @@ def f(A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8): tnpy += timeFunction(f, *args) teq += timeFunction(eq, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("numpy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("numpy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return -def speedTest3(mutate = 2): + +def speedTest3(mutate=2): """Test wrt sympy. Results - sympy is 10 to 24 times faster without using arrays (ouch!). @@ -191,6 +202,7 @@ def speedTest3(mutate = 2): """ from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 20, 0.05) @@ -216,17 +228,30 @@ def speedTest3(mutate = 2): eq.b7.setValue(2.0) eq.b8.setValue(2.0) - from sympy import var, exp, lambdify from numpy import polyval - A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var("A0 qsig sigma1 sigma2 b1 b2 b3 b4 b5 b6 b7 b8 xx") - f = lambdify(vars, A0*exp(-(xx*qsig)**2)*(exp(-((xx-1.0)/sigma1)**2)+exp(-((xx-2.0)/sigma2)**2)) + polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy") + from sympy import exp, lambdify, var + + A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var( + "A0 qsig sigma1 sigma2 b1 b2 b3 b4 b5 b6 b7 b8 xx" + ) + f = lambdify( + vars, + A0 + * exp(-((xx * qsig) ** 2)) + * ( + exp(-(((xx - 1.0) / sigma1) ** 2)) + + exp(-(((xx - 2.0) / sigma2) ** 2)) + ) + + polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), + "numpy", + ) tnpy = 0 teq = 0 # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [1.0]*(len(eq.args)) + args = [1.0] * (len(eq.args)) args.append(x) # The call-loop @@ -247,15 +272,17 @@ def speedTest3(mutate = 2): teq += timeFunction(eq, *(args[:-1])) tnpy += timeFunction(f, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("sympy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("sympy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return -def speedTest4(mutate = 2): + +def speedTest4(mutate=2): """Test wrt sympy. Results - sympy is 10 to 24 times faster without using arrays (ouch!). @@ -264,6 +291,7 @@ def speedTest4(mutate = 2): """ from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 20, 0.05) @@ -274,9 +302,12 @@ def speedTest4(mutate = 2): factory.registerConstant("x", x) eq = factory.makeEquation(eqstr) - from sympy import var, lambdify from numpy import polyval - b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var("b1 b2 b3 b4 b5 b6 b7 b8 xx") + from sympy import lambdify, var + + b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var( + "b1 b2 b3 b4 b5 b6 b7 b8 xx" + ) f = lambdify(vars, polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy") tnpy = 0 @@ -284,7 +315,7 @@ def speedTest4(mutate = 2): # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [1.0]*(len(eq.args)) + args = [1.0] * (len(eq.args)) args.append(x) # The call-loop @@ -305,18 +336,21 @@ def speedTest4(mutate = 2): teq += timeFunction(eq, *(args[:-1])) tnpy += timeFunction(f, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("sympy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("sympy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return -def weightedTest(mutate = 2): + +def weightedTest(mutate=2): """Show the benefits of a properly balanced equation tree.""" from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 10, 0.01) @@ -336,20 +370,21 @@ def weightedTest(mutate = 2): eq.b7.setValue(2.0) eq.b8.setValue(2.0) - #scale = visitors.NodeWeigher() - #eq.root.identify(scale) - #print(scale.output) + # scale = visitors.NodeWeigher() + # eq.root.identify(scale) + # print(scale.output) from numpy import polyval + def f(b1, b2, b3, b4, b5, b6, b7, b8): - return polyval([b8, b7, b6, b5,b4,b3,b2,b1],x) + return polyval([b8, b7, b6, b5, b4, b3, b2, b1], x) tnpy = 0 teq = 0 # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [0.1]*numargs + args = [0.1] * numargs # The call-loop random.seed() @@ -365,23 +400,26 @@ def f(b1, b2, b3, b4, b5, b6, b7, b8): c.remove(idx) args[idx] = random.random() - #print(args) + # print(args) # Time the different functions with these arguments teq += timeFunction(eq, *args) tnpy += timeFunction(f, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("numpy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("numpy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return + def profileTest(): from diffpy.srfit.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 10, 0.001) @@ -404,7 +442,7 @@ def profileTest(): mutate = 8 numargs = len(eq.args) choices = range(numargs) - args = [0.1]*numargs + args = [0.1] * numargs # The call-loop random.seed() @@ -432,15 +470,7 @@ def profileTest(): for i in range(1, 9): weightedTest(i) """ - """ - from diffpy.srfit.equation.builder import EquationFactory - import random - import cProfile - cProfile.run('profileTest()', 'prof') - import pstats - p = pstats.Stats('prof') - p.strip_dirs() - p.sort_stats('time') - p.print_stats(10) - profileTest() - """ + """From diffpy.srfit.equation.builder import EquationFactory import random + import cProfile cProfile.run('profileTest()', 'prof') import pstats p = + pstats.Stats('prof') p.strip_dirs() p.sort_stats('time') p.print_stats(10) + profileTest()""" diff --git a/tests/test_tagmanager.py b/tests/test_tagmanager.py index d4d6c5f8..e340e2a5 100644 --- a/tests/test_tagmanager.py +++ b/tests/test_tagmanager.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for tagmanager.py.""" import unittest from diffpy.srfit.util.tagmanager import TagManager + ############################################################################## class TestTagManager(unittest.TestCase): @@ -71,7 +71,7 @@ def test_union_and_intersection(self): m = self.m m.tag(3, "3", "number") m.tag(4, "4", "number") - objs = set([3,4]) + objs = set([3, 4]) self.assertEqual(m.union(), set()) self.assertEqual(m.union("number"), objs) self.assertEqual(m.union("3"), set([3])) @@ -95,17 +95,18 @@ def test_hasTags(self): m = self.m m.tag(3, "3", "number") m.tag(4, "4", "number") - self.assertTrue( m.hasTags(3, "3") ) - self.assertTrue( m.hasTags(3, "3", "number") ) - self.assertFalse( m.hasTags(3, "3", "4") ) + self.assertTrue(m.hasTags(3, "3")) + self.assertTrue(m.hasTags(3, "3", "number")) + self.assertFalse(m.hasTags(3, "3", "4")) self.assertRaises(KeyError, m.hasTags, 3, "fail") m.silent = True self.assertFalse(m.hasTags(3, "fail")) return + # End of class TestTagManager -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() # End of file diff --git a/tests/test_visitors.py b/tests/test_visitors.py index 28ea05b1..58a7ffcd 100644 --- a/tests/test_visitors.py +++ b/tests/test_visitors.py @@ -12,15 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -import diffpy.srfit.equation.visitors as visitors import diffpy.srfit.equation.literals as literals +import diffpy.srfit.equation.visitors as visitors + from .utils import _makeArgs + class TestValidator(unittest.TestCase): def testSimpleFunction(self): @@ -72,6 +73,7 @@ def testSimpleFunction(self): # Fix the operation of plus import numpy + plus.operation = numpy.add validator.reset() mult.identify(validator) @@ -85,6 +87,7 @@ def testSimpleFunction(self): return + class TestArgFinder(unittest.TestCase): def testSimpleFunction(self): @@ -134,6 +137,7 @@ def testArg(self): self.assertTrue(args[0] is v1) return + class TestSwapper(unittest.TestCase): def testSimpleFunction(self): diff --git a/tests/test_weakrefcallable.py b/tests/test_weakrefcallable.py index 595af03a..5d44d18b 100644 --- a/tests/test_weakrefcallable.py +++ b/tests/test_weakrefcallable.py @@ -12,38 +12,36 @@ # See LICENSE.txt for license information. # ############################################################################## - """Unit tests for the weakrefcallable module.""" -import unittest import pickle +import unittest from diffpy.srfit.fitbase import FitContribution from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.util.weakrefcallable import weak_ref, WeakBoundMethod +from diffpy.srfit.util.weakrefcallable import WeakBoundMethod, weak_ref # ---------------------------------------------------------------------------- + class TestWeakBoundMethod(unittest.TestCase): def setUp(self): - self.f = FitContribution('f') - self.f.setEquation('7') + self.f = FitContribution("f") + self.f.setEquation("7") self.w = weak_ref(self.f._eq._flush, fallback=_fallback_example) return - def tearDown(self): self.f = None self.assertTrue(None is self.w._wref()) - obj, args, kw = self.w('any', 'argument', foo=37) + obj, args, kw = self.w("any", "argument", foo=37) self.assertTrue(obj is self.w) - self.assertEqual(('any', 'argument'), args) - self.assertEqual({'foo' : 37}, kw) + self.assertEqual(("any", "argument"), args) + self.assertEqual({"foo": 37}, kw) return - def test___init__(self): """Check WeakBoundMethod.__init__()""" self.assertTrue(self.w.fallback is _fallback_example) @@ -51,7 +49,6 @@ def test___init__(self): self.assertTrue(None is wf.fallback) return - def test___call__(self): """Check WeakBoundMethod.__call__()""" f = self.f @@ -61,17 +58,16 @@ def test___call__(self): self.w(()) self.assertTrue(None is f._eq._value) # check WeakBoundMethod behavior with no fallback - x = Parameter('x', value=3) + x = Parameter("x", value=3) wgetx = weak_ref(x.getValue) self.assertEqual(3, wgetx()) del x self.assertRaises(ReferenceError, wgetx) return - def test___hash__(self): """Check WeakBoundMethod.__hash__()""" - f1 = FitContribution('f1') + f1 = FitContribution("f1") w1 = weak_ref(f1._flush) h0 = hash(w1) del f1 @@ -82,10 +78,9 @@ def test___hash__(self): self.assertEqual(hash(w1c1), hash(w1c2)) return - def test___eq__(self): """Check WeakBoundMethod.__eq__()""" - f1 = FitContribution('f1') + f1 = FitContribution("f1") w1 = weak_ref(f1._flush) w2 = weak_ref(f1._flush) self.assertEqual(w1, w2) @@ -102,7 +97,6 @@ def test___eq__(self): self.assertEqual(w1, w1cc) return - def test_pickling(self): """Verify unpickling works when it involves __hash__ call.""" holder = set([self.w]) @@ -114,12 +108,11 @@ def test_pickling(self): self.assertTrue(feq2 is w2._wref()) return - def test_observable_deregistration(self): """Check if Observable drops dead Observer.""" f = self.f - x = f.newParameter('x', 5) - f.setEquation('3 * x') + x = f.newParameter("x", 5) + f.setEquation("3 * x") self.assertEqual(15, f.evaluate()) self.assertEqual(15, f._eq._value) # get one of the observer callables that are associated with f @@ -138,15 +131,17 @@ def test_observable_deregistration(self): self.assertEqual(0, len(x._observers)) return + # End of class TestWeakBoundMethod # Local Routines ------------------------------------------------------------- + def _fallback_example(wbm, *args, **kwargs): return (wbm, args, kwargs) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() # End of file diff --git a/tests/utils.py b/tests/utils.py index 37c95376..79a77353 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,16 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Helper routines for testing.""" import sys + import six import diffpy.srfit.equation.literals as literals from diffpy.srfit.sas.sasimport import sasimport -from . import logger +from . import logger # Resolve availability of optional third-party packages. @@ -29,60 +29,69 @@ try: _msg_nosas = "No module named 'sas.pr.invertor'" - sasimport('sas.pr.invertor') + sasimport("sas.pr.invertor") _msg_nosas = "No module named 'sas.models'" - sasimport('sas.models') + sasimport("sas.models") has_sas = True except ImportError as e: has_sas = False - logger.warning('%s, SaS tests skipped.', e) + logger.warning("%s, SaS tests skipped.", e) # diffpy.structure _msg_nostructure = "No module named 'diffpy.structure'" try: - import diffpy.structure as m; del m + import diffpy.structure as m + + del m has_structure = True except ImportError: has_structure = False - logger.warning('Cannot import diffpy.structure, Structure tests skipped.') + logger.warning("Cannot import diffpy.structure, Structure tests skipped.") # pyobjcryst _msg_nopyobjcryst = "No module named 'pyobjcryst'" try: - import pyobjcryst as m; del m + import pyobjcryst as m + + del m has_pyobjcryst = True except ImportError: has_pyobjcryst = False - logger.warning('Cannot import pyobjcryst, pyobjcryst tests skipped.') + logger.warning("Cannot import pyobjcryst, pyobjcryst tests skipped.") # diffpy.srreal _msg_nosrreal = "No module named 'diffpy.srreal'" try: - import diffpy.srreal.pdfcalculator as m; del m + import diffpy.srreal.pdfcalculator as m + + del m has_srreal = True except ImportError: has_srreal = False - logger.warning('Cannot import diffpy.srreal, PDF tests skipped.') + logger.warning("Cannot import diffpy.srreal, PDF tests skipped.") # Helper functions for testing ----------------------------------------------- + def _makeArgs(num): args = [] for i in range(num): - j=i+1 - args.append(literals.Argument(name="v%i"%j, value=j)) + j = i + 1 + args.append(literals.Argument(name="v%i" % j, value=j)) return args def noObserversInGlobalBuilders(): """True if no observer function leaks to global builder objects. - Ensure objects are not immortal due to a reference from static value. + Ensure objects are not immortal due to a reference from static + value. """ from diffpy.srfit.equation.builder import _builders + rv = True for n, b in _builders.items(): if b.literal and b.literal._observers: @@ -93,6 +102,7 @@ def noObserversInGlobalBuilders(): def datafile(filename): from pkg_resources import resource_filename + rv = resource_filename(__name__, "testdata/" + filename) return rv @@ -108,4 +118,5 @@ def capturestdout(f, *args, **kwargs): sys.stdout = savestdout return fp.getvalue() + # End of file From cfb732d61a6cd63a17f730b3a2e05e28842e94b7 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 09:47:38 -0400 Subject: [PATCH 15/45] chore: adding all skpkg files and removing unwanted files from the db --- .coveragerc | 22 -------- .gitarchive.cfg | 5 -- .gitattributes | 7 --- AUTHORS.rst | 10 ++++ CHANGELOG.rst | 5 ++ CODE_OF_CONDUCT.rst | 133 ++++++++++++++++++++++++++++++++++++++++++++ LICENSE.rst | 29 ++++++++++ 7 files changed, 177 insertions(+), 34 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .gitarchive.cfg delete mode 100644 .gitattributes create mode 100644 AUTHORS.rst create mode 100644 CHANGELOG.rst create mode 100644 CODE_OF_CONDUCT.rst create mode 100644 LICENSE.rst diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index d0a0d16d..00000000 --- a/.coveragerc +++ /dev/null @@ -1,22 +0,0 @@ -# Configuration of the coverage.py tool for reporting test coverage. - -[report] -# RE patterns for lines to be excluded from consideration. -exclude_lines = - ## Have to re-enable the standard pragma - pragma: no cover - ## Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - ^[ ]*assert False - - ## Don't complain if non-runnable code isn't run: - ^[ ]*@unittest.skip\b - ^[ ]{4}unittest.main() - if __name__ == .__main__.: - - -[run] -omit = - ## exclude debug.py from codecov report - */tests/debug.py diff --git a/.gitarchive.cfg b/.gitarchive.cfg deleted file mode 100644 index 95e1448c..00000000 --- a/.gitarchive.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[DEFAULT] -commit = $Format:%H$ -date = $Format:%ci$ -timestamp = $Format:%ct$ -refnames = $Format:%D$ diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 9d58a8cd..00000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -/.gitattributes export-ignore -/.gitignore export-ignore -/.travis.yml export-ignore -/conda-recipe/ export-ignore -/devutils export-ignore -.gitarchive.cfg export-subst -*.bat text eol=crlf diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 00000000..c338ade7 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,10 @@ +Authors +======= + +Christopher Farrow, Pavol Juhas, and members of the Billinge Group + +Contributors +------------ + +For a list of contributors, visit +https://github.com/diffpy/diffpy.srfit/graphs/contributors diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..f29d3b53 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,5 @@ +============= +Release notes +============= + +.. current developments diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 00000000..e8199ca5 --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socioeconomic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 00000000..9cc6a9d1 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2025, The Trustees of Columbia University in the City of New York. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 3580b8168080bba62f7cadf479595c60819e4da6 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 09:56:47 -0400 Subject: [PATCH 16/45] fix: update authors and changelog --- .codecov.yml | 41 +++----- AUTHORS.rst | 2 +- AUTHORS.txt | 9 -- CHANGELOG.md | 33 ------- CHANGELOG.rst | 31 ++++++ LICENSE.txt | 137 --------------------------- LICENSE_DANSE.txt | 34 ------- MANIFEST.in | 24 ++--- README.rst | 181 ++++++++++++++++------------------- doc/Makefile | 223 ++++++++++++++++++++++++++++++++++++++------ doc/manual/Makefile | 179 ----------------------------------- 11 files changed, 326 insertions(+), 568 deletions(-) delete mode 100644 AUTHORS.txt delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE.txt delete mode 100644 LICENSE_DANSE.txt delete mode 100644 doc/manual/Makefile diff --git a/.codecov.yml b/.codecov.yml index d6c556b8..4af5eb24 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,33 +1,14 @@ -# codecov can find this file anywhere in the repo, so we don't need to clutter -# the root folder. -#comment: false - -codecov: - notify: - require_ci_to_pass: no - coverage: status: - patch: + project: # more options at https://docs.codecov.com/docs/commit-status default: - target: "80" - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - project: - default: false - library: - target: auto - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - paths: "!*/tests/.*" - - tests: - target: 97.9% - paths: "*/tests/.*" - -flags: - tests: - paths: - - tests/ + target: auto # use the coverage from the base commit, fail if coverage is lower + threshold: 0% # allow the coverage to drop by + +comment: + layout: " diff, flags, files" + behavior: default + require_changes: false + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/AUTHORS.rst b/AUTHORS.rst index c338ade7..b2335043 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,7 +1,7 @@ Authors ======= -Christopher Farrow, Pavol Juhas, and members of the Billinge Group +Christopher Farrow, Pavol Juhas, Simon J. L. Billinge, and members of the Billinge Group Contributors ------------ diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 2e7b1714..00000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,9 +0,0 @@ -Authors: - -Chris Farrow -Pavol Juhas -Simon J.L. Billinge - -Contributors: - -https://github.com/diffpy/diffpy.srfit/graphs/contributors diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7b90ae3f..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,33 +0,0 @@ -# Release notes - -## Version 3.0.0 – 2019-03-14 - -Differences from version 1.3. - -### Added - -- Support for Python 3.7, 3.6, 3.5 in addition to 2.7. - -### Changed - -- Always use lower-case imports from `diffpy.structure`. -- Use numeric-value sort to order variables in `PrintFitHook`. - -### Deprecated - -- Variable `__gitsha__` in the `version` module renamed to `__git_commit__`. - -### Removed - -- Optional upper and lower-bound arguments in `Parameter.setValue`. - The bounds can be set with `Parameter.boundRange` instead. -- Unused classes `ListOperator`, `SetOperator`. - -### Fixed - -- Metadata retrieval from `PDFContribution` hierarchy. -- Refresh `PDFGenerator` when its `rgrid` is changed in-place. -- Zero division in the `nppdfsas.py` example. -- Crash in `ellipsoidsas.py` example because of bug in `Parameter.setValue`. -- Pickling of `ProfileGenerator` objects and of bound class methods. -- Invalid escape sequences in string values. diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f29d3b53..7a65655d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,3 +3,34 @@ Release notes ============= .. current developments + +Version 3.0.0 – 2019-03-14 +========================== + +**Added:** + +* Support for Python 3.7, 3.6, 3.5 in addition to 2.7. + +**Changed:** + +* Always use lower-case imports from `diffpy.structure`. +* Use numeric-value sort to order variables in `PrintFitHook`. + +**Deprecated:** + +* Variable `__gitsha__` in the `version` module renamed to `__git_commit__`. + +**Removed:** + +* Optional upper and lower-bound arguments in `Parameter.setValue`. + The bounds can be set with `Parameter.boundRange` instead. +* Unused classes `ListOperator`, `SetOperator`. + +**Fixed:** + +* Metadata retrieval from `PDFContribution` hierarchy. +* Refresh `PDFGenerator` when its `rgrid` is changed in-place. +* Zero division in the `nppdfsas.py` example. +* Crash in `ellipsoidsas.py` example because of bug in `Parameter.setValue`. +* Pickling of `ProfileGenerator` objects and of bound class methods. +* Invalid escape sequences in string values. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index f6d92af7..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,137 +0,0 @@ -OPEN SOURCE LICENSE AGREEMENT -============================= - -Copyright (c) 2009-2011, University of Tennessee -Copyright (c) 1989, 1991 Free Software Foundation, Inc. -Copyright (c) 2006, The Regents of the University of California through - Lawrence Berkeley National Laboratory -Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") -Copyright (c) 2006-2007, Board of Trustees of Michigan State University -Copyright (c) 2008-2012, The Trustees of Columbia University in the City - of New York - -Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National - Laboratory - - -The "DiffPy-CMI" is distributed subject to the following license conditions: - - -SOFTWARE LICENSE AGREEMENT - - Software: DiffPy-CMI - - -(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either -source code, or binary form and accompanying documentation). - -Part of the software was derived from the DANSE, ObjCryst++ (with permission), -PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of -which the original Copyrights are contained in each individual file. - -Each licensee is addressed as "you" or "Licensee." - - -(2) The copyright holders shown above and their third-party Licensors hereby -grant licensee a royalty-free nonexclusive license, subject to the limitations -stated herein and U.S. Government license rights. - - -(3) You may modify and make a copy or copies of the software for use within -your organization, if you meet the following conditions: - - (a) Copies in source code must include the copyright notice and this - software license agreement. - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy. - - -(4) You may modify a copy or copies of the Software or any portion of it, thus -forming a work based on the Software, and distribute copies of such work -outside your organization, if you meet all of the following conditions: - - (a) Copies in source code must include the copyright notice and this - Software License Agreement; - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy; - - (c) Modified copies and works based on the Software must carry prominent - notices stating that you changed specified portions of the Software. - - (d) Neither the name of Brookhaven Science Associates or Brookhaven - National Laboratory nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - written permission. - - -(5) Portions of the Software resulted from work developed under a U.S. -Government contract and are subject to the following license: -The Government is granted for itself and others acting on its behalf a -paid-up, nonexclusive, irrevocable worldwide license in this computer software -to reproduce, prepare derivative works, and perform publicly and display -publicly. - - -(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT -WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY -LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND -THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING -BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL -LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF -THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE -PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION -UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. - - -(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR -THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF -ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, -CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING -BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, -WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING -NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS -BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. - - -Brookhaven National Laboratory Notice -===================================== - -Acknowledgment of sponsorship ------------------------------ - -This software was produced by the Brookhaven National Laboratory, under -Contract DE-AC02-98CH10886 with the Department of Energy. - - -Government disclaimer of liability ----------------------------------- - -Neither the United States nor the United States Department of Energy, nor -any of their employees, makes any warranty, express or implied, or assumes -any legal liability or responsibility for the accuracy, completeness, or -usefulness of any data, apparatus, product, or process disclosed, or -represents that its use would not infringe privately owned rights. - - -Brookhaven disclaimer of liability ----------------------------------- - -Brookhaven National Laboratory makes no representations or warranties, -express or implied, nor assumes any liability for the use of this software. - - -Maintenance of notice ---------------------- - -In the interest of clarity regarding the origin and status of this -software, Brookhaven National Laboratory requests that any recipient of it -maintain this notice affixed to any distribution by the recipient that -contains a copy or derivative of this software. - - -END OF LICENSE diff --git a/LICENSE_DANSE.txt b/LICENSE_DANSE.txt deleted file mode 100644 index c92ca2d5..00000000 --- a/LICENSE_DANSE.txt +++ /dev/null @@ -1,34 +0,0 @@ -This program is part of the DiffPy and DANSE open-source projects at Columbia -University and is available subject to the conditions and terms laid out below. - -Copyright (c) 2008-2011, The Trustees of Columbia University in -the City of New York. All rights reserved. - -For more information please visit the diffpy web-page at - http://www.diffpy.org -or email Prof. Simon Billinge at sb2896@columbia.edu. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the names of COLUMBIA UNIVERSITY, MICHIGAN STATE UNIVERSITY nor the - names of their contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in index 3894e104..f1a78eec 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,16 +1,12 @@ -recursive-include src * -include AUTHORS.txt LICENSE*.txt README.rst -recursive-exclude src *.pyc -global-exclude .gitattributes .gitignore .gitarchive.cfg -global-exclude .DS_Store +graft src +graft tests +graft requirements -# Avoid user content in setup.cfg to make distribution reproducible. -exclude setup.cfg +include AUTHORS.rst LICENSE*.rst README.rst -# Exclude git-tracked files spuriously added by setuptools_scm -exclude .codecov.yml -exclude .coveragerc -exclude .travis* -prune conda-recipe -prune devutils -prune doc +# Exclude all bytecode files and __pycache__ directories +global-exclude *.py[cod] # Exclude all .pyc, .pyo, and .pyd files. +global-exclude .DS_Store # Exclude Mac filesystem artifacts. +global-exclude __pycache__ # Exclude Python cache directories. +global-exclude .git* # Exclude git files and directories. +global-exclude .idea # Exclude PyCharm project settings. diff --git a/README.rst b/README.rst index 59fefa83..55ccf947 100644 --- a/README.rst +++ b/README.rst @@ -1,146 +1,127 @@ -.. image:: https://travis-ci.org/diffpy/diffpy.srfit.svg?branch=master - :target: https://travis-ci.org/diffpy/diffpy.srfit +|Icon| |title|_ +=============== -.. image:: https://codecov.io/gh/diffpy/diffpy.srfit/branch/master/graph/badge.svg - :target: https://codecov.io/gh/diffpy/diffpy.srfit +.. |title| replace:: diffpy.srfit +.. _title: https://diffpy.github.io/diffpy.srfit +.. |Icon| image:: https://avatars.githubusercontent.com/diffpy + :target: https://diffpy.github.io/diffpy.srfit + :height: 100px -diffpy.srfit -======================================================================== +|PyPI| |Forge| |PythonVersion| |PR| -Configurable code for solving atomic structures. +|CI| |Codecov| |Black| |Tracking| -The diffpy.srfit package provides the framework for building a global optimizer -on the fly from components such as function calculators (that calculate -different data spectra), regression algorithms and structure models. The -software is capable of co-refinement using multiple information sources or -models. It provides a uniform interface for various regression algorithms. The -target function being optimized can be specified by the user according to the -data available. +.. |Black| image:: https://img.shields.io/badge/code_style-black-black + :target: https://github.com/psf/black -Within the diffpy.srfit framework, any parameter used in describing the -structure of a material can be passed as a refinable variable to the global -optimizer. Once parameters are declared as variables they can easily be turned -"on" or "off", i.e. fixed or allowed to vary. Additionally, variables may be -constrained to obey mathematical relationships with other parameters or -variables used in the structural model. Restraints can be applied to -variables, which adds a penalty to the refinement process commensurate with the -deviation from the known value or range. The cost function can also be -customized by the user. If the refinement contains multiple models, each model -can have its own cost function which will be properly weighted and combined to -obtain the total cost function. Additionally, diffpy.srfit is designed to be -extensible, allowing the user to integrate external calculators to perform -co-refinements with other techniques. +.. |CI| image:: https://github.com/diffpy/diffpy.srfit/actions/workflows/matrix-and-codecov-on-merge-to-main.yml/badge.svg + :target: https://github.com/diffpy/diffpy.srfit/actions/workflows/matrix-and-codecov-on-merge-to-main.yml -For more information about the diffpy.srfit library, see the users manual at -http://diffpy.github.io/diffpy.srfit. +.. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.srfit/branch/main/graph/badge.svg + :target: https://codecov.io/gh/diffpy/diffpy.srfit -REQUIREMENTS ------------------------------------------------------------------------- +.. |Forge| image:: https://img.shields.io/conda/vn/conda-forge/diffpy.srfit + :target: https://anaconda.org/conda-forge/diffpy.srfit -The diffpy.srfit package requires Python 3.5 or later or 2.7 and -the following software: +.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff -* ``setuptools`` - software distribution tools for Python -* ``NumPy`` - numerical mathematics and fast array operations for Python -* ``SciPy`` - scientific libraries for Python -* ``matplotlib`` - python plotting library +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.srfit + :target: https://pypi.org/project/diffpy.srfit/ -Recommended software: +.. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.srfit + :target: https://pypi.org/project/diffpy.srfit/ -Optimizations involving crystal structures or molecules require +.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue + :target: https://github.com/diffpy/diffpy.srfit/issues -* ``diffpy.structure`` - crystal structure container and parsers, - https://github.com/diffpy/diffpy.structure -* ``pyobjcryst`` - Crystal and Molecule storage, rigid units, bond - length and bond angle restraints, https://github.com/diffpy/pyobjcryst +Configurable code for solving atomic structures. -Optimizations involving pair distribution functions PDF or bond valence -sums require +* LONGER DESCRIPTION HERE -* ``diffpy.srreal`` - python library for PDF calculation, - https://github.com/diffpy/diffpy.srreal +For more information about the diffpy.srfit library, please consult our `online documentation `_. -Optimizations involving small angle scattering or shape characteristic -functions from the diffpy.srfit.sas module require +Citation +-------- -* ``sas`` - module for calculation of P(R) in small-angle scattering - from the SasView project, http://www.sasview.org +If you use diffpy.srfit in a scientific publication, we would like you to cite this package as -We recommend to use `Anaconda Python `_ -as it allows to install all software dependencies together with -diffpy.srfit. For other Python distributions it is necessary to -install the required software separately. As an example, on Ubuntu -Linux some of the required software can be installed using :: + diffpy.srfit Package, https://github.com/diffpy/diffpy.srfit - sudo apt-get install \ - python3-setuptools python3-numpy python3-scipy python3-matplotlib +Installation +------------ -For other required packages see their respective web pages for installation -instructions. +The preferred method is to use `Miniconda Python +`_ +and install from the "conda-forge" channel of Conda packages. +To add "conda-forge" to the conda channels, run the following in a terminal. :: -INSTALLATION ------------------------------------------------------------------------- + conda config --add channels conda-forge -The preferred method is to use Anaconda Python and install from the -"diffpy" channel of Anaconda packages :: +We want to install our packages in a suitable conda environment. +The following creates and activates a new environment named ``diffpy.srfit_env`` :: - conda config --add channels diffpy - conda install diffpy.srfit + conda create -n diffpy.srfit_env diffpy.srfit + conda activate diffpy.srfit_env -diffpy.srfit is also included in the "diffpy-cmi" collection -of packages for structure analysis :: +To confirm that the installation was successful, type :: - conda install diffpy-cmi + python -c "import diffpy.srfit; print(diffpy.srfit.__version__)" -Another option is to use ``easy_install`` to download and install the -latest release from `Python Package Index `_ :: +The output should print the latest version displayed on the badges above. - easy_install diffpy.srfit +If the above does not work, you can use ``pip`` to download and install the latest release from +`Python Package Index `_. +To install using ``pip`` into your ``diffpy.srfit_env`` environment, type :: -If you prefer to install from sources, make sure all required software -packages are in place and then run :: + pip install diffpy.srfit - python setup.py install +If you prefer to install from sources, after installing the dependencies, obtain the source archive from +`GitHub `_. Once installed, ``cd`` into your ``diffpy.srfit`` directory +and run the following :: -You may need to use ``sudo`` with system Python so the process is -allowed to put files to the system directories. If administrator (root) -access is not available, consult the output from -``python setup.py install --help`` for options to install to a -user-writable locations. The installation integrity can be verified by -changing to the HOME directory and running :: + pip install . - python -m diffpy.srfit.tests.run +Getting Started +--------------- +You may consult our `online documentation `_ for tutorials and API references. -DEVELOPMENT ------------------------------------------------------------------------- +Support and Contribute +---------------------- -diffpy.srfit is an open-source software developed as a part of the DiffPy-CMI -complex modeling initiative at the Brookhaven National Laboratory. The -diffpy.srfit sources are hosted at -https://github.com/diffpy/diffpy.srfit. +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. -Feel free to fork the project and contribute. To install diffpy.srfit +Feel free to fork the project and contribute. To install diffpy.srfit in a development mode, with its sources being directly used by Python -rather than copied to a package directory, use :: +rather than copied to a package directory, use the following in the root +directory :: + + pip install -e . + +To ensure code quality and to prevent accidental commits into the default branch, please set up the use of our pre-commit +hooks. - python setup.py develop --user +1. Install pre-commit in your working environment by running ``conda install pre-commit``. +2. Initialize pre-commit (one time only) ``pre-commit install``. -ACKNOWLEDGEMENT ------------------------------------------------------------------------- +Thereafter your code will be linted by black and isort and checked against flake8 before you can commit. +If it fails by black or isort, just rerun and it should pass (black and isort will modify the files so should +pass after they are modified). If the flake8 test fails please see the error messages and fix them manually before +trying to commit again. -The source code in *observable.py* was derived from the 1.0 version -of the Caltech "Pyre" project. +Improvements and fixes are always appreciated. +Before contributing, please read our `Code of Conduct `_. -CONTACTS ------------------------------------------------------------------------- +Contact +------- -For more information on diffpy.srfit please visit the project web-page +For more information on diffpy.srfit please visit the project `web-page `_ or email Simon Billinge at sb2896@columbia.edu. -http://www.diffpy.org +Acknowledgements +---------------- -or email Prof. Simon Billinge at sb2896@columbia.edu. +``diffpy.srfit`` is built and maintained with `scikit-package `_. diff --git a/doc/Makefile b/doc/Makefile index a265fe5d..3720ec8e 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,33 +1,194 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build +BASENAME = $(subst .,,$(subst $() $(),,diffpy.srfit)) + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" -all: doc examples upload - -RELEASE = alpha9 -TARGET = farrowch@login.cacr.caltech.edu -DOCROOT = ~/docroot/diffraction/ -PKGROOT = ~/dev_danse_us/ - -.PHONY : doc -doc: - epydoc diffpy.srfit --html -vvv -o diffpy.srfitapi -n diffpy.srfit \ ---include-log --exclude diffpy.srfit.structure.cctbxstructure $@ - $(MAKE) -C devmanual $@ - -.PHONY : upload -upload: - rsync -ruvz --delete diffpy.srfitapi $(TARGET):$(DOCROOT) - ssh $(TARGET) "rm -rf $(DOCROOT)/diffpy.srfitapi-$(RELEASE)" - ssh $(TARGET) "cp -r $(DOCROOT)/diffpy.srfitapi $(DOCROOT)/diffpy.srfitapi-$(RELEASE)" - rsync -ruv srfit_examples.zip $(TARGET):$(PKGROOT) - ssh $(TARGET) "rm -rf $(PKGROOT)/srfit_examples-$(RELEASE).zip" - ssh $(TARGET) "cp -r $(PKGROOT)/srfit_examples.zip $(PKGROOT)/srfit_examples-$(RELEASE).zip" - $(MAKE) -C devmanual $@ - -.PHONY : examples -examples: - zip -r srfit_examples.zip ./examples/*.py ./examples/data -x \*svn\* -x \*threedoublepeaks\* -x \*temp\* -x \*test\* - -.PHONY : clean clean: - rm -rf diffpy.srfitapi - rm -f srfit_examples.zip - $(MAKE) -C devmanual $@ + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(BASENAME).qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(BASENAME).qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +# Manual publishing to the gh-pages branch + +GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) +GITREMOTE = origin +GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) +GITLASTCOMMIT = $(shell git rev-parse --short HEAD) + +publish: + @test -d build/html || \ + ( echo >&2 "Run 'make html' first!"; false ) + git show-ref --verify --quiet refs/heads/gh-pages || \ + git branch --track gh-pages $(GITREMOTE)/gh-pages + test -d build/gh-pages || \ + git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages + cd build/gh-pages && \ + git pull $(GITREMOTEURL) gh-pages + rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ + --exclude-from=build/gh-pages/.rsync-exclude \ + --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ + cd build/gh-pages && \ + git add --all . && \ + git diff --cached --quiet || \ + git commit -m "Sync with the source at $(GITLASTCOMMIT)." + cd build/gh-pages && \ + git push origin gh-pages diff --git a/doc/manual/Makefile b/doc/manual/Makefile deleted file mode 100644 index beb126b4..00000000 --- a/doc/manual/Makefile +++ /dev/null @@ -1,179 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext publish - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SrFit.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SrFit.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/SrFit" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SrFit" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -# Manual publishing to the gh-pages branch - -GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) -GITREMOTE = origin -GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) -GITLASTCOMMIT = $(shell git rev-parse --short HEAD) - -publish: - @test -d build/html || \ - ( echo >&2 "Run 'make html' first!"; false ) - git show-ref --verify --quiet refs/heads/gh-pages || \ - git branch --track gh-pages $(GITREMOTE)/gh-pages - test -d build/gh-pages || \ - git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages - cd build/gh-pages && \ - git pull $(GITREMOTEURL) gh-pages - rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ - --exclude-from=build/gh-pages/.rsync-exclude \ - --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ - cd build/gh-pages && \ - git add --all . && \ - git diff --cached --quiet || \ - git commit -m "Sync with the source at $(GITLASTCOMMIT)." - cd build/gh-pages && \ - git push origin gh-pages From 81bac08e173e01f53ec169472fc5be04383f403f Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 10:09:31 -0400 Subject: [PATCH 17/45] chore: licenses and manifest --- LICENSE.rst | 167 ++++++++++++++++++++++++++++++++++++++-------- LICENSE_DANSE.rst | 34 ++++++++++ MANIFEST.in | 2 + 3 files changed, 174 insertions(+), 29 deletions(-) create mode 100644 LICENSE_DANSE.rst diff --git a/LICENSE.rst b/LICENSE.rst index 9cc6a9d1..7342dda5 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,29 +1,138 @@ -BSD 3-Clause License - -Copyright (c) 2025, The Trustees of Columbia University in the City of New York. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +OPEN SOURCE LICENSE AGREEMENT +============================= + +Copyright (c) 2009-2011, University of Tennessee +Copyright (c) 1989, 1991 Free Software Foundation, Inc. +Copyright (c) 2006, The Regents of the University of California through + Lawrence Berkeley National Laboratory +Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") +Copyright (c) 2006-2007, Board of Trustees of Michigan State University +Copyright (c) 2008-2012, The Trustees of Columbia University in the City + of New York +Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National + Laboratory +Copyright (c) 2020-2025, The Trustees of Columbia University in the City + of New York + + +The "DiffPy-CMI" is distributed subject to the following license conditions: + + +SOFTWARE LICENSE AGREEMENT + + Software: DiffPy-CMI + + +(1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either +source code, or binary form and accompanying documentation). + +Part of the software was derived from the DANSE, ObjCryst++ (with permission), +PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of +which the original Copyrights are contained in each individual file. + +Each licensee is addressed as "you" or "Licensee." + + +(2) The copyright holders shown above and their third-party Licensors hereby +grant licensee a royalty-free nonexclusive license, subject to the limitations +stated herein and U.S. Government license rights. + + +(3) You may modify and make a copy or copies of the software for use within +your organization, if you meet the following conditions: + + (a) Copies in source code must include the copyright notice and this + software license agreement. + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy. + + +(4) You may modify a copy or copies of the Software or any portion of it, thus +forming a work based on the Software, and distribute copies of such work +outside your organization, if you meet all of the following conditions: + + (a) Copies in source code must include the copyright notice and this + Software License Agreement; + + (b) Copies in binary form must include the copyright notice and this + Software License Agreement in the documentation and/or other materials + provided with the copy; + + (c) Modified copies and works based on the Software must carry prominent + notices stating that you changed specified portions of the Software. + + (d) Neither the name of Brookhaven Science Associates or Brookhaven + National Laboratory nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + written permission. + + +(5) Portions of the Software resulted from work developed under a U.S. +Government contract and are subject to the following license: +The Government is granted for itself and others acting on its behalf a +paid-up, nonexclusive, irrevocable worldwide license in this computer software +to reproduce, prepare derivative works, and perform publicly and display +publicly. + + +(6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT +WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY +LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND +THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL +LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF +THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE +PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION +UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. + + +(7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR +THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF +ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING +BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, +WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING +NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS +BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. + + +Brookhaven National Laboratory Notice +===================================== + +Acknowledgment of sponsorship +----------------------------- + +This software was produced by the Brookhaven National Laboratory, under +Contract DE-AC02-98CH10886 with the Department of Energy. + + +Government disclaimer of liability +---------------------------------- + +Neither the United States nor the United States Department of Energy, nor +any of their employees, makes any warranty, express or implied, or assumes +any legal liability or responsibility for the accuracy, completeness, or +usefulness of any data, apparatus, product, or process disclosed, or +represents that its use would not infringe privately owned rights. + + +Brookhaven disclaimer of liability +---------------------------------- + +Brookhaven National Laboratory makes no representations or warranties, +express or implied, nor assumes any liability for the use of this software. + + +Maintenance of notice +--------------------- + +In the interest of clarity regarding the origin and status of this +software, Brookhaven National Laboratory requests that any recipient of it +maintain this notice affixed to any distribution by the recipient that +contains a copy or derivative of this software. + + +END OF LICENSE diff --git a/LICENSE_DANSE.rst b/LICENSE_DANSE.rst new file mode 100644 index 00000000..c92ca2d5 --- /dev/null +++ b/LICENSE_DANSE.rst @@ -0,0 +1,34 @@ +This program is part of the DiffPy and DANSE open-source projects at Columbia +University and is available subject to the conditions and terms laid out below. + +Copyright (c) 2008-2011, The Trustees of Columbia University in +the City of New York. All rights reserved. + +For more information please visit the diffpy web-page at + http://www.diffpy.org +or email Prof. Simon Billinge at sb2896@columbia.edu. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the names of COLUMBIA UNIVERSITY, MICHIGAN STATE UNIVERSITY nor the + names of their contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in index f1a78eec..73c554b8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,3 +10,5 @@ global-exclude .DS_Store # Exclude Mac filesystem artifacts. global-exclude __pycache__ # Exclude Python cache directories. global-exclude .git* # Exclude git files and directories. global-exclude .idea # Exclude PyCharm project settings. +exclude .codecov.yml +exclude .coveragerc \ No newline at end of file From ac3011f264817148d09a7a5370bd47fcf2322c9a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 14:10:23 +0000 Subject: [PATCH 18/45] [pre-commit.ci] auto fixes from pre-commit hooks --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 73c554b8..b31fb162 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,4 +11,4 @@ global-exclude __pycache__ # Exclude Python cache directories. global-exclude .git* # Exclude git files and directories. global-exclude .idea # Exclude PyCharm project settings. exclude .codecov.yml -exclude .coveragerc \ No newline at end of file +exclude .coveragerc From 901cf6f401af2354c38fd68aac662b846c6c3052 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 10:17:20 -0400 Subject: [PATCH 19/45] chore: readme blended --- README.rst | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 55ccf947..d8db4cef 100644 --- a/README.rst +++ b/README.rst @@ -35,9 +35,32 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.srfit/issues +diffpy.srfit +============ + Configurable code for solving atomic structures. -* LONGER DESCRIPTION HERE +The diffpy.srfit package provides the framework for building a global optimizer +on the fly from components such as function calculators (that calculate +different data spectra), regression algorithms and structure models. The +software is capable of co-refinement using multiple information sources or +models. It provides a uniform interface for various regression algorithms. The +target function being optimized can be specified by the user according to the +data available. + +Within the diffpy.srfit framework, any parameter used in describing the +structure of a material can be passed as a refinable variable to the global +optimizer. Once parameters are declared as variables they can easily be turned +"on" or "off", i.e. fixed or allowed to vary. Additionally, variables may be +constrained to obey mathematical relationships with other parameters or +variables used in the structural model. Restraints can be applied to +variables, which adds a penalty to the refinement process commensurate with the +deviation from the known value or range. The cost function can also be +customized by the user. If the refinement contains multiple models, each model +can have its own cost function which will be properly weighted and combined to +obtain the total cost function. Additionally, diffpy.srfit is designed to be +extensible, allowing the user to integrate external calculators to perform +co-refinements with other techniques. For more information about the diffpy.srfit library, please consult our `online documentation `_. @@ -46,7 +69,13 @@ Citation If you use diffpy.srfit in a scientific publication, we would like you to cite this package as - diffpy.srfit Package, https://github.com/diffpy/diffpy.srfit + + P. Juhás, C. L. Farrow, X. Yang, K. R. Knox and S. J. L. Billinge, + `Complex modeling: a strategy and software program for combining + multiple information sources to solve ill posed structure and + nanostructure inverse problems + `__, + *Acta Crystallogr. A* **71**, 562-568 (2015). Installation ------------ @@ -71,6 +100,30 @@ To confirm that the installation was successful, type :: The output should print the latest version displayed on the badges above. +This will install the minimal `diffpy.srfit` installation. It will often be used +as along with other packages for manipulating and computing crystal structures +and so on. We also therefore recommend installing the following: + +* ``diffpy.structure`` - crystal structure container and parsers, + https://github.com/diffpy/diffpy.structure +* ``pyobjcryst`` - Crystal and Molecule storage, rigid units, bond + length and bond angle restraints, https://github.com/diffpy/pyobjcryst + +Optimizations involving pair distribution functions PDF or bond valence +sums require + +* ``diffpy.srreal`` - python library for PDF calculation, + https://github.com/diffpy/diffpy.srreal + +Optimizations involving small angle scattering or shape characteristic +functions from the diffpy.srfit.sas module require + +* ``sas`` - module for calculation of P(R) in small-angle scattering + from the SasView project, http://www.sasview.org + +Fallback Installation +--------------------- + If the above does not work, you can use ``pip`` to download and install the latest release from `Python Package Index `_. To install using ``pip`` into your ``diffpy.srfit_env`` environment, type :: @@ -125,3 +178,6 @@ Acknowledgements ---------------- ``diffpy.srfit`` is built and maintained with `scikit-package `_. + +The source code in *observable.py* was derived from the 1.0 version +of the Caltech "Pyre" project. \ No newline at end of file From e223401c5f68c7105fe9a66ea48e065bfd3c018b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 14:19:10 +0000 Subject: [PATCH 20/45] [pre-commit.ci] auto fixes from pre-commit hooks --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d8db4cef..2d4040ec 100644 --- a/README.rst +++ b/README.rst @@ -180,4 +180,4 @@ Acknowledgements ``diffpy.srfit`` is built and maintained with `scikit-package `_. The source code in *observable.py* was derived from the 1.0 version -of the Caltech "Pyre" project. \ No newline at end of file +of the Caltech "Pyre" project. From 9232638a5e79a9a6af52d974d28934c6228f7535 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 11:08:50 -0400 Subject: [PATCH 21/45] chore: codespell --- .codespell/ignore_words.txt | 6 + .travis.yml | 118 ------------------ doc/examples/coreshellnp.py | 2 +- doc/examples/crystalpdf.py | 2 +- doc/examples/crystalpdfall.py | 2 +- doc/examples/crystalpdftwodata.py | 2 +- doc/examples/crystalpdftwophase.py | 2 +- doc/examples/debyemodel.py | 2 +- doc/examples/ellipsoidsas.py | 2 +- doc/examples/gaussianrecipe.py | 2 +- doc/examples/npintensity.py | 2 +- doc/examples/simplepdftwophase.py | 2 +- src/diffpy/srfit/equation/builder.py | 6 +- src/diffpy/srfit/equation/equationmod.py | 2 +- .../srfit/equation/literals/argument.py | 2 +- .../srfit/equation/visitors/argfinder.py | 2 +- src/diffpy/srfit/fitbase/configurable.py | 4 +- src/diffpy/srfit/fitbase/fitcontribution.py | 4 +- src/diffpy/srfit/fitbase/fitrecipe.py | 2 +- src/diffpy/srfit/fitbase/fitresults.py | 4 +- src/diffpy/srfit/fitbase/profile.py | 2 +- src/diffpy/srfit/fitbase/profileparser.py | 2 +- src/diffpy/srfit/fitbase/recipeorganizer.py | 6 +- src/diffpy/srfit/fitbase/restraint.py | 2 +- src/diffpy/srfit/pdf/basepdfgenerator.py | 2 +- .../srfit/pdf/characteristicfunctions.py | 2 +- src/diffpy/srfit/pdf/pdfcontribution.py | 2 +- src/diffpy/srfit/pdf/pdfparser.py | 4 +- src/diffpy/srfit/sas/sasparser.py | 2 +- src/diffpy/srfit/structure/objcrystparset.py | 4 +- src/diffpy/srfit/structure/sgconstraints.py | 4 +- src/diffpy/srfit/util/inpututils.py | 14 +-- src/diffpy/srfit/util/observable.py | 8 +- tests/test_builder.py | 2 +- tests/test_literals.py | 4 +- 35 files changed, 59 insertions(+), 171 deletions(-) delete mode 100644 .travis.yml diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt index 04b4fcfa..3e653cfb 100644 --- a/.codespell/ignore_words.txt +++ b/.codespell/ignore_words.txt @@ -6,3 +6,9 @@ mater ;; Frobenius norm used in np.linalg.norm fro + +;; nin is a legit variable in builder +nin + +;; highT is used for high Temperature in examples/debymodelII +highT diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3de77be9..00000000 --- a/.travis.yml +++ /dev/null @@ -1,118 +0,0 @@ -dist: xenial -language: generic - -os: - - linux - - osx - -env: - - MYUSEMC=true MYPYTHON_VERSION=2.7 - - MYUSEMC=true MYPYTHON_VERSION=3.5 - - MYUSEMC=true MYPYTHON_VERSION=3.6 - - MYUSEMC=true MYPYTHON_VERSION=3.7 - - MYUSEMC=false - -git: - depth: 999999 - -branches: - except: - - /^v[0-9]/ - -before_install: - - MYNAME=diffpy.srfit - - MYCOMMIT="$(git rev-parse HEAD)" - - umask 022 - - git fetch origin --tags - - MYPYTHON=python; MYPIP=pip - - NOSYS=true; NOAPT=true; NOBREW=true; NOMC=true - - if ${MYUSEMC}; then - NOMC=false; - elif [[ ${TRAVIS_OS_NAME} == linux ]]; then - NOAPT=false; NOSYS=false; - MYPIPFLAGS="--user"; - elif [[ ${TRAVIS_OS_NAME} == osx ]]; then - NOBREW=false; NOSYS=false; - MYPYTHON=python3; - MYPIP=pip3; - MYPIPFLAGS="--user"; - fi - - MYMCREPO=https://repo.anaconda.com/miniconda - - case ${TRAVIS_OS_NAME} in - linux) - MYMCBUNDLE=Miniconda3-latest-Linux-x86_64.sh ;; - osx) - MYMCBUNDLE=Miniconda3-latest-MacOSX-x86_64.sh ;; - *) - echo "Unsupported operating system." >&2; - exit 2 ;; - esac - - MYRUNDIR=${PWD}/build/rundir - - - mkdir -p ~/pkgs - - mkdir -p ${MYRUNDIR} - - cp .coveragerc ${MYRUNDIR}/ - - - $NOMC || pushd ~/pkgs - - $NOMC || wget --timestamping ${MYMCREPO}/${MYMCBUNDLE} - - $NOMC || test -x ~/mc/bin/conda || bash ${MYMCBUNDLE} -b -f -p ~/mc - - $NOMC || popd - - $NOMC || source ~/mc/bin/activate base - - $NOMC || conda update --yes conda - - $NOMC || conda install --yes conda-build conda-verify jinja2 - - $NOMC || conda create --name=testenv --yes python=${MYPYTHON_VERSION} coverage - - $NOMC || conda config --add channels diffpy - - - $NOAPT || test "${TRAVIS_OS_NAME}" = "linux" || exit $? - - $NOAPT || PATH="$(echo "$PATH" | sed 's,:/opt/pyenv/[^:]*,,g')" - - $NOAPT || test "$(which python)" = "/usr/bin/python" || ( - which python; exit 1) - - $NOAPT || sudo apt-get update -qq - - $NOAPT || sudo apt-get install -y - python-dev python-setuptools python-numpy - build-essential - - - $NOBREW || test "${TRAVIS_OS_NAME}" = "osx" || exit $? - - $NOBREW || brew update - - $NOBREW || brew upgrade python - - $NOBREW || brew install gcc || brew link --overwrite gcc - - - $NOSYS || devutils/makesdist - - $NOSYS || MYTARBUNDLE="$(ls -t "${PWD}"/dist/*.tar.gz | head -1)" - -install: - - $NOMC || conda build --python=${MYPYTHON_VERSION} conda-recipe - - $NOMC || conda render --python=${MYPYTHON_VERSION} --output conda-recipe | - sed 's,.*/,,; s/[.]tar[.]bz2$//; s/-/=/g' > /tmp/mypackage.txt - - $NOMC || source activate testenv - - $NOMC || conda install --yes --use-local --file=/tmp/mypackage.txt - - $NOMC || conda install --yes - diffpy.structure pyobjcryst "diffpy.srreal>=1.3.0" - # TODO - always install srfit-sasview when ready for Python 3. - - if $MYUSEMC && [[ "$MYPYTHON_VERSION" == 2.7 ]]; then - conda install --yes srfit-sasview; - fi - - - $NOSYS || $MYPIP install $MYPIPFLAGS coverage - - $NOSYS || $MYPIP install $MYPIPFLAGS pycifrw - - $NOSYS || $MYPIP install $MYPIPFLAGS diffpy.structure - - $NOSYS || $MYPIP install $MYPIPFLAGS "${MYTARBUNDLE}" - - - cd ${MYRUNDIR} - - MYGIT_REV=$($MYPYTHON -c "import ${MYNAME}.version as v; print(v.__git_commit__)") - - if [[ "${MYCOMMIT}" != "${MYGIT_REV}" ]]; then - echo "Version mismatch ${MYCOMMIT} vs ${MYGIT_REV}."; - exit 1; - fi - -before_script: - - $NOBREW || USER_BASE="$(python3 -c 'import site; print(site.USER_BASE)')" - - $NOBREW || PATH="${USER_BASE}/bin:${PATH}" - -script: - - coverage run --source ${MYNAME} -m ${MYNAME}.tests.run - -after_success: - # do not post coverage reports when testing with system Python. - - $NOMC || $MYPIP install $MYPIPFLAGS codecov - - $NOMC || codecov diff --git a/doc/examples/coreshellnp.py b/doc/examples/coreshellnp.py index 2ac0a2c5..8030a346 100644 --- a/doc/examples/coreshellnp.py +++ b/doc/examples/coreshellnp.py @@ -101,7 +101,7 @@ def makeRecipe(stru1, stru2, datname): recipe.constrain(generator_zns.scale, "1 - scale_CdS") # We also want the resolution factor to be the same on each. - # Vary the gloabal scale as well. + # Vary the global scale as well. recipe.addVar(contribution.scale, 0.3) # Now we can configure the structural parameters. We tag the different diff --git a/doc/examples/crystalpdf.py b/doc/examples/crystalpdf.py index d770b11c..0d668da5 100644 --- a/doc/examples/crystalpdf.py +++ b/doc/examples/crystalpdf.py @@ -16,7 +16,7 @@ This is example of fitting the fcc nickel structure to measured PDF data. The purpose of this example is to demonstrate and describe the -classes in configuraiton options involved with setting up a fit in this +classes in configuration options involved with setting up a fit in this way. The main benefit of using SrFit for PDF refinement is the flexibility of modifying the PDF profile function for specific needs, adding restraints to a fit and the ability to simultaneously refine a diff --git a/doc/examples/crystalpdfall.py b/doc/examples/crystalpdfall.py index ea2990bb..918b7a9d 100644 --- a/doc/examples/crystalpdfall.py +++ b/doc/examples/crystalpdfall.py @@ -132,7 +132,7 @@ def makeRecipe( recipe.constrain(xgenerator_sini_si.scale, "1 - pscale_sini_ni") # The qdamp parameters are too correlated to vary so we fix them based on - # previous measurments. + # previous measurements. xgenerator_ni.qdamp.value = 0.055 xgenerator_si.qdamp.value = 0.051 ngenerator_ni.qdamp.value = 0.030 diff --git a/doc/examples/crystalpdftwodata.py b/doc/examples/crystalpdftwodata.py index 12e9e9f1..cd2aa0c6 100644 --- a/doc/examples/crystalpdftwodata.py +++ b/doc/examples/crystalpdftwodata.py @@ -117,7 +117,7 @@ def makeRecipe(ciffile, xdatname, ndatname): recipe.addVar(ngenerator.scale, 1, "nscale") recipe.addVar(xgenerator.qdamp, 0.01, "xqdamp") recipe.addVar(ngenerator.qdamp, 0.01, "nqdamp") - # delta2 is a non-structual material propery. Thus, we constrain together + # delta2 is a non-structual material property. Thus, we constrain together # delta2 Parameter from each PDFGenerator. delta2 = recipe.newVar("delta2", 2) recipe.constrain(xgenerator.delta2, delta2) diff --git a/doc/examples/crystalpdftwophase.py b/doc/examples/crystalpdftwophase.py index a5958554..b0fcf30d 100644 --- a/doc/examples/crystalpdftwophase.py +++ b/doc/examples/crystalpdftwophase.py @@ -96,7 +96,7 @@ def makeRecipe(niciffile, siciffile, datname): recipe.constrain(generator_ni.qdamp, "qdamp") recipe.constrain(generator_si.qdamp, "qdamp") - # Vary the gloabal scale as well. + # Vary the global scale as well. recipe.addVar(contribution.scale, 1) # Now we can configure the structural parameters. Since we're using diff --git a/doc/examples/debyemodel.py b/doc/examples/debyemodel.py index a53d24f2..0ec673a7 100644 --- a/doc/examples/debyemodel.py +++ b/doc/examples/debyemodel.py @@ -145,7 +145,7 @@ def makeRecipe(): # We would like to 'suggest' that the offset should remain positive. This # is somethine that we know about the system that might help the refinement # converge to a physically reasonable result. We will do this with a soft - # contraint, or restraint. Here we restrain the offset variable to between + # constraint, or restraint. Here we restrain the offset variable to between # 0 and infinity. We tell the recipe that we want to scale the penalty for # breaking the restraint by the point-average chi^2 value so that the # restraint is roughly as significant as any other data point throughout diff --git a/doc/examples/ellipsoidsas.py b/doc/examples/ellipsoidsas.py index ece4f73a..b3e8d18e 100644 --- a/doc/examples/ellipsoidsas.py +++ b/doc/examples/ellipsoidsas.py @@ -71,7 +71,7 @@ def makeRecipe(datname): ## Configure the fit variables # The SASGenerator uses the parameters from the params and dispersion - # attribues of the model. These vary from model to model, but are adopted + # attributes of the model. These vary from model to model, but are adopted # as SrFit Parameters within the generator. Whereas the dispersion # parameters are accessible as, e.g. "radius.width", within the # SASGenerator these are named like "radius_width". diff --git a/doc/examples/gaussianrecipe.py b/doc/examples/gaussianrecipe.py index d82ad4d0..8f762ba7 100644 --- a/doc/examples/gaussianrecipe.py +++ b/doc/examples/gaussianrecipe.py @@ -31,7 +31,7 @@ Extensions -After reading through the code, try to perform the folowing tasks. The process +After reading through the code, try to perform the following tasks. The process will leave you with a much better understanding of how SrFit works. - Play around with setting the values of the Variables and Parameters. What diff --git a/doc/examples/npintensity.py b/doc/examples/npintensity.py index 5cad99a6..64498fc6 100644 --- a/doc/examples/npintensity.py +++ b/doc/examples/npintensity.py @@ -508,7 +508,7 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl=1): y += bkgd - # Multipy by a scale factor + # Multiply by a scale factor y *= scale # Calculate the uncertainty diff --git a/doc/examples/simplepdftwophase.py b/doc/examples/simplepdftwophase.py index af1c1dae..526c4780 100644 --- a/doc/examples/simplepdftwophase.py +++ b/doc/examples/simplepdftwophase.py @@ -52,7 +52,7 @@ def makeRecipe(niciffile, siciffile, datname): # for free by the PDFContribution. We simply need to add it to the recipe. recipe.addVar(contribution.qdamp, 0.03) - # Vary the gloabal scale as well. + # Vary the global scale as well. recipe.addVar(contribution.scale, 1) # Now we can configure the structural parameters. Since we're using diff --git a/src/diffpy/srfit/equation/builder.py b/src/diffpy/srfit/equation/builder.py index 8e76a105..d19a17a4 100644 --- a/src/diffpy/srfit/equation/builder.py +++ b/src/diffpy/srfit/equation/builder.py @@ -368,7 +368,7 @@ def _getUndefinedArgs(self, eqstr): # builders. These will be treated as arguments that need to be # generated. for tok in set(args): - # Move genuine varibles to the eqargs dictionary + # Move genuine variables to the eqargs dictionary if ( # Check registered builders tok in self.builders @@ -427,13 +427,13 @@ def __evalBinary(self, other, OperatorClass, onleft=True): Other can be an BaseBuilder or a constant. onleft -- Indicates that the operator was passed on the left side - (defualt True). + (default True). """ # Create the Operator op = OperatorClass() # onleft takes care of non-commutative operators, and assures that the - # ordering is perserved. + # ordering is preserved. if onleft: # Add the literals to the operator op.addLiteral(self.literal) diff --git a/src/diffpy/srfit/equation/equationmod.py b/src/diffpy/srfit/equation/equationmod.py index 80f6262d..091f7a55 100644 --- a/src/diffpy/srfit/equation/equationmod.py +++ b/src/diffpy/srfit/equation/equationmod.py @@ -164,7 +164,7 @@ def setRoot(self, root): def __call__(self, *args, **kw): """Call the equation. - New Argument values are acceped as arguments or keyword + New Argument values are accepted as arguments or keyword assignments (or both). The order of accepted arguments is given by the args attribute. The Equation will remember values set in this way. diff --git a/src/diffpy/srfit/equation/literals/argument.py b/src/diffpy/srfit/equation/literals/argument.py index eed3803a..9f9f1cd7 100644 --- a/src/diffpy/srfit/equation/literals/argument.py +++ b/src/diffpy/srfit/equation/literals/argument.py @@ -14,7 +14,7 @@ ############################################################################## """Argument class. -Arguments are the leaves of an equation tree, in essense a variable or a +Arguments are the leaves of an equation tree, in essence a variable or a constant. """ diff --git a/src/diffpy/srfit/equation/visitors/argfinder.py b/src/diffpy/srfit/equation/visitors/argfinder.py index b0338c92..089c8bcc 100644 --- a/src/diffpy/srfit/equation/visitors/argfinder.py +++ b/src/diffpy/srfit/equation/visitors/argfinder.py @@ -34,7 +34,7 @@ def __init__(self, getconsts=True): """Initialize. Arguments - getconsts -- Flag indicating whether to grap constant arguments + getconsts -- Flag indicating whether to parse constant arguments (default True). """ self.args = [] diff --git a/src/diffpy/srfit/fitbase/configurable.py b/src/diffpy/srfit/fitbase/configurable.py index 8de392e1..4aecfd5e 100644 --- a/src/diffpy/srfit/fitbase/configurable.py +++ b/src/diffpy/srfit/fitbase/configurable.py @@ -26,8 +26,8 @@ class Configurable(object): A Configurable has state of which a FitRecipe must be aware. Attributes - _configobjs -- Set of Configureables in a hierarcy or instances. - Messasges get passed up the hierarcy to a FitReciple + _configobjs -- Set of Configureables in a hierarchy or instances. + Messages get passed up the hierarchy to a FitReciple via these objects. """ diff --git a/src/diffpy/srfit/fitbase/fitcontribution.py b/src/diffpy/srfit/fitbase/fitcontribution.py index 3de608d4..7b5abbf1 100644 --- a/src/diffpy/srfit/fitbase/fitcontribution.py +++ b/src/diffpy/srfit/fitbase/fitcontribution.py @@ -19,7 +19,7 @@ optionally one or more ProfileGenerators or Calculators that help in this, and a Profile that holds the observed and calculated signals. -See the examples in the documention for how to use a FitContribution. +See the examples in the documentation for how to use a FitContribution. """ __all__ = ["FitContribution"] @@ -36,7 +36,7 @@ class FitContribution(ParameterSet): FitContributions organize an Equation that calculates the signal, and a Profile that holds the signal. ProfileGenerators and Calculators can be - used as well. Contraints and Restraints can be created as part of a + used as well. Constraints and Restraints can be created as part of a FitContribution. Attributes diff --git a/src/diffpy/srfit/fitbase/fitrecipe.py b/src/diffpy/srfit/fitbase/fitrecipe.py index 8539ab53..f6f0cbd7 100644 --- a/src/diffpy/srfit/fitbase/fitrecipe.py +++ b/src/diffpy/srfit/fitbase/fitrecipe.py @@ -517,7 +517,7 @@ def newVar(self, name, value=None, fixed=False, tag=None, tags=[]): This method lets new variables be created that are not tied to a Parameter. Orphan variables may cause a fit to fail, depending on the optimization routine, and therefore should only be created to be used - in contraint or restraint equations. + in constraint or restraint equations. name -- The name of the variable. The variable will be able to be used by this name in restraint and constraint equations. diff --git a/src/diffpy/srfit/fitbase/fitresults.py b/src/diffpy/srfit/fitbase/fitresults.py index 4a12974c..26c32f5b 100644 --- a/src/diffpy/srfit/fitbase/fitresults.py +++ b/src/diffpy/srfit/fitbase/fitresults.py @@ -155,7 +155,7 @@ def update(self): self.residual = numpy.dot(res, res) self._calculateMetrics() - # Calcualte the restraints penalty + # Calculate the restraints penalty w = self.chi2 / len(res) self.penalty = sum([r.penalty(w) for r in recipe._restraintlist]) @@ -600,7 +600,7 @@ def _init(self, con, weight, fitres): # FIXME: factor rw, chi2, cumrw, cumchi2 to separate functions. def _calculateMetrics(self): - """Calculte chi2 and Rw of the recipe.""" + """Calculate chi2 and Rw of the recipe.""" # We take absolute values in case the signal is complex num = numpy.abs(self.y - self.ycalc) y = numpy.abs(self.y) diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index df129e5b..0da465fb 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -278,7 +278,7 @@ def setCalculationPoints(self, x): if (self.dyobs == 1).all(): self.dy = numpy.ones_like(self.x) else: - # FIXME - This does not follow error propogation rules and it + # FIXME - This does not follow error propagation rules and it # introduces (more) correlation between the data points. self.dy = rebinArray(self.dyobs, self.xobs, self.x) diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index 36ae244f..c38af1a0 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -46,7 +46,7 @@ class ProfileParser(object): dy -- A numpy array containing the uncertainty read from the file. This is None if the uncertainty cannot be read. - _x -- Indpendent variable from the chosen bank + _x -- Independent variable from the chosen bank _y -- Profile from the chosen bank _dx -- Uncertainty in independent variable from the chosen bank _dy -- Uncertainty in profile from the chosen bank diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index a39041f1..e4902734 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -479,7 +479,7 @@ def registerFunction(self, f, name=None, argnames=None): passed in the equation string, as this will be handled automatically. f -- The callable to register. If this is an Equation - instance, then all that needs to be provied is a name. + instance, then all that needs to be provided is a name. name -- The name of the function to be used in equations. If this is None (default), the method will try to determine the name of the function automatically. @@ -722,7 +722,7 @@ def unconstrain(self, *pars): def getConstrainedPars(self, recurse=False): """Get a list of constrained managed Parameters in this object. - recurse -- Recurse into managed objects and retrive their constrained + recurse -- Recurse into managed objects and retrieve their constrained Parameters as well (default False). """ const = self._getConstraints(recurse) @@ -762,7 +762,7 @@ def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}): The penalty is calculated as (max(0, lb - val, val - ub)/sig)**2 - and val is the value of the calculated equation. This is multipled by + and val is the value of the calculated equation. This is multiplied by the average chi^2 if scaled is True. Raises ValueError if ns uses a name that is already used for a diff --git a/src/diffpy/srfit/fitbase/restraint.py b/src/diffpy/srfit/fitbase/restraint.py index a80486d2..f7f2da3c 100644 --- a/src/diffpy/srfit/fitbase/restraint.py +++ b/src/diffpy/srfit/fitbase/restraint.py @@ -43,7 +43,7 @@ class Restraint(Validatable): The penalty is calculated as (max(0, lb - val, val - ub)/sig)**2 - and val is the value of the calculated equation. This is multipled by the + and val is the value of the calculated equation. This is multiplied by the average chi^2 if scaled is True. """ diff --git a/src/diffpy/srfit/pdf/basepdfgenerator.py b/src/diffpy/srfit/pdf/basepdfgenerator.py index 8eeb58da..3fd58805 100644 --- a/src/diffpy/srfit/pdf/basepdfgenerator.py +++ b/src/diffpy/srfit/pdf/basepdfgenerator.py @@ -89,7 +89,7 @@ def __init__(self, name="pdf"): _parnames = ["delta1", "delta2", "qbroad", "scale", "qdamp"] def _setCalculator(self, calc): - """Set the SrReal calulator instance. + """Set the SrReal calculator instance. Setting the calculator creates Parameters from the variable attributes of the SrReal calculator. diff --git a/src/diffpy/srfit/pdf/characteristicfunctions.py b/src/diffpy/srfit/pdf/characteristicfunctions.py index febdf269..3f8a1473 100644 --- a/src/diffpy/srfit/pdf/characteristicfunctions.py +++ b/src/diffpy/srfit/pdf/characteristicfunctions.py @@ -193,7 +193,7 @@ def lognormalSphericalCF(r, psize, psig): psize -- The mean particle diameter psig -- The log-normal width of the particle diameter - Here, r is the independent variable, mu is the mean of the distrubution + Here, r is the independent variable, mu is the mean of the distribution (not of the particle size), and s is the width of the distribution. This is the characteristic function for the lognormal distribution of particle diameter: diff --git a/src/diffpy/srfit/pdf/pdfcontribution.py b/src/diffpy/srfit/pdf/pdfcontribution.py index 981cb91f..b6b0740d 100644 --- a/src/diffpy/srfit/pdf/pdfcontribution.py +++ b/src/diffpy/srfit/pdf/pdfcontribution.py @@ -86,7 +86,7 @@ def loadData(self, data): """Load the data in various formats. This uses the PDFParser to load the data and then passes it to the - build-in profile with loadParsedData. + built-in profile with loadParsedData. data -- An open file-like object, name of a file that contains data or a string containing the data. diff --git a/src/diffpy/srfit/pdf/pdfparser.py b/src/diffpy/srfit/pdf/pdfparser.py index 6211211b..9eb5595f 100644 --- a/src/diffpy/srfit/pdf/pdfparser.py +++ b/src/diffpy/srfit/pdf/pdfparser.py @@ -50,7 +50,7 @@ class PDFParser(ProfileParser): dy -- A numpy array containing the uncertainty read from the file. This is 0 if the uncertainty cannot be read. - _x -- Indpendent variable from the chosen bank + _x -- Independent variable from the chosen bank _y -- Profile from the chosen bank _dx -- Uncertainty in independent variable from the chosen bank _dy -- Uncertainty in profile from the chosen bank @@ -163,7 +163,7 @@ def parseString(self, patstring): if res: meta["doping"] = float(res.groups()[0]) - # parsing gerneral metadata + # parsing general metadata if metadata: regexp = r"\b(\w+)\ *=\ *(%(f)s)\b" % rx while True: diff --git a/src/diffpy/srfit/sas/sasparser.py b/src/diffpy/srfit/sas/sasparser.py index f5bd5680..69f94904 100644 --- a/src/diffpy/srfit/sas/sasparser.py +++ b/src/diffpy/srfit/sas/sasparser.py @@ -48,7 +48,7 @@ class SASParser(ProfileParser): dy -- A numpy array containing the uncertainty read from the file. This is 0 if the uncertainty cannot be read. - _x -- Indpendent variable from the chosen bank + _x -- Independent variable from the chosen bank _y -- Profile from the chosen bank _dx -- Uncertainty in independent variable from the chosen bank _dy -- Uncertainty in profile from the chosen bank diff --git a/src/diffpy/srfit/structure/objcrystparset.py b/src/diffpy/srfit/structure/objcrystparset.py index 24ff151f..3dab67f2 100644 --- a/src/diffpy/srfit/structure/objcrystparset.py +++ b/src/diffpy/srfit/structure/objcrystparset.py @@ -201,7 +201,7 @@ def __init__(self, name, molecule, parent=None): ObjCrystScattererParSet.__init__(self, name, molecule, parent) self.stru = molecule - # Add orientiation quaternion + # Add orientation quaternion self.addParameter(ParameterAdapter("q0", self.scat, attr="Q0")) self.addParameter(ParameterAdapter("q1", self.scat, attr="Q1")) self.addParameter(ParameterAdapter("q2", self.scat, attr="Q2")) @@ -1486,7 +1486,7 @@ def _createSpaceGroup(sgobjcryst): sgobjcryst -- A pyobjcryst.spacegroup.SpaceGroup instance. This uses the actual space group operations from the - pyobjcryst.spacegroup.SpaceGroup instance so there is no abiguity about + pyobjcryst.spacegroup.SpaceGroup instance so there is no ambiguity about the actual space group. """ import copy diff --git a/src/diffpy/srfit/structure/sgconstraints.py b/src/diffpy/srfit/structure/sgconstraints.py index 21bb8378..c3b0d005 100644 --- a/src/diffpy/srfit/structure/sgconstraints.py +++ b/src/diffpy/srfit/structure/sgconstraints.py @@ -58,7 +58,7 @@ def constrainAsSpaceGroup( U22, etc.). The names must be given in the same order as stdUsymbols. isosymbol -- Symbol for isotropic ADP (default "Uiso"). If None, - isotropic ADPs will be constrainted via the anisotropic ADPs. + isotropic ADPs will be constrained via the anisotropic ADPs. New Parameters that are used in constraints are created within a SpaceGroupParameters object, which is returned from this function. @@ -239,7 +239,7 @@ def __init__( same order as diffpy.structure.symmetryutilities.stdUsymbols. isosymbol -- Symbol for isotropic ADP (default "Uiso"). If None, - isotropic ADPs will be constrainted via the anisotropic + isotropic ADPs will be constrained via the anisotropic ADPs. """ BaseSpaceGroupParameters.__init__(self) diff --git a/src/diffpy/srfit/util/inpututils.py b/src/diffpy/srfit/util/inpututils.py index 4117de0b..0645354a 100644 --- a/src/diffpy/srfit/util/inpututils.py +++ b/src/diffpy/srfit/util/inpututils.py @@ -19,13 +19,13 @@ import os.path -def inputToString(inpt): +def inputToString(input): """Convert input from various modes to a string. This is useful when you want a method to accept a string, open file object or file name. - inpt -- An open file-like object, name of a file + input -- An open file-like object, name of a file or a string containing the input. Returns the input in a string @@ -34,15 +34,15 @@ def inputToString(inpt): """ # Get the input into a string inptstr = "" - if hasattr(inpt, "read"): - inptstr = inpt.read() + if hasattr(input, "read"): + inptstr = input.read() # TODO remove handling of string input accept only file or filename # FIXME check for typos in the file name - elif os.path.exists(inpt) or (len(inpt) < 80 and inpt.count("\n") == 0): - with open(inpt, "r") as infile: + elif os.path.exists(input) or (len(input) < 80 and input.count("\n") == 0): + with open(input, "r") as infile: inptstr = infile.read() else: - inptstr = inpt + inptstr = input return inptstr diff --git a/src/diffpy/srfit/util/observable.py b/src/diffpy/srfit/util/observable.py index d638af47..ff71b7d5 100644 --- a/src/diffpy/srfit/util/observable.py +++ b/src/diffpy/srfit/util/observable.py @@ -43,9 +43,9 @@ def notify(self, other=()): """Notify all observers.""" # build a list before notification, just in case the observer's # callback behavior involves removing itself from our callback set - semaphors = (self,) + other + semaphores = (self,) + other for callable in tuple(self._observers): - callable(semaphors) + callable(semaphores) return # callback management @@ -81,11 +81,11 @@ def __init__(self, **kwds): # Local helpers -------------------------------------------------------------- -def _fbRemoveObserver(fobs, semaphors): +def _fbRemoveObserver(fobs, semaphores): # Remove WeakBoundMethod `fobs` from the observers of notifying object. # This is called from Observable.notify when the WeakBoundMethod # associated object dies. - observable = semaphors[0] + observable = semaphores[0] observable.removeObserver(fobs) return diff --git a/tests/test_builder.py b/tests/test_builder.py index d453a122..81f5eb9f 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -263,7 +263,7 @@ def _f(a, b): eq = beq.getEquation() E = builder.wrapOperator("eq", eq) eq2 = (2 * E).getEquation() - # Make sure these evaulate to the same thing + # Make sure these evaluate to the same thing self.assertEqual(eq.args, [A.literal, B.literal]) self.assertEqual(2 * eq(), eq2()) # Pass new arguments to the equation diff --git a/tests/test_literals.py b/tests/test_literals.py index d131b6f8..8155d64a 100644 --- a/tests/test_literals.py +++ b/tests/test_literals.py @@ -27,7 +27,7 @@ class TestArgument(unittest.TestCase): def testInit(self): - """Test that everthing initializes as expected.""" + """Test that everything initializes as expected.""" a = literals.Argument() self.assertEqual(None, a._value) self.assertTrue(False is a.const) @@ -70,7 +70,7 @@ def setUp(self): return def testInit(self): - """Test that everthing initializes as expected.""" + """Test that everything initializes as expected.""" op = self.op self.assertEqual("+", op.symbol) self.assertEqual(numpy.add, op.operation) From c12a58af74392c66a11f5d13817a87f62aad7865 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 15 Jun 2025 12:31:57 -0400 Subject: [PATCH 22/45] fix: version.py now working again --- tests/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index 7f498f76..78f2ce1d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -42,7 +42,8 @@ def testsuite(pattern=""): from itertools import chain from os.path import dirname - from pkg_resources import resource_filename + from importlib.resources import files, as_file + loader = unittest.defaultTestLoader thisdir = resource_filename(__name__, "") From 93ff36890c7e69a4eb7e44d378d6974d3170d452 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:33:32 +0000 Subject: [PATCH 23/45] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 78f2ce1d..ad8c6fda 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -39,12 +39,10 @@ def testsuite(pattern=""): The TestSuite object containing the matching tests. """ import re + from importlib.resources import as_file, files from itertools import chain from os.path import dirname - from importlib.resources import files, as_file - - loader = unittest.defaultTestLoader thisdir = resource_filename(__name__, "") depth = __name__.count(".") + 1 From a23f8deb1cd7e00ec5aabf41d9d3a9af90ed35eb Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 18 Jun 2025 23:14:47 -0400 Subject: [PATCH 24/45] fix: test_sas working with skipping features --- tests/__init__.py | 86 -------------- tests/conftest.py | 77 ++++++++++++- tests/test_sas.py | 288 ++++++++++++++++++++++------------------------ 3 files changed, 211 insertions(+), 240 deletions(-) delete mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 78f2ce1d..00000000 --- a/tests/__init__.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## -"""Unit tests for diffpy.srfit.""" - -import logging -import unittest - -# create logger instance for the tests subpackage -logging.basicConfig() -logger = logging.getLogger(__name__) -del logging - - -def testsuite(pattern=""): - """Create a unit tests suite for diffpy.srfit package. - - Parameters - ---------- - pattern : str, optional - Regular expression pattern for selecting test cases. - Select all tests when empty. Ignore the pattern when - any of unit test modules fails to import. - - Returns - ------- - suite : `unittest.TestSuite` - The TestSuite object containing the matching tests. - """ - import re - from itertools import chain - from os.path import dirname - - from importlib.resources import files, as_file - - - loader = unittest.defaultTestLoader - thisdir = resource_filename(__name__, "") - depth = __name__.count(".") + 1 - topdir = thisdir - for i in range(depth): - topdir = dirname(topdir) - suite_all = loader.discover(thisdir, top_level_dir=topdir) - # always filter the suite by pattern to test-cover the selection code. - suite = unittest.TestSuite() - rx = re.compile(pattern) - tsuites = list(chain.from_iterable(suite_all)) - tsok = all(isinstance(ts, unittest.TestSuite) for ts in tsuites) - if not tsok: # pragma: no cover - return suite_all - tcases = chain.from_iterable(tsuites) - for tc in tcases: - tcwords = tc.id().split(".") - shortname = ".".join(tcwords[-3:]) - if rx.search(shortname): - suite.addTest(tc) - # verify all tests are found for an empty pattern. - assert pattern or suite_all.countTestCases() == suite.countTestCases() - return suite - - -def test(): - """Execute all unit tests for the diffpy.srfit package. - - Returns - ------- - result : `unittest.TestResult` - """ - suite = testsuite() - runner = unittest.TextTestRunner() - result = runner.run(suite) - return result - - -# End of file diff --git a/tests/conftest.py b/tests/conftest.py index e3b63139..918a21af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,74 @@ import json from pathlib import Path - +import importlib.resources import pytest +import logging + +from functools import lru_cache + +logger = logging.getLogger(__name__) + +@lru_cache() +def has_sas(): + try: + __import__("sas.pr.invertor") + __import__("sas.models") + return True + except ImportError: + return False + +# diffpy.structure +@lru_cache() +def has_diffpy_structure(): + _msg_nostructure = "No module named 'diffpy.structure'" + try: + import diffpy.structure as m + del m + return True + except ImportError: + return False + logger.warning("Cannot import diffpy.structure, Structure tests skipped.") + +@lru_cache() +def has_pyobjcryst(): + _msg_nopyobjcryst = "No module named 'pyobjcryst'" + try: + import pyobjcryst as m + del m + return True + except ImportError: + return False + logger.warning("Cannot import pyobjcryst, pyobjcryst tests skipped.") + +# diffpy.srreal + +@lru_cache() +def has_diffpy_srreal(): + _msg_nosrreal = "No module named 'diffpy.srreal'" + try: + import diffpy.srreal.pdfcalculator as m + del m + return True + except ImportError: + return False + logger.warning("Cannot import diffpy.srreal, PDF tests skipped.") + + +@pytest.fixture(scope="session") +def sas_available(): + return has_sas() + +@pytest.fixture(scope="session") +def diffpy_structure_available(): + return has_diffpy_structure() + +@pytest.fixture(scope="session") +def diffpy_srreal_available(): + return has_diffpy_srreal() + +@pytest.fixture(scope="session") +def pyobjcryst_available(): + return has_pyobjcryst() @pytest.fixture @@ -17,3 +84,11 @@ def user_filesystem(tmp_path): json.dump(home_config_data, f) yield tmp_path + + +@pytest.fixture +def datafile(): + """Fixture to load a test data file from the testdata package directory.""" + def _datafile(filename): + return importlib.resources.files("diffpy.srfit.tests.testdata").joinpath(filename) + return _datafile diff --git a/tests/test_sas.py b/tests/test_sas.py index 110c863e..fbb8570a 100644 --- a/tests/test_sas.py +++ b/tests/test_sas.py @@ -14,167 +14,149 @@ ############################################################################## """Tests for sas package.""" -import unittest - +import pytest import numpy from diffpy.srfit.sas import SASGenerator, SASParser, SASProfile from diffpy.srfit.sas.sasimport import sasimport -from .utils import _msg_nosas, datafile, has_sas - # ---------------------------------------------------------------------------- - - -@unittest.skipUnless(has_sas, _msg_nosas) -class TestSASParser(unittest.TestCase): - - def testParser(self): - data = datafile("sas_ascii_test_1.txt") - parser = SASParser() - parser.parseFile(data) - - x, y, dx, dy = parser.getData() - - testx = numpy.array( - [ - 0.002618, - 0.007854, - 0.01309, - 0.01832, - 0.02356, - 0.02879, - 0.03402, - 0.03925, - 0.04448, - 0.0497, - ] - ) - diff = testx - x - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - testy = numpy.array( - [ - 0.02198, - 0.02201, - 0.02695, - 0.02645, - 0.03024, - 0.3927, - 7.305, - 17.43, - 13.43, - 8.346, - ] - ) - diff = testy - y - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - testdy = numpy.array( - [ - 0.002704, - 0.001643, - 0.002452, - 0.001769, - 0.001531, - 0.1697, - 1.006, - 0.5351, - 0.3677, - 0.191, - ] - ) - diff = testdy - dy - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - testdx = numpy.array( - [ - 0.0004091, - 0.005587, - 0.005598, - 0.005624, - 0.005707, - 0.005975, - 0.006264, - 0.006344, - 0.006424, - 0.006516, - ] - ) - diff = testdx - dx - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - return +# FIXME: adjust sensitivity of the pytest.approx statements when ready to test +# with sasview installed. + +def testParser(sas_available, datafile): + if not sas_available: + pytest.skip("sas package not available") + + data = datafile("sas_ascii_test_1.txt") + parser = SASParser() + parser.parseFile(data) + x, y, dx, dy = parser.getData() + testx = numpy.array( + [ + 0.002618, + 0.007854, + 0.01309, + 0.01832, + 0.02356, + 0.02879, + 0.03402, + 0.03925, + 0.04448, + 0.0497, + ] + ) + diff = testx - x + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = numpy.array( + [ + 0.02198, + 0.02201, + 0.02695, + 0.02645, + 0.03024, + 0.3927, + 7.305, + 17.43, + 13.43, + 8.346, + ] + ) + diff = testy - y + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testdy = numpy.array( + [ + 0.002704, + 0.001643, + 0.002452, + 0.001769, + 0.001531, + 0.1697, + 1.006, + 0.5351, + 0.3677, + 0.191, + ] + ) + diff = testdy - dy + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testdx = numpy.array( + [ + 0.0004091, + 0.005587, + 0.005598, + 0.005624, + 0.005707, + 0.005975, + 0.006264, + 0.006344, + 0.006424, + 0.006516, + ] + ) + diff = testdx - dx + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + return # End of class TestSASParser -# ---------------------------------------------------------------------------- - -@unittest.skipUnless(has_sas, _msg_nosas) -class TestSASGenerator(unittest.TestCase): - - def testGenerator(self): - - # Test generator output - SphereModel = sasimport("sas.models.SphereModel").SphereModel - model = SphereModel() - gen = SASGenerator("sphere", model) - - for pname in model.params: - defval = model.getParam(pname) - par = gen.get(pname) - self.assertEqual(defval, par.getValue()) - # Test setting values - par.setValue(1.0) - self.assertEqual(1.0, par.getValue()) - self.assertEqual(1.0, model.getParam(pname)) - par.setValue(defval) - self.assertEqual(defval, par.getValue()) - self.assertEqual(defval, model.getParam(pname)) - - r = numpy.arange(1, 10, 0.1, dtype=float) - y = gen(r) - refy = model.evalDistribution(r) - diff = y - refy - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - return - - def testGenerator2(self): - - # Test generator with a profile - EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel - model = EllipsoidModel() - gen = SASGenerator("ellipsoid", model) - - # Load the data using SAS tools - Loader = sasimport("sas.dataloader.loader").Loader - loader = Loader() - data = datafile("sas_ellipsoid_testdata.txt") - datainfo = loader.load(data) - profile = SASProfile(datainfo) - - gen.setProfile(profile) - gen.scale.value = 1.0 - gen.radius_a.value = 20 - gen.radius_b.value = 400 - gen.background.value = 0.01 - - y = gen(profile.xobs) - diff = profile.yobs - y - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - return - - -# End of class TestSASGenerator - -if __name__ == "__main__": - unittest.main() +def test_generator(sas_available): + if not sas_available: + pytest.skip("sas package not available") + SphereModel = sasimport("sas.models.SphereModel").SphereModel + model = SphereModel() + gen = SASGenerator("sphere", model) + for pname in model.params: + defval = model.getParam(pname) + par = gen.get(pname) + assert defval == par.getValue() + # Test setting values + par.setValue(1.0) + assert 1.0 == par.getValue() + assert 1.0 == model.getParam(pname) + par.setValue(defval) + assert defval == par.getValue() + assert defval == model.getParam(pname) + + r = numpy.arange(1, 10, 0.1, dtype=float) + y = gen(r) + refy = model.evalDistribution(r) + diff = y - refy + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + return + +def testGenerator2(sas_available, datafile): + if not sas_available: + pytest.skip("sas package not available") + EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel + model = EllipsoidModel() + gen = SASGenerator("ellipsoid", model) + + # Load the data using SAS tools + Loader = sasimport("sas.dataloader.loader").Loader + loader = Loader() + data = datafile("sas_ellipsoid_testdata.txt") + datainfo = loader.load(data) + profile = SASProfile(datainfo) + + gen.setProfile(profile) + gen.scale.value = 1.0 + gen.radius_a.value = 20 + gen.radius_b.value = 400 + gen.background.value = 0.01 + + y = gen(profile.xobs) + diff = profile.yobs - y + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + return From e54ef40f347b4705ac14e7a4fc1d47d20ceec238 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 03:20:17 +0000 Subject: [PATCH 25/45] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/conftest.py | 29 +++++++++++++++++++++++------ tests/test_sas.py | 4 +++- tests/test_sgconstraints.py | 1 - tests/utils.py | 3 --- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 918a21af..b7d066e3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,14 @@ -import json -from pathlib import Path import importlib.resources -import pytest +import json import logging - from functools import lru_cache +from pathlib import Path + +import pytest logger = logging.getLogger(__name__) + @lru_cache() def has_sas(): try: @@ -17,36 +18,45 @@ def has_sas(): except ImportError: return False + # diffpy.structure @lru_cache() def has_diffpy_structure(): _msg_nostructure = "No module named 'diffpy.structure'" try: import diffpy.structure as m + del m return True except ImportError: return False - logger.warning("Cannot import diffpy.structure, Structure tests skipped.") + logger.warning( + "Cannot import diffpy.structure, Structure tests skipped." + ) + @lru_cache() def has_pyobjcryst(): _msg_nopyobjcryst = "No module named 'pyobjcryst'" try: import pyobjcryst as m + del m return True except ImportError: return False logger.warning("Cannot import pyobjcryst, pyobjcryst tests skipped.") + # diffpy.srreal + @lru_cache() def has_diffpy_srreal(): _msg_nosrreal = "No module named 'diffpy.srreal'" try: import diffpy.srreal.pdfcalculator as m + del m return True except ImportError: @@ -58,14 +68,17 @@ def has_diffpy_srreal(): def sas_available(): return has_sas() + @pytest.fixture(scope="session") def diffpy_structure_available(): return has_diffpy_structure() + @pytest.fixture(scope="session") def diffpy_srreal_available(): return has_diffpy_srreal() + @pytest.fixture(scope="session") def pyobjcryst_available(): return has_pyobjcryst() @@ -89,6 +102,10 @@ def user_filesystem(tmp_path): @pytest.fixture def datafile(): """Fixture to load a test data file from the testdata package directory.""" + def _datafile(filename): - return importlib.resources.files("diffpy.srfit.tests.testdata").joinpath(filename) + return importlib.resources.files( + "diffpy.srfit.tests.testdata" + ).joinpath(filename) + return _datafile diff --git a/tests/test_sas.py b/tests/test_sas.py index fbb8570a..3a7ee226 100644 --- a/tests/test_sas.py +++ b/tests/test_sas.py @@ -14,8 +14,8 @@ ############################################################################## """Tests for sas package.""" -import pytest import numpy +import pytest from diffpy.srfit.sas import SASGenerator, SASParser, SASProfile from diffpy.srfit.sas.sasimport import sasimport @@ -24,6 +24,7 @@ # FIXME: adjust sensitivity of the pytest.approx statements when ready to test # with sasview installed. + def testParser(sas_available, datafile): if not sas_available: pytest.skip("sas package not available") @@ -135,6 +136,7 @@ def test_generator(sas_available): assert 0 == pytest.approx(res) return + def testGenerator2(sas_available, datafile): if not sas_available: pytest.skip("sas package not available") diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index 9c32dcb5..ac0a96fc 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -18,7 +18,6 @@ import numpy - # ---------------------------------------------------------------------------- diff --git a/tests/utils.py b/tests/utils.py index e1c35ccf..35a017e1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -20,10 +20,8 @@ import diffpy.srfit.equation.literals as literals from diffpy.srfit.sas.sasimport import sasimport - from tests import logger - # Helper functions for testing ----------------------------------------------- @@ -51,7 +49,6 @@ def noObserversInGlobalBuilders(): return rv - def capturestdout(f, *args, **kwargs): """Capture the standard output from a call of function f.""" savestdout = sys.stdout From 2338ba4b142db5c7276e7915554614bd705c2000 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 00:24:49 -0400 Subject: [PATCH 26/45] tests: test_builder.py passing all tests --- tests/conftest.py | 31 +++ tests/test_builder.py | 499 +++++++++++++++++++++--------------------- tests/utils.py | 22 -- 3 files changed, 278 insertions(+), 274 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 918a21af..2b1c62e3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,9 @@ import logging from functools import lru_cache +import diffpy.srfit.equation.literals as literals +from diffpy.srfit.sas.sasimport import sasimport + logger = logging.getLogger(__name__) @@ -92,3 +95,31 @@ def datafile(): def _datafile(filename): return importlib.resources.files("diffpy.srfit.tests.testdata").joinpath(filename) return _datafile + +@pytest.fixture(scope="session") +def make_args(): + def _makeArgs(num): + args = [] + for i in range(num): + j = i + 1 + args.append(literals.Argument(name="v%i" % j, value=j)) + return args + return _makeArgs + +@pytest.fixture(scope="session") +def noObserversInGlobalBuilders(): + def _noObserversInGlobalBuilders(): + """True if no observer function leaks to global builder objects. + + Ensure objects are not immortal due to a reference from static + value. + """ + from diffpy.srfit.equation.builder import _builders + + rv = True + for n, b in _builders.items(): + if b.literal and b.literal._observers: + rv = False + break + return rv + return _noObserversInGlobalBuilders() diff --git a/tests/test_builder.py b/tests/test_builder.py index 81f5eb9f..c59e4669 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -14,274 +14,269 @@ ############################################################################## """Tests for refinableobj module.""" -import unittest - import numpy +import pytest import diffpy.srfit.equation.builder as builder import diffpy.srfit.equation.literals as literals -from .utils import _makeArgs, noObserversInGlobalBuilders - - -class TestBuilder(unittest.TestCase): - - def testRegisterArg(self): - - factory = builder.EquationFactory() - - v1 = _makeArgs(1)[0] - - b1 = factory.registerArgument("v1", v1) - self.assertTrue(factory.builders["v1"] is b1) - self.assertTrue(b1.literal is v1) - - eq = factory.makeEquation("v1") - - self.assertTrue(v1 is eq.args[0]) - self.assertEqual(1, len(eq.args)) - - # Try to parse an equation with buildargs turned off - self.assertRaises(ValueError, factory.makeEquation, "v1 + v2", False) - - # Make sure we can still use constants - eq = factory.makeEquation("v1 + 2", False) - self.assertTrue(v1 is eq.args[0]) - self.assertEqual(1, len(eq.args)) - self.assertTrue(noObserversInGlobalBuilders()) - return - - def testRegisterOperator(self): - """Try to use an operator without arguments in an equation.""" - factory = builder.EquationFactory() - v1, v2, v3, v4 = _makeArgs(4) +def testRegisterArg(make_args, noObserversInGlobalBuilders): - op = literals.AdditionOperator() + factory = builder.EquationFactory() - op.addLiteral(v1) - op.addLiteral(v2) + v1 = make_args(1)[0] - factory.registerArgument("v3", v3) - factory.registerArgument("v4", v4) - factory.registerOperator("op", op) + b1 = factory.registerArgument("v1", v1) + assert factory.builders["v1"] is b1 + assert b1.literal is v1 - # Build an equation where op is treated as a terminal node - eq = factory.makeEquation("op") - self.assertAlmostEqual(3, eq()) + eq = factory.makeEquation("v1") - eq = factory.makeEquation("v3*op") - self.assertAlmostEqual(9, eq()) + assert v1 is eq.args[0] + assert 1 == len(eq.args) - # Now use the op like a function - eq = factory.makeEquation("op(v3, v4)") - self.assertAlmostEqual(7, eq()) + # Try to parse an equation with buildargs turned off + with pytest.raises(ValueError): + factory.makeEquation("v1 + v2", False) - # Make sure we can still access op as itself. - eq = factory.makeEquation("op") - self.assertAlmostEqual(3, eq()) + # Make sure we can still use constants + eq = factory.makeEquation("v1 + 2", False) + assert v1 is eq.args[0] + assert 1 == len(eq.args) + assert noObserversInGlobalBuilders + return - self.assertTrue(noObserversInGlobalBuilders()) - return - - def testSwapping(self): +def testRegisterOperator(make_args, noObserversInGlobalBuilders): + """Try to use an operator without arguments in an equation.""" - def g1(v1, v2, v3, v4): - return (v1 + v2) * (v3 + v4) + factory = builder.EquationFactory() + v1, v2, v3, v4 = make_args(4) - def g2(v1): - return 0.5 * v1 - - factory = builder.EquationFactory() - v1, v2, v3, v4, v5 = _makeArgs(5) - - factory.registerArgument("v1", v1) - factory.registerArgument("v2", v2) - factory.registerArgument("v3", v3) - factory.registerArgument("v4", v4) - b = factory.registerFunction("g", g1, ["v1", "v2", "v3", "v4"]) - - # Now associate args with the wrapped function - op = b.literal - self.assertTrue(op.operation == g1) - self.assertTrue(v1 in op.args) - self.assertTrue(v2 in op.args) - self.assertTrue(v3 in op.args) - self.assertTrue(v4 in op.args) - self.assertAlmostEqual(21, op.value) - - eq1 = factory.makeEquation("g") - self.assertTrue(eq1.root is op) - self.assertAlmostEqual(21, eq1()) - - # Swap out an argument by registering it under a taken name - b = factory.registerArgument("v4", v5) - self.assertTrue(factory.builders["v4"] is b) - self.assertTrue(b.literal is v5) - self.assertTrue(op._value is None) - self.assertTrue(op.args == [v1, v2, v3, v5]) - self.assertAlmostEqual(24, eq1()) - - # Now swap out the function - b = factory.registerFunction("g", g2, ["v1"]) - op = b.literal - self.assertTrue(op.operation == g2) - self.assertTrue(v1 in op.args) - self.assertTrue(eq1.root is op) - self.assertAlmostEqual(0.5, op.value) - self.assertAlmostEqual(0.5, eq1()) - - # Make an equation - eqeq = factory.makeEquation("v1 + v2") - # Register this "g" - b = factory.registerFunction("g", eqeq, eqeq.argdict.keys()) - op = b.literal - self.assertTrue(v1 in op.args) - self.assertTrue(v2 in op.args) - self.assertTrue(eq1.root is op) - self.assertAlmostEqual(3, op.value) - self.assertAlmostEqual(3, eq1()) - - self.assertTrue(noObserversInGlobalBuilders()) - return - - def testParseEquation(self): - - from numpy import array_equal, divide, e, sin, sqrt - - factory = builder.EquationFactory() - - # Scalar equation - eq = factory.makeEquation("A*sin(0.5*x)+divide(B,C)") - A = 1 - x = numpy.pi - B = 4.0 - C = 2.0 - eq.A.setValue(A) - eq.x.setValue(x) - eq.B.setValue(B) - eq.C.setValue(C) - f = lambda A, x, B, C: A * sin(0.5 * x) + divide(B, C) - self.assertTrue(array_equal(eq(), f(A, x, B, C))) - - # Make sure that the arguments of eq are listed in the order in which - # they appear in the equations. - self.assertEqual(eq.args, [eq.A, eq.x, eq.B, eq.C]) - - # Vector equation - eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") - x = numpy.arange(0, 1, 0.05) - sigma = 0.1 - eq.x.setValue(x) - eq.sigma.setValue(sigma) - f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) - self.assertTrue(numpy.allclose(eq(), f(x, sigma))) - - self.assertEqual(eq.args, [eq.x, eq.sigma]) - - # Equation with constants - factory.registerConstant("x", x) - eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") - self.assertTrue("sigma" in eq.argdict) - self.assertTrue("x" not in eq.argdict) - self.assertTrue(numpy.allclose(eq(sigma=sigma), f(x, sigma))) - - self.assertEqual(eq.args, [eq.sigma]) - - # Equation with user-defined functions - factory.registerFunction("myfunc", eq, ["sigma"]) - eq2 = factory.makeEquation("c*myfunc(sigma)") - self.assertTrue(numpy.allclose(eq2(c=2, sigma=sigma), 2 * f(x, sigma))) - self.assertTrue("sigma" in eq2.argdict) - self.assertTrue("c" in eq2.argdict) - self.assertEqual(eq2.args, [eq2.c, eq2.sigma]) - - self.assertTrue(noObserversInGlobalBuilders()) - return - - def test_parse_constant(self): - """Verify parsing of constant numeric expressions.""" - factory = builder.EquationFactory() - eq = factory.makeEquation("3.12 + 2") - self.assertTrue(isinstance(eq, builder.Equation)) - self.assertEqual(set(), factory.equations) - self.assertEqual(5.12, eq()) - self.assertRaises(ValueError, eq, 3) - return - - def testBuildEquation(self): - - from numpy import array_equal - - # simple equation - sin = builder.getBuilder("sin") - a = builder.ArgumentBuilder(name="a", value=1) - A = builder.ArgumentBuilder(name="A", value=2) - x = numpy.arange(0, numpy.pi, 0.1) - - beq = A * sin(a * x) - eq = beq.getEquation() - - self.assertTrue("a" in eq.argdict) - self.assertTrue("A" in eq.argdict) - self.assertTrue(array_equal(eq(), 2 * numpy.sin(x))) - - self.assertEqual(eq.args, [eq.A, eq.a]) - - # Check the number of arguments - self.assertRaises(ValueError, sin) - - # custom function - def _f(a, b): - return (a - b) * 1.0 / (a + b) - - f = builder.wrapFunction("f", _f, 2, 1) - a = builder.ArgumentBuilder(name="a", value=2) - b = builder.ArgumentBuilder(name="b", value=1) - - beq = sin(f(a, b)) - eq = beq.getEquation() - self.assertEqual(eq(), numpy.sin(_f(2, 1))) - - # complex function - sqrt = builder.getBuilder("sqrt") - e = numpy.e - _x = numpy.arange(0, 1, 0.05) - x = builder.ArgumentBuilder(name="x", value=_x, const=True) - sigma = builder.ArgumentBuilder(name="sigma", value=0.1) - beq = sqrt(e ** (-0.5 * (x / sigma) ** 2)) - eq = beq.getEquation() - f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) - self.assertTrue( - numpy.allclose(eq(), numpy.sqrt(e ** (-0.5 * (_x / 0.1) ** 2))) - ) - - # Equation with Equation - A = builder.ArgumentBuilder(name="A", value=2) - B = builder.ArgumentBuilder(name="B", value=4) - beq = A + B - eq = beq.getEquation() - E = builder.wrapOperator("eq", eq) - eq2 = (2 * E).getEquation() - # Make sure these evaluate to the same thing - self.assertEqual(eq.args, [A.literal, B.literal]) - self.assertEqual(2 * eq(), eq2()) - # Pass new arguments to the equation - C = builder.ArgumentBuilder(name="C", value=5) - D = builder.ArgumentBuilder(name="D", value=6) - eq3 = (E(C, D) + 1).getEquation() - self.assertEqual(12, eq3()) - # Pass old and new arguments to the equation - # If things work right, A has been given the value of C in the last - # evaluation (5) - eq4 = (3 * E(A, D) - 1).getEquation() - self.assertEqual(32, eq4()) - # Try to pass the wrong number of arguments - self.assertRaises(ValueError, E, A) - self.assertRaises(ValueError, E, A, B, C) - - self.assertTrue(noObserversInGlobalBuilders()) - return + op = literals.AdditionOperator() + + op.addLiteral(v1) + op.addLiteral(v2) + + factory.registerArgument("v3", v3) + factory.registerArgument("v4", v4) + factory.registerOperator("op", op) + + # Build an equation where op is treated as a terminal node + eq = factory.makeEquation("op") + assert 3 == pytest.approx(eq()) + + eq = factory.makeEquation("v3*op") + assert 9 == pytest.approx(eq()) + + # Now use the op like a function + eq = factory.makeEquation("op(v3, v4)") + assert 7 == pytest.approx(eq()) + + # Make sure we can still access op as itself. + eq = factory.makeEquation("op") + assert 3 == pytest.approx(eq()) + + assert noObserversInGlobalBuilders + return + +def testSwapping(make_args, noObserversInGlobalBuilders): + + def g1(v1, v2, v3, v4): + return (v1 + v2) * (v3 + v4) + + def g2(v1): + return 0.5 * v1 + + factory = builder.EquationFactory() + v1, v2, v3, v4, v5 = make_args(5) + + factory.registerArgument("v1", v1) + factory.registerArgument("v2", v2) + factory.registerArgument("v3", v3) + factory.registerArgument("v4", v4) + b = factory.registerFunction("g", g1, ["v1", "v2", "v3", "v4"]) + + # Now associate args with the wrapped function + op = b.literal + assert op.operation == g1 + assert v1 in op.args + assert v2 in op.args + assert v3 in op.args + assert v4 in op.args + assert round(abs(21 - op.value), 7) == 0 + + eq1 = factory.makeEquation("g") + assert eq1.root is op + assert round(abs(21 - eq1()), 7) == 0 + + # Swap out an argument by registering it under a taken name + b = factory.registerArgument("v4", v5) + assert factory.builders["v4"] is b + assert b.literal is v5 + assert op._value is None + assert op.args == [v1, v2, v3, v5] + assert round(abs(24 - eq1()), 7) == 0 + + # Now swap out the function + b = factory.registerFunction("g", g2, ["v1"]) + op = b.literal + assert op.operation == g2 + assert v1 in op.args + assert eq1.root is op + assert round(abs(0.5 - op.value), 7) == 0 + assert round(abs(0.5 - eq1()), 7) == 0 + + # Make an equation + eqeq = factory.makeEquation("v1 + v2") + # Register this "g" + b = factory.registerFunction("g", eqeq, eqeq.argdict.keys()) + op = b.literal + assert v1 in op.args + assert v2 in op.args + assert eq1.root is op + assert round(abs(3 - op.value), 7) == 0 + assert round(abs(3 - eq1()), 7) == 0 + assert noObserversInGlobalBuilders + return + +def testParseEquation(noObserversInGlobalBuilders): + + from numpy import array_equal, divide, e, sin, sqrt + + factory = builder.EquationFactory() + + # Scalar equation + eq = factory.makeEquation("A*sin(0.5*x)+divide(B,C)") + A = 1 + x = numpy.pi + B = 4.0 + C = 2.0 + eq.A.setValue(A) + eq.x.setValue(x) + eq.B.setValue(B) + eq.C.setValue(C) + f = lambda A, x, B, C: A * sin(0.5 * x) + divide(B, C) + assert array_equal(eq(), f(A, x, B, C)) + + # Make sure that the arguments of eq are listed in the order in which + # they appear in the equations. + assert eq.args == [eq.A, eq.x, eq.B, eq.C] + + # Vector equation + eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") + x = numpy.arange(0, 1, 0.05) + sigma = 0.1 + eq.x.setValue(x) + eq.sigma.setValue(sigma) + f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) + assert numpy.allclose(eq(), f(x, sigma)) + + + assert eq.args == [eq.x, eq.sigma] + + # Equation with constants + factory.registerConstant("x", x) + eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") + assert "sigma" in eq.argdict + assert "x" not in eq.argdict + assert numpy.allclose(eq(sigma=sigma), f(x, sigma)) + assert eq.args == [eq.sigma] + + # Equation with user-defined functions + factory.registerFunction("myfunc", eq, ["sigma"]) + eq2 = factory.makeEquation("c*myfunc(sigma)") + assert numpy.allclose(eq2(c=2, sigma=sigma), 2 * f(x, sigma)) + assert "sigma" in eq2.argdict + assert "c" in eq2.argdict + assert eq2.args == [eq2.c, eq2.sigma] + assert noObserversInGlobalBuilders + return + +def test_parse_constant(): + """Verify parsing of constant numeric expressions.""" + factory = builder.EquationFactory() + eq = factory.makeEquation("3.12 + 2") + assert isinstance(eq, builder.Equation) + assert set() == factory.equations + assert 5.12 == eq() + with pytest.raises(ValueError): + eq(3) + return + +def testBuildEquation(noObserversInGlobalBuilders): + + from numpy import array_equal + + # simple equation + sin = builder.getBuilder("sin") + a = builder.ArgumentBuilder(name="a", value=1) + A = builder.ArgumentBuilder(name="A", value=2) + x = numpy.arange(0, numpy.pi, 0.1) + + beq = A * sin(a * x) + eq = beq.getEquation() + + assert "a" in eq.argdict + assert "A" in eq.argdict + assert array_equal(eq(), 2 * numpy.sin(x)) + + assert eq.args == [eq.A, eq.a] + + # Check the number of arguments + with pytest.raises(ValueError): + sin() + + # custom function + def _f(a, b): + return (a - b) * 1.0 / (a + b) + + f = builder.wrapFunction("f", _f, 2, 1) + a = builder.ArgumentBuilder(name="a", value=2) + b = builder.ArgumentBuilder(name="b", value=1) + + beq = sin(f(a, b)) + eq = beq.getEquation() + assert eq() == numpy.sin(_f(2, 1)) + + # complex function + sqrt = builder.getBuilder("sqrt") + e = numpy.e + _x = numpy.arange(0, 1, 0.05) + x = builder.ArgumentBuilder(name="x", value=_x, const=True) + sigma = builder.ArgumentBuilder(name="sigma", value=0.1) + beq = sqrt(e ** (-0.5 * (x / sigma) ** 2)) + eq = beq.getEquation() + f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) + assert numpy.allclose(eq(), numpy.sqrt(e ** (-0.5 * (_x / 0.1) ** 2))) + + # Equation with Equation + A = builder.ArgumentBuilder(name="A", value=2) + B = builder.ArgumentBuilder(name="B", value=4) + beq = A + B + eq = beq.getEquation() + E = builder.wrapOperator("eq", eq) + eq2 = (2 * E).getEquation() + # Make sure these evaluate to the same thing + assert eq.args == [A.literal, B.literal] + assert 2 * eq() == eq2() + # Pass new arguments to the equation + C = builder.ArgumentBuilder(name="C", value=5) + D = builder.ArgumentBuilder(name="D", value=6) + eq3 = (E(C, D) + 1).getEquation() + assert 12 == eq3() + # Pass old and new arguments to the equation + # If things work right, A has been given the value of C in the last + # evaluation (5) + eq4 = (3 * E(A, D) - 1).getEquation() + assert 32 == eq4() + # Try to pass the wrong number of arguments + with pytest.raises(ValueError): + E(A) + with pytest.raises(ValueError): + E(A, B, C) + assert noObserversInGlobalBuilders + return if __name__ == "__main__": diff --git a/tests/utils.py b/tests/utils.py index e1c35ccf..94033cea 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -27,28 +27,6 @@ # Helper functions for testing ----------------------------------------------- -def _makeArgs(num): - args = [] - for i in range(num): - j = i + 1 - args.append(literals.Argument(name="v%i" % j, value=j)) - return args - - -def noObserversInGlobalBuilders(): - """True if no observer function leaks to global builder objects. - - Ensure objects are not immortal due to a reference from static - value. - """ - from diffpy.srfit.equation.builder import _builders - - rv = True - for n, b in _builders.items(): - if b.literal and b.literal._observers: - rv = False - break - return rv From 4ddf81e6da925056b5396bd50145e172924d7115 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:28:27 +0000 Subject: [PATCH 27/45] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/conftest.py | 6 +++++- tests/test_builder.py | 6 +++++- tests/utils.py | 2 -- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 02ecc2e8..2d63f1aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,10 +5,10 @@ from pathlib import Path import pytest + import diffpy.srfit.equation.literals as literals from diffpy.srfit.sas.sasimport import sasimport - logger = logging.getLogger(__name__) @@ -113,6 +113,7 @@ def _datafile(filename): return _datafile + @pytest.fixture(scope="session") def make_args(): def _makeArgs(num): @@ -121,8 +122,10 @@ def _makeArgs(num): j = i + 1 args.append(literals.Argument(name="v%i" % j, value=j)) return args + return _makeArgs + @pytest.fixture(scope="session") def noObserversInGlobalBuilders(): def _noObserversInGlobalBuilders(): @@ -139,4 +142,5 @@ def _noObserversInGlobalBuilders(): rv = False break return rv + return _noObserversInGlobalBuilders() diff --git a/tests/test_builder.py b/tests/test_builder.py index c59e4669..dfabc8fb 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -47,6 +47,7 @@ def testRegisterArg(make_args, noObserversInGlobalBuilders): assert noObserversInGlobalBuilders return + def testRegisterOperator(make_args, noObserversInGlobalBuilders): """Try to use an operator without arguments in an equation.""" @@ -80,6 +81,7 @@ def testRegisterOperator(make_args, noObserversInGlobalBuilders): assert noObserversInGlobalBuilders return + def testSwapping(make_args, noObserversInGlobalBuilders): def g1(v1, v2, v3, v4): @@ -140,6 +142,7 @@ def g2(v1): assert noObserversInGlobalBuilders return + def testParseEquation(noObserversInGlobalBuilders): from numpy import array_equal, divide, e, sin, sqrt @@ -172,7 +175,6 @@ def testParseEquation(noObserversInGlobalBuilders): f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) assert numpy.allclose(eq(), f(x, sigma)) - assert eq.args == [eq.x, eq.sigma] # Equation with constants @@ -193,6 +195,7 @@ def testParseEquation(noObserversInGlobalBuilders): assert noObserversInGlobalBuilders return + def test_parse_constant(): """Verify parsing of constant numeric expressions.""" factory = builder.EquationFactory() @@ -204,6 +207,7 @@ def test_parse_constant(): eq(3) return + def testBuildEquation(noObserversInGlobalBuilders): from numpy import array_equal diff --git a/tests/utils.py b/tests/utils.py index 8d2f810b..4574cf27 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -25,8 +25,6 @@ # Helper functions for testing ----------------------------------------------- - - def capturestdout(f, *args, **kwargs): """Capture the standard output from a call of function f.""" savestdout = sys.stdout From 9c825ece2b104d6f92d194bc1ad260ba4e507e43 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 08:05:36 -0400 Subject: [PATCH 28/45] tests: characteristicfunctions and contribution --- tests/test_characteristicfunctions.py | 241 +++++++++++++------------- tests/test_contribution.py | 212 +++++++++++----------- 2 files changed, 227 insertions(+), 226 deletions(-) diff --git a/tests/test_characteristicfunctions.py b/tests/test_characteristicfunctions.py index ddd02c32..95d1a8bc 100644 --- a/tests/test_characteristicfunctions.py +++ b/tests/test_characteristicfunctions.py @@ -17,10 +17,10 @@ import unittest import numpy +import pytest from diffpy.srfit.sas.sasimport import sasimport - -from .utils import _msg_nosas, has_sas +import diffpy.srfit.pdf.characteristicfunctions as cf # Global variables to be assigned in setUp cf = None @@ -28,125 +28,124 @@ # ---------------------------------------------------------------------------- -@unittest.skipUnless(has_sas, _msg_nosas) -class TestSASCF(unittest.TestCase): - - def setUp(self): - global cf - import diffpy.srfit.pdf.characteristicfunctions as cf - - return - - def testSphere(self): - radius = 25 - # Calculate sphere cf from SphereModel - SphereModel = sasimport("sas.models.SphereModel").SphereModel - model = SphereModel() - model.setParam("radius", radius) - ff = cf.SASCF("sphere", model) - r = numpy.arange(1, 60, 0.1, dtype=float) - fr1 = ff(r) - - # Calculate sphere cf analytically - fr2 = cf.sphericalCF(r, 2 * radius) - diff = fr1 - fr2 - res = numpy.dot(diff, diff) - res /= numpy.dot(fr2, fr2) - self.assertAlmostEqual(0, res, 4) - return - - def testSpheroid(self): - prad = 20.9 - erad = 33.114 - # Calculate cf from EllipsoidModel - EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel - model = EllipsoidModel() - model.setParam("radius_a", prad) - model.setParam("radius_b", erad) - ff = cf.SASCF("spheroid", model) - r = numpy.arange(0, 100, 1 / numpy.pi, dtype=float) - fr1 = ff(r) - - # Calculate cf analytically - fr2 = cf.spheroidalCF(r, erad, prad) - diff = fr1 - fr2 - res = numpy.dot(diff, diff) - res /= numpy.dot(fr2, fr2) - self.assertAlmostEqual(0, res, 4) - return - - def testShell(self): - radius = 19.2 - thickness = 7.8 - # Calculate cf from VesicleModel - VesicleModel = sasimport("sas.models.VesicleModel").VesicleModel - model = VesicleModel() - model.setParam("radius", radius) - model.setParam("thickness", thickness) - ff = cf.SASCF("vesicle", model) - r = numpy.arange(0, 99.45, 0.1, dtype=float) - fr1 = ff(r) - - # Calculate sphere cf analytically - fr2 = cf.shellCF(r, radius, thickness) - diff = fr1 - fr2 - res = numpy.dot(diff, diff) - res /= numpy.dot(fr2, fr2) - self.assertAlmostEqual(0, res, 4) - return - - def testCylinder(self): - """Make sure cylinder works over different r-ranges.""" - radius = 100 - length = 30 - - CylinderModel = sasimport("sas.models.CylinderModel").CylinderModel - model = CylinderModel() - model.setParam("radius", radius) - model.setParam("length", length) - - ff = cf.SASCF("cylinder", model) - - r1 = numpy.arange(0, 10, 0.1, dtype=float) - r2 = numpy.arange(0, 50, 0.1, dtype=float) - r3 = numpy.arange(0, 100, 0.1, dtype=float) - r4 = numpy.arange(0, 500, 0.1, dtype=float) - - fr1 = ff(r1) - fr2 = ff(r2) - fr3 = ff(r3) - fr4 = ff(r4) - - d = fr1 - numpy.interp(r1, r2, fr2) - res12 = numpy.dot(d, d) - res12 /= numpy.dot(fr1, fr1) - self.assertAlmostEqual(0, res12, 4) - - d = fr1 - numpy.interp(r1, r3, fr3) - res13 = numpy.dot(d, d) - res13 /= numpy.dot(fr1, fr1) - self.assertAlmostEqual(0, res13, 4) - - d = fr1 - numpy.interp(r1, r4, fr4) - res14 = numpy.dot(d, d) - res14 /= numpy.dot(fr1, fr1) - self.assertAlmostEqual(0, res14, 4) - - d = fr2 - numpy.interp(r2, r3, fr3) - res23 = numpy.dot(d, d) - res23 /= numpy.dot(fr2, fr2) - self.assertAlmostEqual(0, res23, 4) - - d = fr2 - numpy.interp(r2, r4, fr4) - res24 = numpy.dot(d, d) - res24 /= numpy.dot(fr2, fr2) - self.assertAlmostEqual(0, res24, 4) - - d = fr3 - numpy.interp(r3, r4, fr4) - res34 = numpy.dot(d, d) - res34 /= numpy.dot(fr3, fr3) - self.assertAlmostEqual(0, res34, 4) - return +def testSphere(sas_available): + if not sas_available: + pytest.skip("sas package not available") + radius = 25 + # Calculate sphere cf from SphereModel + SphereModel = sasimport("sas.models.SphereModel").SphereModel + model = SphereModel() + model.setParam("radius", radius) + ff = cf.SASCF("sphere", model) + r = numpy.arange(1, 60, 0.1, dtype=float) + fr1 = ff(r) + + # Calculate sphere cf analytically + fr2 = cf.sphericalCF(r, 2 * radius) + diff = fr1 - fr2 + res = numpy.dot(diff, diff) + res /= numpy.dot(fr2, fr2) + assert res == pytest.approx(0, abs=1e-4) + return + +def testSpheroid(sas_available): + if not sas_available: + pytest.skip("sas package not available") + prad = 20.9 + erad = 33.114 + # Calculate cf from EllipsoidModel + EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel + model = EllipsoidModel() + model.setParam("radius_a", prad) + model.setParam("radius_b", erad) + ff = cf.SASCF("spheroid", model) + r = numpy.arange(0, 100, 1 / numpy.pi, dtype=float) + fr1 = ff(r) + + # Calculate cf analytically + fr2 = cf.spheroidalCF(r, erad, prad) + diff = fr1 - fr2 + res = numpy.dot(diff, diff) + res /= numpy.dot(fr2, fr2) + assert res == pytest.approx(0, abs=1e-4) + return + +def testShell(sas_available): + if not sas_available: + pytest.skip("sas package not available") + radius = 19.2 + thickness = 7.8 + # Calculate cf from VesicleModel + VesicleModel = sasimport("sas.models.VesicleModel").VesicleModel + model = VesicleModel() + model.setParam("radius", radius) + model.setParam("thickness", thickness) + ff = cf.SASCF("vesicle", model) + r = numpy.arange(0, 99.45, 0.1, dtype=float) + fr1 = ff(r) + + # Calculate sphere cf analytically + fr2 = cf.shellCF(r, radius, thickness) + diff = fr1 - fr2 + res = numpy.dot(diff, diff) + res /= numpy.dot(fr2, fr2) + assert res == pytest.approx(0, abs=1e-4) + return + +def testCylinder(sas_available): + if not sas_available: + pytest.skip("sas package not available") + """Make sure cylinder works over different r-ranges.""" + radius = 100 + length = 30 + + CylinderModel = sasimport("sas.models.CylinderModel").CylinderModel + model = CylinderModel() + model.setParam("radius", radius) + model.setParam("length", length) + + ff = cf.SASCF("cylinder", model) + + r1 = numpy.arange(0, 10, 0.1, dtype=float) + r2 = numpy.arange(0, 50, 0.1, dtype=float) + r3 = numpy.arange(0, 100, 0.1, dtype=float) + r4 = numpy.arange(0, 500, 0.1, dtype=float) + + fr1 = ff(r1) + fr2 = ff(r2) + fr3 = ff(r3) + fr4 = ff(r4) + + d = fr1 - numpy.interp(r1, r2, fr2) + res12 = numpy.dot(d, d) + res12 /= numpy.dot(fr1, fr1) + assert res12 == pytest.approx(0, abs=1e-4) + + d = fr1 - numpy.interp(r1, r3, fr3) + res13 = numpy.dot(d, d) + res13 /= numpy.dot(fr1, fr1) + assert res13 == pytest.approx(0, abs=1e-4) + + d = fr1 - numpy.interp(r1, r4, fr4) + res14 = numpy.dot(d, d) + res14 /= numpy.dot(fr1, fr1) + assert res14 == pytest.approx(0, abs=1e-4) + + d = fr2 - numpy.interp(r2, r3, fr3) + res23 = numpy.dot(d, d) + res23 /= numpy.dot(fr2, fr2) + assert res23 == pytest.approx(0, abs=1e-4) + + d = fr2 - numpy.interp(r2, r4, fr4) + res24 = numpy.dot(d, d) + res24 /= numpy.dot(fr2, fr2) + assert res24 == pytest.approx(0, abs=1e-4) + + d = fr3 - numpy.interp(r3, r4, fr4) + res34 = numpy.dot(d, d) + res34 /= numpy.dot(fr3, fr3) + assert res34 == pytest.approx(0, abs=1e-4) + return # End of class TestSASCF diff --git a/tests/test_contribution.py b/tests/test_contribution.py index 60616a8b..fc34a049 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -13,9 +13,10 @@ # ############################################################################## """Tests for refinableobj module.""" - import unittest +import numpy as np +import pytest from numpy import arange, array_equal, dot, sin from diffpy.srfit.exceptions import SrFitError @@ -24,8 +25,6 @@ from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator -from .utils import noObserversInGlobalBuilders - class TestContribution(unittest.TestCase): @@ -144,108 +143,6 @@ def testReplacements(self): self.assertEqual(len(xobs2), len(fc.residual())) return - def testResidual(self): - """Test the residual, which requires all other methods.""" - fc = self.fitcontribution - profile = self.profile - gen = self.gen - - # Add the calculator and profile - fc.setProfile(profile) - self.assertTrue(fc.profile is profile) - fc.addProfileGenerator(gen, "I") - self.assertTrue(fc._eq._value is None) - self.assertTrue(fc._reseq._value is None) - self.assertEqual(1, len(fc._generators)) - self.assertTrue(gen.name in fc._generators) - - # Let's create some data - xobs = arange(0, 10, 0.5) - yobs = xobs - profile.setObservedProfile(xobs, yobs) - - # Check our fitting equation. - self.assertTrue(array_equal(fc._eq(), gen(xobs))) - - # Now calculate the residual - chiv = fc.residual() - self.assertAlmostEqual(0, dot(chiv, chiv)) - - # Now change the equation - fc.setEquation("2*I") - self.assertTrue(fc._eq._value is None) - self.assertTrue(fc._reseq._value is None) - chiv = fc.residual() - self.assertAlmostEqual(dot(yobs, yobs), dot(chiv, chiv)) - - # Try to add a parameter - c = Parameter("c", 2) - fc._addParameter(c) - fc.setEquation("c*I") - self.assertTrue(fc._eq._value is None) - self.assertTrue(fc._reseq._value is None) - chiv = fc.residual() - self.assertAlmostEqual(dot(yobs, yobs), dot(chiv, chiv)) - - # Try something more complex - c.setValue(3) - fc.setEquation("c**2*sin(I)") - self.assertTrue(fc._eq._value is None) - self.assertTrue(fc._reseq._value is None) - xobs = arange(0, 10, 0.5) - yobs = 9 * sin(xobs) - profile.setObservedProfile(xobs, yobs) - self.assertTrue(fc._eq._value is None) - self.assertTrue(fc._reseq._value is None) - - chiv = fc.residual() - self.assertAlmostEqual(0, dot(chiv, chiv)) - - # Choose a new residual. - fc.setEquation("2*I") - fc.setResidualEquation("resv") - chiv = fc.residual() - self.assertAlmostEqual( - sum((2 * xobs - yobs) ** 2) / sum(yobs**2), dot(chiv, chiv) - ) - - # Make a custom residual. - fc.setResidualEquation("abs(eq-y)**0.5") - chiv = fc.residual() - self.assertAlmostEqual(sum(abs(2 * xobs - yobs)), dot(chiv, chiv)) - - # Test configuration checks - fc1 = FitContribution("test1") - self.assertRaises(SrFitError, fc1.setResidualEquation, "chiv") - fc1.setProfile(self.profile) - self.assertRaises(SrFitError, fc1.setResidualEquation, "chiv") - fc1.setEquation("A * x") - fc1.setResidualEquation("chiv") - self.assertTrue(noObserversInGlobalBuilders()) - return - - def test_setEquation(self): - """Check replacement of removed parameters.""" - fc = self.fitcontribution - fc.setEquation("x + 5") - fc.x.setValue(2) - self.assertEqual(7, fc.evaluate()) - fc.removeParameter(fc.x) - x = arange(0, 10, 0.5) - fc.newParameter("x", x) - self.assertTrue(array_equal(5 + x, fc.evaluate())) - self.assertTrue(noObserversInGlobalBuilders()) - return - - def test_getEquation(self): - """Check getting the current profile simulation formula.""" - fc = self.fitcontribution - self.assertEqual("", fc.getEquation()) - fc.setEquation("A * sin(x + 5)") - self.assertEqual("(A * sin((x + 5)))", fc.getEquation()) - self.assertTrue(noObserversInGlobalBuilders()) - return - def test_getResidualEquation(self): """Check getting the current formula for residual equation.""" fc = self.fitcontribution @@ -286,6 +183,111 @@ def test_registerFunction(self): self.assertEqual(6, fc.evaluate()) return +def testResidual(noObserversInGlobalBuilders): + """Test the residual, which requires all other methods.""" + gen = ProfileGenerator("test") + profile = Profile() + fc = FitContribution("test") + + # Add the calculator and profile + fc.setProfile(profile) + assert fc.profile is profile + fc.addProfileGenerator(gen, "I") + assert fc._eq._value is None + assert fc._reseq._value is None + assert 1 == len(fc._generators) + assert gen.name in fc._generators + + # Let's create some data) + xobs = arange(0, 10, 0.5) + yobs = xobs + profile.setObservedProfile(xobs, yobs) + + # Check our fitting equation. + assert np.array_equal(fc._eq(), gen(xobs)) + + # Now calculate the residual + chiv = fc.residual() + assert dot(chiv, chiv) == pytest.approx(0) + + # Now change the equation + fc.setEquation("2*I") + assert fc._eq._value is None + assert fc._reseq._value is None + chiv = fc.residual() + assert dot(chiv, chiv) == pytest.approx(dot(yobs, yobs)) + + # Try to add a parameter + c = Parameter("c", 2) + fc._addParameter(c) + fc.setEquation("c*I") + assert fc._eq._value is None + assert fc._reseq._value is None + chiv = fc.residual() + assert dot(chiv, chiv) == pytest.approx(dot(yobs, yobs)) + + # Try something more complex + c.setValue(3) + fc.setEquation("c**2*sin(I)") + assert fc._eq._value is None + assert fc._reseq._value is None + xobs = arange(0, 10, 0.5) + yobs = 9 * sin(xobs) + profile.setObservedProfile(xobs, yobs) + assert fc._eq._value is None + assert fc._reseq._value is None + + chiv = fc.residual() + assert dot(chiv, chiv) == pytest.approx(0) + + # Choose a new residual. + fc.setEquation("2*I") + fc.setResidualEquation("resv") + chiv = fc.residual() + assert (dot(chiv, chiv) == + pytest.approx(sum((2 * xobs - yobs) ** 2) / sum(yobs**2))) + + + # Make a custom residual. + fc.setResidualEquation("abs(eq-y)**0.5") + chiv = fc.residual() + assert dot(chiv, chiv) == pytest.approx(sum(abs(2 * xobs - yobs))) + + # Test configuration checks + fc1 = FitContribution("test1") + with pytest.raises(SrFitError): + fc1.setResidualEquation("chiv") + fc1.setProfile(profile) + with pytest.raises(SrFitError): + fc1.setResidualEquation("chiv") + fc1.setEquation("A * x") + fc1.setResidualEquation("chiv") + assert noObserversInGlobalBuilders + return + +def test_setEquation(noObserversInGlobalBuilders): + """Check replacement of removed parameters.""" + fc = FitContribution("test") + fc.setEquation("x + 5") + fc.x.setValue(2) + assert 7 == fc.evaluate() + fc.removeParameter(fc.x) + x = arange(0, 10, 0.5) + fc.newParameter("x", x) + assert np.array_equal(5 + x, fc.evaluate()) + assert noObserversInGlobalBuilders + return + +def test_getEquation(noObserversInGlobalBuilders): + """Check getting the current profile simulation formula.""" + fc = FitContribution("test") + assert "" == fc.getEquation() + fc.setEquation("A * sin(x + 5)") + assert "(A * sin((x + 5)))" == fc.getEquation() + assert noObserversInGlobalBuilders + return + + if __name__ == "__main__": unittest.main() From b791c523371fafa5047b20f1b8fb8c24e329303f Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 08:29:08 -0400 Subject: [PATCH 29/45] tests: diffpyparset --- tests/test_diffpyparset.py | 227 ++++++++++++++++++------------------- 1 file changed, 113 insertions(+), 114 deletions(-) diff --git a/tests/test_diffpyparset.py b/tests/test_diffpyparset.py index b4868de8..8463d80d 100644 --- a/tests/test_diffpyparset.py +++ b/tests/test_diffpyparset.py @@ -17,122 +17,121 @@ import pickle import unittest -import numpy - -from .utils import _msg_nostructure, has_structure - -# Global variables to be assigned in setUp -Atom = Lattice = Structure = DiffpyStructureParSet = None - -# ---------------------------------------------------------------------------- - - -@unittest.skipUnless(has_structure, _msg_nostructure) -class TestParameterAdapter(unittest.TestCase): - - def setUp(self): - global Atom, Lattice, Structure, DiffpyStructureParSet - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - from diffpy.structure import Atom, Lattice, Structure - - return - - def testDiffpyStructureParSet(self): - """Test the structure conversion.""" - - a1 = Atom("Cu", xyz=numpy.array([0.0, 0.1, 0.2]), Uisoequiv=0.003) - a2 = Atom("Ag", xyz=numpy.array([0.3, 0.4, 0.5]), Uisoequiv=0.002) - l = Lattice(2.5, 2.5, 2.5, 90, 90, 90) - - dsstru = Structure([a1, a2], l) - # Structure makes copies - a1 = dsstru[0] - a2 = dsstru[1] - - s = DiffpyStructureParSet("CuAg", dsstru) - - self.assertEqual(s.name, "CuAg") - - def _testAtoms(): - # Check the atoms thoroughly - self.assertEqual(a1.element, s.Cu0.element) - self.assertEqual(a2.element, s.Ag0.element) - self.assertEqual(a1.Uisoequiv, s.Cu0.Uiso.getValue()) - self.assertEqual(a2.Uisoequiv, s.Ag0.Uiso.getValue()) - self.assertEqual(a1.Bisoequiv, s.Cu0.Biso.getValue()) - self.assertEqual(a2.Bisoequiv, s.Ag0.Biso.getValue()) - for i in range(1, 4): - for j in range(i, 4): - uijstru = getattr(a1, "U%i%i" % (i, j)) - uij = getattr(s.Cu0, "U%i%i" % (i, j)).getValue() - uji = getattr(s.Cu0, "U%i%i" % (j, i)).getValue() - self.assertEqual(uijstru, uij) - self.assertEqual(uijstru, uji) - bijstru = getattr(a1, "B%i%i" % (i, j)) - bij = getattr(s.Cu0, "B%i%i" % (i, j)).getValue() - bji = getattr(s.Cu0, "B%i%i" % (j, i)).getValue() - self.assertEqual(bijstru, bij) - self.assertEqual(bijstru, bji) - - self.assertEqual(a1.xyz[0], s.Cu0.x.getValue()) - self.assertEqual(a1.xyz[1], s.Cu0.y.getValue()) - self.assertEqual(a1.xyz[2], s.Cu0.z.getValue()) - return - - def _testLattice(): - - # Test the lattice - self.assertEqual(dsstru.lattice.a, s.lattice.a.getValue()) - self.assertEqual(dsstru.lattice.b, s.lattice.b.getValue()) - self.assertEqual(dsstru.lattice.c, s.lattice.c.getValue()) - self.assertEqual(dsstru.lattice.alpha, s.lattice.alpha.getValue()) - self.assertEqual(dsstru.lattice.beta, s.lattice.beta.getValue()) - self.assertEqual(dsstru.lattice.gamma, s.lattice.gamma.getValue()) - - _testAtoms() - _testLattice() - - # Now change some values from the diffpy Structure - a1.xyz[1] = 0.123 - a1.U11 = 0.321 - a1.B32 = 0.111 - dsstru.lattice.setLatPar(a=3.0, gamma=121) - _testAtoms() - _testLattice() - - # Now change values from the srfit DiffpyStructureParSet - s.Cu0.x.setValue(0.456) - s.Cu0.U22.setValue(0.441) - s.Cu0.B13.setValue(0.550) - d = dsstru.lattice.dist(a1.xyz, a2.xyz) - s.lattice.b.setValue(4.6) - s.lattice.alpha.setValue(91.3) - _testAtoms() - _testLattice() - # Make sure the distance changed - self.assertNotEqual(d, dsstru.lattice.dist(a1.xyz, a2.xyz)) +import numpy as np +import pytest + +from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + + +def testDiffpyStructureParSet(diffpy_structure_available): + """Test the structure conversion.""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Atom, Lattice, Structure + + a1 = Atom("Cu", xyz=np.array([0.0, 0.1, 0.2]), Uisoequiv=0.003) + a2 = Atom("Ag", xyz=np.array([0.3, 0.4, 0.5]), Uisoequiv=0.002) + lattice = Lattice(2.5, 2.5, 2.5, 90, 90, 90) + + dsstru = Structure([a1, a2], lattice) + # Structure makes copies + a1 = dsstru[0] + a2 = dsstru[1] + + s = DiffpyStructureParSet("CuAg", dsstru) + + assert s.name == "CuAg" + + def _testAtoms(): + # Check the atoms thoroughly + assert a1.element == s.Cu0.element + assert a2.element == s.Ag0.element + assert a1.Uisoequiv == s.Cu0.Uiso.getValue() + assert a2.Uisoequiv == s.Ag0.Uiso.getValue() + assert a1.Bisoequiv == s.Cu0.Biso.getValue() + assert a2.Bisoequiv == s.Ag0.Biso.getValue() + for i in range(1, 4): + for j in range(i, 4): + uijstru = getattr(a1, "U%i%i" % (i, j)) + uij = getattr(s.Cu0, "U%i%i" % (i, j)).getValue() + uji = getattr(s.Cu0, "U%i%i" % (j, i)).getValue() + assert uijstru == uij + assert uijstru == uji + bijstru = getattr(a1, "B%i%i" % (i, j)) + bij = getattr(s.Cu0, "B%i%i" % (i, j)).getValue() + bji = getattr(s.Cu0, "B%i%i" % (j, i)).getValue() + assert bijstru == bij + assert bijstru == bji + + assert a1.xyz[0] == s.Cu0.x.getValue() + assert a1.xyz[1] == s.Cu0.y.getValue() + assert a1.xyz[2] == s.Cu0.z.getValue() return - def test___repr__(self): - """Test representation of DiffpyStructureParSet objects.""" - lat = Lattice(3, 3, 2, 90, 90, 90) - atom = Atom("C", [0, 0.2, 0.5]) - stru = Structure([atom], lattice=lat) - dsps = DiffpyStructureParSet("dsps", stru) - self.assertEqual(repr(stru), repr(dsps)) - self.assertEqual(repr(lat), repr(dsps.lattice)) - self.assertEqual(repr(atom), repr(dsps.atoms[0])) - return - - def test_pickling(self): - """Test pickling of DiffpyStructureParSet.""" - stru = Structure([Atom("C", [0, 0.2, 0.5])]) - dsps = DiffpyStructureParSet("dsps", stru) - data = pickle.dumps(dsps) - dsps2 = pickle.loads(data) - self.assertEqual(1, len(dsps2.atoms)) - self.assertEqual(0.2, dsps2.atoms[0].y.value) - return + def _testLattice(): + + # Test the lattice + assert dsstru.lattice.a == s.lattice.a.getValue() + assert dsstru.lattice.b == s.lattice.b.getValue() + assert dsstru.lattice.c == s.lattice.c.getValue() + assert dsstru.lattice.alpha == s.lattice.alpha.getValue() + assert dsstru.lattice.beta == s.lattice.beta.getValue() + assert dsstru.lattice.gamma == s.lattice.gamma.getValue() + + _testAtoms() + _testLattice() + + # Now change some values from the diffpy Structure + a1.xyz[1] = 0.123 + a1.U11 = 0.321 + a1.B32 = 0.111 + dsstru.lattice.setLatPar(a=3.0, gamma=121) + _testAtoms() + _testLattice() + + # Now change values from the srfit DiffpyStructureParSet + s.Cu0.x.setValue(0.456) + s.Cu0.U22.setValue(0.441) + s.Cu0.B13.setValue(0.550) + d = dsstru.lattice.dist(a1.xyz, a2.xyz) + s.lattice.b.setValue(4.6) + s.lattice.alpha.setValue(91.3) + _testAtoms() + _testLattice() + # Make sure the distance changed + assert d != dsstru.lattice.dist(a1.xyz, a2.xyz) + return + + +def test___repr__(diffpy_structure_available): + """Test representation of DiffpyStructureParSet objects.""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Atom, Lattice, Structure + + lat = Lattice(3, 3, 2, 90, 90, 90) + atom = Atom("C", [0, 0.2, 0.5]) + stru = Structure([atom], lattice=lat) + dsps = DiffpyStructureParSet("dsps", stru) + assert repr(stru) == repr(dsps) + assert repr(lat) == repr(dsps.lattice) + assert repr(atom) == repr(dsps.atoms[0]) + return + + +def test_pickling(diffpy_structure_available): + """Test pickling of DiffpyStructureParSet.""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Atom, Structure + + stru = Structure([Atom("C", [0, 0.2, 0.5])]) + dsps = DiffpyStructureParSet("dsps", stru) + data = pickle.dumps(dsps) + dsps2 = pickle.loads(data) + assert 1 == len(dsps2.atoms) + assert 0.2 == dsps2.atoms[0].y.value + return # End of class TestParameterAdapter From c2dc20e249d6885659fecdc921cfa9d4a08a6bf5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:33:27 +0000 Subject: [PATCH 30/45] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_characteristicfunctions.py | 5 ++++- tests/test_contribution.py | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_characteristicfunctions.py b/tests/test_characteristicfunctions.py index 95d1a8bc..dd846baf 100644 --- a/tests/test_characteristicfunctions.py +++ b/tests/test_characteristicfunctions.py @@ -19,8 +19,8 @@ import numpy import pytest -from diffpy.srfit.sas.sasimport import sasimport import diffpy.srfit.pdf.characteristicfunctions as cf +from diffpy.srfit.sas.sasimport import sasimport # Global variables to be assigned in setUp cf = None @@ -48,6 +48,7 @@ def testSphere(sas_available): assert res == pytest.approx(0, abs=1e-4) return + def testSpheroid(sas_available): if not sas_available: pytest.skip("sas package not available") @@ -70,6 +71,7 @@ def testSpheroid(sas_available): assert res == pytest.approx(0, abs=1e-4) return + def testShell(sas_available): if not sas_available: pytest.skip("sas package not available") @@ -92,6 +94,7 @@ def testShell(sas_available): assert res == pytest.approx(0, abs=1e-4) return + def testCylinder(sas_available): if not sas_available: pytest.skip("sas package not available") diff --git a/tests/test_contribution.py b/tests/test_contribution.py index fc34a049..4461647d 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -183,6 +183,7 @@ def test_registerFunction(self): self.assertEqual(6, fc.evaluate()) return + def testResidual(noObserversInGlobalBuilders): """Test the residual, which requires all other methods.""" gen = ProfileGenerator("test") @@ -244,9 +245,9 @@ def testResidual(noObserversInGlobalBuilders): fc.setEquation("2*I") fc.setResidualEquation("resv") chiv = fc.residual() - assert (dot(chiv, chiv) == - pytest.approx(sum((2 * xobs - yobs) ** 2) / sum(yobs**2))) - + assert dot(chiv, chiv) == pytest.approx( + sum((2 * xobs - yobs) ** 2) / sum(yobs**2) + ) # Make a custom residual. fc.setResidualEquation("abs(eq-y)**0.5") @@ -265,6 +266,7 @@ def testResidual(noObserversInGlobalBuilders): assert noObserversInGlobalBuilders return + def test_setEquation(noObserversInGlobalBuilders): """Check replacement of removed parameters.""" fc = FitContribution("test") @@ -278,6 +280,7 @@ def test_setEquation(noObserversInGlobalBuilders): assert noObserversInGlobalBuilders return + def test_getEquation(noObserversInGlobalBuilders): """Check getting the current profile simulation formula.""" fc = FitContribution("test") @@ -288,6 +291,5 @@ def test_getEquation(noObserversInGlobalBuilders): return - if __name__ == "__main__": unittest.main() From be7eeb6c3e5d082dc66b79b53ff4b7f2696e7c99 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 08:47:18 -0400 Subject: [PATCH 31/45] tests: equation.py --- tests/test_equation.py | 359 +++++++++++++++++++++-------------------- 1 file changed, 185 insertions(+), 174 deletions(-) diff --git a/tests/test_equation.py b/tests/test_equation.py index 1c9fe6b9..ff44a3d8 100644 --- a/tests/test_equation.py +++ b/tests/test_equation.py @@ -14,181 +14,192 @@ ############################################################################## """Tests for refinableobj module.""" -import unittest +import pytest import diffpy.srfit.equation.literals as literals from diffpy.srfit.equation import Equation -from .utils import _makeArgs, noObserversInGlobalBuilders - - -class TestEquation(unittest.TestCase): - - def testSimpleFunction(self): - """Test a simple function.""" - - # Make some variables - v1, v2, v3, v4, c = _makeArgs(5) - c.name = "c" - c.const = True - - # Make some operations - mult = literals.MultiplicationOperator() - root = mult2 = literals.MultiplicationOperator() - plus = literals.AdditionOperator() - minus = literals.SubtractionOperator() - - # Create the equation c*(v1+v3)*(v4-v2) - plus.addLiteral(v1) - plus.addLiteral(v3) - minus.addLiteral(v4) - minus.addLiteral(v2) - mult.addLiteral(plus) - mult.addLiteral(minus) - mult2.addLiteral(mult) - mult2.addLiteral(c) - - # Set the values of the variables. - # The equation should evaluate to 2.5*(1+3)*(4-2) = 20 - v1.setValue(1) - v2.setValue(2) - v3.setValue(3) - v4.setValue(4) - c.setValue(2.5) - - # Make an equation and test - eq = Equation("eq", mult2) - - self.assertTrue(eq._value is None) - args = eq.args - self.assertTrue(v1 in args) - self.assertTrue(v2 in args) - self.assertTrue(v3 in args) - self.assertTrue(v4 in args) - self.assertTrue(c not in args) - self.assertTrue(root is eq.root) - - self.assertTrue(v1 is eq.v1) - self.assertTrue(v2 is eq.v2) - self.assertTrue(v3 is eq.v3) - self.assertTrue(v4 is eq.v4) - - self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) - self.assertEqual(20, eq.getValue()) # same as above - self.assertEqual(20, eq.value) # same as above - self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) - self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) - self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) - self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) - - # Try some swapping - eq.swap(v4, v1) - self.assertTrue(eq._value is None) - self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) - args = eq.args - self.assertTrue(v4 not in args) - - # Try to create a dependency loop - self.assertRaises(ValueError, eq.swap, v1, eq.root) - self.assertRaises(ValueError, eq.swap, v1, plus) - self.assertRaises(ValueError, eq.swap, v1, minus) - self.assertRaises(ValueError, eq.swap, v1, mult) - self.assertRaises(ValueError, eq.swap, v1, root) - - # Swap the root - eq.swap(eq.root, v1) - self.assertTrue(eq._value is None) - self.assertEqual(v1.value, eq()) - - self.assertTrue(noObserversInGlobalBuilders()) - return - - def testEmbeddedEquation(self): - """Test a simple function.""" - - # Make some variables - v1, v2, v3, v4, c = _makeArgs(5) - c.name = "c" - c.const = True - - # Make some operations - mult = literals.MultiplicationOperator() - mult2 = literals.MultiplicationOperator() - plus = literals.AdditionOperator() - minus = literals.SubtractionOperator() - - # Create the equation c*(v1+v3)*(v4-v2) - plus.addLiteral(v1) - plus.addLiteral(v3) - minus.addLiteral(v4) - minus.addLiteral(v2) - mult.addLiteral(plus) - mult.addLiteral(minus) - mult2.addLiteral(mult) - mult2.addLiteral(c) - - # Set the values of the variables. - # The equation should evaluate to 2.5*(1+3)*(4-2) = 20 - v1.setValue(1) - v2.setValue(2) - v3.setValue(3) - v4.setValue(4) - c.setValue(2.5) - - # Make an equation and test - root = Equation("root", mult2) - eq = Equation("eq", root) - - self.assertTrue(eq._value is None) - args = eq.args - self.assertTrue(v1 in args) - self.assertTrue(v2 in args) - self.assertTrue(v3 in args) - self.assertTrue(v4 in args) - self.assertTrue(c not in args) - self.assertTrue(root is eq.root) - - self.assertTrue(v1 is eq.v1) - self.assertTrue(v2 is eq.v2) - self.assertTrue(v3 is eq.v3) - self.assertTrue(v4 is eq.v4) - - # Make sure the right messages get sent - v1.value = 0 - self.assertTrue(root._value is None) - self.assertTrue(eq._value is None) - v1.value = 1 - - self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) - self.assertEqual(20, eq.getValue()) # same as above - self.assertEqual(20, eq.value) # same as above - self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) - self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) - self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) - self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) - - # Try some swapping. - eq.swap(v4, v1) - self.assertTrue(eq._value is None) - self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) - args = eq.args - self.assertTrue(v4 not in args) - - # Try to create a dependency loop - self.assertRaises(ValueError, eq.swap, v1, eq.root) - self.assertRaises(ValueError, eq.swap, v1, plus) - self.assertRaises(ValueError, eq.swap, v1, minus) - self.assertRaises(ValueError, eq.swap, v1, mult) - self.assertRaises(ValueError, eq.swap, v1, root) - - # Swap the root - eq.swap(eq.root, v1) - self.assertTrue(eq._value is None) - self.assertEqual(v1.value, eq()) - - self.assertTrue(noObserversInGlobalBuilders()) - return - - -if __name__ == "__main__": - unittest.main() + +def testSimpleFunction(make_args, noObserversInGlobalBuilders): + """Test a simple function.""" + + # Make some variables + v1, v2, v3, v4, c = make_args(5) + c.name = "c" + c.const = True + + # Make some operations + mult = literals.MultiplicationOperator() + root = mult2 = literals.MultiplicationOperator() + plus = literals.AdditionOperator() + minus = literals.SubtractionOperator() + + # Create the equation c*(v1+v3)*(v4-v2) + plus.addLiteral(v1) + plus.addLiteral(v3) + minus.addLiteral(v4) + minus.addLiteral(v2) + mult.addLiteral(plus) + mult.addLiteral(minus) + mult2.addLiteral(mult) + mult2.addLiteral(c) + + # Set the values of the variables. + # The equation should evaluate to 2.5*(1+3)*(4-2) = 20 + v1.setValue(1) + v2.setValue(2) + v3.setValue(3) + v4.setValue(4) + c.setValue(2.5) + + # Make an equation and test + eq = Equation("eq", mult2) + + assert eq._value is None + args = eq.args + assert v1 in args + assert v2 in args + assert v3 in args + assert v4 in args + assert c not in args + assert root is eq.root + + assert v1 is eq.v1 + assert v2 is eq.v2 + assert v3 is eq.v3 + assert v4 is eq.v4 + + assert 20 == eq() # 20 = 2.5*(1+3)*(4-2) + assert 20 == eq.getValue() # same as above + assert 20 == eq.value # same as above + assert 25 == eq(v1=2) # 25 = 2.5*(2+3)*(4-2) + assert 50 == eq(v2=0) # 50 = 2.5*(2+3)*(4-0) + assert 30 == eq(v3=1) # 30 = 2.5*(2+1)*(4-0) + assert 0 == eq(v4=0) # 20 = 2.5*(2+1)*(0-0) + + # Try some swapping + eq.swap(v4, v1) + assert eq._value is None + assert 15 == eq() # 15 = 2.5*(2+1)*(2-0) + args = eq.args + assert v4 not in args + + # Try to create a dependency loop + with pytest.raises(ValueError): + eq.swap(v1, eq.root) + + with pytest.raises(ValueError): + eq.swap(v1, plus) + + with pytest.raises(ValueError): + eq.swap(v1, minus) + + with pytest.raises(ValueError): + eq.swap(v1, mult) + + with pytest.raises(ValueError): + eq.swap(v1, root) + + # Swap the root + eq.swap(eq.root, v1) + assert eq._value is None + assert v1.value, eq() + + assert noObserversInGlobalBuilders + return + + +def testEmbeddedEquation(make_args, noObserversInGlobalBuilders): + """Test a simple function.""" + + # Make some variables + v1, v2, v3, v4, c = make_args(5) + c.name = "c" + c.const = True + + # Make some operations + mult = literals.MultiplicationOperator() + mult2 = literals.MultiplicationOperator() + plus = literals.AdditionOperator() + minus = literals.SubtractionOperator() + + # Create the equation c*(v1+v3)*(v4-v2) + plus.addLiteral(v1) + plus.addLiteral(v3) + minus.addLiteral(v4) + minus.addLiteral(v2) + mult.addLiteral(plus) + mult.addLiteral(minus) + mult2.addLiteral(mult) + mult2.addLiteral(c) + + # Set the values of the variables. + # The equation should evaluate to 2.5*(1+3)*(4-2) = 20 + v1.setValue(1) + v2.setValue(2) + v3.setValue(3) + v4.setValue(4) + c.setValue(2.5) + + # Make an equation and test + root = Equation("root", mult2) + eq = Equation("eq", root) + + assert eq._value is None + args = eq.args + assert v1 in args + assert v2 in args + assert v3 in args + assert v4 in args + assert c not in args + assert root is eq.root + + assert v1 is eq.v1 + assert v2 is eq.v2 + assert v3 is eq.v3 + assert v4 is eq.v4 + + # Make sure the right messages get sent + v1.value = 0 + assert root._value is None + assert eq._value is None + v1.value = 1 + + assert 20 == eq() # 20 = 2.5*(1+3)*(4-2) + assert 20 == eq.getValue() # same as above + assert 20 == eq.value # same as above + assert 25 == eq(v1=2) # 25 = 2.5*(2+3)*(4-2) + assert 50 == eq(v2=0) # 50 = 2.5*(2+3)*(4-0) + assert 30 == eq(v3=1) # 30 = 2.5*(2+1)*(4-0) + assert 0 == eq(v4=0) # 20 = 2.5*(2+1)*(0-0) + + # Try some swapping. + eq.swap(v4, v1) + assert eq._value is None + assert 15 == eq() # 15 = 2.5*(2+1)*(2-0) + args = eq.args + assert v4 not in args + + # Try to create a dependency loop + with pytest.raises(ValueError): + eq.swap(v1, eq.root) + + with pytest.raises(ValueError): + eq.swap(v1, plus) + + with pytest.raises(ValueError): + eq.swap(v1, minus) + + with pytest.raises(ValueError): + eq.swap(v1, mult) + + with pytest.raises(ValueError): + eq.swap(v1, root) + + # Swap the root + eq.swap(eq.root, v1) + assert eq._value is None + assert v1.value == eq() + + assert noObserversInGlobalBuilders + return From f27ab7a5d92809112495fd87a2c955264d50dad5 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 09:13:30 -0400 Subject: [PATCH 32/45] tests: fitrecipe --- tests/conftest.py | 29 +++++++++++++----- tests/test_fitrecipe.py | 65 ++++++++++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2d63f1aa..f7717318 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,12 @@ import importlib.resources import json import logging +import sys from functools import lru_cache from pathlib import Path import pytest +import six import diffpy.srfit.equation.literals as literals from diffpy.srfit.sas.sasimport import sasimport @@ -15,8 +17,8 @@ @lru_cache() def has_sas(): try: - __import__("sas.pr.invertor") - __import__("sas.models") + sasimport("sas.pr.invertor") + sasimport("sas.models") return True except ImportError: return False @@ -25,7 +27,6 @@ def has_sas(): # diffpy.structure @lru_cache() def has_diffpy_structure(): - _msg_nostructure = "No module named 'diffpy.structure'" try: import diffpy.structure as m @@ -40,7 +41,6 @@ def has_diffpy_structure(): @lru_cache() def has_pyobjcryst(): - _msg_nopyobjcryst = "No module named 'pyobjcryst'" try: import pyobjcryst as m @@ -56,7 +56,6 @@ def has_pyobjcryst(): @lru_cache() def has_diffpy_srreal(): - _msg_nosrreal = "No module named 'diffpy.srreal'" try: import diffpy.srreal.pdfcalculator as m @@ -87,7 +86,7 @@ def pyobjcryst_available(): return has_pyobjcryst() -@pytest.fixture +@pytest.fixture(scope="session") def user_filesystem(tmp_path): base_dir = Path(tmp_path) home_dir = base_dir / "home_dir" @@ -102,7 +101,7 @@ def user_filesystem(tmp_path): yield tmp_path -@pytest.fixture +@pytest.fixture(scope="session") def datafile(): """Fixture to load a test data file from the testdata package directory.""" @@ -144,3 +143,19 @@ def _noObserversInGlobalBuilders(): return rv return _noObserversInGlobalBuilders() + + +@pytest.fixture(scope="session") +def capturestdout(): + def _capturestdout(f, *args, **kwargs): + """Capture the standard output from a call of function f.""" + savestdout = sys.stdout + fp = six.StringIO() + try: + sys.stdout = fp + f(*args, **kwargs) + finally: + sys.stdout = savestdout + return fp.getvalue() + + return _capturestdout diff --git a/tests/test_fitrecipe.py b/tests/test_fitrecipe.py index 1945e063..1a2b2368 100644 --- a/tests/test_fitrecipe.py +++ b/tests/test_fitrecipe.py @@ -23,8 +23,6 @@ from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.fitbase.profile import Profile -from .utils import capturestdout - class TestFitRecipe(unittest.TestCase): @@ -239,32 +237,51 @@ def testResidual(self): return - def testPrintFitHook(self): - "check output from default PrintFitHook." - self.recipe.addVar(self.fitcontribution.c) - self.recipe.restrain("c", lb=5) - (pfh,) = self.recipe.getFitHooks() - out = capturestdout(self.recipe.scalarResidual) - self.assertEqual("", out) - pfh.verbose = 1 - out = capturestdout(self.recipe.scalarResidual) - self.assertTrue(out.strip().isdigit()) - self.assertFalse("\nRestraints:" in out) - pfh.verbose = 2 - out = capturestdout(self.recipe.scalarResidual) - self.assertTrue("\nResidual:" in out) - self.assertTrue("\nRestraints:" in out) - self.assertFalse("\nVariables" in out) - pfh.verbose = 3 - out = capturestdout(self.recipe.scalarResidual) - self.assertTrue("\nVariables" in out) - self.assertTrue("c = " in out) - return - # End of class TestFitRecipe + # ---------------------------------------------------------------------------- +def testPrintFitHook(capturestdout): + "check output from default PrintFitHook." + recipe = FitRecipe("recipe") + recipe.fithooks[0].verbose = 0 + + # Set up the Profile + profile = Profile() + x = linspace(0, pi, 10) + y = sin(x) + profile.setObservedProfile(x, y) + + # Set up the FitContribution + fitcontribution = FitContribution("cont") + fitcontribution.setProfile(profile) + fitcontribution.setEquation("A*sin(k*x + c)") + fitcontribution.A.setValue(1) + fitcontribution.k.setValue(1) + fitcontribution.c.setValue(0) + + recipe.addContribution(fitcontribution) + + recipe.addVar(fitcontribution.c) + recipe.restrain("c", lb=5) + (pfh,) = recipe.getFitHooks() + out = capturestdout(recipe.scalarResidual) + assert "" == out + pfh.verbose = 1 + out = capturestdout(recipe.scalarResidual) + assert out.strip().isdigit() + assert "\nRestraints:" not in out + pfh.verbose = 2 + out = capturestdout(recipe.scalarResidual) + assert "\nResidual:" in out + assert "\nRestraints:" in out + assert "\nVariables" not in out + pfh.verbose = 3 + out = capturestdout(recipe.scalarResidual) + assert "\nVariables" in out + assert "c = " in out + return if __name__ == "__main__": From 8e0376ab4c3a1a0e65f10c74815792cedd2dbf1c Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 09:41:43 -0400 Subject: [PATCH 33/45] tests: fitresults --- tests/conftest.py | 4 +- tests/test_fitresults.py | 108 ++++++++++++++++++++++----------------- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f7717318..bd53d38d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -106,9 +106,7 @@ def datafile(): """Fixture to load a test data file from the testdata package directory.""" def _datafile(filename): - return importlib.resources.files( - "diffpy.srfit.tests.testdata" - ).joinpath(filename) + return importlib.resources.files("tests.testdata").joinpath(filename) return _datafile diff --git a/tests/test_fitresults.py b/tests/test_fitresults.py index 841c7a73..5702b03f 100644 --- a/tests/test_fitresults.py +++ b/tests/test_fitresults.py @@ -16,64 +16,76 @@ import unittest +import pytest + from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitresults import initializeRecipe -from .utils import datafile +def testInitializeFromFileName(datafile): + recipe = FitRecipe("recipe") + recipe.newVar("A", 0) + recipe.newVar("sig", 0) + recipe.newVar("x0", 0) + filename = datafile("results.res") + Aval = 5.77619823e-01 + sigval = -9.22758690e-01 + x0val = 6.12422115e00 + + assert 0 == recipe.A.value + assert 0 == recipe.sig.value + assert 0 == recipe.x0.value + initializeRecipe(recipe, filename) + assert Aval == pytest.approx(recipe.A.value) + assert sigval == pytest.approx(recipe.sig.value) + assert x0val == pytest.approx(recipe.x0.value) + return -class TestInitializeRecipe(unittest.TestCase): - def setUp(self): - self.recipe = recipe = FitRecipe("recipe") - recipe.newVar("A", 0) - recipe.newVar("sig", 0) - recipe.newVar("x0", 0) - self.filename = datafile("results.res") +def testInitializeFromFileObj(datafile): + recipe = FitRecipe("recipe") + recipe.newVar("A", 0) + recipe.newVar("sig", 0) + recipe.newVar("x0", 0) + filename = datafile("results.res") + Aval = 5.77619823e-01 + sigval = -9.22758690e-01 + x0val = 6.12422115e00 - self.Aval = 5.77619823e-01 - self.sigval = -9.22758690e-01 - self.x0val = 6.12422115e00 - return + assert 0 == recipe.A.value + assert 0 == recipe.sig.value + assert 0 == recipe.x0.value + infile = open(filename, "r") + initializeRecipe(recipe, infile) + assert not infile.closed + infile.close() + assert Aval == pytest.approx(recipe.A.value) + assert sigval == pytest.approx(recipe.sig.value) + assert x0val == pytest.approx(recipe.x0.value) + return - def testInitializeFromFileName(self): - recipe = self.recipe - self.assertEqual(0, recipe.A.value) - self.assertEqual(0, recipe.sig.value) - self.assertEqual(0, recipe.x0.value) - initializeRecipe(recipe, self.filename) - self.assertAlmostEqual(self.Aval, recipe.A.value) - self.assertAlmostEqual(self.sigval, recipe.sig.value) - self.assertAlmostEqual(self.x0val, recipe.x0.value) - return - def testInitializeFromFileObj(self): - recipe = self.recipe - self.assertEqual(0, recipe.A.value) - self.assertEqual(0, recipe.sig.value) - self.assertEqual(0, recipe.x0.value) - infile = open(self.filename, "r") - initializeRecipe(recipe, infile) - self.assertFalse(infile.closed) - infile.close() - self.assertAlmostEqual(self.Aval, recipe.A.value) - self.assertAlmostEqual(self.sigval, recipe.sig.value) - self.assertAlmostEqual(self.x0val, recipe.x0.value) - return +def testInitializeFromString(datafile): + recipe = FitRecipe("recipe") + recipe.newVar("A", 0) + recipe.newVar("sig", 0) + recipe.newVar("x0", 0) + filename = datafile("results.res") + Aval = 5.77619823e-01 + sigval = -9.22758690e-01 + x0val = 6.12422115e00 - def testInitializeFromString(self): - recipe = self.recipe - self.assertEqual(0, recipe.A.value) - self.assertEqual(0, recipe.sig.value) - self.assertEqual(0, recipe.x0.value) - infile = open(self.filename, "r") - resstr = infile.read() - infile.close() - initializeRecipe(recipe, resstr) - self.assertAlmostEqual(self.Aval, recipe.A.value) - self.assertAlmostEqual(self.sigval, recipe.sig.value) - self.assertAlmostEqual(self.x0val, recipe.x0.value) - return + assert 0 == recipe.A.value + assert 0 == recipe.sig.value + assert 0 == recipe.x0.value + infile = open(filename, "r") + resstr = infile.read() + infile.close() + initializeRecipe(recipe, resstr) + assert Aval == pytest.approx(recipe.A.value) + assert sigval == pytest.approx(recipe.sig.value) + assert x0val == pytest.approx(recipe.x0.value) + return if __name__ == "__main__": From 632798e5e67e9e069eaac729ff020ac5b9a98604 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 11:06:54 -0400 Subject: [PATCH 34/45] tests pdf --- tests/test_pdf.py | 570 +++++++++++++++++++++++----------------------- 1 file changed, 284 insertions(+), 286 deletions(-) diff --git a/tests/test_pdf.py b/tests/test_pdf.py index f9ad04bc..929b16ed 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -17,303 +17,301 @@ import io import pickle import unittest +from itertools import chain import numpy +import pytest from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser -from .utils import ( - _msg_nosrreal, - _msg_nostructure, - datafile, - has_srreal, - has_structure, -) - -# ---------------------------------------------------------------------------- - - -class TestPDFParset(unittest.TestCase): - - def setUp(self): - return - - def testParser1(self): - data = datafile("ni-q27r100-neutron.gr") - parser = PDFParser() - parser.parseFile(data) - - meta = parser._meta - - self.assertEqual(data, meta["filename"]) - self.assertEqual(1, meta["nbanks"]) - self.assertEqual("N", meta["stype"]) - self.assertEqual(27, meta["qmax"]) - self.assertEqual(300, meta.get("temperature")) - self.assertEqual(None, meta.get("qdamp")) - self.assertEqual(None, meta.get("qbroad")) - self.assertEqual(None, meta.get("spdiameter")) - self.assertEqual(None, meta.get("scale")) - self.assertEqual(None, meta.get("doping")) - - x, y, dx, dy = parser.getData() - self.assertTrue(dx is None) - self.assertTrue(dy is None) - - testx = numpy.linspace(0.01, 100, 10000) - diff = testx - x - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - testy = numpy.array( - [ - 1.144, - 2.258, - 3.312, - 4.279, - 5.135, - 5.862, - 6.445, - 6.875, - 7.150, - 7.272, - ] - ) - diff = testy - y[:10] - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - return - - def testParser2(self): - data = datafile("si-q27r60-xray.gr") - parser = PDFParser() - parser.parseFile(data) - - meta = parser._meta - - self.assertEqual(data, meta["filename"]) - self.assertEqual(1, meta["nbanks"]) - self.assertEqual("X", meta["stype"]) - self.assertEqual(27, meta["qmax"]) - self.assertEqual(300, meta.get("temperature")) - self.assertEqual(None, meta.get("qdamp")) - self.assertEqual(None, meta.get("qbroad")) - self.assertEqual(None, meta.get("spdiameter")) - self.assertEqual(None, meta.get("scale")) - self.assertEqual(None, meta.get("doping")) - - x, y, dx, dy = parser.getData() - testx = numpy.linspace(0.01, 60, 5999, endpoint=False) - diff = testx - x - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - testy = numpy.array( - [ - 0.1105784, - 0.2199684, - 0.3270088, - 0.4305913, - 0.5296853, - 0.6233606, - 0.7108060, - 0.7913456, - 0.8644501, - 0.9297440, - ] - ) - diff = testy - y[:10] - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - testdy = numpy.array( - [ - 0.001802192, - 0.003521449, - 0.005079115, - 0.006404892, - 0.007440527, - 0.008142955, - 0.008486813, - 0.008466340, - 0.008096858, - 0.007416456, - ] - ) - diff = testdy - dy[:10] - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - - self.assertTrue(dx is None) - return - - -# End of class TestPDFParset - # ---------------------------------------------------------------------------- -@unittest.skipUnless(has_srreal, _msg_nosrreal) -@unittest.skipUnless(has_structure, _msg_nostructure) -class TestPDFGenerator(unittest.TestCase): - - def setUp(self): - self.gen = PDFGenerator() - return - - def testGenerator(self): - qmax = 27.0 - gen = self.gen - gen.setScatteringType("N") - self.assertEqual("N", gen.getScatteringType()) - gen.setQmax(qmax) - self.assertAlmostEqual(qmax, gen.getQmax()) - from diffpy.structure import PDFFitStructure - - stru = PDFFitStructure() - ciffile = datafile("ni.cif") - stru.read(ciffile) - for i in range(4): - stru[i].Bisoequiv = 1 - gen.setStructure(stru) - - calc = gen._calc - # Test parameters - for par in gen.iterPars(recurse=False): - pname = par.name - defval = calc._getDoubleAttr(pname) - self.assertEqual(defval, par.getValue()) - # Test setting values - par.setValue(1.0) - self.assertEqual(1.0, par.getValue()) - par.setValue(defval) - self.assertEqual(defval, par.getValue()) - - r = numpy.arange(0, 10, 0.1) - y = gen(r) - - # Now create a reference PDF. Since the calculator is testing its - # output, we just have to make sure we can calculate from the - # PDFGenerator interface. - from diffpy.srreal.pdfcalculator import PDFCalculator - - calc = PDFCalculator() - calc.rstep = r[1] - r[0] - calc.rmin = r[0] - calc.rmax = r[-1] + 0.5 * calc.rstep - calc.qmax = qmax - calc.setScatteringFactorTableByType("N") - calc.eval(stru) - yref = calc.pdf - - diff = y - yref - res = numpy.dot(diff, diff) - self.assertAlmostEqual(0, res) - return - - def test_setQmin(self): - """Verify qmin is propagated to the calculator object.""" - gen = self.gen - self.assertEqual(0, gen.getQmin()) - self.assertEqual(0, gen._calc.qmin) - gen.setQmin(0.93) - self.assertEqual(0.93, gen.getQmin()) - self.assertEqual(0.93, gen._calc.qmin) - return - - -# End of class TestPDFGenerator - -# ---------------------------------------------------------------------------- - - -@unittest.skipUnless(has_srreal, _msg_nosrreal) -@unittest.skipUnless(has_structure, _msg_nostructure) -class TestPDFContribution(unittest.TestCase): - - def setUp(self): - self.pc = PDFContribution("pdf") - return - - def test_setQmax(self): - """Check PDFContribution.setQmax()""" - from diffpy.structure import Structure - - pc = self.pc - pc.setQmax(21) - pc.addStructure("empty", Structure()) - self.assertEqual(21, pc.empty.getQmax()) - pc.setQmax(22) - self.assertEqual(22, pc.getQmax()) - self.assertEqual(22, pc.empty.getQmax()) - return - - def test_getQmax(self): - """Check PDFContribution.getQmax()""" - from diffpy.structure import Structure - - # cover all code branches in PDFContribution._getMetaValue - # (1) contribution metadata - pc1 = self.pc - self.assertIsNone(pc1.getQmax()) - pc1.setQmax(17) - self.assertEqual(17, pc1.getQmax()) - # (2) contribution metadata - pc2 = PDFContribution("pdf") - pc2.addStructure("empty", Structure()) - pc2.empty.setQmax(18) - self.assertEqual(18, pc2.getQmax()) - # (3) profile metadata - pc3 = PDFContribution("pdf") - pc3.profile.meta["qmax"] = 19 - self.assertEqual(19, pc3.getQmax()) - return - - def test_savetxt(self): - "check PDFContribution.savetxt()" - from diffpy.structure import Structure - - pc = self.pc - pc.loadData(datafile("si-q27r60-xray.gr")) - pc.setCalculationRange(0, 10) - pc.addStructure("empty", Structure()) - fp = io.BytesIO() - self.assertRaises(SrFitError, pc.savetxt, fp) - pc.evaluate() +def testParser1(datafile): + data = datafile("ni-q27r100-neutron.gr") + parser = PDFParser() + parser.parseFile(data) + + meta = parser._meta + + assert data == meta["filename"] + assert 1 == meta["nbanks"] + assert "N" == meta["stype"] + assert 27 == meta["qmax"] + assert 300 == meta.get("temperature") + assert meta.get("qdamp") is None + assert meta.get("qbroad") is None + assert meta.get("spdiameter") is None + assert meta.get("scale") is None + assert meta.get("doping") is None + + x, y, dx, dy = parser.getData() + assert dx is None + assert dy is None + + testx = numpy.linspace(0.01, 100, 10000) + diff = testx - x + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = numpy.array( + [ + 1.144, + 2.258, + 3.312, + 4.279, + 5.135, + 5.862, + 6.445, + 6.875, + 7.150, + 7.272, + ] + ) + diff = testy - y[:10] + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + return + + +def testParser2(datafile): + data = datafile("si-q27r60-xray.gr") + parser = PDFParser() + parser.parseFile(data) + + meta = parser._meta + + assert data == meta["filename"] + assert 1 == meta["nbanks"] + assert "X" == meta["stype"] + assert 27 == meta["qmax"] + assert 300 == meta.get("temperature") + assert meta.get("qdamp") is None + assert meta.get("qbroad") is None + assert meta.get("spdiameter") is None + assert meta.get("scale") is None + assert meta.get("doping") is None + + x, y, dx, dy = parser.getData() + testx = numpy.linspace(0.01, 60, 5999, endpoint=False) + diff = testx - x + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = numpy.array( + [ + 0.1105784, + 0.2199684, + 0.3270088, + 0.4305913, + 0.5296853, + 0.6233606, + 0.7108060, + 0.7913456, + 0.8644501, + 0.9297440, + ] + ) + diff = testy - y[:10] + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testdy = numpy.array( + [ + 0.001802192, + 0.003521449, + 0.005079115, + 0.006404892, + 0.007440527, + 0.008142955, + 0.008486813, + 0.008466340, + 0.008096858, + 0.007416456, + ] + ) + diff = testdy - dy[:10] + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + assert dx is None + return + + +def testGenerator( + diffpy_srreal_available, diffpy_structure_available, datafile +): + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + from diffpy.srreal.pdfcalculator import PDFCalculator + from diffpy.structure import PDFFitStructure + + qmax = 27.0 + gen = PDFGenerator() + gen.setScatteringType("N") + assert "N" == gen.getScatteringType() + gen.setQmax(qmax) + assert qmax == pytest.approx(gen.getQmax()) + + stru = PDFFitStructure() + ciffile = datafile("ni.cif") + stru.read(ciffile) + for i in range(4): + stru[i].Bisoequiv = 1 + gen.setStructure(stru) + + calc = gen._calc + # Test parameters + for par in gen.iterPars(recurse=False): + pname = par.name + defval = calc._getDoubleAttr(pname) + assert defval == par.getValue() + # Test setting values + par.setValue(1.0) + assert 1.0 == par.getValue() + par.setValue(defval) + assert defval == par.getValue() + + r = numpy.arange(0, 10, 0.1) + y = gen(r) + + # Now create a reference PDF. Since the calculator is testing its + # output, we just have to make sure we can calculate from the + # PDFGenerator interface. + + calc = PDFCalculator() + calc.rstep = r[1] - r[0] + calc.rmin = r[0] + calc.rmax = r[-1] + 0.5 * calc.rstep + calc.qmax = qmax + calc.setScatteringFactorTableByType("N") + calc.eval(stru) + yref = calc.pdf + + diff = y - yref + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + return + + +def test_setQmin(diffpy_structure_available, diffpy_srreal_available): + """Verify qmin is propagated to the calculator object.""" + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + gen = PDFGenerator() + assert 0 == gen.getQmin() + assert 0 == gen._calc.qmin + gen.setQmin(0.93) + assert 0.93 == gen.getQmin() + assert 0.93 == gen._calc.qmin + return + + +def test_setQmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.setQmax()""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Structure + + if not diffpy_srreal_available: + pytest.skip("diffpy.structure package not available") + + pc = PDFContribution("pdf") + pc.setQmax(21) + pc.addStructure("empty", Structure()) + assert 21 == pc.empty.getQmax() + pc.setQmax(22) + assert 22 == pc.getQmax() + assert 22 == pc.empty.getQmax() + return + + +def test_getQmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.getQmax()""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Structure + + if not diffpy_srreal_available: + pytest.skip("diffpy.structure package not available") + + # cover all code branches in PDFContribution._getMetaValue + # (1) contribution metadata + pc1 = PDFContribution("pdf") + assert pc1.getQmax() is None + pc1.setQmax(17) + assert 17 == pc1.getQmax() + # (2) contribution metadata + pc2 = PDFContribution("pdf") + pc2.addStructure("empty", Structure()) + pc2.empty.setQmax(18) + assert 18 == pc2.getQmax() + # (3) profile metadata + pc3 = PDFContribution("pdf") + pc3.profile.meta["qmax"] = 19 + assert 19 == pc3.getQmax() + return + + +def test_savetxt( + diffpy_structure_available, diffpy_srreal_available, datafile +): + "check PDFContribution.savetxt()" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Structure + + if not diffpy_srreal_available: + pytest.skip("diffpy.structure package not available") + + pc = PDFContribution("pdf") + pc.loadData(datafile("si-q27r60-xray.gr")) + pc.setCalculationRange(0, 10) + pc.addStructure("empty", Structure()) + fp = io.BytesIO() + with pytest.raises(SrFitError): pc.savetxt(fp) - txt = fp.getvalue().decode() - nlines = len(txt.strip().split("\n")) - self.assertEqual(1001, nlines) - return - - def test_pickling(self): - "validate PDFContribution.residual() after pickling." - from itertools import chain - - from diffpy.structure import loadStructure - - pc = self.pc - pc.loadData(datafile("ni-q27r100-neutron.gr")) - ni = loadStructure(datafile("ni.cif")) - ni.Uisoequiv = 0.003 - pc.addStructure("ni", ni) - pc.setCalculationRange(0, 10) - pc2 = pickle.loads(pickle.dumps(pc)) - res0 = pc.residual() - self.assertTrue(numpy.array_equal(res0, pc2.residual())) - for p in chain(pc.iterPars("Uiso"), pc2.iterPars("Uiso")): - p.value = 0.004 - res1 = pc.residual() - self.assertFalse(numpy.allclose(res0, res1)) - self.assertTrue(numpy.array_equal(res1, pc2.residual())) - return - - -# End of class TestPDFContribution + pc.evaluate() + pc.savetxt(fp) + txt = fp.getvalue().decode() + nlines = len(txt.strip().split("\n")) + assert 1001 == nlines + return + + +def test_pickling( + diffpy_structure_available, diffpy_srreal_available, datafile +): + "validate PDFContribution.residual() after pickling." + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import loadStructure + + if not diffpy_srreal_available: + pytest.skip("diffpy.structure package not available") + + pc = PDFContribution("pdf") + pc.loadData(datafile("ni-q27r100-neutron.gr")) + ni = loadStructure(datafile("ni.cif")) + ni.Uisoequiv = 0.003 + pc.addStructure("ni", ni) + pc.setCalculationRange(0, 10) + pc2 = pickle.loads(pickle.dumps(pc)) + res0 = pc.residual() + assert numpy.array_equal(res0, pc2.residual()) + for p in chain(pc.iterPars("Uiso"), pc2.iterPars("Uiso")): + p.value = 0.004 + res1 = pc.residual() + assert not numpy.allclose(res0, res1) + assert numpy.array_equal(res1, pc2.residual()) + return -# ---------------------------------------------------------------------------- if __name__ == "__main__": unittest.main() From fb5001aa1d2b698753eab20284984cfb6fc34da0 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 11:17:24 -0400 Subject: [PATCH 35/45] tests: profile --- tests/test_profile.py | 67 +++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/tests/test_profile.py b/tests/test_profile.py index f19adef6..29da2162 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -18,13 +18,12 @@ import re import unittest +import pytest from numpy import allclose, arange, array, array_equal, ones_like from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.profile import Profile -from .utils import datafile - class TestProfile(unittest.TestCase): @@ -178,37 +177,6 @@ def testSetCalculationPoints(self): return - def testLoadtxt(self): - """Test the loadtxt method.""" - - prof = self.profile - data = datafile("testdata.txt") - - def _test(p): - self.assertAlmostEqual(1e-2, p.x[0]) - self.assertAlmostEqual(1.105784e-1, p.y[0]) - self.assertAlmostEqual(1.802192e-3, p.dy[0]) - - # Test normal load - prof.loadtxt(data, usecols=(0, 1, 3)) - _test(prof) - - # Test trying to not set unpack - prof.loadtxt(data, usecols=(0, 1, 3), unpack=False) - _test(prof) - prof.loadtxt(data, float, "#", None, None, 0, (0, 1, 3), False) - _test(prof) - - # Try not including dy - prof.loadtxt(data, usecols=(0, 1)) - self.assertAlmostEqual(1e-2, prof.x[0]) - self.assertAlmostEqual(1.105784e-1, prof.y[0]) - self.assertAlmostEqual(1, prof.dy[0]) - - # Try to include too little - self.assertRaises(ValueError, prof.loadtxt, data, usecols=(0,)) - return - def test_savetxt(self): "Check the savetxt method." prof = self.profile @@ -226,9 +194,38 @@ def test_savetxt(self): return -# End of class TestProfile +def testLoadtxt(datafile): + """Test the loadtxt method.""" + + prof = Profile() + data = datafile("testdata.txt") + + def _test(p): + assert 1e-2 == pytest.approx(p.x[0]) + assert 1.105784e-1 == pytest.approx(p.y[0]) + assert 1.802192e-3 == pytest.approx(p.dy[0]) + + # Test normal load + prof.loadtxt(data, usecols=(0, 1, 3)) + _test(prof) + + # Test trying to not set unpack + prof.loadtxt(data, usecols=(0, 1, 3), unpack=False) + _test(prof) + prof.loadtxt(data, float, "#", None, None, 0, (0, 1, 3), False) + _test(prof) + + # Try not including dy + prof.loadtxt(data, usecols=(0, 1)) + assert 1e-2 == pytest.approx(prof.x[0]) + assert 1.105784e-1 == pytest.approx(prof.y[0]) + assert 1 == pytest.approx(prof.dy[0]) + + # Try to include too little + with pytest.raises(ValueError): + prof.loadtxt(data, usecols=(0,)) + return -# ---------------------------------------------------------------------------- if __name__ == "__main__": unittest.main() From 59a56c7f33039cdc8cc96ef9f3254bd13705e210 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 19 Jun 2025 11:30:47 -0400 Subject: [PATCH 36/45] tests: recipeorganizer --- tests/test_recipeorganizer.py | 99 ++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/tests/test_recipeorganizer.py b/tests/test_recipeorganizer.py index 60b2e4da..88909b91 100644 --- a/tests/test_recipeorganizer.py +++ b/tests/test_recipeorganizer.py @@ -27,8 +27,6 @@ equationFromString, ) -from .utils import capturestdout - # ---------------------------------------------------------------------------- @@ -519,52 +517,57 @@ def test_releaseOldEquations(self): self.assertEqual(0, len(self.m._eqfactory.equations)) return - def test_show(self): - """Verify output from the show function.""" - - def capture_show(*args, **kwargs): - rv = capturestdout(self.m.show, *args, **kwargs) - return rv - - self.assertEqual("", capture_show()) - self.m._newParameter("x", 1) - self.m._newParameter("y", 2) - out1 = capture_show() - lines1 = out1.strip().split("\n") - self.assertEqual(4, len(lines1)) - self.assertTrue("Parameters" in lines1) - self.assertFalse("Constraints" in lines1) - self.assertFalse("Restraints" in lines1) - self.m._newParameter("z", 7) - self.m.constrain("y", "3 * z") - out2 = capture_show() - lines2 = out2.strip().split("\n") - self.assertEqual(9, len(lines2)) - self.assertTrue("Parameters" in lines2) - self.assertTrue("Constraints" in lines2) - self.assertFalse("Restraints" in lines2) - self.m.restrain("z", lb=2, ub=3, sig=0.001) - out3 = capture_show() - lines3 = out3.strip().split("\n") - self.assertEqual(13, len(lines3)) - self.assertTrue("Parameters" in lines3) - self.assertTrue("Constraints" in lines3) - self.assertTrue("Restraints" in lines3) - out4 = capture_show(pattern="x") - lines4 = out4.strip().split("\n") - self.assertEqual(9, len(lines4)) - out5 = capture_show(pattern="^") - self.assertEqual(out3, out5) - # check output with another level of hierarchy - self.m._addObject(RecipeOrganizer("foo"), self.m._containers) - self.m.foo._newParameter("bar", 13) - out6 = capture_show() - self.assertTrue("foo.bar" in out6) - # filter out foo.bar - out7 = capture_show("^(?!foo).") - self.assertFalse("foo.bar" in out7) - self.assertEqual(out3, out7) - return + +def test_show(capturestdout): + """Verify output from the show function.""" + organizer = RecipeOrganizer("test") + # Add a managed container so we can do more in-depth tests. + organizer._containers = {} + organizer._manage(organizer._containers) + + def capture_show(*args, **kwargs): + rv = capturestdout(organizer.show, *args, **kwargs) + return rv + + assert "" == capture_show() + organizer._newParameter("x", 1) + organizer._newParameter("y", 2) + out1 = capture_show() + lines1 = out1.strip().split("\n") + assert 4 == len(lines1) + assert "Parameters" in lines1 + assert "Constraints" not in lines1 + assert "Restraints" not in lines1 + organizer._newParameter("z", 7) + organizer.constrain("y", "3 * z") + out2 = capture_show() + lines2 = out2.strip().split("\n") + assert 9 == len(lines2) + assert "Parameters" in lines2 + assert "Constraints" in lines2 + assert "Restraints" not in lines2 + organizer.restrain("z", lb=2, ub=3, sig=0.001) + out3 = capture_show() + lines3 = out3.strip().split("\n") + assert 13 == len(lines3) + assert "Parameters" in lines3 + assert "Constraints" in lines3 + assert "Restraints" in lines3 + out4 = capture_show(pattern="x") + lines4 = out4.strip().split("\n") + assert 9 == len(lines4) + out5 = capture_show(pattern="^") + assert out3 == out5 + # check output with another level of hierarchy + organizer._addObject(RecipeOrganizer("foo"), organizer._containers) + organizer.foo._newParameter("bar", 13) + out6 = capture_show() + assert "foo.bar" in out6 + # filter out foo.bar + out7 = capture_show("^(?!foo).") + assert "foo.bar" not in out7 + assert out3 == out7 + return # ---------------------------------------------------------------------------- From f144bae3a23874d1c15cd6b9ce5cf25b553d3485 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Jun 2025 07:51:36 -0400 Subject: [PATCH 37/45] tests: objcrystparset and sgconstraints passing locally --- tests/test_objcrystparset.py | 197 ++++++++++---------- tests/test_sgconstraints.py | 350 ++++++++++++++++++----------------- 2 files changed, 277 insertions(+), 270 deletions(-) diff --git a/tests/test_objcrystparset.py b/tests/test_objcrystparset.py index 2e516136..c21a6d28 100644 --- a/tests/test_objcrystparset.py +++ b/tests/test_objcrystparset.py @@ -17,8 +17,7 @@ import unittest import numpy - -from .utils import _msg_nopyobjcryst, has_pyobjcryst +import pytest # Global variables to be assigned in setUp ObjCrystCrystalParSet = spacegroups = None @@ -112,10 +111,13 @@ def makeC60(): # ---------------------------------------------------------------------------- -@unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) -class TestParameterAdapter(unittest.TestCase): +class TestParameterAdapter: + @pytest.fixture(autouse=True) + def setup(self, pyobjcryst_available): + # shared setup + if not pyobjcryst_available: + pytest.skip("pyobjcryst package not available") - def setUp(self): global ObjCrystCrystalParSet, Crystal, Atom, Molecule global ScatteringPowerAtom from pyobjcryst.atom import Atom @@ -134,60 +136,83 @@ def tearDown(self): del self.ocmol return + def testImplicitBondAngleRestraints(self): + """Test the structure with implicit bond angles.""" + occryst = self.occryst + ocmol = self.ocmol + + # Add some bond angles to the molecule + ocmol.AddBondAngle(ocmol[0], ocmol[5], ocmol[8], 1.1, 0.1, 0.1) + ocmol.AddBondAngle(ocmol[0], ocmol[7], ocmol[44], 1.3, 0.1, 0.1) + + # make our crystal + cryst = ObjCrystCrystalParSet("bucky", occryst) + m = cryst.c60 + m.wrapRestraints() + + # make sure that we have some restraints in the molecule + assert 2 == len(m._restraints) + + # make sure these evaluate to whatver we get from objcryst + res0, res1 = m._restraints + p0 = set([res0.penalty(), res1.penalty()]) + angles = ocmol.GetBondAngleList() + p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) + assert p0 == p1 + + return + def testObjCrystParSet(self): """Test the structure conversion.""" occryst = self.occryst ocmol = self.ocmol - cryst = ObjCrystCrystalParSet("bucky", occryst) m = cryst.c60 - self.assertEqual(cryst.name, "bucky") + assert cryst.name == "bucky" def _testCrystal(): - # Test the lattice - self.assertAlmostEqual(occryst.a, cryst.a.value) - self.assertAlmostEqual(occryst.b, cryst.b.getValue()) - self.assertAlmostEqual(occryst.c, cryst.c.getValue()) - self.assertAlmostEqual(occryst.alpha, cryst.alpha.getValue()) - self.assertAlmostEqual(occryst.beta, cryst.beta.getValue()) - self.assertAlmostEqual(occryst.gamma, cryst.gamma.getValue()) - + assert occryst.a == pytest.approx(cryst.a.value) + assert occryst.b == pytest.approx(cryst.b.getValue()) + assert occryst.c == pytest.approx(cryst.c.getValue()) + assert occryst.alpha == pytest.approx(cryst.alpha.getValue()) + assert occryst.beta == pytest.approx(cryst.beta.getValue()) + assert occryst.gamma == pytest.approx(cryst.gamma.getValue()) return def _testMolecule(): # Test position / occupancy - self.assertAlmostEqual(ocmol.X, m.x.getValue()) - self.assertAlmostEqual(ocmol.Y, m.y.getValue()) - self.assertAlmostEqual(ocmol.Z, m.z.getValue()) - self.assertAlmostEqual(ocmol.Occupancy, m.occ.getValue()) + assert ocmol.X == pytest.approx(m.x.getValue()) + assert ocmol.Y == pytest.approx(m.y.getValue()) + assert ocmol.Z == pytest.approx(m.z.getValue()) + assert ocmol.Occupancy == pytest.approx(m.occ.getValue()) # Test orientation - self.assertAlmostEqual(ocmol.Q0, m.q0.getValue()) - self.assertAlmostEqual(ocmol.Q1, m.q1.getValue()) - self.assertAlmostEqual(ocmol.Q2, m.q2.getValue()) - self.assertAlmostEqual(ocmol.Q3, m.q3.getValue()) + assert ocmol.Q0 == pytest.approx(m.q0.getValue()) + assert ocmol.Q1 == pytest.approx(m.q1.getValue()) + assert ocmol.Q2 == pytest.approx(m.q2.getValue()) + assert ocmol.Q3 == pytest.approx(m.q3.getValue()) # Check the atoms thoroughly for i in range(len(ocmol)): oca = ocmol[i] ocsp = oca.GetScatteringPower() a = m.atoms[i] - self.assertEqual(ocsp.GetSymbol(), a.element) - self.assertAlmostEqual(oca.X, a.x.getValue()) - self.assertAlmostEqual(oca.Y, a.y.getValue()) - self.assertAlmostEqual(oca.Z, a.z.getValue()) - self.assertAlmostEqual(oca.Occupancy, a.occ.getValue()) - self.assertAlmostEqual(ocsp.Biso, a.Biso.getValue()) + assert ocsp.GetSymbol() == a.element + assert oca.X == pytest.approx(a.x.getValue()) + assert oca.Y == pytest.approx(a.y.getValue()) + assert oca.Z == pytest.approx(a.z.getValue()) + assert oca.Occupancy == pytest.approx(a.occ.getValue()) + assert ocsp.Biso == pytest.approx(a.Biso.getValue()) return _testCrystal() _testMolecule() - ## Now change some values from ObjCryst + # Now change some values from ObjCryst ocmol[0].X *= 1.1 ocmol[0].Occupancy *= 1.1 ocmol[0].GetScatteringPower().Biso *= 1.1 @@ -197,7 +222,7 @@ def _testMolecule(): _testCrystal() _testMolecule() - ## Now change values from the srfit StructureParSet + # Now change values from the srfit StructureParSet cryst.c60.C44.x.setValue(1.1) cryst.c60.C44.occ.setValue(1.1) cryst.c60.C44.Biso.setValue(1.1) @@ -223,40 +248,14 @@ def testImplicitBondLengthRestraints(self): m.wrapRestraints() # make sure that we have some restraints in the molecule - self.assertTrue(2, len(m._restraints)) + assert 2 == len(m._restraints) # make sure these evaluate to whatver we get from objcryst res0, res1 = m._restraints p0 = set([res0.penalty(), res1.penalty()]) bonds = ocmol.GetBondList() p1 = set([bonds[0].GetLogLikelihood(), bonds[1].GetLogLikelihood()]) - self.assertEqual(p0, p1) - - return - - def testImplicitBondAngleRestraints(self): - """Test the structure with implicit bond angles.""" - occryst = self.occryst - ocmol = self.ocmol - - # Add some bond angles to the molecule - ocmol.AddBondAngle(ocmol[0], ocmol[5], ocmol[8], 1.1, 0.1, 0.1) - ocmol.AddBondAngle(ocmol[0], ocmol[7], ocmol[44], 1.3, 0.1, 0.1) - - # make our crystal - cryst = ObjCrystCrystalParSet("bucky", occryst) - m = cryst.c60 - m.wrapRestraints() - - # make sure that we have some restraints in the molecule - self.assertTrue(2, len(m._restraints)) - - # make sure these evaluate to whatver we get from objcryst - res0, res1 = m._restraints - p0 = set([res0.penalty(), res1.penalty()]) - angles = ocmol.GetBondAngleList() - p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - self.assertEqual(p0, p1) + assert p0 == p1 return @@ -279,14 +278,14 @@ def testImplicitDihedralAngleRestraints(self): m.wrapRestraints() # make sure that we have some restraints in the molecule - self.assertTrue(2, len(m._restraints)) + assert 2 == len(m._restraints) # make sure these evaluate to whatver we get from objcryst res0, res1 = m._restraints p0 = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetDihedralAngleList() p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - self.assertEqual(p0, p1) + assert p0 == p1 return @@ -309,14 +308,14 @@ def testExplicitBondLengthRestraints(self): res1 = m.restrainBondLength(m.atoms[0], m.atoms[7], 3.3, 0.1, 0.1) # make sure that we have some restraints in the molecule - self.assertTrue(2, len(m._restraints)) + assert 2 == len(m._restraints) # make sure these evaluate to whatver we get from objcryst p0 = [res0.penalty(), res1.penalty()] bonds = ocmol.GetBondList() - self.assertEqual(2, len(bonds)) + assert 2 == len(bonds) p1 = [b.GetLogLikelihood() for b in bonds] - self.assertEqual(p0, p1) + assert p0 == p1 return @@ -342,13 +341,13 @@ def testExplicitBondAngleRestraints(self): ) # make sure that we have some restraints in the molecule - self.assertTrue(2, len(m._restraints)) + assert 2 == len(m._restraints) # make sure these evaluate to whatver we get from objcryst p0 = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetBondAngleList() p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - self.assertEqual(p0, p1) + assert p0 == p1 return @@ -370,13 +369,13 @@ def testExplicitDihedralAngleRestraints(self): ) # make sure that we have some restraints in the molecule - self.assertTrue(2, len(m._restraints)) + assert 2 == len(m._restraints) # make sure these evaluate to whatver we get from objcryst p0 = set([res0.penalty(), res1.penalty()]) angles = ocmol.GetDihedralAngleList() p1 = set([angles[0].GetLogLikelihood(), angles[1].GetLogLikelihood()]) - self.assertEqual(p0, p1) + assert p0 == p1 return @@ -405,7 +404,7 @@ def testExplicitBondLengthParameter(self): dd = xyz0 - xyz7 d0 = numpy.dot(dd, dd) ** 0.5 - self.assertAlmostEqual(d0, p1.getValue(), 6) + assert d0 == pytest.approx(p1.getValue(), abs=1e-6) # Record the unit direction of change for later u = dd / d0 @@ -415,7 +414,7 @@ def testExplicitBondLengthParameter(self): p1.setValue(scale * d0) # Verify that it has changed. - self.assertAlmostEqual(scale * d0, p1.getValue()) + assert scale * d0 == pytest.approx(p1.getValue(), abs=1e-6) xyz0a = numpy.array( [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] @@ -430,19 +429,19 @@ def testExplicitBondLengthParameter(self): dda = xyz0a - xyz7a d1 = numpy.dot(dda, dda) ** 0.5 - self.assertAlmostEqual(scale * d0, d1) + assert scale * d0 == pytest.approx(d1, abs=1e-6) # Verify that only the second and third atoms have moved. - self.assertTrue(numpy.array_equal(xyz0, xyz0a)) + assert numpy.array_equal(xyz0, xyz0a) xyz7calc = xyz7 + (1 - scale) * d0 * u for i in range(3): - self.assertAlmostEqual(xyz7a[i], xyz7calc[i], 6) + assert xyz7a[i] == pytest.approx(xyz7calc[i], abs=1e-5) xyz20calc = xyz20 + (1 - scale) * d0 * u for i in range(3): - self.assertAlmostEqual(xyz20a[i], xyz20calc[i], 6) + assert xyz20a[i] == pytest.approx(xyz20calc[i], abs=1e-6) return @@ -480,14 +479,14 @@ def testExplicitBondAngleParameter(self): # Have another atom tag along for the ride p1.addAtoms([a25]) - self.assertAlmostEqual(angle0, p1.getValue(), 6) + assert angle0 == pytest.approx(p1.getValue(), abs=1e-6) # Change the value scale = 1.05 p1.setValue(scale * angle0) # Verify that it has changed. - self.assertAlmostEqual(scale * angle0, p1.getValue(), 6) + assert scale * angle0 == pytest.approx(p1.getValue(), abs=1e-6) xyz0a = numpy.array( [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] @@ -509,14 +508,14 @@ def testExplicitBondAngleParameter(self): angle1 = numpy.arccos(numpy.dot(v1a, v2a) / (d1a * d2a)) - self.assertAlmostEqual(scale * angle0, angle1) + assert scale * angle0 == pytest.approx(angle1, abs=1e-6) # Verify that only the last two atoms have moved. - self.assertTrue(numpy.array_equal(xyz0, xyz0a)) - self.assertTrue(numpy.array_equal(xyz7, xyz7a)) - self.assertFalse(numpy.array_equal(xyz20, xyz20a)) - self.assertFalse(numpy.array_equal(xyz25, xyz25a)) + assert numpy.array_equal(xyz0, xyz0a) + assert numpy.array_equal(xyz7, xyz7a) + assert not numpy.array_equal(xyz20, xyz20a) + assert not numpy.array_equal(xyz25, xyz25a) return @@ -561,14 +560,14 @@ def testExplicitDihedralAngleParameter(self): # Have another atom tag along for the ride p1.addAtoms([a33]) - self.assertAlmostEqual(angle0, p1.getValue(), 6) + assert angle0 == pytest.approx(p1.getValue(), abs=1e-6) # Change the value scale = 1.05 p1.setValue(scale * angle0) # Verify that it has changed. - self.assertAlmostEqual(scale * angle0, p1.getValue(), 6) + assert scale * angle0 == pytest.approx(p1.getValue(), abs=1e-6) xyz0a = numpy.array( [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] @@ -595,34 +594,34 @@ def testExplicitDihedralAngleParameter(self): d123a = numpy.dot(v123a, v123a) ** 0.5 d234a = numpy.dot(v234a, v234a) ** 0.5 angle1 = -numpy.arccos(numpy.dot(v123a, v234a) / (d123a * d234a)) - - self.assertAlmostEqual(scale * angle0, angle1) + assert scale * angle0 == pytest.approx(angle1, abs=1e-6) # Verify that only the last two atoms have moved. - self.assertTrue(numpy.array_equal(xyz0, xyz0a)) - self.assertTrue(numpy.array_equal(xyz7, xyz7a)) - self.assertTrue(numpy.array_equal(xyz20, xyz20a)) - self.assertFalse(numpy.array_equal(xyz25, xyz25a)) - self.assertFalse(numpy.array_equal(xyz33, xyz33a)) + assert numpy.array_equal(xyz0, xyz0a) + assert numpy.array_equal(xyz7, xyz7a) + assert numpy.array_equal(xyz20, xyz20a) + assert not numpy.array_equal(xyz25, xyz25a) + assert not numpy.array_equal(xyz33, xyz33a) return -# End of class TestParameterAdapter - -# ---------------------------------------------------------------------------- - - -@unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) -class TestCreateSpaceGroup(unittest.TestCase): +class TestCreateSpaceGroup: """Test space group creation from pyobjcryst structures. This makes sure that the space groups created by the structure parameter set are correct. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, diffpy_structure_available, pyobjcryst_available): + # shared setup + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + if not pyobjcryst_available: + pytest.skip("pyobjcryst package not available") + global ObjCrystCrystalParSet, spacegroups from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet from diffpy.structure import spacegroups @@ -665,7 +664,7 @@ def xtestCreateSpaceGroup(self): sg = spacegroups.GetSpaceGroup(shn) sgnew = self.getObjCrystParSetSpaceGroup(sg) # print("dbsg: " + repr(self.sgsEquivalent(sg, sgnew))) - self.assertTrue(self.sgsEquivalent(sg, sgnew)) + assert self.sgsEquivalent(sg, sgnew) return diff --git a/tests/test_sgconstraints.py b/tests/test_sgconstraints.py index ac0a96fc..f0696321 100644 --- a/tests/test_sgconstraints.py +++ b/tests/test_sgconstraints.py @@ -17,181 +17,189 @@ import unittest import numpy +import pytest # ---------------------------------------------------------------------------- -class TestSGConstraints(unittest.TestCase): - - @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) - def test_ObjCryst_constrainSpaceGroup(self): - """Make sure that all Parameters are constrained properly. - - This tests constrainSpaceGroup from - diffpy.srfit.structure.sgconstraints, which is performed - automatically when an ObjCrystCrystalParSet is created. - """ - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet - - pi = numpy.pi - - occryst = makeLaMnO3() - stru = ObjCrystCrystalParSet(occryst.GetName(), occryst) - # Make sure we actually create the constraints - stru._constrainSpaceGroup() - # Make the space group parameters individually - stru.sgpars.latpars - stru.sgpars.xyzpars - stru.sgpars.adppars - - # Check the orthorhombic lattice - l = stru.getLattice() - self.assertTrue(l.alpha.const) - self.assertTrue(l.beta.const) - self.assertTrue(l.gamma.const) - self.assertEqual(pi / 2, l.alpha.getValue()) - self.assertEqual(pi / 2, l.beta.getValue()) - self.assertEqual(pi / 2, l.gamma.getValue()) - - self.assertFalse(l.a.const) - self.assertFalse(l.b.const) - self.assertFalse(l.c.const) - self.assertEqual(0, len(l._constraints)) - - # Now make sure the scatterers are constrained properly - scatterers = stru.getScatterers() - la = scatterers[0] - self.assertFalse(la.x.const) - self.assertFalse(la.y.const) - self.assertTrue(la.z.const) - self.assertEqual(0, len(la._constraints)) - - mn = scatterers[1] - self.assertTrue(mn.x.const) - self.assertTrue(mn.y.const) - self.assertTrue(mn.z.const) - self.assertEqual(0, len(mn._constraints)) - - o1 = scatterers[2] - self.assertFalse(o1.x.const) - self.assertFalse(o1.y.const) - self.assertTrue(o1.z.const) - self.assertEqual(0, len(o1._constraints)) - - o2 = scatterers[3] - self.assertFalse(o2.x.const) - self.assertFalse(o2.y.const) - self.assertFalse(o2.z.const) - self.assertEqual(0, len(o2._constraints)) - - # Make sure we can't constrain these - self.assertRaises(ValueError, mn.constrain, mn.x, "y") - self.assertRaises(ValueError, mn.constrain, mn.y, "z") - self.assertRaises(ValueError, mn.constrain, mn.z, "x") - - # Nor can we make them into variables - from diffpy.srfit.fitbase.fitrecipe import FitRecipe - - f = FitRecipe() - self.assertRaises(ValueError, f.addVar, mn.x) - - return - - @unittest.skipUnless(has_structure, _msg_nostructure) - def test_DiffPy_constrainAsSpaceGroup(self): - """Test the constrainAsSpaceGroup function.""" - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup - - stru = makeLaMnO3_P1() - parset = DiffpyStructureParSet("LaMnO3", stru) - - sgpars = constrainAsSpaceGroup( - parset, - "P b n m", - scatterers=parset.getScatterers()[::2], - constrainadps=True, - ) - - # Make sure that the new parameters were created - for par in sgpars: - self.assertNotEqual(None, par) - self.assertNotEqual(None, par.getValue()) - - # Test the unconstrained atoms - for scatterer in parset.getScatterers()[1::2]: - self.assertFalse(scatterer.x.const) - self.assertFalse(scatterer.y.const) - self.assertFalse(scatterer.z.const) - self.assertFalse(scatterer.U11.const) - self.assertFalse(scatterer.U22.const) - self.assertFalse(scatterer.U33.const) - self.assertFalse(scatterer.U12.const) - self.assertFalse(scatterer.U13.const) - self.assertFalse(scatterer.U23.const) - self.assertEqual(0, len(scatterer._constraints)) - - proxied = [p.par for p in sgpars] - - def _consttest(par): - return par.const - - def _constrainedtest(par): - return par.constrained - - def _proxytest(par): - return par in proxied - - def _alltests(par): - return _consttest(par) or _constrainedtest(par) or _proxytest(par) - - for idx, scatterer in enumerate(parset.getScatterers()[::2]): - # Under this scheme, atom 6 is free to vary - test = False - for par in [scatterer.x, scatterer.y, scatterer.z]: - test |= _alltests(par) - self.assertTrue(test) - - test = False - for par in [ - scatterer.U11, - scatterer.U22, - scatterer.U33, - scatterer.U12, - scatterer.U13, - scatterer.U23, - ]: - test |= _alltests(par) - - self.assertTrue(test) - - return - - @unittest.skipUnless(has_structure, _msg_nostructure) - def test_ConstrainAsSpaceGroup_args(self): - """Test the arguments processing of constrainAsSpaceGroup function.""" - from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup - from diffpy.structure.spacegroups import GetSpaceGroup - - stru = makeLaMnO3_P1() - parset = DiffpyStructureParSet("LaMnO3", stru) - sgpars = constrainAsSpaceGroup(parset, "P b n m") - sg = GetSpaceGroup("P b n m") - parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1()) - sgpars2 = constrainAsSpaceGroup(parset2, sg) - list(sgpars) - list(sgpars2) - self.assertEqual(sgpars.names, sgpars2.names) - return - - -# End of class TestSGConstraints - -# Local helper functions ----------------------------------------------------- - - -def makeLaMnO3_P1(): +def test_ObjCryst_constrainSpaceGroup(pyobjcryst_available): + """Make sure that all Parameters are constrained properly. + + This tests constrainSpaceGroup from + diffpy.srfit.structure.sgconstraints, which is performed + automatically when an ObjCrystCrystalParSet is created. + """ + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + + pi = numpy.pi + + occryst = makeLaMnO3() + stru = ObjCrystCrystalParSet(occryst.GetName(), occryst) + # Make sure we actually create the constraints + stru._constrainSpaceGroup() + # Make the space group parameters individually + stru.sgpars.latpars + stru.sgpars.xyzpars + stru.sgpars.adppars + + # Check the orthorhombic lattice + lattice = stru.getLattice() + assert lattice.alpha.const + assert lattice.beta.const + assert lattice.gamma.const + assert pi / 2 == lattice.alpha.getValue() + assert pi / 2 == lattice.beta.getValue() + assert pi / 2 == lattice.gamma.getValue() + + assert not lattice.a.const + assert not lattice.b.const + assert not lattice.c.const + assert 0 == len(lattice._constraints) + + # Now make sure the scatterers are constrained properly + scatterers = stru.getScatterers() + la = scatterers[0] + assert not la.x.const + assert not la.y.const + assert la.z.const + assert 0 == len(la._constraints) + + mn = scatterers[1] + assert mn.x.const + assert mn.y.const + assert mn.z.const + assert 0 == len(mn._constraints) + + o1 = scatterers[2] + assert not o1.x.const + assert not o1.y.const + assert o1.z.const + assert 0 == len(o1._constraints) + + o2 = scatterers[3] + assert not o2.x.const + assert not o2.y.const + assert not o2.z.const + assert 0 == len(o2._constraints) + + # Make sure we can't constrain these + with pytest.raises(ValueError): + mn.constrain(mn.x, "y") + + with pytest.raises(ValueError): + mn.constrain(mn.y, "z") + + with pytest.raises(ValueError): + mn.constrain(mn.z, "x") + + # Nor can we make them into variables + from diffpy.srfit.fitbase.fitrecipe import FitRecipe + + f = FitRecipe() + with pytest.raises(ValueError): + f.addVar(mn.x) + + return + + +def test_DiffPy_constrainAsSpaceGroup(datafile, pyobjcryst_available): + """Test the constrainAsSpaceGroup function.""" + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup + + stru = makeLaMnO3_P1(datafile) + parset = DiffpyStructureParSet("LaMnO3", stru) + + sgpars = constrainAsSpaceGroup( + parset, + "P b n m", + scatterers=parset.getScatterers()[::2], + constrainadps=True, + ) + + # Make sure that the new parameters were created + for par in sgpars: + assert par is not None + assert par.getValue() is not None + + # Test the unconstrained atoms + for scatterer in parset.getScatterers()[1::2]: + assert not scatterer.x.const + assert not scatterer.y.const + assert not scatterer.z.const + assert not scatterer.U11.const + assert not scatterer.U22.const + assert not scatterer.U33.const + assert not scatterer.U12.const + assert not scatterer.U13.const + assert not scatterer.U23.const + assert 0 == len(scatterer._constraints) + + proxied = [p.par for p in sgpars] + + def _consttest(par): + return par.const + + def _constrainedtest(par): + return par.constrained + + def _proxytest(par): + return par in proxied + + def _alltests(par): + return _consttest(par) or _constrainedtest(par) or _proxytest(par) + + for idx, scatterer in enumerate(parset.getScatterers()[::2]): + # Under this scheme, atom 6 is free to vary + test = False + for par in [scatterer.x, scatterer.y, scatterer.z]: + test |= _alltests(par) + assert test + + test = False + for par in [ + scatterer.U11, + scatterer.U22, + scatterer.U33, + scatterer.U12, + scatterer.U13, + scatterer.U23, + ]: + test |= _alltests(par) + + assert test + + return + + +def test_ConstrainAsSpaceGroup_args(pyobjcryst_available, datafile): + """Test the arguments processing of constrainAsSpaceGroup function.""" + if not pyobjcryst_available: + pytest.skip("pyobjcrysta package not available") + + from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup + from diffpy.structure.spacegroups import GetSpaceGroup + + stru = makeLaMnO3_P1(datafile) + parset = DiffpyStructureParSet("LaMnO3", stru) + sgpars = constrainAsSpaceGroup(parset, "P b n m") + sg = GetSpaceGroup("P b n m") + parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1(datafile)) + sgpars2 = constrainAsSpaceGroup(parset2, sg) + list(sgpars) + list(sgpars2) + assert sgpars.names == sgpars2.names + return + + +def makeLaMnO3_P1(datafile): from diffpy.structure import Structure stru = Structure() From cb65f5463b5212db2ef3188501adf8b11a2a9403 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Jun 2025 08:22:18 -0400 Subject: [PATCH 38/45] tests: visitors and speed. added sympy requirement to test --- requirements/test.txt | 1 + tests/test_speed.py | 33 ++++++++--------- tests/test_visitors.py | 84 ++++++++++++++++++++++++------------------ 3 files changed, 65 insertions(+), 53 deletions(-) diff --git a/requirements/test.txt b/requirements/test.txt index a7277865..c31d2af1 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,3 +4,4 @@ codecov coverage pytest-cov pytest-env +sympy diff --git a/tests/test_speed.py b/tests/test_speed.py index 19af219b..6bc75014 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -14,8 +14,6 @@ ############################################################################## """Tests for refinableobj module.""" -from __future__ import print_function - import random import numpy @@ -23,16 +21,14 @@ import diffpy.srfit.equation.literals as literals import diffpy.srfit.equation.visitors as visitors -from .utils import _makeArgs - x = numpy.arange(0, 20, 0.05) -def makeLazyEquation(): +def makeLazyEquation(make_args): """Make a lazy equation and see how fast it is.""" # Make some variables - v1, v2, v3, v4, v5, v6, v7 = _makeArgs(7) + v1, v2, v3, v4, v5, v6, v7 = make_args(7) # Make some operations mult = literals.MultiplicationOperator() @@ -463,14 +459,17 @@ def profileTest(): return -if __name__ == "__main__": - for i in range(1, 13): - speedTest2(i) - """ - for i in range(1, 9): - weightedTest(i) - """ - """From diffpy.srfit.equation.builder import EquationFactory import random - import cProfile cProfile.run('profileTest()', 'prof') import pstats p = - pstats.Stats('prof') p.strip_dirs() p.sort_stats('time') p.print_stats(10) - profileTest()""" +# if __name__ == "__main__": +# for i in range(1, 13): +# speedTest2(i) +# """ +# for i in range(1, 9): +# weightedTest(i) +# """ +# """From diffpy.srfit.equation.builder import +# EquationFactory import random +# import cProfile cProfile.run('profileTest()', 'prof') +# import pstats p = +# pstats.Stats('prof') p.strip_dirs() p.sort_stats('time') +# p.print_stats(10) +# profileTest()""" diff --git a/tests/test_visitors.py b/tests/test_visitors.py index b2c7bf06..546edaaa 100644 --- a/tests/test_visitors.py +++ b/tests/test_visitors.py @@ -16,17 +16,22 @@ import unittest +import pytest + import diffpy.srfit.equation.literals as literals import diffpy.srfit.equation.visitors as visitors -class TestValidator(unittest.TestCase): +class TestValidator: + @pytest.fixture(autouse=True) + def setup(self, make_args): + self.make_args = make_args def testSimpleFunction(self): """Test a simple function.""" # Make some variables - v1, v2, v3, v4 = _makeArgs(4) + v1, v2, v3, v4 = self.make_args(4) # Make some operations mult = literals.MultiplicationOperator() @@ -49,25 +54,25 @@ def testSimpleFunction(self): # Now validate validator = visitors.Validator() mult.identify(validator) - self.assertEqual(4, len(validator.errors)) + assert 4 == len(validator.errors) # Fix the equation minus.addLiteral(v3) validator.reset() mult.identify(validator) - self.assertEqual(3, len(validator.errors)) + assert 3 == len(validator.errors) # Fix the name of plus plus.name = "add" validator.reset() mult.identify(validator) - self.assertEqual(2, len(validator.errors)) + assert 2 == len(validator.errors) # Fix the symbol of plus plus.symbol = "+" validator.reset() mult.identify(validator) - self.assertEqual(1, len(validator.errors)) + assert 1 == len(validator.errors) # Fix the operation of plus import numpy @@ -75,24 +80,27 @@ def testSimpleFunction(self): plus.operation = numpy.add validator.reset() mult.identify(validator) - self.assertEqual(0, len(validator.errors)) + assert 0 == len(validator.errors) # Add another literal to minus minus.addLiteral(v1) validator.reset() mult.identify(validator) - self.assertEqual(1, len(validator.errors)) + assert 1 == len(validator.errors) return -class TestArgFinder(unittest.TestCase): +class TestArgFinder: + @pytest.fixture(autouse=True) + def setup(self, make_args): + self.make_args = make_args def testSimpleFunction(self): """Test a simple function.""" # Make some variables - v1, v2, v3, v4 = _makeArgs(4) + v1, v2, v3, v4 = self.make_args(4) # Make some operations mult = literals.MultiplicationOperator() @@ -116,33 +124,36 @@ def testSimpleFunction(self): # now get the args args = visitors.getArgs(mult) - self.assertEqual(4, len(args)) - self.assertTrue(v1 in args) - self.assertTrue(v2 in args) - self.assertTrue(v3 in args) - self.assertTrue(v4 in args) + assert 4 == len(args) + assert v1 in args + assert v2 in args + assert v3 in args + assert v4 in args return def testArg(self): """Test just an Argument equation.""" # Make some variables - v1 = _makeArgs(1)[0] + v1 = self.make_args(1)[0] args = visitors.getArgs(v1) - self.assertEqual(1, len(args)) - self.assertTrue(args[0] is v1) + assert 1 == len(args) + assert args[0] == v1 return -class TestSwapper(unittest.TestCase): +class TestSwapper: + @pytest.fixture(autouse=True) + def setup(self, make_args): + self.make_args = make_args def testSimpleFunction(self): """Test a simple function.""" # Make some variables - v1, v2, v3, v4, v5 = _makeArgs(5) + v1, v2, v3, v4, v5 = self.make_args(5) # Make some operations mult = literals.MultiplicationOperator() @@ -166,43 +177,44 @@ def testSimpleFunction(self): v5.setValue(5) # Evaluate - self.assertEqual(8, mult.value) + assert 8 == mult.value # Now swap an argument visitors.swap(mult, v2, v5) # Check that the operator value is invalidated - self.assertTrue(mult._value is None) - self.assertFalse(v2.hasObserver(minus._flush)) - self.assertTrue(v5.hasObserver(minus._flush)) + assert mult._value is None + assert not v2.hasObserver(minus._flush) + assert v5.hasObserver(minus._flush) # now get the args args = visitors.getArgs(mult) - self.assertEqual(4, len(args)) - self.assertTrue(v1 in args) - self.assertTrue(v2 not in args) - self.assertTrue(v3 in args) - self.assertTrue(v4 in args) - self.assertTrue(v5 in args) + assert 4 == len(args) + assert v1 in args + assert v2 not in args + assert v3 in args + assert v4 in args + assert v5 in args # Re-evaluate (1+3)*(4-5) = -4 - self.assertEqual(-4, mult.value) + assert -4 == mult.value # Swap out the "-" operator plus2 = literals.AdditionOperator() visitors.swap(mult, minus, plus2) - self.assertTrue(mult._value is None) - self.assertFalse(minus.hasObserver(mult._flush)) - self.assertTrue(plus2.hasObserver(mult._flush)) + assert mult._value is None + assert not minus.hasObserver(mult._flush) + assert plus2.hasObserver(mult._flush) # plus2 has no arguments yet. Verify this. - self.assertRaises(TypeError, mult.getValue) + with pytest.raises(TypeError): + mult.getValue() # Add the arguments to plus2. plus2.addLiteral(v4) plus2.addLiteral(v5) # Re-evaluate (1+3)*(4+5) = 36 - self.assertEqual(36, mult.value) + assert 36 == mult.value return From 9046255262621c429f4de7f632a0ff6e2717e6d6 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Jun 2025 09:29:19 -0400 Subject: [PATCH 39/45] flake8: equation --- src/diffpy/__init__.py | 3 +- src/diffpy/srfit/__init__.py | 3 +- src/diffpy/srfit/equation/builder.py | 55 ++++++++++--------- src/diffpy/srfit/equation/equationmod.py | 2 +- .../srfit/equation/literals/operators.py | 6 +- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 406751a6..3254c0a6 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -4,7 +4,8 @@ # (c) 2008-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Chris Farrow and Billinge Group members and community contributors. +# File coded by: Chris Farrow and Billinge Group members and community +# contributors. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.srfit/graphs/contributors diff --git a/src/diffpy/srfit/__init__.py b/src/diffpy/srfit/__init__.py index 79dc93c5..0374fdcf 100644 --- a/src/diffpy/srfit/__init__.py +++ b/src/diffpy/srfit/__init__.py @@ -4,7 +4,8 @@ # (c) 2008-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Christopher Farrow, Pavol Juhas, and members of the Billinge Group. +# File coded by: Christopher Farrow, Pavol Juhas, and members of the +# Billinge Group. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.srfit/graphs/contributors diff --git a/src/diffpy/srfit/equation/builder.py b/src/diffpy/srfit/equation/builder.py index d19a17a4..dcd58ca2 100644 --- a/src/diffpy/srfit/equation/builder.py +++ b/src/diffpy/srfit/equation/builder.py @@ -75,6 +75,17 @@ > beq = c*f(a,b) > eq = beq.makeEquation() """ +import inspect +import numbers +import token +import tokenize + +import numpy +import six + +import diffpy.srfit.equation.literals as literals +from diffpy.srfit.equation.equationmod import Equation +from diffpy.srfit.equation.literals.literal import Literal __all__ = [ "EquationFactory", @@ -95,17 +106,6 @@ _builders = {} -import inspect -import numbers - -import numpy -import six - -import diffpy.srfit.equation.literals as literals -from diffpy.srfit.equation.equationmod import Equation -from diffpy.srfit.equation.literals.literal import Literal - - class EquationFactory(object): """A Factory for equations. @@ -113,7 +113,8 @@ class EquationFactory(object): factory, indexed by name. newargs -- A set of new arguments created by makeEquation. This is redefined whenever makeEquation is called. - equations -- Set of equations that have been built by the EquationFactory. + equations -- Set of equations that have been built by the + EquationFactory. """ symbols = ("+", "-", "*", "/", "**", "%", "|") @@ -213,10 +214,10 @@ def registerFunction(self, name, func, argnames): if n not in self.builders: self.registerConstant(n, 0) opbuilder = wrapFunction(name, func, len(argnames)) - for n in argnames: - b = self.builders[n] - l = b.literal - opbuilder.literal.addLiteral(l) + for argname in argnames: + builder = self.builders[argname] + argliteral = builder.literal + opbuilder.literal.addLiteral(argliteral) return self.registerBuilder(name, opbuilder) @@ -341,9 +342,6 @@ def _getUndefinedArgs(self, eqstr): Raises SyntaxError if the equation string uses invalid syntax. """ - import token - import tokenize - interface = six.StringIO(eqstr).readline # output is an iterator. Each entry (token) is a 5-tuple # token[0] = token type @@ -512,7 +510,7 @@ def __neg__(self): return self.__evalUnary(literals.NegationOperator) -## These are used by the class. +# These are used by the class. class ArgumentBuilder(BaseBuilder): @@ -681,15 +679,18 @@ def __wrapSrFitOperators(): instances in the module namespace.""" opmod = literals.operators excluded_types = set((opmod.CustomOperator, opmod.UFuncOperator)) + # check if opmod member should be wrapped as OperatorBuilder - is_exported_type = lambda cls: ( - inspect.isclass(cls) - and issubclass(cls, opmod.Operator) - and not inspect.isabstract(cls) - and not cls in excluded_types - ) + def _is_exported_type(cls): + return ( + inspect.isclass(cls) + and issubclass(cls, opmod.Operator) + and not inspect.isabstract(cls) + and cls not in excluded_types + ) + # create OperatorBuilder objects - for nm, opclass in inspect.getmembers(opmod, is_exported_type): + for nm, opclass in inspect.getmembers(opmod, _is_exported_type): op = opclass() assert op.name, "Unnamed Operator should never appear here." _builders[op.name] = OperatorBuilder(op.name, op) diff --git a/src/diffpy/srfit/equation/equationmod.py b/src/diffpy/srfit/equation/equationmod.py index 091f7a55..2b9c116e 100644 --- a/src/diffpy/srfit/equation/equationmod.py +++ b/src/diffpy/srfit/equation/equationmod.py @@ -117,7 +117,7 @@ def __getattr__(self, name): """Gives access to the Arguments as attributes.""" # Avoid infinite loop on argdict lookup. argdict = object.__getattribute__(self, "argdict") - if not name in argdict: + if name not in argdict: raise AttributeError("No argument named '%s' here" % name) return argdict[name] diff --git a/src/diffpy/srfit/equation/literals/operators.py b/src/diffpy/srfit/equation/literals/operators.py index cfe612e9..1f26c2e6 100644 --- a/src/diffpy/srfit/equation/literals/operators.py +++ b/src/diffpy/srfit/equation/literals/operators.py @@ -122,7 +122,7 @@ def addLiteral(self, literal): def getValue(self): """Get or evaluate the value of the operator.""" if self._value is None: - vals = [l.value for l in self.args] + vals = [arg.value for arg in self.args] self._value = self.operation(*vals) return self._value @@ -135,8 +135,8 @@ def _loopCheck(self, literal): # Check to see if I am a dependency of the literal. if hasattr(literal, "args"): - for l in literal.args: - self._loopCheck(l) + for lit_arg in literal.args: + self._loopCheck(lit_arg) return From 09a11ee422c95caed9fad39dd160e9b5ea67c411 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 25 Jun 2025 11:43:02 -0400 Subject: [PATCH 40/45] removed unused fixture from conftest.py --- tests/conftest.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index bd53d38d..4b8af955 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,7 @@ import importlib.resources -import json import logging import sys from functools import lru_cache -from pathlib import Path import pytest import six @@ -86,21 +84,6 @@ def pyobjcryst_available(): return has_pyobjcryst() -@pytest.fixture(scope="session") -def user_filesystem(tmp_path): - base_dir = Path(tmp_path) - home_dir = base_dir / "home_dir" - home_dir.mkdir(parents=True, exist_ok=True) - cwd_dir = base_dir / "cwd_dir" - cwd_dir.mkdir(parents=True, exist_ok=True) - - home_config_data = {"username": "home_username", "email": "home@email.com"} - with open(home_dir / "diffpyconfig.json", "w") as f: - json.dump(home_config_data, f) - - yield tmp_path - - @pytest.fixture(scope="session") def datafile(): """Fixture to load a test data file from the testdata package directory.""" From 5980b0083d52e47007e3b8e6d34692592709d7a3 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 26 Jun 2025 07:36:09 -0400 Subject: [PATCH 41/45] style: line lengths in src --- src/diffpy/srfit/fitbase/fithook.py | 8 ++- src/diffpy/srfit/fitbase/fitresults.py | 62 ++++++++++---------- src/diffpy/srfit/fitbase/parameter.py | 21 +++---- src/diffpy/srfit/fitbase/profile.py | 4 +- src/diffpy/srfit/fitbase/profilegenerator.py | 2 +- src/diffpy/srfit/fitbase/profileparser.py | 3 +- src/diffpy/srfit/fitbase/recipeorganizer.py | 10 +++- src/diffpy/srfit/interface/__init__.py | 12 ++-- src/diffpy/srfit/pdf/pdfparser.py | 8 +-- src/diffpy/srfit/sas/sasparser.py | 8 +-- src/diffpy/srfit/sas/sasprofile.py | 4 +- src/diffpy/srfit/structure/__init__.py | 4 +- src/diffpy/srfit/structure/objcrystparset.py | 13 ++-- src/diffpy/srfit/structure/sgconstraints.py | 2 +- src/diffpy/srfit/version.py | 3 +- 15 files changed, 89 insertions(+), 75 deletions(-) diff --git a/src/diffpy/srfit/fitbase/fithook.py b/src/diffpy/srfit/fitbase/fithook.py index 35e775f6..01bb2652 100644 --- a/src/diffpy/srfit/fitbase/fithook.py +++ b/src/diffpy/srfit/fitbase/fithook.py @@ -144,13 +144,17 @@ def postcall(self, recipe, chiv): print("Variables") vnames = recipe.getNames() vals = recipe.getValues() - byname = lambda nv: sortKeyForNumericString(nv[0]) - items = sorted(zip(vnames, vals), key=byname) + # byname = _byname() + items = sorted(zip(vnames, vals), key=_byname) for name, val in items: print(" %s = %f" % (name, val)) return +def _byname(nv): + return sortKeyForNumericString(nv[0]) + + # End class PrintFitHook diff --git a/src/diffpy/srfit/fitbase/fitresults.py b/src/diffpy/srfit/fitbase/fitresults.py index 26c32f5b..12c15120 100644 --- a/src/diffpy/srfit/fitbase/fitresults.py +++ b/src/diffpy/srfit/fitbase/fitresults.py @@ -108,8 +108,8 @@ def __init__(self, recipe, update=True, showfixed=True, showcon=False): def update(self): """Update the results according to the current state of the recipe.""" - ## Note that the order of these operations are chosen to reduce - ## computation time. + # Note that the order of these operations are chosen to reduce + # computation time. recipe = self.recipe @@ -173,8 +173,8 @@ def _calculateCovariance(self): self.cov = numpy.dot(vh.T.conj() / s**2, vh) except numpy.linalg.LinAlgError: self.messages.append("Cannot compute covariance matrix.") - l = len(self.varvals) - self.cov = numpy.zeros((l, l), dtype=float) + lvarvals = len(self.varvals) + self.cov = numpy.zeros((lvarvals, lvarvals), dtype=float) return def _calculateJacobian(self): @@ -324,17 +324,19 @@ def formatResults(self, header="", footer="", update=False): lines.append(header) if not certain: - l = "Some quantities invalid due to missing profile uncertainty" - if not l in self.messages: - self.messages.append(l) + err_msg = ( + "Some quantities invalid due to missing profile uncertainty" + ) + if err_msg not in self.messages: + self.messages.append(err_msg) lines.extend(self.messages) - ## Overall results - l = "Overall" + # Overall results + err_msg = "Overall" if not certain: - l += " (Chi2 and Reduced Chi2 invalid)" - lines.append(l) + err_msg += " (Chi2 and Reduced Chi2 invalid)" + lines.append(err_msg) lines.append(_DASHEDLINE) formatstr = "%-14s %.8f" lines.append(formatstr % ("Residual", self.residual)) @@ -346,16 +348,16 @@ def formatResults(self, header="", footer="", update=False): lines.append(formatstr % ("Reduced Chi2", self.rchi2)) lines.append(formatstr % ("Rw", self.rw)) - ## Per-FitContribution results + # Per-FitContribution results if len(self.conresults) > 1: keys = list(self.conresults.keys()) keys.sort(key=numstr) lines.append("") - l = "Contributions" + err_msg = "Contributions" if not certain: - l += " (Chi2 and Reduced Chi2 invalid)" - lines.append(l) + err_msg += " (Chi2 and Reduced Chi2 invalid)" + lines.append(err_msg) lines.append(_DASHEDLINE) formatstr = "%-10s %-42.8f" for name in keys: @@ -368,14 +370,14 @@ def formatResults(self, header="", footer="", update=False): lines.append(formatstr % ("Chi2", res.chi2)) lines.append(formatstr % ("Rw", res.rw)) - ## The variables + # The variables if self.varnames: lines.append("") - l = "Variables" + err_msg = "Variables" if not certain: - m = "Uncertainties invalid" - l += " (%s)" % m - lines.append(l) + err_msg2 = "Uncertainties invalid" + err_msg += " (%s)" % err_msg2 + lines.append(err_msg) lines.append(_DASHEDLINE) varnames = self.varnames @@ -407,13 +409,13 @@ def formatResults(self, header="", footer="", update=False): varlines.sort() lines.extend(varlines) - ## The constraints + # The constraints if self.connames and self.showcon: lines.append("") - l = "Constrained Parameters" + err_msg = "Constrained Parameters" if not certain: - l += " (Uncertainties invalid)" - lines.append(l) + err_msg += " (Uncertainties invalid)" + lines.append(err_msg) lines.append(_DASHEDLINE) w = 0 @@ -436,13 +438,13 @@ def formatResults(self, header="", footer="", update=False): val, unc = vals[name] lines.append(formatstr % (name, val, unc)) - ## Variable correlations + # Variable correlations lines.append("") corint = int(corrmin * 100) - l = "Variable Correlations greater than %i%%" % corint + err_msg = "Variable Correlations greater than %i%%" % corint if not certain: - l += " (Correlations invalid)" - lines.append(l) + err_msg += " (Correlations invalid)" + lines.append(err_msg) lines.append(_DASHEDLINE) tup = [] cornames = [] @@ -563,8 +565,8 @@ def __init__(self, con, weight, fitres): def _init(self, con, weight, fitres): """Initialize the attributes, for real.""" - ## Note that the order of these operations is chosen to reduce - ## computation time. + # Note that the order of these operations is chosen to reduce + # computation time. if con.profile is None: return diff --git a/src/diffpy/srfit/fitbase/parameter.py b/src/diffpy/srfit/fitbase/parameter.py index 21625b6d..e9e6cf8b 100644 --- a/src/diffpy/srfit/fitbase/parameter.py +++ b/src/diffpy/srfit/fitbase/parameter.py @@ -253,17 +253,18 @@ def __init__(self, name, obj, getter=None, setter=None, attr=None): name -- The name of this Parameter. obj -- The object to be wrapped. getter -- The unbound function that can be used to access the - attribute containing the parameter value. getter(obj) should - return the Parameter value. If getter is None (default), - it is assumed that an attribute is accessed via attr. If - attr is also specified, then the Parameter value will be - accessed via getter(obj, attr). - setter -- The unbound function that can be used to modify the - attribute containing the parameter value. setter(obj, value) - should set the attribute to the passed value. If setter is - None (default), it is assumed that an attribute is accessed + attribute containing the parameter value. getter(obj) + should return the Parameter value. If getter is None + (default), it is assumed that an attribute is accessed via attr. If attr is also specified, then the Parameter - value will be set via setter(obj, attr, value). + value will be accessed via getter(obj, attr). + setter -- The unbound function that can be used to modify the + attribute containing the parameter value. + setter(obj, value) should set the attribute to the + passed value. If setter is None (default), it is assumed + that an attribute is accessed via attr. If attr is also + specified, then the Parameter value will be set via + setter(obj, attr, value). attr -- The name of the attribute that contains the value of the parameter. If attr is None (default), then both getter and setter must be specified. diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index 0da465fb..da0e04ce 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -47,8 +47,8 @@ class Profile(Observable, Validatable): xobs -- Read-only property of _xobs. _yobs -- A numpy array of the observed signal (default None) yobs -- Read-only property of _yobs. - _dyobs -- A numpy array of the uncertainty of the observed signal (default - None, optional). + _dyobs -- A numpy array of the uncertainty of the observed signal + (default None, optional). dyobs -- Read-only property of _dyobs. x -- A numpy array of the calculated independent variable (default None, property for xpar accessors). diff --git a/src/diffpy/srfit/fitbase/profilegenerator.py b/src/diffpy/srfit/fitbase/profilegenerator.py index c1f0d2e7..2df9916e 100644 --- a/src/diffpy/srfit/fitbase/profilegenerator.py +++ b/src/diffpy/srfit/fitbase/profilegenerator.py @@ -111,7 +111,7 @@ def __call__(self, x): """ return x - ## No need to overload anything below here + # No need to overload anything below here def operation(self): """Evaluate the profile. diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index c38af1a0..fd2d8d04 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -34,7 +34,8 @@ class ProfileParser(object): _format -- Name of the data format that this parses (string, default ""). The format string is a unique identifier for the data format handled by the parser. - _banks -- The data from each bank. Each bank contains a (x, y, dx, dy) + _banks -- The data from each bank. Each bank contains a (x, y, dx, + dy) tuple: x -- A numpy array containing the independent variable read from the file. diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index e4902734..b9127201 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -510,7 +510,7 @@ def registerFunction(self, f, name=None, argnames=None): self._eqfactory.registerOperator(name, f) return f - #### Introspection code + # Introspection code if name is None or argnames is None: import inspect @@ -547,7 +547,7 @@ def registerFunction(self, f, name=None, argnames=None): argnames = list(fncode.co_varnames) argnames = argnames[offset : fncode.co_argcount] - #### End introspection code + # End introspection code # Make missing Parameters for pname in argnames: @@ -741,7 +741,7 @@ def clearConstraints(self, recurse=False): self.unconstrain(*self._constraints) if recurse: - f = lambda m: hasattr(m, "clearConstraints") + _constraint_clearer = lambda m: hasattr(m, "clearConstraints") for m in filter(f, self._iterManaged()): m.clearConstraints(recurse) return @@ -1057,3 +1057,7 @@ def equationFromString( factory.deRegisterBuilder(name) return eq + + +def _constraint_clearer(msg): + return hasattr(msg, "clearConstraints") diff --git a/src/diffpy/srfit/interface/__init__.py b/src/diffpy/srfit/interface/__init__.py index a611f118..3157c1ec 100644 --- a/src/diffpy/srfit/interface/__init__.py +++ b/src/diffpy/srfit/interface/__init__.py @@ -20,16 +20,14 @@ """ -from diffpy.srfit.interface.interface import ParameterInterface +from diffpy.srfit.interface.interface import ( + FitRecipeInterface, + ParameterInterface, + RecipeOrganizerInterface, +) _parameter_interface = ParameterInterface - -from diffpy.srfit.interface.interface import RecipeOrganizerInterface - _recipeorganizer_interface = RecipeOrganizerInterface - -from diffpy.srfit.interface.interface import FitRecipeInterface - _fitrecipe_interface = FitRecipeInterface # End of file diff --git a/src/diffpy/srfit/pdf/pdfparser.py b/src/diffpy/srfit/pdf/pdfparser.py index 9eb5595f..d3a617b9 100644 --- a/src/diffpy/srfit/pdf/pdfparser.py +++ b/src/diffpy/srfit/pdf/pdfparser.py @@ -38,15 +38,15 @@ class PDFParser(ProfileParser): _format -- Name of the data format that this parses (string, default ""). The format string is a unique identifier for the data format handled by the parser. - _banks -- The data from each bank. Each bank contains a (x, y, dx, dy) - tuple: + _banks -- The data from each bank. Each bank contains a + (x, y, dx, dy) tuple: x -- A numpy array containing the independent variable read from the file. y -- A numpy array containing the profile from the file. dx -- A numpy array containing the uncertainty in x - read from the file. This is 0 if the uncertainty - cannot be read. + read from the file. This is 0 if the + uncertainty cannot be read. dy -- A numpy array containing the uncertainty read from the file. This is 0 if the uncertainty cannot be read. diff --git a/src/diffpy/srfit/sas/sasparser.py b/src/diffpy/srfit/sas/sasparser.py index 69f94904..113648e9 100644 --- a/src/diffpy/srfit/sas/sasparser.py +++ b/src/diffpy/srfit/sas/sasparser.py @@ -36,15 +36,15 @@ class SASParser(ProfileParser): _format -- Name of the data format that this parses (string, default ""). The format string is a unique identifier for the data format handled by the parser. - _banks -- The data from each bank. Each bank contains a (x, y, dx, dy) - tuple: + _banks -- The data from each bank. Each bank contains a + (x, y, dx, dy) tuple: x -- A numpy array containing the independent variable read from the file. y -- A numpy array containing the profile from the file. dx -- A numpy array containing the uncertainty in x - read from the file. This is 0 if the uncertainty - cannot be read. + read from the file. This is 0 if the + uncertainty cannot be read. dy -- A numpy array containing the uncertainty read from the file. This is 0 if the uncertainty cannot be read. diff --git a/src/diffpy/srfit/sas/sasprofile.py b/src/diffpy/srfit/sas/sasprofile.py index b18dfa76..517b2de1 100644 --- a/src/diffpy/srfit/sas/sasprofile.py +++ b/src/diffpy/srfit/sas/sasprofile.py @@ -36,8 +36,8 @@ class SASProfile(Profile): xobs -- Read-only property of _xobs. _yobs -- A numpy array of the observed signal (default None) yobs -- Read-only property of _yobs. - _dyobs -- A numpy array of the uncertainty of the observed signal (default - None, optional). + _dyobs -- A numpy array of the uncertainty of the observed signal + (default None, optional). dyobs -- Read-only property of _dyobs. x -- A numpy array of the calculated independent variable (default None, property for xpar accessors). diff --git a/src/diffpy/srfit/structure/__init__.py b/src/diffpy/srfit/structure/__init__.py index d1a1ecd1..b48bb60d 100644 --- a/src/diffpy/srfit/structure/__init__.py +++ b/src/diffpy/srfit/structure/__init__.py @@ -16,6 +16,8 @@ interface and automatic structure constraint generation from space group information.""" +from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup + def struToParameterSet(name, stru): """Creates a ParameterSet from an structure. @@ -51,8 +53,6 @@ def struToParameterSet(name, stru): raise TypeError("Unadaptable structure format") -from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup - # silence pyflakes checker assert constrainAsSpaceGroup diff --git a/src/diffpy/srfit/structure/objcrystparset.py b/src/diffpy/srfit/structure/objcrystparset.py index 3dab67f2..5feef034 100644 --- a/src/diffpy/srfit/structure/objcrystparset.py +++ b/src/diffpy/srfit/structure/objcrystparset.py @@ -378,8 +378,10 @@ def restrainBondLength( by the unrestrained point-average chi^2 (chi^2/numpoints) (default False) - Returns the ObjCrystBondLengthRestraint object for use with the 'unrestrain' - method. + Returns + ------- + The ObjCrystBondLengthRestraint object for use with the + 'unrestrain' method. """ res = ObjCrystBondLengthRestraint( atom1, atom2, length, sigma, delta, scaled @@ -586,7 +588,8 @@ def addDihedralAngleParameter( ObjCrystMoleculeParSet that can be adjusted during the fit. name -- The name of the ObjCrystDihedralAngleParameter. - atom1 -- The first atom (ObjCrystMolAtomParSet) in the dihderal angle + atom1 -- The first atom (ObjCrystMolAtomParSet) in the dihderal + angle. atom2 -- The second (central) atom (ObjCrystMolAtomParSet) in the dihderal angle atom3 -- The third (central) atom (ObjCrystMolAtomParSet) in the @@ -1486,8 +1489,8 @@ def _createSpaceGroup(sgobjcryst): sgobjcryst -- A pyobjcryst.spacegroup.SpaceGroup instance. This uses the actual space group operations from the - pyobjcryst.spacegroup.SpaceGroup instance so there is no ambiguity about - the actual space group. + pyobjcryst.spacegroup.SpaceGroup instance so there is no ambiguity + about the actual space group. """ import copy diff --git a/src/diffpy/srfit/structure/sgconstraints.py b/src/diffpy/srfit/structure/sgconstraints.py index c3b0d005..68cb7734 100644 --- a/src/diffpy/srfit/structure/sgconstraints.py +++ b/src/diffpy/srfit/structure/sgconstraints.py @@ -362,7 +362,7 @@ def _clearConstraints(self): lattice.unconstrain(par) par.setConst(False) - ## Clear ADPs + # Clear ADPs if self.constrainadps: for scatterer in scatterers: if isosymbol: diff --git a/src/diffpy/srfit/version.py b/src/diffpy/srfit/version.py index a0caae47..f0833049 100644 --- a/src/diffpy/srfit/version.py +++ b/src/diffpy/srfit/version.py @@ -4,7 +4,8 @@ # (c) 2008-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Christopher Farrow, Pavol Juhas, and members of the Billinge Group. +# File coded by: Christopher Farrow, Pavol Juhas, and members of the +# Billinge Group. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.srfit/graphs/contributors From a0eeed1bf84d6e6d73361dbaff7ee0bdcc666a39 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 26 Jun 2025 08:54:44 -0400 Subject: [PATCH 42/45] lambdas in tests --- tests/test_builder.py | 24 +++++----- tests/test_characteristicfunctions.py | 5 +- tests/test_parameter.py | 66 +++++++++++++-------------- tests/utils.py | 40 ---------------- 4 files changed, 47 insertions(+), 88 deletions(-) delete mode 100644 tests/utils.py diff --git a/tests/test_builder.py b/tests/test_builder.py index dfabc8fb..4045dd6d 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -145,8 +145,6 @@ def g2(v1): def testParseEquation(noObserversInGlobalBuilders): - from numpy import array_equal, divide, e, sin, sqrt - factory = builder.EquationFactory() # Scalar equation @@ -159,8 +157,7 @@ def testParseEquation(noObserversInGlobalBuilders): eq.x.setValue(x) eq.B.setValue(B) eq.C.setValue(C) - f = lambda A, x, B, C: A * sin(0.5 * x) + divide(B, C) - assert array_equal(eq(), f(A, x, B, C)) + assert numpy.array_equal(eq(), f_equation(A, x, B, C)) # Make sure that the arguments of eq are listed in the order in which # they appear in the equations. @@ -172,9 +169,7 @@ def testParseEquation(noObserversInGlobalBuilders): sigma = 0.1 eq.x.setValue(x) eq.sigma.setValue(sigma) - f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) - assert numpy.allclose(eq(), f(x, sigma)) - + assert numpy.allclose(eq(), gaussian_test(x, sigma)) assert eq.args == [eq.x, eq.sigma] # Equation with constants @@ -182,13 +177,13 @@ def testParseEquation(noObserversInGlobalBuilders): eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") assert "sigma" in eq.argdict assert "x" not in eq.argdict - assert numpy.allclose(eq(sigma=sigma), f(x, sigma)) + assert numpy.allclose(eq(sigma=sigma), gaussian_test(x, sigma)) assert eq.args == [eq.sigma] # Equation with user-defined functions factory.registerFunction("myfunc", eq, ["sigma"]) eq2 = factory.makeEquation("c*myfunc(sigma)") - assert numpy.allclose(eq2(c=2, sigma=sigma), 2 * f(x, sigma)) + assert numpy.allclose(eq2(c=2, sigma=sigma), 2 * gaussian_test(x, sigma)) assert "sigma" in eq2.argdict assert "c" in eq2.argdict assert eq2.args == [eq2.c, eq2.sigma] @@ -251,8 +246,7 @@ def _f(a, b): sigma = builder.ArgumentBuilder(name="sigma", value=0.1) beq = sqrt(e ** (-0.5 * (x / sigma) ** 2)) eq = beq.getEquation() - f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) - assert numpy.allclose(eq(), numpy.sqrt(e ** (-0.5 * (_x / 0.1) ** 2))) + assert numpy.allclose(eq(), numpy.sqrt(numpy.exp(-0.5 * (_x / 0.1) ** 2))) # Equation with Equation A = builder.ArgumentBuilder(name="A", value=2) @@ -283,5 +277,9 @@ def _f(a, b): return -if __name__ == "__main__": - unittest.main() +def f_equation(a, x, b, c): + return a * numpy.sin(0.5 * x) + numpy.divide(b, c) + + +def gaussian_test(x, sigma): + return numpy.sqrt(numpy.exp(-0.5 * (x / sigma) ** 2)) diff --git a/tests/test_characteristicfunctions.py b/tests/test_characteristicfunctions.py index dd846baf..ecbbf0e0 100644 --- a/tests/test_characteristicfunctions.py +++ b/tests/test_characteristicfunctions.py @@ -22,8 +22,9 @@ import diffpy.srfit.pdf.characteristicfunctions as cf from diffpy.srfit.sas.sasimport import sasimport -# Global variables to be assigned in setUp -cf = None +# # Global variables to be assigned in setUp +# cf = None +# Fixme: remove this code if imports don't break on testing # ---------------------------------------------------------------------------- diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 56e00d4f..04ecd4c7 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -27,29 +27,29 @@ class TestParameter(unittest.TestCase): def testSetValue(self): """Test initialization.""" - l = Parameter("l") + par_l = Parameter("l") - l.setValue(3.14) - self.assertAlmostEqual(3.14, l.getValue()) + par_l.setValue(3.14) + self.assertAlmostEqual(3.14, par_l.getValue()) # Try array import numpy x = numpy.arange(0, 10, 0.1) - l.setValue(x) - self.assertTrue(l.getValue() is x) - self.assertTrue(l.value is x) + par_l.setValue(x) + self.assertTrue(par_l.getValue() is x) + self.assertTrue(par_l.value is x) # Change the array y = numpy.arange(0, 10, 0.5) - l.value = y - self.assertTrue(l.getValue() is y) - self.assertTrue(l.value is y) + par_l.value = y + self.assertTrue(par_l.getValue() is y) + self.assertTrue(par_l.value is y) # Back to scalar - l.setValue(1.01) - self.assertAlmostEqual(1.01, l.getValue()) - self.assertAlmostEqual(1.01, l.value) + par_l.setValue(1.01) + self.assertAlmostEqual(1.01, par_l.getValue()) + self.assertAlmostEqual(1.01, par_l.value) return @@ -57,23 +57,23 @@ class TestParameterProxy(unittest.TestCase): def testProxy(self): """Test the ParameterProxy class.""" - l = Parameter("l", 3.14) + par_l = Parameter("l", 3.14) # Try Accessor adaptation - la = ParameterProxy("l2", l) + la = ParameterProxy("l2", par_l) self.assertEqual("l2", la.name) - self.assertEqual(l.getValue(), la.getValue()) + self.assertEqual(par_l.getValue(), la.getValue()) # Change the parameter - l.value = 2.3 - self.assertEqual(l.getValue(), la.getValue()) - self.assertEqual(l.value, la.value) + par_l.value = 2.3 + self.assertEqual(par_l.getValue(), la.getValue()) + self.assertEqual(par_l.value, la.value) # Change the proxy la.value = 3.2 - self.assertEqual(l.getValue(), la.getValue()) - self.assertEqual(l.value, la.value) + self.assertEqual(par_l.getValue(), la.getValue()) + self.assertEqual(par_l.value, la.value) return @@ -85,38 +85,38 @@ def testWrapper(self): This adapts a Parameter to the Parameter interface. :) """ - l = Parameter("l", 3.14) + par_l = Parameter("l", 3.14) # Try Accessor adaptation la = ParameterAdapter( - "l", l, getter=Parameter.getValue, setter=Parameter.setValue + "l", par_l, getter=Parameter.getValue, setter=Parameter.setValue ) - self.assertEqual(l.name, la.name) - self.assertEqual(l.getValue(), la.getValue()) + self.assertEqual(par_l.name, la.name) + self.assertEqual(par_l.getValue(), la.getValue()) # Change the parameter - l.setValue(2.3) - self.assertEqual(l.getValue(), la.getValue()) + par_l.setValue(2.3) + self.assertEqual(par_l.getValue(), la.getValue()) # Change the adapter la.setValue(3.2) - self.assertEqual(l.getValue(), la.getValue()) + self.assertEqual(par_l.getValue(), la.getValue()) # Try Attribute adaptation - la = ParameterAdapter("l", l, attr="value") + la = ParameterAdapter("l", par_l, attr="value") - self.assertEqual(l.name, la.name) + self.assertEqual(par_l.name, la.name) self.assertEqual("value", la.attr) - self.assertEqual(l.getValue(), la.getValue()) + self.assertEqual(par_l.getValue(), la.getValue()) # Change the parameter - l.setValue(2.3) - self.assertEqual(l.getValue(), la.getValue()) + par_l.setValue(2.3) + self.assertEqual(par_l.getValue(), la.getValue()) # Change the adapter la.setValue(3.2) - self.assertEqual(l.getValue(), la.getValue()) + self.assertEqual(par_l.getValue(), la.getValue()) return diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 4574cf27..00000000 --- a/tests/utils.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## -"""Helper routines for testing.""" - -import sys - -import six - -import diffpy.srfit.equation.literals as literals -from diffpy.srfit.sas.sasimport import sasimport -from tests import logger - -# Helper functions for testing ----------------------------------------------- - - -def capturestdout(f, *args, **kwargs): - """Capture the standard output from a call of function f.""" - savestdout = sys.stdout - fp = six.StringIO() - try: - sys.stdout = fp - f(*args, **kwargs) - finally: - sys.stdout = savestdout - return fp.getvalue() - - -# End of file From f44dde2aeedd5dc911cbbd340e8d1b7891233fd8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 26 Jun 2025 09:31:51 -0400 Subject: [PATCH 43/45] style: last few lambdas etc --- src/diffpy/srfit/fitbase/recipeorganizer.py | 43 ++++++++++++------- .../srfit/pdf/characteristicfunctions.py | 6 ++- src/diffpy/srfit/sas/prcalculator.py | 9 +++- tests/test_contribution.py | 7 ++- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index b9127201..7b22a735 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -24,6 +24,7 @@ import re from collections import OrderedDict +from functools import partial from itertools import chain, groupby import six @@ -741,8 +742,7 @@ def clearConstraints(self, recurse=False): self.unconstrain(*self._constraints) if recurse: - _constraint_clearer = lambda m: hasattr(m, "clearConstraints") - for m in filter(f, self._iterManaged()): + for m in filter(_has_clear_constraints, self._iterManaged()): m.clearConstraints(recurse) return @@ -822,19 +822,16 @@ def clearRestraints(self, recurse=False): found there as well. """ self.unrestrain(*self._restraints) - if recurse: - f = lambda m: hasattr(m, "clearRestraints") - for m in filter(f, self._iterManaged()): - m.clearRestraints(recurse) + for msg in filter(_has_clear_restraints(), self._iterManaged()): + msg.clearRestraints(recurse) return def _getConstraints(self, recurse=True): """Get the constrained Parameters for this and managed sub-objects.""" constraints = {} if recurse: - f = lambda m: hasattr(m, "_getConstraints") - for m in filter(f, self._iterManaged()): + for m in filter(_has_get_constraints(), self._iterManaged()): constraints.update(m._getConstraints(recurse)) constraints.update(self._constraints) @@ -848,8 +845,7 @@ def _getRestraints(self, recurse=True): """ restraints = set(self._restraints) if recurse: - f = lambda m: hasattr(m, "_getRestraints") - for m in filter(f, self._iterManaged()): + for m in filter(_has_get_restraints(), self._iterManaged()): restraints.update(m._getRestraints(recurse)) return restraints @@ -968,15 +964,13 @@ def show(self, pattern="", textwidth=78): the screen width. Do not trim when negative or 0. """ regexp = re.compile(pattern) - pmatch = lambda s: ( - len(s.split(None, 1)) < 2 or regexp.search(s.split(None, 1)[0]) - ) + _pmatch_with_re = partial(_pmatch, regexp=regexp) # Show sub objects and their parameters lines = [] tlines = self._formatManaged() if tlines: lines.extend(["Parameters", _DASHEDLINE]) - linesok = filter(pmatch, tlines) + linesok = filter(_pmatch_with_re, tlines) lastnotblank = False # squeeze repeated blank lines for lastnotblank, g in groupby(linesok, bool): @@ -1002,7 +996,7 @@ def show(self, pattern="", textwidth=78): if lines: lines.append("") lines.extend(["Restraints", _DASHEDLINE]) - lines.extend(filter(pmatch, tlines)) + lines.extend(filter(_pmatch_with_re, tlines)) # Determine effective text width tw. tw = textwidth if (textwidth is not None and textwidth > 0) else None @@ -1059,5 +1053,22 @@ def equationFromString( return eq -def _constraint_clearer(msg): +def _has_clear_constraints(msg): return hasattr(msg, "clearConstraints") + + +def _has_clear_restraints(msg): + return hasattr(msg, "clearRestraints") + + +def _has_get_restraints(msg): + return hasattr(msg, "_getRestraints") + + +def _has_get_constraints(msg): + return hasattr(msg, "_getConstraints") + + +def _pmatch(inp_str, regexp): + parts = inp_str.split(None, 1) + return len(parts) < 2 or regexp.search(parts[0]) diff --git a/src/diffpy/srfit/pdf/characteristicfunctions.py b/src/diffpy/srfit/pdf/characteristicfunctions.py index 3f8a1473..a8718e57 100644 --- a/src/diffpy/srfit/pdf/characteristicfunctions.py +++ b/src/diffpy/srfit/pdf/characteristicfunctions.py @@ -216,8 +216,6 @@ def lognormalSphericalCF(r, psize, psig): if psig <= 0: return sphericalCF(r, psize) - erfc = lambda x: 1.0 - erf(x) - sqrt2 = sqrt(2.0) s = sqrt(log(psig * psig / (1.0 * psize * psize) + 1)) mu = log(psize) - s * s / 2 @@ -427,4 +425,8 @@ def __call__(self, r): return fr +def erfc(x): + return 1.0 - erf(x) + + # End of file diff --git a/src/diffpy/srfit/sas/prcalculator.py b/src/diffpy/srfit/sas/prcalculator.py index 0af67260..1dc6414d 100644 --- a/src/diffpy/srfit/sas/prcalculator.py +++ b/src/diffpy/srfit/sas/prcalculator.py @@ -23,6 +23,8 @@ __all__ = ["PrCalculator", "CFCalculator"] +from functools import partial + import numpy from diffpy.srfit.fitbase import Calculator @@ -90,12 +92,15 @@ def __call__(self, r): self._invertor.y = iq self._invertor.err = diq c, c_cov = self._invertor.invert_optimize() - l = lambda x: self._invertor.pr(c, x) - pr = map(l, r) + _inverted_w_c = partial(self._inverted, c=c) + pr = map(_inverted_w_c, r) pr = numpy.array(pr) return self.scale.value * pr + def _inverted(self, x, c): + self._invertor.pr(c, x) + # End class PrCalculator diff --git a/tests/test_contribution.py b/tests/test_contribution.py index 4461647d..4d3fc7e3 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -170,8 +170,7 @@ def test_releaseOldEquations(self): def test_registerFunction(self): """Ensure registered function works after second setEquation call.""" fc = self.fitcontribution - fsquare = lambda x: x**2 - fc.registerFunction(fsquare, name="fsquare") + fc.registerFunction(_fsquare, name="fsquare") fc.setEquation("fsquare") fc.x.setValue(5) self.assertEqual(25, fc.evaluate()) @@ -184,6 +183,10 @@ def test_registerFunction(self): return +def _fsquare(x): + return x**2 + + def testResidual(noObserversInGlobalBuilders): """Test the residual, which requires all other methods.""" gen = ProfileGenerator("test") From dd7b99acc8c493cef66e40af553cc7791d2f3a03 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 26 Jun 2025 09:41:40 -0400 Subject: [PATCH 44/45] style: no ##'s in the docs examples to make flake8 happy --- doc/examples/crystalpdf.py | 13 +++++++------ doc/examples/crystalpdfall.py | 9 +++++---- doc/examples/crystalpdfobjcryst.py | 11 ++++++----- doc/examples/crystalpdftwodata.py | 9 +++++---- doc/examples/crystalpdftwophase.py | 11 ++++++----- doc/examples/debyemodel.py | 9 +++++---- doc/examples/debyemodelII.py | 3 ++- doc/examples/ellipsoidsas.py | 13 +++++++------ doc/examples/gaussiangenerator.py | 11 ++++++----- doc/examples/gaussianrecipe.py | 9 +++++---- doc/examples/interface.py | 3 ++- doc/examples/npintensity.py | 15 ++++++++------- doc/examples/npintensityII.py | 9 +++++---- doc/examples/nppdfobjcryst.py | 9 +++++---- doc/examples/simplepdf.py | 7 ++++--- doc/examples/simplepdftwophase.py | 5 +++-- doc/examples/simplerecipe.py | 3 ++- doc/examples/threedoublepeaks.py | 7 ++++--- 18 files changed, 87 insertions(+), 69 deletions(-) diff --git a/doc/examples/crystalpdf.py b/doc/examples/crystalpdf.py index 0d668da5..41dbca6d 100644 --- a/doc/examples/crystalpdf.py +++ b/doc/examples/crystalpdf.py @@ -36,13 +36,14 @@ from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.structure import Structure -####### Example Code +###### +# Example Code def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" - ## The Profile + # The Profile # This will be used to store the observed and calculated PDF profile. profile = Profile() @@ -57,7 +58,7 @@ def makeRecipe(ciffile, datname): profile.loadParsedData(parser) profile.setCalculationRange(xmax=20) - ## The ProfileGenerator + # The ProfileGenerator # The PDFGenerator is for configuring and calculating a PDF profile. Here, # we want to refine a Structure object from diffpy.structure. We tell the # PDFGenerator that with the 'setStructure' method. All other configuration @@ -69,18 +70,18 @@ def makeRecipe(ciffile, datname): stru.read(ciffile) generator.setStructure(stru) - ## The FitContribution + # The FitContribution # Here we associate the Profile and ProfileGenerator, as has been done # before. contribution = FitContribution("nickel") contribution.addProfileGenerator(generator) contribution.setProfile(profile, xname="r") - ## Make the FitRecipe and add the FitContribution. + # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() recipe.addContribution(contribution) - ## Configure the fit variables + # Configure the fit variables # The PDFGenerator class holds the ParameterSet associated with the # Structure passed above in a data member named "phase". (We could have diff --git a/doc/examples/crystalpdfall.py b/doc/examples/crystalpdfall.py index 918b7a9d..056f8a80 100644 --- a/doc/examples/crystalpdfall.py +++ b/doc/examples/crystalpdfall.py @@ -30,7 +30,8 @@ ) from diffpy.srfit.pdf import PDFGenerator, PDFParser -####### Example Code +###### +# Example Code def makeProfile(datafile): @@ -56,14 +57,14 @@ def makeRecipe( ): """Create a fitting recipe for crystalline PDF data.""" - ## The Profiles + # The Profiles # We need a profile for each data set. xprofile_ni = makeProfile(xdata_ni) xprofile_si = makeProfile(xdata_si) nprofile_ni = makeProfile(ndata_ni) xprofile_sini = makeProfile(xdata_sini) - ## The ProfileGenerators + # The ProfileGenerators # We create one for each phase and share the phases. xgenerator_ni = PDFGenerator("xG_ni") stru = loadCrystal(ciffile_ni) @@ -84,7 +85,7 @@ def makeRecipe( xgenerator_sini_si = PDFGenerator("xG_sini_si") xgenerator_sini_si.setPhase(phase_si) - ## The FitContributions + # The FitContributions # We one of these for each data set. xcontribution_ni = makeContribution("xnickel", xgenerator_ni, xprofile_ni) xcontribution_si = makeContribution("xsilicon", xgenerator_si, xprofile_si) diff --git a/doc/examples/crystalpdfobjcryst.py b/doc/examples/crystalpdfobjcryst.py index 48f94e0e..49195434 100644 --- a/doc/examples/crystalpdfobjcryst.py +++ b/doc/examples/crystalpdfobjcryst.py @@ -31,13 +31,14 @@ ) from diffpy.srfit.pdf import PDFGenerator, PDFParser -####### Example Code +###### +# Example Code def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" - ## The Profile + # The Profile # This will be used to store the observed and calculated PDF profile. profile = Profile() @@ -50,7 +51,7 @@ def makeRecipe(ciffile, datname): profile.loadParsedData(parser) profile.setCalculationRange(xmax=20) - ## The ProfileGenerator + # The ProfileGenerator # This time we use the CreateCrystalFromCIF method of pyobjcryst.crystal to # create a Crystal object. That object is passed to the PDFGenerator as in # the previous example. @@ -59,7 +60,7 @@ def makeRecipe(ciffile, datname): generator.setStructure(stru) generator.setQmax(40.0) - ## The FitContribution + # The FitContribution contribution = FitContribution("nickel") contribution.addProfileGenerator(generator) contribution.setProfile(profile, xname="r") @@ -68,7 +69,7 @@ def makeRecipe(ciffile, datname): recipe = FitRecipe() recipe.addContribution(contribution) - ## Configure the fit variables + # Configure the fit variables # As before, we get a handle to the structure parameter set. In this case, # it is a ObjCrystCrystalParSet instance that was created when we called diff --git a/doc/examples/crystalpdftwodata.py b/doc/examples/crystalpdftwodata.py index cd2aa0c6..debefc0f 100644 --- a/doc/examples/crystalpdftwodata.py +++ b/doc/examples/crystalpdftwodata.py @@ -32,13 +32,14 @@ ) from diffpy.srfit.pdf import PDFGenerator, PDFParser -####### Example Code +###### +# Example Code def makeRecipe(ciffile, xdatname, ndatname): """Create a fitting recipe for crystalline PDF data.""" - ## The Profiles + # The Profiles # We need a profile for each data set. This means that we will need two # FitContributions as well. xprofile = Profile() @@ -55,7 +56,7 @@ def makeRecipe(ciffile, xdatname, ndatname): nprofile.loadParsedData(parser) nprofile.setCalculationRange(xmax=20) - ## The ProfileGenerators + # The ProfileGenerators # We need one of these for the x-ray data. xgenerator = PDFGenerator("G") stru = loadCrystal(ciffile) @@ -80,7 +81,7 @@ def makeRecipe(ciffile, xdatname, ndatname): ngenerator = PDFGenerator("G") ngenerator.setPhase(xgenerator.phase) - ## The FitContributions + # The FitContributions # We associate the x-ray PDFGenerator and Profile in one FitContribution... xcontribution = FitContribution("xnickel") xcontribution.addProfileGenerator(xgenerator) diff --git a/doc/examples/crystalpdftwophase.py b/doc/examples/crystalpdftwophase.py index b0fcf30d..98a87905 100644 --- a/doc/examples/crystalpdftwophase.py +++ b/doc/examples/crystalpdftwophase.py @@ -32,13 +32,14 @@ ) from diffpy.srfit.pdf import PDFGenerator, PDFParser -####### Example Code +###### +# Example Code def makeRecipe(niciffile, siciffile, datname): """Create a fitting recipe for crystalline PDF data.""" - ## The Profile + # The Profile profile = Profile() # Load data and add it to the profile @@ -47,7 +48,7 @@ def makeRecipe(niciffile, siciffile, datname): profile.loadParsedData(parser) profile.setCalculationRange(xmax=20) - ## The ProfileGenerator + # The ProfileGenerator # In order to fit two phases simultaneously, we must use two PDFGenerators. # PDFGenerator is designed to take care of as little information as it # must. (Don't do too much, and do it well.) A PDFGenerator can generate @@ -67,7 +68,7 @@ def makeRecipe(niciffile, siciffile, datname): stru = loadCrystal(siciffile) generator_si.setStructure(stru) - ## The FitContribution + # The FitContribution # Add both generators to the FitContribution. Add the Profile. This will # send the metadata to the generators. contribution = FitContribution("nisi") @@ -85,7 +86,7 @@ def makeRecipe(niciffile, siciffile, datname): recipe = FitRecipe() recipe.addContribution(contribution) - ## Configure the fit variables + # Configure the fit variables # Start by configuring the scale factor and resolution factors. # We want the sum of the phase scale factors to be 1. recipe.newVar("scale_ni", 0.1) diff --git a/doc/examples/debyemodel.py b/doc/examples/debyemodel.py index 0ec673a7..e3b6c212 100644 --- a/doc/examples/debyemodel.py +++ b/doc/examples/debyemodel.py @@ -57,7 +57,8 @@ 500.0 0.03946 0.00250 """ -####### Example Code +###### +# Example Code def makeRecipe(): @@ -84,7 +85,7 @@ def makeRecipe(): won't do that here. """ - ## The Profile + # The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() @@ -94,7 +95,7 @@ def makeRecipe(): x, y, dy = numpy.hsplit(xydy, 3) profile.setObservedProfile(x, y, dy) - ## The FitContribution + # The FitContribution # The FitContribution associates the profile with the Debye function. contribution = FitContribution("pb") # Tell the contribution about the Profile. We will need to use the @@ -127,7 +128,7 @@ def makeRecipe(): # can specify that as well. contribution.setEquation("debye(T, 207.2, abs(thetaD)) + offset") - ## The FitRecipe + # The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can # create variables, constraints and restraints. If we had multiple profiles # to fit simultaneously, the contribution from each could be added to the diff --git a/doc/examples/debyemodelII.py b/doc/examples/debyemodelII.py index 6ff8d1e6..9872f71d 100644 --- a/doc/examples/debyemodelII.py +++ b/doc/examples/debyemodelII.py @@ -37,7 +37,8 @@ from diffpy.srfit.fitbase import FitRecipe, FitResults -####### Example Code +###### +# Example Code def makeRecipeII(): diff --git a/doc/examples/ellipsoidsas.py b/doc/examples/ellipsoidsas.py index b3e8d18e..23ee5f08 100644 --- a/doc/examples/ellipsoidsas.py +++ b/doc/examples/ellipsoidsas.py @@ -24,13 +24,14 @@ ) from diffpy.srfit.sas import SASGenerator, SASParser -####### Example Code +###### +# Example Code def makeRecipe(datname): """Create a fitting recipe for ellipsoidal SAS data.""" - ## The Profile + # The Profile # This will be used to store the observed and calculated I(Q) data. profile = Profile() @@ -40,7 +41,7 @@ def makeRecipe(datname): parser.parseFile(datname) profile.loadParsedData(parser) - ## The ProfileGenerator + # The ProfileGenerator # The SASGenerator is for configuring and calculating a SAS profile. We use # a sas model to configure and serve as the calculation engine of the # generator. This allows us to use the full sas model creation @@ -52,7 +53,7 @@ def makeRecipe(datname): model = EllipsoidModel() generator = SASGenerator("generator", model) - ## The FitContribution + # The FitContribution # Here we associate the Profile and ProfileGenerator, as has been done # before. contribution = FitContribution("ellipsoid") @@ -65,11 +66,11 @@ def makeRecipe(datname): # will have on the estimated parameter uncertainties. contribution.setResidualEquation("log(eq) - log(y)") - ## Make the FitRecipe and add the FitContribution. + # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() recipe.addContribution(contribution) - ## Configure the fit variables + # Configure the fit variables # The SASGenerator uses the parameters from the params and dispersion # attributes of the model. These vary from model to model, but are adopted # as SrFit Parameters within the generator. Whereas the dispersion diff --git a/doc/examples/gaussiangenerator.py b/doc/examples/gaussiangenerator.py index 6df50694..fedff5a5 100644 --- a/doc/examples/gaussiangenerator.py +++ b/doc/examples/gaussiangenerator.py @@ -47,7 +47,8 @@ ProfileGenerator, ) -####### Example Code +###### +# Example Code class GaussianGenerator(ProfileGenerator): @@ -124,7 +125,7 @@ def makeRecipe(): associate this with a Profile, and use this to define a FitRecipe. """ - ## The Profile + # The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() @@ -132,12 +133,12 @@ def makeRecipe(): # numpy. profile.loadtxt("data/gaussian.dat") - ## The ProfileGenerator + # The ProfileGenerator # Create a GaussianGenerator named "g". This will be the name we use to # refer to the generator from within the FitContribution equation. generator = GaussianGenerator("g") - ## The FitContribution + # The FitContribution # Create a FitContribution that will associate the Profile with the # GaussianGenerator. The GaussianGenerator will be accessible as an # attribute of the FitContribution by its name ("g"). Note that this will @@ -146,7 +147,7 @@ def makeRecipe(): contribution.addProfileGenerator(generator) contribution.setProfile(profile) - ## The FitRecipe + # The FitRecipe # Now we create the FitRecipe and add the FitContribution. recipe = FitRecipe() recipe.addContribution(contribution) diff --git a/doc/examples/gaussianrecipe.py b/doc/examples/gaussianrecipe.py index 8f762ba7..646afc71 100644 --- a/doc/examples/gaussianrecipe.py +++ b/doc/examples/gaussianrecipe.py @@ -52,7 +52,8 @@ Profile, ) -####### Example Code +###### +# Example Code def main(): @@ -95,7 +96,7 @@ def makeRecipe(): optimized. See the 'scipyOptimize' function. """ - ## The Profile + # The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() @@ -103,7 +104,7 @@ def makeRecipe(): # numpy. profile.loadtxt("data/gaussian.dat") - ## The FitContribution + # The FitContribution # The FitContribution associates the Profile with a fitting equation. The # FitContribution also stores the parameters of the fitting equation. We # give our FitContribution then name "g1". We will be able to access the @@ -131,7 +132,7 @@ def makeRecipe(): # attribute. Parameters also have a 'name' attribute. contribution.A.value = 1.0 - ## The FitRecipe + # The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can # create variables, constraints and restraints. recipe = FitRecipe() diff --git a/doc/examples/interface.py b/doc/examples/interface.py index 5b1a250e..ee70e699 100644 --- a/doc/examples/interface.py +++ b/doc/examples/interface.py @@ -25,7 +25,8 @@ Profile, ) -####### Example Code +###### +# Example Code def main(): diff --git a/doc/examples/npintensity.py b/doc/examples/npintensity.py index 64498fc6..210aa4c6 100644 --- a/doc/examples/npintensity.py +++ b/doc/examples/npintensity.py @@ -55,7 +55,8 @@ ) from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet -####### Example Code +###### +# Example Code class IntensityGenerator(ProfileGenerator): @@ -181,21 +182,21 @@ def makeRecipe(strufile, datname): associate this with a Profile, and use this to define a FitRecipe. """ - ## The Profile + # The Profile # Create a Profile. This will hold the experimental and calculated signal. profile = Profile() # Load data and add it to the profile x, y, u = profile.loadtxt(datname) - ## The ProfileGenerator + # The ProfileGenerator # Create an IntensityGenerator named "I". This will be the name we use to # refer to the generator from within the FitContribution equation. We also # need to load the model structure we're using. generator = IntensityGenerator("I") generator.setStructure(strufile) - ## The FitContribution + # The FitContribution # Create a FitContribution, that will associate the Profile with the # ProfileGenerator. The ProfileGenerator will be accessible as an # attribute of the FitContribution by its name ("I"). We also want to tell @@ -339,14 +340,14 @@ def plotResults(recipe): # All this should be pretty familiar by now. q = recipe.bucky.profile.x - I = recipe.bucky.profile.y + Imeas = recipe.bucky.profile.y Icalc = recipe.bucky.profile.ycalc bkgd = recipe.bucky.evaluateEquation("bkgd") - diff = I - Icalc + diff = Imeas - Icalc import pylab - pylab.plot(q, I, "ob", label="I(Q) Data") + pylab.plot(q, Imeas, "ob", label="I(Q) Data") pylab.plot(q, Icalc, "r-", label="I(Q) Fit") pylab.plot(q, diff, "g-", label="I(Q) diff") pylab.plot(q, bkgd, "c-", label="Bkgd. Fit") diff --git a/doc/examples/npintensityII.py b/doc/examples/npintensityII.py index e4607f90..45751d47 100644 --- a/doc/examples/npintensityII.py +++ b/doc/examples/npintensityII.py @@ -44,7 +44,8 @@ Profile, ) -####### Example Code +###### +# Example Code def makeRecipe(strufile, datname1, datname2): @@ -62,7 +63,7 @@ def makeRecipe(strufile, datname1, datname2): structure) in both generators. """ - ## The Profiles + # The Profiles # Create two Profiles for the two FitContributions. profile1 = Profile() profile2 = Profile() @@ -71,7 +72,7 @@ def makeRecipe(strufile, datname1, datname2): profile1.loadtxt(datname1) x, y, u = profile2.loadtxt(datname2) - ## The ProfileGenerators + # The ProfileGenerators # Create two IntensityGenerators named "I". There will not be a name # conflict, since the name is only meaningful within the FitContribution # that holds the ProfileGenerator. Load the structure into one and make @@ -84,7 +85,7 @@ def makeRecipe(strufile, datname1, datname2): generator2 = IntensityGenerator("I") generator2.addParameterSet(generator1.phase) - ## The FitContributions + # The FitContributions # Create the FitContributions. contribution1 = FitContribution("bucky1") contribution1.addProfileGenerator(generator1) diff --git a/doc/examples/nppdfobjcryst.py b/doc/examples/nppdfobjcryst.py index ad420944..a05dbaa8 100644 --- a/doc/examples/nppdfobjcryst.py +++ b/doc/examples/nppdfobjcryst.py @@ -28,20 +28,21 @@ ) from diffpy.srfit.pdf import DebyePDFGenerator -####### Example Code +###### +# Example Code def makeRecipe(molecule, datname): """Create a recipe that uses the DebyePDFGenerator.""" - ## The Profile + # The Profile profile = Profile() # Load data and add it to the profile profile.loadtxt(datname) profile.setCalculationRange(xmin=1.2, xmax=8) - ## The ProfileGenerator + # The ProfileGenerator # Create a DebyePDFGenerator named "G". generator = DebyePDFGenerator("G") generator.setStructure(molecule) @@ -49,7 +50,7 @@ def makeRecipe(molecule, datname): generator.setQmin(0.68) generator.setQmax(22) - ## The FitContribution + # The FitContribution contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) contribution.setProfile(profile, xname="r") diff --git a/doc/examples/simplepdf.py b/doc/examples/simplepdf.py index dc36862a..027a6d76 100644 --- a/doc/examples/simplepdf.py +++ b/doc/examples/simplepdf.py @@ -25,7 +25,8 @@ from diffpy.srfit.pdf import PDFContribution from diffpy.structure import Structure -####### Example Code +###### +# Example Code def makeRecipe(ciffile, datname): @@ -41,11 +42,11 @@ def makeRecipe(ciffile, datname): stru.read(ciffile) contribution.addStructure("nickel", stru) - ## Make the FitRecipe and add the FitContribution. + # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() recipe.addContribution(contribution) - ## Configure the fit variables + # Configure the fit variables phase = contribution.nickel.phase from diffpy.srfit.structure import constrainAsSpaceGroup diff --git a/doc/examples/simplepdftwophase.py b/doc/examples/simplepdftwophase.py index 526c4780..a8017c23 100644 --- a/doc/examples/simplepdftwophase.py +++ b/doc/examples/simplepdftwophase.py @@ -21,7 +21,8 @@ from diffpy.srfit.fitbase import FitRecipe, FitResults from diffpy.srfit.pdf import PDFContribution -####### Example Code +###### +# Example Code def makeRecipe(niciffile, siciffile, datname): @@ -42,7 +43,7 @@ def makeRecipe(niciffile, siciffile, datname): recipe = FitRecipe() recipe.addContribution(contribution) - ## Configure the fit variables + # Configure the fit variables # Start by configuring the scale factor and resolution factors. # We want the sum of the phase scale factors to be 1. recipe.newVar("scale_ni", 0.1) diff --git a/doc/examples/simplerecipe.py b/doc/examples/simplerecipe.py index 92ed185e..0ec297a5 100644 --- a/doc/examples/simplerecipe.py +++ b/doc/examples/simplerecipe.py @@ -21,7 +21,8 @@ from diffpy.srfit.fitbase import SimpleRecipe -####### Example Code +###### +# Example Code def main(): diff --git a/doc/examples/threedoublepeaks.py b/doc/examples/threedoublepeaks.py index 75c3d9ef..c0c91400 100644 --- a/doc/examples/threedoublepeaks.py +++ b/doc/examples/threedoublepeaks.py @@ -25,7 +25,8 @@ Profile, ) -####### Example Code +###### +# Example Code def makeRecipe(): @@ -42,7 +43,7 @@ def makeRecipe(): For this example, we use l1 = 1.012, l2 = 1.0 and r = 0.23. """ - ## The Profile + # The Profile # Create a Profile to hold the experimental and calculated signal. profile = Profile() x, y, dy = profile.loadtxt("data/threedoublepeaks.dat") @@ -89,7 +90,7 @@ def delta(t, mu): # c is the center of the gaussian. contribution.c.value = x[len(x) // 2] - ## The FitRecipe + # The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can # create variables, constraints and restraints. recipe = FitRecipe() From 0726694e3817116b491b2c378dc66437e02b1443 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 26 Jun 2025 10:47:32 -0400 Subject: [PATCH 45/45] fix: correct call of functions in filter in recipeorganizer.py --- src/diffpy/srfit/fitbase/recipeorganizer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index 7b22a735..ea2641cb 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -823,7 +823,7 @@ def clearRestraints(self, recurse=False): """ self.unrestrain(*self._restraints) if recurse: - for msg in filter(_has_clear_restraints(), self._iterManaged()): + for msg in filter(_has_clear_restraints, self._iterManaged()): msg.clearRestraints(recurse) return @@ -831,7 +831,7 @@ def _getConstraints(self, recurse=True): """Get the constrained Parameters for this and managed sub-objects.""" constraints = {} if recurse: - for m in filter(_has_get_constraints(), self._iterManaged()): + for m in filter(_has_get_constraints, self._iterManaged()): constraints.update(m._getConstraints(recurse)) constraints.update(self._constraints) @@ -845,7 +845,7 @@ def _getRestraints(self, recurse=True): """ restraints = set(self._restraints) if recurse: - for m in filter(_has_get_restraints(), self._iterManaged()): + for m in filter(_has_get_restraints, self._iterManaged()): restraints.update(m._getRestraints(recurse)) return restraints