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
76 changes: 76 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,79 @@
# Unreleased

- Enabling magnetism no longer jumps to another page. Ticking a layer's
"Magn." box while the current engine cannot model magnetism now asks whether
to switch the project to refl1d and does both in one step.
The calculation engine has its own group on the Sample page.
- Selecting an engine that cannot model magnetism, while the sample already has
magnetic layers, is refused with a message on both pages.
- Fixed the calculation-engine selector showing the wrong engine after loading
a project that used a different one.
- Magnetic depth profiles on the SLD chart (Sample and Analysis share the
chart):
- Once a layer is magnetic, the chart adds the spin-up and spin-down
potentials ρ ± ρM·cos(θM − A) for each magnetic model, dashed in the
model's colour. ρM and the moment angle θM each have a checkbox; θM uses
its own right-hand axis. The y-range covers every visible curve and grows
when a curve is switched on, so ρ + ρM is not clipped. θM is only defined
where there is a moment, and that curve is drawn in pieces, not joined
across the gaps.
- The switches are in a new "Magnetic profile" group under Magnetism on the
Sample page, and in Analysis under "Plot control". Both use the same
selection.
- If no model is magnetic, the chart, legend and sidebar are unchanged.
- Spin-asymmetry view, SA(q) = (R↑↑ − R↓↓)/(R↑↑ + R↓↓):
- A "Spin asymmetry" tab on the Experiment page (measured data) and a third
tab on the Analysis page's lower panel (measured data plus the model).
Neither is shown unless the experiment measured both non-spin-flip
channels. Without one, Experiment has no tab strip.
- Error bars come from the channel uncertainties. Points where R↑↑ + R↓↓ is
not significantly above zero (the background-dominated tail) are dropped,
with a note of how many, so they do not wreck the axis. Same for points
where the two channels do not share the same q. The axis shows the full
[−1, 1] window and expands if background-subtracted data go outside it.
- Polarized (spin-channel) experiment import and display:
- New "Load polarized experiment (file per channel)" flow: multi-select one
file per spin channel, then review and edit the automatic assignment (from
the ORSO `polarization` header or the file name). Any number of channels
can be assigned, including a single channel. Files can be marked "not
used". Duplicate or unknown channels, missing files, or nothing assigned
are rejected with a message.
- The experiment chart draws one measured series (plus error bounds) per
visible spin channel in a fixed palette (↑↑ pp, ↑↓ pm, ↓↑ mp, ↓↓ mm), with
a channel selector in the sidebar and a per-channel legend. At least one
measured channel stays visible. With several experiments selected, each
polarized experiment contributes one series per visible channel, using the
experiment colour as the hue base.
- Experiment lists mark polarized experiments with a `⇅N` badge for the
number of measured spin channels.
- Report figures plot each channel's spin cross-section in its channel
colour. A channel that cannot be calculated (for example spin-flip on a
non-magnetic model) is shown as measured data only.
- **Limitations:** one resolution function per polarized experiment (taken
from the first assigned channel; the import dialog says so); Bayesian
sampling of polarized experiments is not supported yet.
- Project save/load now fully supports polarized experiments.
- Magnetism editing and polarized fitting:
- New "Magnetism" group on the Sample page: one row per layer of the current
assembly, with a magnetic on/off checkbox, magnetic SLD (ρM) and in-plane
moment angle (θM). Only refl1d can model magnetic layers. The group says
so and offers to switch the project's calculation engine when a layer is
made magnetic (see above).
- ρM and θM appear in the Analysis parameter table like other layer
parameters, named after their assembly and model (`Model Fe rho_m`), with
default limits, fit checkboxes and constraints. The name filter accepts
"magnetic" as a keyword.
- Fitting a polarized experiment fits all its measured spin channels at once
against the shared model. Thickness, roughness, nuclear SLD, scale and
background are common to every channel; ρM and θM are constrained by all
of them. Polarized and ordinary experiments can be fitted together. This
used to report "not supported yet".
- The analysis and residual charts draw one measured/calculated pair per
visible spin channel, each with that channel's cross-section. Previously
only the first visible channel was shown. A channel the model cannot
calculate (spin-flip on a non-magnetic sample) shows measured points only
and contributes no residuals.

# Version 1.4.0 (3 Aug 2026)

