Description
This tutorial fits a spinwave model to simulated powder data.
Contents
Define instrument resolution parameters for MARI
This is a simplified parameterisation of the resolution of a Time-of-Flight spectrometer which will be used to broaden the simulated data. These parameters will depend on the instrument used - the instrument contact should be able to provide these.
Ei = 20;
+
+
+eres = @(en) 2.1750e-04*sqrt((Ei-en).^3 .* ( (32.27217*(0.168+0.400*(Ei./(Ei-en)).^1.5)).^2 + (14.53577*(1.168+0.400*(Ei./(Ei-en)).^1.5)).^2) );
+
+e2k = @(en) sqrt( en .* (2*1.67492728e-27*1.60217653e-22) )./1.05457168e-34./1e10;
+L1 = 11.8;
+L2 = 4.0;
+ws = 0.05;
+wm = 0.12;
+wd = 0.025;
+ki = e2k(Ei);
+a1 = ws/L1;
+a2 = wm/L1;
+a3 = wd/L2;
+a4 = ws/L2;
+dQ = 2.35 * sqrt( (ki*a1)^2/12 + (ki*a2)^2/12 + (ki*a3)^2/12 + (ki*a4)^2/12 );
+
Generate data
Simulate a powder spectrum of a triangular AFM with constant background and normally distributed noise. This can also be done using the powder fitting class if an empty data array is passed.
+tri = sw_model('triAF',1);
+scale = 1000;
+
+
+E = linspace(0,4,80);
+Q = linspace(0.1,3,60);
+spec = tri.powspec(Q,'Evect',E,'nRand',2e2);
+
+spec.swConv= scale*(spec.swConv + 0.1*mean(spec.swConv, 'all'));
+
+spec = sw_instrument(spec, 'dQ', dQ, 'dE', eres, 'ThetaMin', 3.5, 'Ei', Ei);
+
+e = sqrt(spec.swConv);
+spec.swConv = spec.swConv + e.*randn(size(spec.swConv));
+
+
+data = struct('x', {{0.5*(E(1:end-1) + E(2:end)), Q}}, ...
+ 'y', spec.swConv', ...
+ 'e', e');
+
Initialise the sw_fitpowder class and plot data to inspect initial guess
To initialise the powder fitting class (sw_fitpowder) object requires a spinw model with a magnetic strucutre and couplings added. Additional arguments are the data (can be xye structs for 2D data a cell array of xye structs for 1D data, or HORACE objects - see sw_fitpowder help for more details), a fit function which alters the matrices on the spinw object and a vector of initial guesses for the fit parameters.
Optinally a background strategy can be specified as a 5th positional argument ("planar" background in energy transfer and Q or "independent" background for each 1D cut (1D only)).
The class will call other spinw algorithms such as powspec and sw_instrument. The parameters used in these calls can be set by the user by modifying structs as shown below.
Data can be cropped in Q and energy transfer - for example it is advisible not to include the low-energy transfer region close to the elastic line (relative to the resolution).
fit_func = @(obj, p) matparser(obj, 'param', p, 'mat', {'J_1'}, 'init', true);
+J1 = 0.85;
+
+fitpow = sw_fitpowder(tri, data, fit_func, [J1]);
+fitpow.set_caching(true);
+
+
+fitpow.powspec_args.dE = eres;
+fitpow.powspec_args.fastmode = true;
+fitpow.powspec_args.neutron_output = true;
+fitpow.powspec_args.nRand = 2e2;
+fitpow.powspec_args.hermit = true;
+
+fitpow.sw_instrument_args = struct('dQ', dQ, 'ThetaMin', 3.5, 'Ei', Ei);
+
+
+fitpow.crop_energy_range(1.5*eres(0), inf);
+fitpow.crop_q_range(0.25, 3);
+fitpow.powspec_args.dE = eres;
+fitpow.powspec_args.fastmode = true;
+fitpow.powspec_args.neutron_output = true;
+
+
+fitpow.estimate_constant_background();
+fitpow.estimate_scale_factor();
+
+
+
+
+fitpow.plot_result(fitpow.params, 'EdgeAlpha', 0.9, 'LineWidth', 1 ,'EdgeColor', 'k');
+
Fit and inspect the result
It can be desirable to set bounds on fit parameters and fix some parameters initially to get a reasonable fit with as few degrees of freedom as possible. For example one might assume the background is constant in an initial fit.
The cost function minimised in the fit can be chi-squared ("chisq") or the unweighted sum of the squared residuals ("Rsq"). Also the minimiser can be set to any callable as long as it shares the API of the ndbase minimisers. Typically users would be recommended ndbase.lm or ndbase.simplex.
+fitpow.cost_function = "Rsq";
+fitpow.optimizer = @ndbase.simplex;
+
+
+
+
+fitpow.fix_bg_parameters(1:2);
+fitpow.set_bg_parameter_bounds(3, 0.0, [])
+
+fitpow.fit_background();
+fitpow.fix_bg_parameters(3);
+
+
+fitpow.set_model_parameter_bounds(1, 0, [])
+
+[pfit, cost_val, stat] = fitpow.fit();
+
+
+fitpow.plot_result(pfit, 10, 'EdgeAlpha', 0.9, 'LineWidth', 1 ,'EdgeColor', 'k')
+
+
+qcens = [0.8:0.4:1.6];
+dq = 0.05;
+fitpow.plot_1d_cuts_of_2d_data(qcens-dq, qcens+dq, pfit)
+
Fit 1D cuts
As discussed the sw_fitpowder class can be initialised with a cell array of xye structs correpsonding to 1D cuts at constant Q. Alternatively, having loaded in 2D data the sw_fitpowder class has a convenient method to take 1D cuts and replace the data.
To evaluate the cost function for 1D cuts, the class averages the model over nQ points. Depending on the size of the 2D data and the value of nQ it can be much quicker to fit 1D cuts.
The background for 1D cuts can be handled in two ways:
- A planar background as in the 2D case
- A linear background in energy transfer that's independent for each cut.
The background parameters and bounds are reset if the background strategy is changed to independent.
+fitpow.nQ = 5;
+
+fitpow.replace_2D_data_with_1D_cuts(qcens-dq, qcens+dq, "independent");
+
+
+
+fitpow.fix_bg_parameters(1);
+
+fitpow.set_bg_parameter_bounds(2, 0.0, [])
+
+fitpow.estimate_constant_background();
+fitpow.fit_background();
+fitpow.fix_bg_parameters(2);
+
+[pfit, cost_val, stat] = fitpow.fit();
+
+fitpow.plot_result(pfit)
+
Warning: Clearing 2D data previously added.
+Warning: Overwriting background parmaweters with 0.
+Warning: Background strategy changed - background parameters and bounds
+will be cleared.
+
\ No newline at end of file
diff --git a/tutorials/publish/tutorial39/tutorial39.png b/tutorials/publish/tutorial39/tutorial39.png
new file mode 100644
index 000000000..d7ccdd837
Binary files /dev/null and b/tutorials/publish/tutorial39/tutorial39.png differ
diff --git a/tutorials/publish/tutorial39/tutorial39.txt b/tutorials/publish/tutorial39/tutorial39.txt
new file mode 100644
index 000000000..be7b9df33
--- /dev/null
+++ b/tutorials/publish/tutorial39/tutorial39.txt
@@ -0,0 +1,305 @@
+Description
This tutorial fits a spinwave model to simulated powder data.
Contents
Define instrument resolution parameters for MARI
This is a simplified parameterisation of the resolution of a Time-of-Flight spectrometer which will be used to broaden the simulated data. These parameters will depend on the instrument used - the instrument contact should be able to provide these.
Ei = 20;
+
+
+eres = @(en) 2.1750e-04*sqrt((Ei-en).^3 .* ( (32.27217*(0.168+0.400*(Ei./(Ei-en)).^1.5)).^2 + (14.53577*(1.168+0.400*(Ei./(Ei-en)).^1.5)).^2) );
+
+e2k = @(en) sqrt( en .* (2*1.67492728e-27*1.60217653e-22) )./1.05457168e-34./1e10;
+L1 = 11.8;
+L2 = 4.0;
+ws = 0.05;
+wm = 0.12;
+wd = 0.025;
+ki = e2k(Ei);
+a1 = ws/L1;
+a2 = wm/L1;
+a3 = wd/L2;
+a4 = ws/L2;
+dQ = 2.35 * sqrt( (ki*a1)^2/12 + (ki*a2)^2/12 + (ki*a3)^2/12 + (ki*a4)^2/12 );
+
Generate data
Simulate a powder spectrum of a triangular AFM with constant background and normally distributed noise. This can also be done using the powder fitting class if an empty data array is passed.
+tri = sw_model('triAF',1);
+scale = 1000;
+
+
+E = linspace(0,4,80);
+Q = linspace(0.1,3,60);
+spec = tri.powspec(Q,'Evect',E,'nRand',2e2);
+
+spec.swConv= scale*(spec.swConv + 0.1*mean(spec.swConv, 'all'));
+
+spec = sw_instrument(spec, 'dQ', dQ, 'dE', eres, 'ThetaMin', 3.5, 'Ei', Ei);
+
+e = sqrt(spec.swConv);
+spec.swConv = spec.swConv + e.*randn(size(spec.swConv));
+
+
+data = struct('x', {{0.5*(E(1:end-1) + E(2:end)), Q}}, ...
+ 'y', spec.swConv', ...
+ 'e', e');
+
Initialise the sw_fitpowder class and plot data to inspect initial guess
To initialise the powder fitting class (sw_fitpowder) object requires a spinw model with a magnetic strucutre and couplings added. Additional arguments are the data (can be xye structs for 2D data a cell array of xye structs for 1D data, or HORACE objects - see sw_fitpowder help for more details), a fit function which alters the matrices on the spinw object and a vector of initial guesses for the fit parameters.
Optinally a background strategy can be specified as a 5th positional argument ("planar" background in energy transfer and Q or "independent" background for each 1D cut (1D only)).
The class will call other spinw algorithms such as powspec and sw_instrument. The parameters used in these calls can be set by the user by modifying structs as shown below.
Data can be cropped in Q and energy transfer - for example it is advisible not to include the low-energy transfer region close to the elastic line (relative to the resolution).
fit_func = @(obj, p) matparser(obj, 'param', p, 'mat', {'J_1'}, 'init', true);
+J1 = 0.85;
+
+fitpow = sw_fitpowder(tri, data, fit_func, [J1]);
+fitpow.set_caching(true);
+
+
+fitpow.powspec_args.dE = eres;
+fitpow.powspec_args.fastmode = true;
+fitpow.powspec_args.neutron_output = true;
+fitpow.powspec_args.nRand = 2e2;
+fitpow.powspec_args.hermit = true;
+
+fitpow.sw_instrument_args = struct('dQ', dQ, 'ThetaMin', 3.5, 'Ei', Ei);
+
+
+fitpow.crop_energy_range(1.5*eres(0), inf);
+fitpow.crop_q_range(0.25, 3);
+fitpow.powspec_args.dE = eres;
+fitpow.powspec_args.fastmode = true;
+fitpow.powspec_args.neutron_output = true;
+
+
+fitpow.estimate_constant_background();
+fitpow.estimate_scale_factor();
+
+
+
+
+fitpow.plot_result(fitpow.params, 'EdgeAlpha', 0.9, 'LineWidth', 1 ,'EdgeColor', 'k');
+
Fit and inspect the result
It can be desirable to set bounds on fit parameters and fix some parameters initially to get a reasonable fit with as few degrees of freedom as possible. For example one might assume the background is constant in an initial fit.
The cost function minimised in the fit can be chi-squared ("chisq") or the unweighted sum of the squared residuals ("Rsq"). Also the minimiser can be set to any callable as long as it shares the API of the ndbase minimisers. Typically users would be recommended ndbase.lm or ndbase.simplex.
+fitpow.cost_function = "Rsq";
+fitpow.optimizer = @ndbase.simplex;
+
+
+
+
+fitpow.fix_bg_parameters(1:2);
+fitpow.set_bg_parameter_bounds(3, 0.0, [])
+
+fitpow.fit_background();
+fitpow.fix_bg_parameters(3);
+
+
+fitpow.set_model_parameter_bounds(1, 0, [])
+
+[pfit, cost_val, stat] = fitpow.fit();
+
+
+fitpow.plot_result(pfit, 10, 'EdgeAlpha', 0.9, 'LineWidth', 1 ,'EdgeColor', 'k')
+
+
+qcens = [0.8:0.4:1.6];
+dq = 0.05;
+fitpow.plot_1d_cuts_of_2d_data(qcens-dq, qcens+dq, pfit)
+
Fit 1D cuts
As discussed the sw_fitpowder class can be initialised with a cell array of xye structs correpsonding to 1D cuts at constant Q. Alternatively, having loaded in 2D data the sw_fitpowder class has a convenient method to take 1D cuts and replace the data.
To evaluate the cost function for 1D cuts, the class averages the model over nQ points. Depending on the size of the 2D data and the value of nQ it can be much quicker to fit 1D cuts.
The background for 1D cuts can be handled in two ways:
- A planar background as in the 2D case
- A linear background in energy transfer that's independent for each cut.
The background parameters and bounds are reset if the background strategy is changed to independent.
+fitpow.nQ = 5;
+
+fitpow.replace_2D_data_with_1D_cuts(qcens-dq, qcens+dq, "independent");
+
+
+
+fitpow.fix_bg_parameters(1);
+
+fitpow.set_bg_parameter_bounds(2, 0.0, [])
+
+fitpow.estimate_constant_background();
+fitpow.fit_background();
+fitpow.fix_bg_parameters(2);
+
+[pfit, cost_val, stat] = fitpow.fit();
+
+fitpow.plot_result(pfit)
+
Warning: Clearing 2D data previously added.
+Warning: Overwriting background parmaweters with 0.
+Warning: Background strategy changed - background parameters and bounds
+will be cleared.
+
\ No newline at end of file
diff --git a/tutorials/publish/tutorial39/tutorial39_01.png b/tutorials/publish/tutorial39/tutorial39_01.png
new file mode 100644
index 000000000..0ea55e3df
Binary files /dev/null and b/tutorials/publish/tutorial39/tutorial39_01.png differ
diff --git a/tutorials/publish/tutorial39/tutorial39_02.png b/tutorials/publish/tutorial39/tutorial39_02.png
new file mode 100644
index 000000000..c79717e79
Binary files /dev/null and b/tutorials/publish/tutorial39/tutorial39_02.png differ
diff --git a/tutorials/publish/tutorial39/tutorial39_03.png b/tutorials/publish/tutorial39/tutorial39_03.png
new file mode 100644
index 000000000..c0e10c9a6
Binary files /dev/null and b/tutorials/publish/tutorial39/tutorial39_03.png differ
diff --git a/tutorials/publish/tutorial39/tutorial39_04.png b/tutorials/publish/tutorial39/tutorial39_04.png
new file mode 100644
index 000000000..7134e8193
Binary files /dev/null and b/tutorials/publish/tutorial39/tutorial39_04.png differ
diff --git a/tutorials/publish/tutorial4.m b/tutorials/publish/tutorial4.m
index 1805e32af..3a693a3cf 100644
--- a/tutorials/publish/tutorial4.m
+++ b/tutorials/publish/tutorial4.m
@@ -2,7 +2,7 @@
% We define a square lattice in the ab plane, with Cu+ ions with S=1 spin.
AFsq = spinw;
-AFsq.genlattice('lat_const',[3 3 6],'angled',[90 90 90],'spgr',0)
+AFsq.genlattice('lat_const',[3 3 6],'angled',[90 90 90],'sym',0)
AFsq.addatom('r',[0 0 0],'S', 1,'label','Cu1','color','b')
AFsq.table('atom')
plot(AFsq)
diff --git a/tutorials/publish/tutorial5.m b/tutorials/publish/tutorial5.m
index 86e75db6b..9b31c07de 100644
--- a/tutorials/publish/tutorial5.m
+++ b/tutorials/publish/tutorial5.m
@@ -7,7 +7,7 @@
% are Cu+ with S=1 spin.
FMkagome = spinw;
-FMkagome.genlattice('lat_const',[6 6 5],'angled',[90 90 120],'spgr','P -3')
+FMkagome.genlattice('lat_const',[6 6 5],'angled',[90 90 120],'sym','P -3')
FMkagome.addatom('r', [1/2 0 0], 'S', 1, 'label','MCu1','color','r')
disp('Magnetic atom positions:')
FMkagome.table('matom')
diff --git a/tutorials/publish/tutorial6.m b/tutorials/publish/tutorial6.m
index 80e67c248..4dcfd787d 100644
--- a/tutorials/publish/tutorial6.m
+++ b/tutorials/publish/tutorial6.m
@@ -5,7 +5,7 @@
% positions of the i-th independent position.
kagome4 = spinw;
-kagome4.genlattice('lat_const',[6 6 8],'angled',[90 90 120],'spgr','P -3');
+kagome4.genlattice('lat_const',[6 6 8],'angled',[90 90 120],'sym','P -3');
kagome4.addatom('r', [1/2 0 0],'S', 1,'label','MCu1','color','r');
disp('Atomic positions:')
kagome4.table('atom')
diff --git a/tutorials/publish/tutorial7.m b/tutorials/publish/tutorial7.m
index 1939709b8..312f828c8 100644
--- a/tutorials/publish/tutorial7.m
+++ b/tutorials/publish/tutorial7.m
@@ -3,7 +3,7 @@
% bonds are symmetry equivalent and add a magnetic Cr+ with S=1 spin.
AFkagome = spinw;
-AFkagome.genlattice('lat_const',[6 6 10],'angled',[90 90 120],'spgr','P -3')
+AFkagome.genlattice('lat_const',[6 6 10],'angled',[90 90 120],'sym','P -3')
AFkagome.addatom('r',[1/2 0 0],'S', 1,'label','MCu1','color','r')
plot(AFkagome,'range',[2 2 1])
@@ -31,7 +31,7 @@
% (abc).
S0 = [1 -2 1; 2 -1 -1; 0 0 0];
-AFkagome.genmagstr('mode','direct','k',[0 0 0],'n',[0 0 1],'unitS','lu','S',S0);
+AFkagome.genmagstr('mode','direct','k',[0 0 0],'n',[0 0 1],'unit','lu','S',S0);
disp('Magnetic structure:')
AFkagome.table('mag')
AFkagome.energy
@@ -47,7 +47,7 @@
%% Powder spectrum
afkPow = AFkagome.powspec(linspace(0,2.5,150),'Evect',linspace(0,3,250),...
- 'nRand',1e3,'hermit',false);
+ 'nRand',1e3,'hermit',false,'imagChk',false);
figure
sw_plotspec(afkPow,'axLim',[0 0.2])
diff --git a/tutorials/publish/tutorial8.m b/tutorials/publish/tutorial8.m
index 16d945c5b..cbb061b5b 100644
--- a/tutorials/publish/tutorial8.m
+++ b/tutorials/publish/tutorial8.m
@@ -3,7 +3,7 @@
% bonds are symmetry equivalent and add a magnetic Cr+ with S=1 spin.
AF33kagome = spinw;
-AF33kagome.genlattice('lat_const',[6 6 40],'angled',[90 90 120],'spgr','P -3')
+AF33kagome.genlattice('lat_const',[6 6 40],'angled',[90 90 120],'sym','P -3')
AF33kagome.addatom('r',[1/2 0 0],'S', 1,'label','MCu1','color','r')
plot(AF33kagome,'range',[2 2 1/2],'cellMode','inside')
@@ -31,7 +31,7 @@
S0 = [0 0 -1; 1 1 -1; 0 0 0];
AF33kagome.genmagstr('mode','helical','k',[-1/3 -1/3 0],...
- 'n',[0 0 1],'unitS','lu','S',S0,'nExt',[3 3 1]);
+ 'n',[0 0 1],'unit','lu','S',S0,'nExt',[3 3 1]);
disp('Magnetic structure:')
AF33kagome.table('mag')
AF33kagome.energy
@@ -44,8 +44,8 @@
% magnetic ground state. After calculating the diagonal of the correlation
% function we can see that only a few modes have non-zero intensity.
-kag33Spec = AF33kagome.spinwave({[-1/2 0 0] [0 0 0] [1/2 1/2 0] 250});
-kag33Spec = sw_egrid(kag33Spec,'component','Sxx+Syy+Szz');
+kag33Spec = AF33kagome.spinwave({[-1/2 0 0] [0 0 0] [1/2 1/2 0] 250},'hermit',false);
+kag33Spec = sw_egrid(kag33Spec,'component','Sxx+Syy+Szz','imagChk',false);
figure
subplot(2,1,1)
sw_plotspec(kag33Spec,'mode',1,'axLim',[0 2.5],'colorbar',false',...
@@ -64,7 +64,7 @@
S0 = [0 0 -1; 1 1 -1; 0 0 0];
AF33kagome.genmagstr('mode','helical','k',[-1/3 -1/3 0],...
- 'n',[0 0 1],'unitS','lu','S',S0,'nExt',[1 1 1]);
+ 'n',[0 0 1],'unit','lu','S',S0,'nExt',[1 1 1]);
disp('Magnetic structure:')
AF33kagome.table('mag')
AF33kagome.energy
@@ -82,7 +82,7 @@
% only be calculated using the magnetic supercell.
kag33Spec = AF33kagome.spinwave({[-1/2 0 0] [0 0 0] [1/2 1/2 0] 250},'hermit',false);
-kag33Spec = sw_egrid(kag33Spec,'component','Sxx+Syy+Szz','imagChk',false);
+kag33Spec = sw_egrid(kag33Spec,'component','Sxx+Syy+Szz');
figure
subplot(2,1,1)
sw_plotspec(kag33Spec,'mode',1,'axLim',[0 2.5],'colorbar',false',...
diff --git a/tutorials/publish/tutorial9.m b/tutorials/publish/tutorial9.m
index 9d84f1c56..f1ed2b4f0 100644
--- a/tutorials/publish/tutorial9.m
+++ b/tutorials/publish/tutorial9.m
@@ -3,7 +3,7 @@
% spin.
DMkag = spinw;
-DMkag.genlattice('lat_const',[6 6 40],'angled',[90 90 120],'spgr','P -3')
+DMkag.genlattice('lat_const',[6 6 40],'angled',[90 90 120],'sym','P -3')
DMkag.addatom('r', [1/2 0 0],'S',1,'label', 'Cu1', 'color','r')
plot(DMkag,'range',[2 2 1])
swplot.zoom(4)
@@ -38,7 +38,7 @@
% previously given spin quantum number in the spinw.addatom method.
S0 = [1 -2 1; 2 -1 -1; 0 0 0];
-DMkag.genmagstr('mode','direct','k',[0 0 0],'n',[0 0 1],'unitS','lu', 'S',S0);
+DMkag.genmagstr('mode','direct','k',[0 0 0],'n',[0 0 1],'unit','lu', 'S',S0);
DMkag.energy
plot(DMkag,'range',[3 3 1/2])
diff --git a/tutorials/tutorial/aniso_rotation.m b/tutorials/tutorial/aniso_rotation.m
index 81f4b8b29..f50cea1f7 100644
--- a/tutorials/tutorial/aniso_rotation.m
+++ b/tutorials/tutorial/aniso_rotation.m
@@ -5,22 +5,22 @@
v1 = [1 0 0];
v2 = [1 1 1]/sqrt(3);
-Find the axis around which v1 can be rotated to v2:
+%Find the axis around which v1 can be rotated to v2:
ax = cross(v1,v2);
-Find the rotation angle:
+%Find the rotation angle:
phi = atan2(norm(cross(v1,v2)),dot(v1,v2));
-Create a rotation matrix:
+%Create a rotation matrix:
R = sw_rotmat(ax,phi);
-Rotate the Aniso matrix:
+% Rotate the Aniso matrix:
-A = R*Aniso*R?;
+A = R*Aniso*R;
-This A matrix will define an easy axis anisotropy along the (111) direction with the size of 1 meV. To double check that it is right, we calculate the eigenvalues:
+%This A matrix will define an easy axis anisotropy along the (111) direction with the size of 1 meV. To double check that it is right, we calculate the eigenvalues:
-[V,Aniso2] = eig(A);
+[V, Aniso2] = eig(A);