Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion refl1d/bumps_interface/fitplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ..experiment import Experiment
from ..sample.materialdb import air, silicon
from ..probe.data_loaders import ncnrdata as NCNR
from ..uncertainty import calc_errors, show_errors
from ..uncertainty import calc_errors_v2 as calc_errors, show_errors

# List of modules that contain dataclasses for the saved json file format

Expand Down
73 changes: 55 additions & 18 deletions refl1d/uncertainty.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
__all__ = [
"align_profiles",
"calc_errors",
"calc_errors_v2",
"reload_errors",
"run_errors",
"show_errors",
Expand Down Expand Up @@ -118,7 +119,13 @@ def _usage():
print(run_errors.__doc__)


# CRUFT: user scripts may be calling calc_errors, so don't change the interface
def calc_errors(problem, points):
profile, slabs, Q, residuals, theory = calc_errors_v2(problem, points)
return profile, slabs, Q, residuals


def calc_errors_v2(problem, points):
"""
Align the sample profiles and compute the residual difference from the
measured reflectivity for a set of points.
Expand Down Expand Up @@ -152,27 +159,32 @@ def calc_errors(problem, points):

*residuals*

Array of (theory-data)/uncertainty for each data point in
the measurement. There will be one array returned per error sample.
Array of (theory-data)/uncertainty for each dataset in
the measurement.

*theory*

Array of theory curves for each dataset.
"""
# Find Q
Q = [_residQ(m) for m in _experiments(problem)]

# Put best at slot 0, no alignment
data = [_eval_point(problem, problem.getp())]
curves = [_eval_point(problem, problem.getp())]
for p in points:
data.append(_eval_point(problem, p))
curves.append(_eval_point(problem, p))

profiles, slabs, residuals = zip(*data)
profiles, slabs, residuals, theory = zip(*curves)

# TODO: return sane datastructure
# Make a hashable version of model which just contains the name
# attribute, which is all that the rest of this code accesses.
models = [_HashableModel(m, i) for i, m in enumerate(_experiments(problem))]
models = [_HashableModel(m, k) for k, m in enumerate(_experiments(problem))]

profiles = {h: [v[k] for v in profiles] for k, h in enumerate(models)}
slabs = {h: [v[k] for v in slabs] for k, h in enumerate(models)}
residuals = {h: np.asarray([v[k] for v in residuals]).T for k, h in enumerate(models)}
theory = {h: np.asarray([v[k] for v in theory]).T for k, h in enumerate(models)}
Q = {h: Q[k] for k, h in enumerate(models)}

# from .pstruct import pstruct, sstruct
Expand All @@ -182,7 +194,7 @@ def calc_errors(problem, points):
# print("Q", sstruct(Q))
# import sys; sys.exit()

return profiles, slabs, Q, residuals
return profiles, slabs, Q, residuals, theory


class _HashableModel:
Expand All @@ -200,19 +212,26 @@ def __str__(self):
def _eval_point(problem, p):
problem.chisq_str() # Force reflectivity recalculation
problem.setp(p)
profiles, residuals, slabs = [], [], []
profiles, slabs, residuals, theory = [], [], [], []
for m in _experiments(problem):
# Residuals
D = m.residuals()
residuals.append(D + 0)
# Slab thicknesses
slabs_i = [L.thickness.value for L in m.sample[1:-1]]
slabs.append(np.array(slabs_i))
# Reflectivity curves (Q,R) – only need R, shared Q per model
QR = m.reflectivity()
theory_i = np.hstack([Rk for Qk, Rk in QR]) if m.probe.polarized else QR[1]
theory.append(theory_i)
# Profiles
if m.ismagnetic:
z, rho, irho, rhoM, thetaM = m.magnetic_smooth_profile()
profiles.append((z + 0, rho + 0, irho + 0, rhoM + 0, thetaM + 0))
else:
z, rho, irho = m.smooth_profile()
profiles.append((z + 0, rho + 0, irho + 0))
return profiles, slabs, residuals
return profiles, slabs, residuals, theory


def _experiments(problem):
Expand Down Expand Up @@ -278,8 +297,9 @@ def show_errors(errors, contours=CONTOURS, npoints=200, align="auto", plots=1, s
raise ValueError("can only pass in a figure object if exactly 1 plot is requested")

if save: # Don't create plots, just save the data
_save_profile_data(errors, contours=contours, npoints=npoints, align=align, save=save)
_save_residual_data(errors, contours=contours, save=save)
_save_profile_contour(errors, contours=contours, npoints=npoints, align=align, save=save)
_save_residual_contour(errors, contours=contours, save=save)
_save_theory_contour(errors, contours=contours, save=save)
if plots == 0:
... # Contours saved but no plotting
elif plots == 1: # Subplots for profiles/residuals
Expand All @@ -300,7 +320,7 @@ def show_errors(errors, contours=CONTOURS, npoints=200, align="auto", plots=1, s
if save:
plt.savefig(save + "-err2.png")
else: # Multiple plots
profiles, slabs, Q, residuals = errors
profiles, slabs, Q, residuals = errors[:4]
fignum = 1
for m in profiles.keys():
plt.figure()
Expand All @@ -319,7 +339,7 @@ def show_errors(errors, contours=CONTOURS, npoints=200, align="auto", plots=1, s


def show_profiles(errors, align, contours, npoints, axes=None):
profiles, slabs, _, _ = errors
profiles, slabs = errors[:2]
if align is not None:
profiles = align_profiles(profiles, slabs, align)
_profiles_draw_align_lines(profiles, slabs, align, axes)
Expand All @@ -331,16 +351,16 @@ def show_profiles(errors, align, contours, npoints, axes=None):


def show_residuals(errors, contours, axes=None):
_, _, Q, residuals = errors
Q, residuals = errors[2:4]

if False and contours:
_residuals_contour(Q, residuals, contours=contours)
else:
_residuals_overplot(Q, residuals, axes=axes)


def _save_profile_data(errors, align, contours, npoints, save):
profiles, slabs, _, _ = errors
def _save_profile_contour(errors, align, contours, npoints, save):
profiles, slabs = errors[:2]
if align is not None:
profiles = align_profiles(profiles, slabs, align)
k = 1
Expand All @@ -355,6 +375,7 @@ def _save_profile_data(errors, align, contours, npoints, save):
magnetic = len(group[0]) > 3
twist = magnetic and any((L[4] != BASE_GUIDE_ANGLE).any() for L in group)

# TODO: filenames should use dash instead of underscore (here and in resid and theory)
data, columns = _build_profile_matrix(group, 1, zp, contours)
_write_file(save + "_rho_contour%d.dat" % k, data, title, columns)
if absorbing:
Expand All @@ -380,8 +401,8 @@ def _build_profile_matrix(group, index, zp, contours):
return data, columns


def _save_residual_data(errors, contours, save):
_, _, Q, residuals = errors
def _save_residual_contour(errors, contours, save):
Q, residuals = errors[2:4]
k = 1
for title, _, x, r in sorted(
[(m.name, m.index, Q[m], v) for m, v in residuals.items()], key=lambda x: (x[0], x[1])
Expand All @@ -394,6 +415,22 @@ def _save_residual_data(errors, contours, save):
k += 1


def _save_theory_contour(errors, contours, save):
# Do nothing for old calc_errors output, which doesn't have theory curves
if len(errors) < 5:
return

Q, theory = errors[2], errors[4]
k = 1
for title, _, x, r in sorted([(m.name, m.index, Q[m], v) for m, v in theory.items()], key=lambda x: (x[0], x[1])):
q, qval = form_quantiles(r.T, contours)
# TODO: should have columns for R, dR as well.
data = np.vstack((x, r[:, 0], np.reshape(qval, (-1, qval.shape[2]))))
columns = ["q", "best"] + list("%g%%" % v for v in 100 * q.flatten())
_write_file(save + "_refl_contour%d.dat" % k, data, title, columns)
k += 1


def _write_file(path, data, title, columns):
with open(path, "wb") as fid:
fid.write(asbytes("# " + title + "\n"))
Expand Down
5 changes: 3 additions & 2 deletions refl1d/webview/server/profile_uncertainty.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
dict[str, np.ndarray], # slabs
dict[str, np.ndarray], # Q
dict[str, np.ndarray], # residuals
dict[str, np.ndarray], # theory
]

CONTOURS = (68, 95)
Expand Down Expand Up @@ -45,7 +46,7 @@ def show_profiles(
row: Optional[int] = None,
col: Optional[int] = None,
) -> "go.Figure":
profiles, slabs, _, _ = errors
profiles, slabs = errors[:2]
if align is not None:
# align_profiles always shifts so the alignment interface is at z=0
profiles = align_profiles(profiles, slabs, align)
Expand All @@ -69,7 +70,7 @@ def show_residuals(
row: Optional[int] = None,
col: Optional[int] = None,
):
_, _, Q, residuals = errors
Q, residuals = errors[2:4]

if contours is not None:
_residuals_contour(Q, residuals, contours, fig, row, col)
Expand Down
Loading