- Added Bayesian analysis: run MCMC sampling alongside classical fitting, with posterior median and credibility intervals on the main chart, trace/corner-style plots per parameter, a dedicated status display with cancellation support, and plot export.
Expand Down
3 changes: 3 additions & 0 deletions EasyReflectometryApp/Backends/Mock/Analysis.qml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ QtObject {

readonly property var experimentsAvailable: ['experiment_1', 'experiment_2', 'experiment_3']
readonly property int experimentCurrentIndex: 2
// Polarization badge / channel count columns of the experiment lists
readonly property var experimentsPolarized: [false, false, false]
readonly property var experimentsChannelCount: [0, 0, 0]

// Minimizer
readonly property double minimizerTolerance: 1.0
Expand Down
28 changes: 28 additions & 0 deletions EasyReflectometryApp/Backends/Mock/Experiment.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,32 @@ QtObject {
function load(path) {
console.debug(`Loading experiment from ${path}`)
}

// Filename-token suggestion, so the assignment dialog can be exercised
// against the mock backend as well.
function suggestPolarizedChannels(paths) {
console.debug(`Suggesting polarized channels for ${paths}`)
const list = Array.isArray(paths) ? paths : String(paths).split(',')
const tokens = {uu: 'pp', pp: 'pp', dd: 'mm', mm: 'mm', ud: 'pm', pm: 'pm', du: 'mp', mp: 'mp'}
return list.filter(path => path !== '').map(path => {
const name = String(path).split(/[\\/]/).pop()
let channel = ''
for (const token in tokens) {
if (name.toLowerCase().indexOf('_' + token) !== -1) {
channel = tokens[token]
break
}
}
return {path: path, name: name, channel: channel}
})
}

function loadPolarized(assignments) {
console.debug(`Loading polarized experiment from ${assignments.length} file(s)`)
}

// Emitted with a user-facing message when an import is rejected.
signal loadFailed(string message)
// Emitted with the list position of a newly imported experiment.
signal experimentLoaded(int index)
}
57 changes: 57 additions & 0 deletions EasyReflectometryApp/Backends/Mock/Plotting.qml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,63 @@ QtObject {

property int modelCount: 1

// Polarized experiment (spin channel) support
property bool currentExperimentIsPolarized: false
property var experimentChannelList: []
signal channelSelectionChanged()
signal experimentChannelsChanged()
function setChannelVisible(channel, visible) {
console.debug(`setChannelVisible ${channel} ${visible}`)
}
function getExperimentChannels(index) {
return []
}
function getExperimentChannelDataPoints(index, channel) {
return []
}

// Magnetic depth profiles (no magnetic model in the mock)
property bool anyModelHasMagnetism: false
property var visibleSldCurves: ['spin_up', 'spin_down']
property double sldThetaMinY: 0
property double sldThetaMaxY: 360
signal magneticProfileChanged()
function modelHasMagnetism(index) {
return false
}
function getMagneticSldDataPointsForModel(index, curve) {
return []
}
function getMagneticSldSegmentsForModel(index, curve) {
return []
}
function getMagneticSldSegment(index, curve, segment) {
return []
}
function sldCurveVisible(curve) {
return visibleSldCurves.indexOf(curve) !== -1
}
function setSldCurveVisible(curve, visible) {
console.debug(`setSldCurveVisible ${curve} ${visible}`)
}

// Spin asymmetry (no polarized experiment in the mock)
property bool spinAsymmetryAvailable: false
property bool spinAsymmetryCalculatedAvailable: false
property int spinAsymmetryMaskedPoints: 0
property int spinAsymmetryOutOfOverlapPoints: 0
property double spinAsymmetryMinX: 0
property double spinAsymmetryMaxX: 1
property double spinAsymmetryMinY: -1
property double spinAsymmetryMaxY: 1
signal spinAsymmetryChanged()
function getSpinAsymmetryPoints(index) {
return []
}
function getSpinAsymmetryCalculatedPoints(index) {
return []
}

// Plot mode properties
property bool plotRQ4: false
property string yMainAxisTitle: 'R(q)'
Expand Down
31 changes: 31 additions & 0 deletions EasyReflectometryApp/Backends/Mock/Sample.qml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ pragma Singleton
import QtQuick

QtObject {

// Calculation engine (project-wide setting, also shown on the Sample page)
property var calculationEngines: ['refnx', 'refl1d']
property int calculationEngineIndex: 0
property var calculationEnginesSupportingMagnetism: ['refl1d']
signal calculationEngineChanged()
signal magnetismNeedsEngine(int index, string engine)
function setCalculationEngineIndex(value) {
console.debug(`setCalculationEngineIndex ${value}`)
}
function enableMagnetismWithEngineAtIndex(index, engine) {
console.debug(`enableMagnetismWithEngineAtIndex ${index} ${engine}`)
}

// Signals to match the Python backend
signal constraintsChanged
// MATERIALS
Expand Down Expand Up @@ -250,6 +264,23 @@ QtObject {
console.debug(`setCurrentLayerSolvation ${value}`)
}

// Layer magnetism (polarized analysis)
readonly property bool magnetismSupported: true
readonly property var layersMagnetism: [
{ 'label': 'label 1', 'magnetic': 'True', 'rho_m': '5.0', 'theta_m': '40.0' },
{ 'label': 'label 2', 'magnetic': 'False', 'rho_m': '0.0', 'theta_m': '270.0' },
{ 'label': 'label 3', 'magnetic': 'False', 'rho_m': '0.0', 'theta_m': '270.0' },
]
function setLayerMagneticAtIndex(index, value) {
console.debug(`setLayerMagneticAtIndex ${index} ${value}`)
}
function setLayerRhoMAtIndex(index, value) {
console.debug(`setLayerRhoMAtIndex ${index} ${value}`)
}
function setLayerThetaMAtIndex(index, value) {
console.debug(`setLayerThetaMAtIndex ${index} ${value}`)
}

// Table functions
function removeLayer(value) {
console.debug(`removeLayer ${value}`)
Expand Down
Loading
Loading