Skip to content

Commit

Permalink
RELEASE PySpinW v4 (#190)
Browse files Browse the repository at this point in the history
* Fix it so example ipynb works. Update pyspinw CI

Uses new matlab2python script from libpymcr to
  generate wrappers (and so pydoc works on spinw)
Remove non-ascii characters in mtimesx which was
  confusing mcc
Update Python __init__.py so initialized check is global
Fix issue in addmatrix where Python could input
  an integer vector value breaking the code.
Fix bug in sw_plotspec where handles were unassigned.

* Add python command to compile all ctfs

* Fix addmatrix test fail

* Add wrappers for common Matlab plotting commands

* Update change log. Fix addmatrix again.

* Change compile ctf CI to use mcc_all.py
  • Loading branch information
mducle authored May 31, 2024
1 parent 3b929c4 commit 89c63fa
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 39 deletions.
28 changes: 19 additions & 9 deletions .github/workflows/build_pyspinw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ jobs:

build_ctfs:
needs: compile_mex
strategy:
matrix:
matlab_version: [R2021a, R2021b, R2022a, R2022b, R2023a]
runs-on: self-hosted
steps:
- name: Check out SpinW
Expand All @@ -71,11 +68,12 @@ jobs:
- name: Build ctf
run: |
cd python
/Applications/MATLAB_${{ matrix.matlab_version }}.app/bin/matlab -nodisplay -r "build_ctf; exit"
mkdir ctf
python mcc_all.py
- name: Upload CTF results
uses: actions/upload-artifact@v4
with:
name: ctf-${{ matrix.matlab_version }}
name: ctf-all
path: ${{ github.workspace }}/python/ctf/*.ctf

build_wheel:
Expand All @@ -101,15 +99,27 @@ jobs:
echo "PYSPINW_VERSION=$( cat pyproject.toml | grep "version = \"" | awk -F'"' '$0=$2' | sed 's/ //g' )" >> $GITHUB_ENV
mkdir pyspinw/ctfs
find ctf/ -name "*.ctf" -exec mv '{}' pyspinw/ctfs \;
- name: Build Wheel
run: |
cd ${{ github.workspace }}/python
python -m pip wheel --no-deps --wheel-dir build .
- name: Set up MATLAB
uses: matlab-actions/setup-matlab@v2
with:
release: R2023a
products: MATLAB_Compiler_SDK
# Cannot run matlab directly from the setup (gives license error) need to download a runner with the run-command actions
- name: Download Matlab command runner
uses: matlab-actions/run-command@v2
with:
command: "ver"
- name: Generate wrappers
run: |
python -m pip install libpymcr
wget https://gist.github.com/mducle/9186d062b42f05507d831af3d6677a5d/raw/cd0b0d3ed059f4e13d0364e98312dcddc2690ced/run_gh_matlab.sh
chmod 777 run_gh_matlab.sh
matlab2python -a swfiles -a external --preamble "import pyspinw; m = pyspinw.Matlab()" --matlabexec `pwd`/run_gh_matlab.sh
mv matlab_wrapped python/pyspinw
- name: Build Wheel
run: |
cd ${{ github.workspace }}/python
python -m pip wheel --no-deps --wheel-dir build .
- name: Run python test
run: |
pip install scipy
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# [Unreleased](https://github.com/spinw/spinw/compare/v3.2.0...master)
# [v4.0.0](https://github.com/spinw/spinw/compare/v3.2.0...v4.0.0)

## New Features

- Add a function to output Mantid MDHistogramWorkspaces (`sw_spec2MDHisto`)
- Add Python plotting of magnetic structure using [vispy](https://vispy.org/)
- Add mex files to compute main loop in `spinwave()` enabling a 2x - 4x speed up depending on system size
- Add python wrapper for all Matlab functions so e.g. `sw_plotspec` etc can be called without the `m.` prefix in pyspinw:q

## Improvements

Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ authors:
- family-names: "Waite"
given-names: "Richard"
title: "libpymcr"
version: "3.2.0"
version: "4.0.0"
date-released: "2023-06-12"
license: "GPL-3.0-only"
repository: "https://github.com/spinw/spinw"
Expand Down
4 changes: 2 additions & 2 deletions external/mtimesx/sw_mtimesx.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@
% exception is a sparse scalar times an nD full array. In that special case,
% mtimesx will treat the sparse scalar as a full scalar and return a full nD result.
%
% Note: The ‘N’, ‘T’, and ‘C’ have the same meanings as the direct inputs to the BLAS
% routines. The ‘G’ input has no direct BLAS counterpart, but was relatively easy to
% Note: The N, T, and C have the same meanings as the direct inputs to the BLAS
% routines. The G input has no direct BLAS counterpart, but was relatively easy to
% implement in mtimesx and saves time (as opposed to computing conj(A) or conj(B)
% explicitly before calling mtimesx).
%
Expand Down
17 changes: 6 additions & 11 deletions python/build_ctf.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@
package_name = ['SpinW_', VERSION];
full_package = ['SpinW_', VERSION, '.ctf'];

opts = compiler.build.ProductionServerArchiveOptions( ...
['matlab', filesep, 'call.m'], ...
'ArchiveName', package_name, ...
'OutputDir', out_dir, ...
'AutoDetectDataFiles', 'on', ...
'AdditionalFiles', { ...
['..', filesep, 'swfiles'], ...
['..', filesep, 'external'], ...
['..', filesep, 'dat_files']});

compiler.build.productionServerArchive(opts);
mcc('-U', '-W', ['CTF:' package_name], ...
'-d', out_dir, ...
'matlab/call.m', ...
'-a', '../swfiles', ...
'-a', '../external', ...
'-a', '../dat_files')
11 changes: 11 additions & 0 deletions python/mcc_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from libpymcr.utils import checkPath
import os
import subprocess

for v in ['R2021a', 'R2021b', 'R2022a', 'R2022b', 'R2023a', 'R2023b', 'R2024a']:
print(f'Compiling for {v}')
mlPath = checkPath(v)
rv = subprocess.run([os.path.join(mlPath, 'bin', 'matlab'), '-batch', '"build_ctf; exit"'], capture_output=True)
if rv.returncode != 0:
print(rv.stdout.decode())
print(rv.stderr.decode())
7 changes: 4 additions & 3 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ include = [ { path = "pyspinw/ctfs/*" } ]

[tool.poetry.dependencies]
python = ">=3.8,<=3.12"
libpymcr = ">=0.1.3"
libpymcr = ">=0.1.7"
numpy = "^1.21.4"
pyqt5="~5.15"
vispy=">=0.14.1"
pyqt5 = "~5.15"
vispy = ">=0.14.1"
scipy = ">=1.0.0"

# Optional dependencies
pytest = {version = ">=7.0.0", optional = true}
Expand Down
29 changes: 20 additions & 9 deletions python/pyspinw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
'version': 'R' + file.name.split('.')[0].split('SpinW_')[1]
})

VERSION = ''
INITIALIZED = False


class Matlab(libpymcr.Matlab):
def __init__(self, matlab_path: Optional[str] = None, matlab_version: Optional[str] = None):
Expand All @@ -30,31 +33,39 @@ def __init__(self, matlab_path: Optional[str] = None, matlab_version: Optional[s
than 1 MATLAB installation.
"""

initialized = False
if matlab_version is None:
global INITIALIZED
global VERSION
if INITIALIZED:
super().__init__(VERSION, mlPath=matlab_path)
elif matlab_version is None:
for version in _VERSIONS:
if initialized:
if INITIALIZED:
break
try:
print(f"Trying MATLAB version: {version['version']} ({version['file']}))")
super().__init__(version['file'], mlPath=matlab_path)
initialized = True
INITIALIZED = True
VERSION = version['version']
except RuntimeError:
continue
else:
ctf = [version['file'] for version in _VERSIONS if version['version'].lower() == matlab_version.lower()]
ctf = [version for version in _VERSIONS if version['version'].lower() == matlab_version.lower()]
if len(ctf) == 0:
raise RuntimeError(
f"Compiled library for MATLAB version {matlab_version} not found. Please use: [{', '.join([version['version'] for version in _VERSIONS])}]\n ")
else:
ctf = ctf[0]
try:
super().__init__(ctf, mlPath=matlab_path)
initialized = True
super().__init__(ctf['file'], mlPath=matlab_path)
INITIALIZED = True
VERSION = ctf['version']
except RuntimeError:
pass
if not initialized:
if not INITIALIZED:
raise RuntimeError(
f"No MATLAB versions found. Please use: [{', '.join([version['version'] for version in _VERSIONS])}]\n "
f"No supported MATLAB versions [{', '.join([version['version'] for version in _VERSIONS])}] found.\n "
f"If installed, please specify the root directory (`matlab_path` and `matlab_version`) of the MATLAB "
f"installation.")

from .matlab_wrapped import *
from .matlab_plotting import *
97 changes: 97 additions & 0 deletions python/pyspinw/matlab_plotting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
A set of wrappers for comman Matlab plotting commands so you don't have to use the m. prefix
"""

import pyspinw
m = pyspinw.Matlab()

def plot(*args, **kwargs):
"""
Wrapper around Matlab plot() function
"""
return m.plot(*args, **kwargs)

def subplot(*args, **kwargs):
"""
Wrapper around Matlab subplot() function
"""
return m.subplot(*args, **kwargs)

def xlim(*args, **kwargs):
"""
Wrapper around Matlab xlim() function
"""
if args and isinstance(args[0], str):
return m.xlim(*args, **kwargs)
else:
m.xlim(*args, **kwargs)

def ylim(*args, **kwargs):
"""
Wrapper around Matlab ylim() function
"""
if args and isinstance(args[0], str):
return m.ylim(*args, **kwargs)
else:
m.ylim(*args, **kwargs)

def xlabel(*args, **kwargs):
"""
Wrapper around Matlab xlabel() function
"""
return m.xlabel(*args, **kwargs)

def ylabel(*args, **kwargs):
"""
Wrapper around Matlab ylabel() function
"""
return m.ylabel(*args, **kwargs)

def set(*args, **kwargs):
"""
Wrapper around Matlab set() function
"""
m.set(*args, **kwargs)

def get(*args, **kwargs):
"""
Wrapper around Matlab get() function
"""
return m.get(*args, **kwargs)

def gca(*args, **kwargs):
"""
Wrapper around Matlab gca() function
"""
return m.gca(*args, **kwargs)

def gcf(*args, **kwargs):
"""
Wrapper around Matlab gcf() function
"""
return m.gcf(*args, **kwargs)

def legend(*args, **kwargs):
"""
Wrapper around Matlab legend() function
"""
return m.legend(*args, **kwargs)

def hold(*args, **kwargs):
"""
Wrapper around Matlab hold() function
"""
m.hold(*args, **kwargs)

def pcolor(*args, **kwargs):
"""
Wrapper around Matlab pcolor() function
"""
return m.pcolor(*args, **kwargs)

def contour(*args, **kwargs):
"""
Wrapper around Matlab contour() function
"""
return m.contour(*args, **kwargs)

4 changes: 4 additions & 0 deletions swfiles/@spinw/addmatrix.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ function addmatrix(obj, varargin)
error('spinw:addmatrix:WrongInput','Matrix value has to be numeric or symbolic variable!')
end

if isnumeric(newMat.value)
newMat.value = double(newMat.value);
end

if ~isempty(newMat.value)
newMat.mat = newMat.value;
end
Expand Down
6 changes: 3 additions & 3 deletions swfiles/sw_plotspec.m
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
param.mode = 3;
param.dashed = true;
param.colorbar = false;
[fHandle0, pHandle0] = plotspec_internal(spectra, param);
[fHandle, pHandle] = plotspec_internal(spectra, param);
end
% Fallthrough - plots dispersion line on top of colormap if applicable
if numel(spectra.hklA) ~= length(spectra.hklA)
Expand All @@ -265,13 +265,13 @@
param.imag = ~pColor & param.imag;
param.colormap = cMap0;
param.axLim = [0, 1.1*Emax];
[fHandle0, pHandle0] = plotspec_internal(spectra, param);
[fHandle, pHandle] = plotspec_internal(spectra, param);
else
error('sw_plotspec:WrongInput', ['Input looks like a powder spectrum but has no '...
'''swConv'' field, something has gone wrong somewhere']);
end
else
[fHandle0, pHandle0] = plotspec_internal(spectra, param);
[fHandle, pHandle] = plotspec_internal(spectra, param);
end

if ~isfield(spectra,'swConv') && param.mode>1 && param.mode<4
Expand Down

0 comments on commit 89c63fa

Please sign in to comment.