reflectometry / refl1d

1-D reflectometry fitting
https://refl1d.readthedocs.io/
Other
17 stars 23 forks source link

Change of behaviour between 0.8.14 and 0.8.15: 'PolarizedQProbe' object has no attribute '_theta_offsets' #161

Closed jfkcooper closed 12 months ago

jfkcooper commented 1 year ago

Below I have trimmed down to what I believe is close to the minimum code to reproduce what I am seeing, though i hacked some hard paths into it for the filenames in __create_experiment() so apologies. I was using this code (very similar at least) to make magnetic samples in my code, but something changed in release 0.8.15 which I'm pretty sure is to do with "preliminary support for theta_offset in PolarizedNeutronProbe" which now results in the code throwing AttributeError: "PolarizedQProbe" object has no attribute "_theta_offsets" when run. I'm not sure if this is expected and I should change my implementation or a bug, either way any advice is appreciated.

import numpy as np

import refl1d.material
import refl1d.model
import refl1d.probe
import refl1d.experiment
import refl1d.magnetism

import bumps.parameter
import bumps.fitproblem

class SampleYIG:
    """Defines a magnetic model describing yttrium iron garnet (YIG) film
       grown on a yttrium aluminium garnet (YAG) substrate.

    Attributes:
        name (str): name of the magnetic sample.
        labels (list): label for each measured data set.
        scale (float): experimental scale factor
        bkg (float): level of instrument background noise.
        dq (float): instrument resolution.
        pt_sld (bumps.parameter.Parameter): platinum layer SLD.
        pt_thick (bumps.parameter.Parameter): platinum layer thickness.
        pt_rough (bumps.parameter.Parameter): air/platinum roughness.
        yag_sld (bumps.parameter.Parameter): YAG substrate SLD.
        yag_rough (bumps.parameter.Parameter): YIG/YAG roughness.
        params (list): parameters of the model.
        structure (refl1d.model.Stack): Refl1D representation of sample.
        experiment (refl1d.experiment.Experiment): fittable experiment for sample.

    """
    def __init__(self):
        self.name = 'YIG_sample'
        self.labels = ['Up', 'Down']
        self.scale = 1.025
        self.bkg = 4e-7
        self.dq = 2.8

        # Define the parameters of the model.
        self.pt_sld = bumps.parameter.Parameter(5.646, name='Pt SLD')
        self.pt_thick = bumps.parameter.Parameter(21.08, name='Pt Thickness')
        self.pt_rough = bumps.parameter.Parameter(8.211, name='Air/Pt Roughness')

        self.yag_sld = bumps.parameter.Parameter(5.304, name='YAG SLD')
        self.yag_rough = bumps.parameter.Parameter(30, name='YIG/YAG Roughness')

        self.params = []

        # Load the experimentally-measured data for the sample.
        self.__create_experiment()

    def __create_experiment(self):
        """Creates an experiment corresponding to the measured data."""
        # Load the "up" and "down" spin state data sets.
        pp = refl1d.probe.load4(r'D:\github_projects\HOGBEN\hogben\data\YIG_sample\YIG_down.txt', sep='\t',intensity=self.scale, background=self.bkg)

        mm = refl1d.probe.load4(r'D:\github_projects\HOGBEN\hogben\data\YIG_sample\YIG_down.txt', sep='\t',intensity=self.scale, background=self.bkg)

        # Set the resolution to be constant dQ/q.
        self.__set_dq(pp)
        self.__set_dq(mm)

        # Combine the up and down probes into a single polarised Q probe.
        probe = refl1d.probe.PolarizedQProbe(xs=(pp, None, None, mm), name='')

        # Define the experiment using the probe and sample structure.
        air = refl1d.material.SLD(rho=0, name='Air')
        pt = refl1d.material.SLD(rho=self.pt_sld, name='Pt')(self.pt_thick, self.pt_rough)
        yag = refl1d.material.SLD(rho=self.yag_sld, name='Substrate')(0, self.yag_rough)

        self.structure = yag | pt | air
        self.experiment = refl1d.experiment.Experiment(sample=self.structure, probe=probe)

    def __set_dq(self, probe):
        """Sets the resolution of a given `probe` to be constant dQ/Q.

        Args:
            probe (refl1d.probe.QProbe): probe to set the resolution for.

        """
        # Transform the resolution from refnx to Refl1D format.
        dq = self.dq / (100*np.sqrt(8*np.log(2)))

        q_array = probe.Q

        # Calculate the dQ array and update the QProbe.
        dq_array = probe.Q * dq
        probe.dQ = dq_array

        # Adjust probe calculation for constant resolution.
        argmin, argmax = np.argmin(q_array), np.argmax(q_array)
        probe.calc_Qo = np.linspace(q_array[argmin] - 3.5*dq_array[argmin],
                                    q_array[argmax] + 3.5*dq_array[argmax],
                                    21*len(q_array))

    def print_refl_values(self):
        print(self.experiment.reflectivity())

if __name__ == '__main__':
    sample = SampleYIG()
    sample.print_refl_values()

YIG_down.txt

bmaranville commented 1 year ago

This is probably a bug... and probably one that I introduced. We should be testing things like PolarizedQProbe but I don't think we currently do.

jfkcooper commented 1 year ago

I seem to remember that it seemed the most appropriate probe for simulating polarised ToF data when we wrote our code. I'm currently fine just pinning to 0.8.14, but if there is a more appropriate probe to be using I can change too.

bmaranville commented 1 year ago

This fixes it, I think... do you think you could try it out? We can make another release soon.

diff --git a/refl1d/probe.py b/refl1d/probe.py
index 1c9fb04..a5ea6a1 100644
--- a/refl1d/probe.py
+++ b/refl1d/probe.py
@@ -2011,5 +2011,9 @@ class PolarizedQProbe(PolarizedNeutronProbe):
         self.Q, self.dQ = Qmeasurement_union(xs)
         self.calc_Qo = self.Q

+    @property
+    def calc_Q(self):
+        return self.calc_Qo
+
 # Deprecated old long name
 PolarizedNeutronQProbe = PolarizedQProbe
jfkcooper commented 1 year ago

yup, that works

bmaranville commented 1 year ago

Will push, and probably release as "bugfix release"

jfkcooper commented 1 year ago

thanks!

bmaranville commented 1 year ago

Fixed in v0.8.16