From 172a77d737514a474a42f22de32c3611b8607b4a Mon Sep 17 00:00:00 2001 From: abuts Date: Wed, 9 Oct 2024 16:17:19 +0100 Subject: [PATCH] horace-spinW tests (#13) * Re #12 Base file for Matlab unit test runner * Re #12 existing fitting test modified according to Horace standards * Re #12 tests for sqw model calculations * Re #12 tests passing locally trough runner script * Re #12 removed Jenkinsfile as github actions are not used. * Re #12 attempt to fix tests on two levels -- proper? environmental variables exports in build.yml and use current path if previous step fails * Re #12 Attempt to fix workflows/build.yml * Re #12 chasing syntax error in build.yml * Re #12 chasing typo in build.yml * Re #12 still chasing typo in build.yml * Re #12 Trying to simplify Horace build * Re #12 error in build.yml * Re #12 Push again to overcome 404 Removed presumably unnecessary pack/unpack stuff * Re #12 trying to fix issue with location of the Horace installation. * Re #12 fixing location of spinw and position of Horace wrt euphonic * Re #12 identifying and fixing path-es * Re #12 fixing pathes * Re #12 attempt to fix spinW installation and unit test reactions * Re #12 fixing spinw installation * Re #12 fixing spinW installation * Re #12 fixing spinw installation. Remove (unknown?) spinw_init * Re #12 extracted run_gh_matlab.sh from gists to the build repository * Re #12 slightly refactor installation script to separate installation code. Temporary broken to validate reaction to errors. * Re #12 enabling horace installation without spinw (should still work) * Re #12 check if failing test is indeed fails actions * Re #12 fix the tests and minor comments added --- .github/workflows/build.yml | 33 +- Jenkinsfile | 289 ------------------ iron_spinwaves/Jenkinsfile | 131 -------- iron_spinwaves/run_spinw_horace_test.m | 66 ---- ..._tests.m => setup_and_run_euphonic_tests.m | 17 +- .../fe_cut.sqw | Bin .../test_model_evaluation.m | 102 +++++++ test_spinW_horace_matlab/test_spinw_fitting.m | 65 ++++ .../extract_artifact.ps1 | 0 tools/run_gh_matlab.sh | 12 + validate_horace_spinW_matlab_interface.m | 210 +++++++++++++ 11 files changed, 420 insertions(+), 505 deletions(-) delete mode 100644 Jenkinsfile delete mode 100644 iron_spinwaves/Jenkinsfile delete mode 100644 iron_spinwaves/run_spinw_horace_test.m rename setup_and_run_tests.m => setup_and_run_euphonic_tests.m (63%) rename {iron_spinwaves => test_spinW_horace_matlab}/fe_cut.sqw (100%) create mode 100644 test_spinW_horace_matlab/test_model_evaluation.m create mode 100644 test_spinW_horace_matlab/test_spinw_fitting.m rename {powershell_scripts => tools}/extract_artifact.ps1 (100%) create mode 100644 tools/run_gh_matlab.sh create mode 100644 validate_horace_spinW_matlab_interface.m diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4020d6a..175c635 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,9 +59,8 @@ jobs: - name: Install dependencies run: | python -m pip install psutil numpy brille requests wheel - git clone --depth 1 https://github.com/spinw/spinw - wget https://gist.github.com/mducle/9186d062b42f05507d831af3d6677a5d/raw/cd0b0d3ed059f4e13d0364e98312dcddc2690ced/run_gh_matlab.sh - chmod 755 run_gh_matlab.sh + git clone --depth 1 https://github.com/spinw/spinw spinw_git + chmod 755 ./tools/run_gh_matlab.sh - name: Build euphonic run: | git clone --depth 1 https://github.com/pace-neutrons/euphonic euphonic.git @@ -74,32 +73,31 @@ jobs: git submodule update --init light_python_wrapper python create_mltbx.py cd mltbx - ../../run_gh_matlab.sh create_mltbx + echo "EUPHONIC_TOOLBOX=$(pwd)" >> $GITHUB_ENV + ../../tools/run_gh_matlab.sh create_mltbx cp horace_euphonic_interface.mltbx ../../ - name: Build Horace-master run: | if [[ "${{ inputs.jenkins_id }}" == "" ]] then - git clone --depth 1 https://github.com/pace-neutrons/horace horace.git - cd horace.git + git clone --depth 1 https://github.com/pace-neutrons/horace horace_git + cd horace_git else # Using sparse checkout for a specific SHA - mkdir horace.git - cd horace.git + mkdir horace_git + cd horace_git git init git remote add origin https://github.com/pace-neutrons/horace git fetch origin ${{ inputs.jenkins_id }} git checkout FETCH_HEAD fi - ./tools/build_config/build.sh -p - mkdir -p build/Horace - cd build/Horace - find .. -maxdepth 1 -name "Horace*tar.gz" -exec tar zxf '{}' --strip-components=1 \; - cd ../../../ - mv horace.git/build/Horace . + echo "HORACE_PATH=$(pwd)" >> $GITHUB_ENV + ./tools/build_config/build.sh -b + cd ./../ - name: Run spinw test run: | - ./run_gh_matlab.sh "run('iron_spinwaves/run_spinw_horace_test.m')" + export HORACE_PATH=${{ env.HORACE_PATH }} + ./tools/run_gh_matlab.sh "run('validate_horace_spinW_matlab_interface')" # Brille not integrated into SpinW properly yet - skip the test for now #- name: Run brille test # run: | @@ -109,7 +107,10 @@ jobs: - name: Run euphonic test run: | export PYTHON_EX_PATH=`which python` - ./run_gh_matlab.sh setup_and_run_tests + export HORACE_PATH=${{ env.HORACE_PATH }} + export EUPHONIC_TOOLBOX=${{ env.EUPHONIC_TOOLBOX}} + + ./tools/run_gh_matlab.sh setup_and_run_euphonic_tests #- name: Setup tmate # if: ${{ failure() }} # uses: mxschmitt/action-tmate@v3 diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index bc5be7d..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,289 +0,0 @@ -#!groovy - -@Library('PACE-shared-lib') import pace.common.PipeLineInfo - -pli = new PipeLineInfo(env.JOB_BASE_NAME) - -def is_master_build(String build_type) { - if (build_type == 'Nightly') { - return true - } else { - return false - } -} - -def get_readable_os(String os) { - if (os == 'sl7') { - return 'Scientific-Linux-7' - } else if (os == 'win10') { - return 'Windows-10' - } else { - return '' - } -} - -def get_build_info(String repo, String branch, String match_context) { - def job_name - def build_num - def script_cmd = "python get_build_info.py ${repo} ${branch}" - if (match_context) { - script_cmd += " --match-context ${match_context}" - } - withCredentials([string(credentialsId: 'GitHub_API_Token', - variable: 'api_token')]) { - if (isUnix()) { - build_info = sh(script: "module load conda/3 && ${script_cmd}", returnStdout: true) - } else { - build_info = bat(script: script_cmd, returnStdout: true) - } - } - println build_info - // Index from the end to ignore any previous output - job_name = build_info.tokenize(' |\n')[-2].trim() - build_num = build_info.tokenize(' |\n')[-1].trim() - return [job_name, build_num] -} - -def get_artifact_url(String branch) { - def artifact_url - def script_cmd = "python get_artifact_url.py ${branch}" - if (isUnix()) { - artifact_url = sh(script: "module load conda/3 && ${script_cmd}", returnStdout: true) - } else { - artifact_url = bat(script: script_cmd, returnStdout: true) - } - println artifact_url - return artifact_url.tokenize(' |\n')[-1].trim() -} - -properties([ - parameters([ - string( - defaultValue: '', - description: 'The version of Matlab to use e.g. 2019b.', - name: 'MATLAB_VERSION', - trim: true - ), - string( - defaultValue: utilities.get_agent(pli.os), - description: 'The agent to execute the pipeline on.', - name: 'AGENT', - trim: true - ), - string( - defaultValue: '', - description: 'The branch of Horace to test against', - name: 'HORACE_BRANCH', - trim: true - ), - string( - defaultValue: '', - description: 'The branch of Euphonic to test against', - name: 'EUPHONIC_BRANCH', - trim: true - ), - string( - defaultValue: '', - description: 'The branch of horace-euphonic-interface to test against', - name: 'HORACE_EUPHONIC_INTERFACE_BRANCH', - trim: true - ) - ]) -]) - -pipeline { - - agent { - label env.AGENT - } - - environment { - MATLAB_VERSION = utilities.get_param('MATLAB_VERSION', pli.matlab_release.replace('R', '')) - CONDA_ENV_NAME = "py37_pace_integration_${env.MATLAB_VERSION}" - CONDA_PY_VERSION = "3.7" - HORACE_BRANCH = utilities.get_param('HORACE_BRANCH', 'master') - EUPHONIC_BRANCH = utilities.get_param('EUPHONIC_BRANCH', 'master') - HORACE_EUPHONIC_INTERFACE_BRANCH = utilities.get_param('HORACE_EUPHONIC_INTERFACE_BRANCH', 'master') - } - - triggers { - cron(is_master_build(pli.build_type) ? 'H 5 * * 2-6' : '') - } - - stages { - stage("Get-PACE-jenkins-shared-library") { - steps { - dir('PACE-jenkins-shared-library') { - checkout([ - $class: 'GitSCM', - branches: [[name: "refs/heads/main"]], - extensions: [[$class: 'WipeWorkspace']], - userRemoteConfigs: [[url: 'https://github.com/pace-neutrons/PACE-jenkins-shared-library.git']] - ]) - } - } - } - - stage("Get-Horace") { - steps { - script { - def project_name = "PACE-neutrons/Horace/" - def selec - if (is_master_build(pli.build_type) || env.HORACE_BRANCH == 'master') { - selec = lastSuccessful() - project_name = project_name + get_readable_os(pli.os) + "-${env.MATLAB_VERSION}" - } else { - def (job_name, build_num) = get_build_info( - 'Horace', env.HORACE_BRANCH, get_readable_os(pli.os) + "-${env.MATLAB_VERSION}") - selec = specific(buildNumber: build_num) - project_name = project_name + job_name - } - copyArtifacts( - filter: 'build/Horace-*', - fingerprintArtifacts: true, - projectName: project_name, - selector: selec - ) - if (isUnix()) { - sh ''' - archive_name="\$(find -name Horace-*.tar.gz)" - mkdir Horace && tar --strip-components=1 -xf \$archive_name -C Horace - ''' - } - else { - powershell './powershell_scripts/extract_artifact.ps1 "build/Horace-*.zip" "Horace"' - } - - } - } - } - - stage("Get-Horace-Euphonic-Interface-Matlab") { - steps { - script { - withCredentials([string(credentialsId: 'GitHub_API_Token', - variable: 'api_token')]) { - def artifact_url = get_artifact_url(env.HORACE_EUPHONIC_INTERFACE_BRANCH) - if (isUnix()) { - sh """ - curl -LO -H "Authorization: token ${api_token}" --request GET ${artifact_url} - unzip zip - """ - } - else { - powershell """ - [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" - Invoke-RestMethod -Uri ${artifact_url} \ - -Headers @{Authorization = "token ${api_token}"} \ - -Method 'GET' \ - -ContentType 'application/zip' \ - -OutFile 'horace_euphonic_interface.mltbx.zip' - ./powershell_scripts/extract_artifact.ps1 "horace_euphonic_interface.mltbx.zip" "horace_euphonic_interface.mltbx" - """ - } - } - } - } - } - - stage("Get-Euphonic") { - steps { - dir('Euphonic') { - checkout([ - $class: 'GitSCM', - branches: [[name: "refs/heads/${env.EUPHONIC_BRANCH}"]], - extensions: [[$class: 'WipeWorkspace']], - userRemoteConfigs: [[url: 'https://github.com/pace-neutrons/Euphonic.git']] - ]) - } - } - } - - stage("Create-Conda-Environment") { - steps { - script { - if (isUnix()) { - sh """ - module load conda/3 && - conda create --name \$CONDA_ENV_NAME python=\$CONDA_PY_VERSION -y - """ - } - else { - powershell './PACE-jenkins-shared-library/powershell_scripts/create_conda_environment.ps1' - } - } - } - } - - stage("Install-Euphonic") { - steps { - dir('Euphonic') { - // Note psutil is euphonic_sqw_models dependency - script { - if (isUnix()) { - sh ''' - module load conda/3 && - module load gcc && - conda activate \$CONDA_ENV_NAME && - python -mpip install --upgrade pip && - python -mpip install psutil && - python -mpip install numpy && - python -mpip install . - ''' - } - else { - bat """ - CALL "%VS2019_VCVARSALL%" x86_amd64 - CALL conda activate %CONDA_ENV_NAME% - python -mpip install --upgrade pip - python -mpip install psutil - python -mpip install numpy - python -mpip install . - """ - } - } - } - } - } - - stage("Set-Up-Matlab-And-Run-Tests") { - steps { - script { - if (isUnix()) { - sh ''' - module load conda/3 && - conda activate \$CONDA_ENV_NAME && - export PYTHON_EX_PATH=`which python` && - module load matlab/R\$MATLAB_VERSION && - matlab -nosplash -nodesktop -batch "setup_and_run_tests" - ''' - } - else { - powershell './PACE-jenkins-shared-library/powershell_scripts/execute_matlab_command.ps1 "setup_and_run_tests"' - } - } - } - } - } - post { - unsuccessful { - withCredentials([string(credentialsId: 'Euphonic_contact_email', variable: 'euphonic_email'), - string(credentialsId: 'Horace_contact_email', variable: 'horace_email')]){ - script { - if (is_master_build(pli.build_type)) { - mail ( - to: "${euphonic_email},${horace_email}", - subject: "PACE integration pipeline failed: ${env.JOB_BASE_NAME}", - body: "See ${env.BUILD_URL}" - ) - } - } - } - } - - cleanup { - deleteDir() - } - - } -} diff --git a/iron_spinwaves/Jenkinsfile b/iron_spinwaves/Jenkinsfile deleted file mode 100644 index a0bd001..0000000 --- a/iron_spinwaves/Jenkinsfile +++ /dev/null @@ -1,131 +0,0 @@ -#!groovy - -def get_os(String jobname) { - if (jobname.contains('Scientific-Linux-7')) { - return 'Scientific-Linux-7' - } else if (jobname.contains('Windows-10')) { - return 'Windows-10' - } else { - return '' - } -} - -def get_matlab_ver(String jobname) { - return jobname[-5..-1] -} - -def get_agent(String jobname) { - if (jobname.contains('Scientific-Linux-7')) { - withCredentials([string(credentialsId: 'sl7_agent', variable: 'agent')]) { - return "${agent}" - } - } else if (jobname.contains('Windows-10')) { - withCredentials([string(credentialsId: 'win10_agent', variable: 'agent')]) { - return "${agent}" - } - } else { - return '' - } -} - -pipeline { - - agent { - label get_agent(env.JOB_BASE_NAME) - } - - environment { - MATLAB_VERSION = get_matlab_ver(env.JOB_BASE_NAME) - } - - triggers { - cron('H 6 * * 2-6') - } - - stages { - stage("Get-PACE-jenkins-shared-library") { - steps { - dir('PACE-jenkins-shared-library') { - checkout([ - $class: 'GitSCM', - branches: [[name: "refs/heads/main"]], - extensions: [[$class: 'WipeWorkspace']], - userRemoteConfigs: [[url: 'https://github.com/pace-neutrons/PACE-jenkins-shared-library.git']] - ]) - } - } - } - - stage("Get-Horace") { - steps { - script { - copyArtifacts( - filter: 'build/Horace-*', - fingerprintArtifacts: true, - projectName: "PACE-neutrons/Horace/" + get_os(env.JOB_BASE_NAME) + "-${env.MATLAB_VERSION}", - selector: lastSuccessful() - ) - if (isUnix()) { - sh ''' - archive_name="\$(find -name Horace-*.tar.gz)" - mkdir Horace && tar --strip-components=1 -xf \$archive_name -C Horace - ''' - } - else { - powershell './powershell_scripts/extract_artifact.ps1 "build/Horace-*.zip" "Horace"' - } - - } - } - } - - stage("Get-SpinW") { - steps { - dir('spinw') { - checkout([ - $class: 'GitSCM', - branches: [[name: "refs/heads/main"]], - extensions: [[$class: 'WipeWorkspace']], - userRemoteConfigs: [[url: 'https://github.com/spinw/spinw4']] - ]) - } - } - } - - stage("Set-Up-Matlab-And-Run-Tests") { - steps { - script { - if (isUnix()) { - sh ''' - module load matlab/R\$MATLAB_VERSION && - matlab -nosplash -nodesktop -batch "run('iron_spinwaves/run_spinw_horace_test.m')" - ''' - } - else { - powershell './PACE-jenkins-shared-library/powershell_scripts/execute_matlab_command.ps1 "run(\'iron_spinwaves/run_spinw_horace_test.m\')"' - } - } - } - } - } - - post { - unsuccessful { - withCredentials([string(credentialsId: 'SpinW_contact_email', variable: 'spinw_email'), - string(credentialsId: 'Horace_contact_email', variable: 'horace_email')]){ - script { - mail ( - to: "${spinw_email},${horace_email}", - subject: "PACE integration pipeline failed: ${env.JOB_BASE_NAME}", - body: "See ${env.BUILD_URL}" - ) - } - } - } - - cleanup { - deleteDir() - } - - } -} diff --git a/iron_spinwaves/run_spinw_horace_test.m b/iron_spinwaves/run_spinw_horace_test.m deleted file mode 100644 index 21d7f81..0000000 --- a/iron_spinwaves/run_spinw_horace_test.m +++ /dev/null @@ -1,66 +0,0 @@ -this_dir = fileparts(mfilename('fullpath')); -if isempty(which('herbert_init.m')) - parent_dir = split(this_dir, filesep); - parent_dir = join(parent_dir(1:end-1), filesep); - parent_dir = parent_dir{1}; - cd(fullfile(parent_dir, 'Horace')); - cleanup = onCleanup(@()cd(parent_dir)) - horace_install; - horace_on; - addpath(genpath(fullfile(parent_dir, 'spinw'))); - clear('cleanup'); -end - -% Reads in the cut data -w_fe = read_sqw(fullfile(this_dir, 'fe_cut.sqw')); - -% Set up SpinW model -a = 2.87; -fe = spinw(); -fe.genlattice('lat_const', [a, a, a], 'angled', [90, 90, 90], 'spgr', 'I m -3 m') -fe.addatom('label', 'MFe3', 'r', [0, 0, 0], 'S', 5/2, 'color', 'gold') -fe.gencoupling() -fe.addmatrix('label', 'J1', 'value', 1, 'color', 'gray') -fe.addmatrix('label', 'D', 'value', diag([0, 0, -1]), 'color', 'green') -fe.addcoupling('mat', 'J1', 'bond', 1) -fe.addaniso('D') -fe.genmagstr('mode', 'direct', 'S', [0., 0., 1.; 0., 0., 1.]'); - -% Set up multi-fit -% Starting parameters for fit -J = 35; % Exchange interaction in meV -D = 0; % Single-ion anisotropy in meV -gam = 30; % Intrinsic linewidth in meV (inversely proportional to excitation lifetime) -temp = 10; % Sample measurement temperature in Kelvin -amp = 300; % Magnitude of the intensity of the excitation (arbitrary units) - -cpars = {'mat', {'J1', 'D(3,3)'}, 'hermit', false, 'optmem', 1, ... - 'useFast', true, 'resfun', 'sho', 'formfact', true}; - -kk = multifit_sqw(w_fe); -kk = kk.set_fun (@fe.horace_sqw, {[J, D, gam, temp, amp], cpars{:}}); -kk = kk.set_free ([1, 0, 1, 0, 1]); -kk = kk.set_bfun (@linear_bg, [0.1, 0]); -kk = kk.set_bfree ([1, 0]); -kk = kk.set_options ('list', 2); - -tic(); -wsim = kk.simulate(); -t_spinw_single = toc(); -fprintf('Time to evaluate a single iteration: %s s\n', t_spinw_single); - -tic(); -[wfit, fitpars] = kk.fit(); -t_spinw_fit = toc(); -fprintf('Time to run fit: %s s\n', t_spinw_fit); - -if sum(abs(fitpars.p - [15.2440 0 65.9076 10 135.4074])) > 0.1 - disp('FAILURE: fitted parameters found as'); - disp(fitpars.p); - disp('Expected:'); - disp([15.2440 0 65.9076 10 135.4074]); - quit(1); -else - disp('SUCCESS'); -end -pause(1); diff --git a/setup_and_run_tests.m b/setup_and_run_euphonic_tests.m similarity index 63% rename from setup_and_run_tests.m rename to setup_and_run_euphonic_tests.m index 82e6870..0fa239f 100644 --- a/setup_and_run_tests.m +++ b/setup_and_run_euphonic_tests.m @@ -14,10 +14,19 @@ end % Install and load Horace -cd('Horace') +horace_path = getenv('HORACE_PATH'); +if isempty(horace_path) % try default path + rootpath = fileparts(mfilename('fullpath')); + horace_path = fullfile(rootpath,'horace_git'); +else + rootpath = fileparts(horace_path ); +end + +cd(fullfile(horace_path,'admin')) horace_install -cd('..') horace_on +cd(rootpath); + % Install Horace-Euphonic-Interface toolboxes = matlab.addons.toolbox.installedToolboxes; @@ -27,7 +36,9 @@ break; end end -matlab.addons.toolbox.installToolbox(['horace_euphonic_interface.mltbx']); +euphonic_toolbox_path = getenv('EUPHONIC_TOOLBOX'); +toolbox = fullfile(euphonic_toolbox_path,'horace_euphonic_interface.mltbx'); +matlab.addons.toolbox.installToolbox(toolbox); matlab.addons.toolbox.installedToolboxes % Run tests diff --git a/iron_spinwaves/fe_cut.sqw b/test_spinW_horace_matlab/fe_cut.sqw similarity index 100% rename from iron_spinwaves/fe_cut.sqw rename to test_spinW_horace_matlab/fe_cut.sqw diff --git a/test_spinW_horace_matlab/test_model_evaluation.m b/test_spinW_horace_matlab/test_model_evaluation.m new file mode 100644 index 0000000..ad1646e --- /dev/null +++ b/test_spinW_horace_matlab/test_model_evaluation.m @@ -0,0 +1,102 @@ +classdef test_model_evaluation < TestCase + properties + visual_inspection = false + end + % + methods + function obj=test_model_evaluation(varargin) + if nargin == 0 + name = 'test_model_evaluation'; + else + name = varargin{1}; + end + obj = obj@TestCase(name); + % enable visual inspection when investigating models manually + %obj.visual_inspection = true; + end + % + function test_evaluate_model(obj) + clWarn = set_temporary_warning('off','sw_nb:WrongInput'); + sq = sw_model('squareAF',1,0); + + % We add magnetic form factor after the model is defined. Using the same + % atom label and position as an existing atom in the model, the atom + % properties will be updated, no new atom is created using the + % spinw.addatom method. We will use the form factor of Ni2+ that has S=1. + + sq.addatom('label','atom_1','r',[0 0 0],'formfact','MNi2+','S',1) + if obj.visual_inspection + plot(sq) + swplot.zoom(2) + end + + %Spin wave + %We need to define a grid in reciprocal space, here we use the (Qh, Qk, 0) square lattice plane by calling ndgrid() function. + + nQ = 201; + nE = 501; + Qhv = linspace(0,2,nQ); + Qkv = linspace(0,2,nQ); + Qlv = 0; + [Qh, Qk, Ql] = ndgrid(Qhv,Qkv,Qlv); + + % Create a list of Q point, with dimensions of [3 nQ^2]. + Q = [Qh(:) Qk(:) Ql(:)]'; + + % Spin wave spectrum + % We calculates the spin wave spectrum at the list of Q points, bin the diagonal of the spin-spin correlation function (Sxx+Syy+Szz) in energy and convolute with a finite instrumental resolution. + + spec = sq.spinwave(Q); + + Ev = linspace(0,5,nE); + spec = sw_egrid(spec,'component','Sxx+Syy+Szz','Evect',Ev); + spec = sw_instrument(spec,'dE',0.1); + + %Creat the Q map + %The calculated intensity map is stored in spec.swConv, we reshape it into a 3D matrix using Matlab commands. + + spec3D = reshape(spec.swConv,nE-1,nQ,nQ); + + %Plotting E=const cut + % A constant energy cut takes the (Eidx,:,:) elements of the matrix and + % plots it using the Matlab function imagesc(). We also integrate in energy + % the same way Horace does by taking the average of the points and + % rescaling with the energy bin size. + + Ecut = [3.5 4.0]; %meV + Eidx = find(Ev>=Ecut(1) & Ev<=Ecut(2)); + cut1 = squeeze(sum(spec3D(Eidx,:,:),1))/numel(Eidx)/(Ev(2)-Ev(1)); + if obj.visual_inspection + figure; + imagesc(Qhv,Qkv,cut1); + set(gca,'YDir','normal') + xlabel('(H 0 0) (r.l.u.)') + ylabel('(0 K 0) (r.l.u.)') + title('Spin wave spectrum at E = 3 meV, square lattice Heisenberg AF') + clim([0 3]) + colorbar + end + %Constant energy cut using Horace + %We can do the same cut much easyer using Horace (http://horace.isis.rl.ac.uk). Assuming that Horace is installed and initialized we can do the same constant energy cut with just three steps. First we create an empty d3d object that defines the (h,k,0) plane with ranges in momentum and energy. Second we call Horace to fill up the empty d3d object with the simulated spin wave data and finally we plot a constant energy cut. + + Ebin = [0,0.01,5]; + fwhm0 = 0.1; + d3dobj = d3d(sq.abc,[1 0 0 0],[0,0.01,2],[0 1 0 0],[0,0.01,2],[0 0 0 1],Ebin); + d3dobj = disp2sqw_eval(d3dobj,@sq.horace,{'component','Sxx+Syy+Szz'},fwhm0,'-all'); + w2 = cut(d3dobj,[],[],[3.5 4]); + if obj.visual_inspection + plot(w2 ); + colorslider('delete') + title('') + clim([0 3]) + colorbar + end + % TODO: + % Resolution function in sw_instrument and resolution function + % in sw.horace disp2sqw_eval are calculated differently, + % so images look similar but actually different. This should + % eventually be fixed. + assertElementsAlmostEqual(w2.s,cut1,'absolute',0.6); + end + end +end \ No newline at end of file diff --git a/test_spinW_horace_matlab/test_spinw_fitting.m b/test_spinW_horace_matlab/test_spinw_fitting.m new file mode 100644 index 0000000..2eb51c0 --- /dev/null +++ b/test_spinW_horace_matlab/test_spinw_fitting.m @@ -0,0 +1,65 @@ +classdef test_spinw_fitting < TestCase + properties + w_fe % workspace with 1D iron dataset + end + % + methods + function obj=test_spinw_fitting(varargin) + if nargin == 0 + name = 'test_spinw_fitting'; + else + name = varargin{1}; + end + obj = obj@TestCase(name); + this_dir = fileparts(mfilename("fullpath")); + obj.w_fe = read_sqw(fullfile(this_dir, 'fe_cut.sqw')); + end + % + function test_fit(obj) + % Reads in the cut data + + + % Set up SpinW model + a = 2.87; + fe = spinw(); + fe.genlattice('lat_const', [a, a, a], 'angled', [90, 90, 90], 'sym', 'I m -3 m') + fe.addatom('label', 'MFe3', 'r', [0, 0, 0], 'S', 5/2, 'color', 'gold') + fe.gencoupling() + fe.addmatrix('label', 'J1', 'value', 1, 'color', 'gray') + fe.addmatrix('label', 'D', 'value', diag([0, 0, -1]), 'color', 'green') + fe.addcoupling('mat', 'J1', 'bond', 1) + fe.addaniso('D') + fe.genmagstr('mode', 'direct', 'S', [0., 0., 1.; 0., 0., 1.]'); + + % Set up multi-fit + % Starting parameters for fit + J = 35; % Exchange interaction in meV + D = 0; % Single-ion anisotropy in meV + gam = 30; % Intrinsic linewidth in meV (inversely proportional to excitation lifetime) + temp = 10; % Sample measurement temperature in Kelvin + amp = 300; % Magnitude of the intensity of the excitation (arbitrary units) + + cpars = {'mat', {'J1', 'D(3,3)'}, 'hermit', false, 'optmem', 1, ... + 'useFast', true, 'resfun', 'sho', 'formfact', true}; + + kk = multifit_sqw(obj.w_fe); + kk = kk.set_fun (@fe.horace_sqw, {[J, D, gam, temp, amp], cpars{:}}); + kk = kk.set_free ([1, 0, 1, 0, 1]); + kk = kk.set_bfun (@linear_bg, [0.1, 0]); + kk = kk.set_bfree ([1, 0]); + kk = kk.set_options ('list', 2); + + tic(); + wsim = kk.simulate(); + t_spinw_single = toc(); + fprintf('Time to evaluate a single iteration: %s s\n', t_spinw_single); + + tic(); + [wfit, fitpars] = kk.fit(); + t_spinw_fit = toc(); + fprintf('Time to run fit: %s s\n', t_spinw_fit); + + assertEqualToTol(fitpars.p,[15.2440 0 65.9076 10 135.4074],'tol',[1.e-3,1.e-3]); + end + end +end \ No newline at end of file diff --git a/powershell_scripts/extract_artifact.ps1 b/tools/extract_artifact.ps1 similarity index 100% rename from powershell_scripts/extract_artifact.ps1 rename to tools/extract_artifact.ps1 diff --git a/tools/run_gh_matlab.sh b/tools/run_gh_matlab.sh new file mode 100644 index 0000000..9b785b6 --- /dev/null +++ b/tools/run_gh_matlab.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +args=() +for var; do + [[ $var != '-batch' ]] && args+=("$var") +done + +tmpmfile=mlscript_$(mktemp -u XXXXXXXX) +echo "${args[@]}" > ${tmpmfile}.m +runner_path=/home/runner/work/_actions/matlab-actions/run-command/v2/dist/bin/glnxa64/ + +${runner_path}/run-matlab-command "setenv('MW_ORIG_WORKING_FOLDER', cd('`pwd`'));${tmpmfile}" \ No newline at end of file diff --git a/validate_horace_spinW_matlab_interface.m b/validate_horace_spinW_matlab_interface.m new file mode 100644 index 0000000..837da88 --- /dev/null +++ b/validate_horace_spinW_matlab_interface.m @@ -0,0 +1,210 @@ +function err = validate_horace_spinW_matlab_interface(varargin) +% Run unit tests on Horace-spinW integration using MATLAB +% +% Expected to run with github or jenkins actions +% +% +% Exits with non-zero error code if any tests failed +% TODO: Currently throws HORACE:spinw_validation:runtime_error if any test fails +% This should be fixed by modifying github actions workflow to +% correctly process return codes +% +horace_path = getenv('HORACE_PATH'); +% expect spinW is located alongside Horace in the same folder. +install_horace_and_spinw(horace_path); + +if isempty(which('horace_init')) + % enable Horace if not already enabled + horace_on(); +else + fprintf("********** Horace already initialized\n") +end + +% Parse arguments +% --------------- +options = {'-talkative', '-nomex', '-forcemex','-exit_on_completion'}; +[ok, mess, talkative, nomex, forcemex, ... + exit_on_completion,test_folders] = ... + parse_char_options(varargin, options); +if ~ok + error('HORACE:validate_horace:invalid_argument', mess) +end + +if isempty(test_folders) + % no tests specified on command line - run them all + test_folders = ... + {'test_spinW_horace_matlab', ... + }; +end + + +% Generate full test paths to unit tests +% -------------------------------------- +pths = horace_paths; +test_path = pths.test; +test_folders_full = fullfile(test_path, test_folders); + +hor = hor_config(); +hpc = hpc_config(); +par = parallel_config(); +% Validation must always return Horace and Herbert to their initial states, +% regardless of any changes made in the test routines + +% On exit always revert to initial Horace and Herbert configurations +% ------------------------------------------------------------------ +initial_warn_state = warning(); +warning('off', 'MATLAB:class:DestructorError'); + +% only get the public i.e. not sealed, fields +cur_horace_config = hor.get_data_to_store(); +cur_hpc_config = hpc.get_data_to_store(); +cur_par_config = par.get_data_to_store(); + +% remove configurations from memory. Ensure only stored configurations are +% stored +clear config_store; + +% Create cleanup object (*** MUST BE DONE BEFORE ANY CHANGES TO CONFIGURATIONS) +cleanup_obj = onCleanup(@() ... + validate_horace_cleanup(cur_horace_config, ... + cur_hpc_config, ... + cur_par_config, ... + test_folders, ... + initial_warn_state)); + +hor.init_tests = true; +% Run unit tests +% -------------- +argi = {}; +if talkative + argi = [argi, {'-verbose'}]; +end + +test_ok = false(1, numel(test_folders_full)); +time = bigtic(); +for i = 1:numel(test_folders_full) + test_stage_reset(i, hor, hpc, par, nomex, forcemex, talkative); + test_ok(i) = runtests(test_folders_full{i}, argi{:}); +end + +bigtoc(time, '===COMPLETED UNIT TESTS RUN '); + + +close all +clear config_store; + +err = ~all(test_ok); +% This is not a good practice but what is currently supported by github +% actions +% TODO: +% make it more aligned with standard unit test practice. Change will +% probably affect this and gihub actions workflow. +if err + n_failed = sum(~test_ok); + error('HORACE:spinw_validation:runtime_error', ... + '%d out of %d unit tests have failed',n_failed,numel(test_ok)); +end + +end +%------------------------------------------------------------------------------- +function test_stage_reset(icount, hor, hpc, par, nomex, forcemex, talkative) +% Run before each stage +% Set Horace configurations to the defaults (but don't save) +% (The validation should be done starting with the defaults, otherwise an error +% may be due to a poor choice by the user of configuration parameters) + +% Set the default configurations, printing warning only the first time round to +% avoid copious warning messages +warn_state = warning(); +cleanup_obj = onCleanup(@()warning(warn_state)); +if icount>1 + warning('off', 'all'); +end + +set(hor, 'defaults'); +set(hpc, 'defaults'); +% set(par, 'defaults'); + +% Return warning state to incoming state +warning(warn_state) + +% Special unit tests settings. +hor.init_tests = true; % initialise unit tests +hor.use_mex = ~nomex; +hor.force_mex_if_use_mex = forcemex; + +if talkative + hor.log_level = 1; % force log level high. +else + hor.log_level = -1; % turn off informational output +end + +end +%-------------------------------------------------------------------------- +function validate_horace_cleanup(cur_horace_config, cur_hpc_config, ... + cur_par_config, test_folders, initial_warn_state) +% Reset the configurations, and remove unit test folders from the path + +set(hor_config, cur_horace_config); +set(hpc_config, cur_hpc_config); +set(parallel_config, cur_par_config); + +warning('off', 'all'); % avoid warning on deleting non-existent path + +% Clear up the test folders, previously placed on the path +for i = 1:numel(test_folders) + rmpath(test_folders{i}); +end + +warning(initial_warn_state); +end + +function install_horace_and_spinw(horace_path) +% Given horace_path, install horace and spinw if Horace has not been +% installed. +% If Horace was installed, we expect horace_on script initialize spinw. +% +% spinw expected to be located alongside Horace +% +% store reference point to avoid function's side-effects +current_path = pwd; + +if isempty(which('horace_on')) + fprintf("********** Installing Horace\n") + % install Horace first + + if isempty(horace_path) + % are we already in the folder requested? + this_path = fullfile(fileparts(mfilename("fullpath"))); + if isfolder(fullfile(this_path,'admin')) + horace_path = this_path; + else + error('HORACE:validate_horace:runtime_error', ... + 'Horace is not installed and the path %s does not refer to Horace', ... + this_path) + end + end + spinw_path = fullfile(fileparts(horace_path),'spinw_git'); + + if ~isfolder(spinw_path) + error('HORACE:validate_horace:runtime_error', ... + 'Can not find spinW at path: %s',spinw_path); + else + cd(spinw_path) + install_spinw('silent',true); + end + if isfile(fullfile(horace_path,'horace_install.m')) + admin_path = horace_path; + else + admin_path = fullfile(horace_path,'admin'); % do not check further + % it will fail on the next step anyway, if not found + end + + cd(admin_path); + %horace_install('spinW_folder',spinw_path); + horace_install(); + cd(current_path); +else + fprintf("********** Horace already installed\n") +end +end \ No newline at end of file