Skip to content

Commit

Permalink
PySpinW implementation (#143)
Browse files Browse the repository at this point in the history
* Initial test of pySpinW

* Initial test of pySpinW

* Delete SpinW_2023a.ctf

* Update versions

* Modify build

* Update build_pyspinw.yml

* Update build_ctf.m

* Update build_pyspinw.yml

* Update build_pyspinw.yml

Remove R2020a, R2020b as too old

* Update build_pyspinw.yml

Try to fix upload

* Update build_pyspinw.yml

Try full build script

* Adapt package init

* Update build_pyspinw.yml

Add  ctf directory as it was removed

* Update build_pyspinw.yml

Fix depreciation warning

* Update test_spinw.py

* Fix license, init and an example

* Update test_spinw.py

* Update build_pyspinw.yml

* Create release_notes.md

* Update build_pyspinw.yml

Add Tag from version

* Try to build mex

* Update build_pyspinw.yml

* Update build_pyspinw.yml

* Update build_pyspinw.yml

* Modify remove and find

* Update build_pyspinw.yml

* Update pyproject.toml

* Update build_pyspinw.yml

* Some pre-release optimisations

* Update build_pyspinw.yml

* Reverting back to manual removal of old mex files

The find command does not seem to be working on windows, even with a bash shell

* Update pyproject.toml

* Add version synchronisation

* Fix flaky test by specifying seed

* Update build_pyspinw.yml
  • Loading branch information
wardsimon committed May 17, 2023
1 parent 4ede5fa commit 80d30ff
Show file tree
Hide file tree
Showing 9 changed files with 535 additions and 2 deletions.
2 changes: 1 addition & 1 deletion +sw_tests/+unit_tests/unittest_spinw_optmagk.m
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function test_wrong_shape_kbase_raises_error(testCase)
function test_fm_chain_optk(testCase)
testCase.swobj.addmatrix('label', 'J1', 'value', -1);
testCase.swobj.addcoupling('mat', 'J1', 'bond', 1);
out = testCase.swobj.optmagk;
out = testCase.swobj.optmagk('seed', 1);
out.stat = rmfield(out.stat, 'nFunEvals');

expected_mag_str = testCase.default_mag_str;
Expand Down
118 changes: 118 additions & 0 deletions .github/workflows/build_pyspinw.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: pySpinW

on: [push, workflow_dispatch]

jobs:
compile_mex:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
matlab_version: [latest]
include:
- os: macos-latest
INSTALL_DEPS: brew install llvm libomp
fail-fast: true
runs-on: ${{ matrix.os }}
steps:
- name: Check out SpinW
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up MATLAB
uses: matlab-actions/setup-matlab@v1 # v1.1.0 required for Windows/MacOS support
with:
release: ${{ matrix.matlab_version }}

- name: Remove old mex # This is due to find not working :-/ # find ${{ github.workspace }} -name "*.mex*" -type f -delete
run: |
rm external/chol_omp/chol_omp.mexa64
rm external/chol_omp/chol_omp.mexmaci64
rm external/chol_omp/chol_omp.mexw64
rm external/eig_omp/eig_omp.mexa64
rm external/eig_omp/eig_omp.mexmaci64
rm external/eig_omp/eig_omp.mexw64
rm external/mtimesx/sw_mtimesx.mexa64
rm external/mtimesx/sw_mtimesx.mexmaci64
rm external/mtimesx/sw_mtimesx.mexw64
- name: Run MEXing
uses: matlab-actions/run-command@v1
with:
command: "addpath(genpath('swfiles')); addpath(genpath('external')); sw_mex('compile', true, 'test', false, 'swtest', false);"
- name: Upload MEX results
uses: actions/upload-artifact@v3
with:
name: MEX
path: ${{ github.workspace }}/external/**/*.mex*

build_ctfs:
needs: compile_mex
strategy:
matrix:
matlab_version: [R2021a, R2021b, R2022a, R2022b, R2023a]
runs-on: self-hosted
steps:
- name: Check out SpinW
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Download MEX artifacts
uses: actions/download-artifact@v3
with:
name: MEX
path: ${{ github.workspace }}/external
- name: Build ctf
run: |
cd python
/Applications/MATLAB_${{ matrix.matlab_version }}.app/bin/matlab -nodisplay -r "build_ctf; exit"
- name: Upload CTF results
uses: actions/upload-artifact@v3
with:
name: CTF
path: ${{ github.workspace }}/python/ctf/*.ctf

build_wheel:
runs-on: ubuntu-latest
needs: build_ctfs
permissions:
contents: write
steps:
- name: Checkout SpinW
uses: actions/checkout@v3
- name: Download CTF artifacts
uses: actions/download-artifact@v3
with:
name: CTF
path: python/ctf
- name: Set up Python environment
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Move files
run: |
cd python
echo "PYSPINW_VERSION=$( cat pyproject.toml | grep "version = \"" | awk -F'"' '$0=$2' | sed 's/ //g' )" >> $GITHUB_ENV
mkdir pyspinw/ctfs
mv ctf/*.ctf pyspinw/ctfs
- name: Update Versions
if: startsWith(github.ref, 'refs/tags/v')
run: |
pip install poetry
cd ${{ github.workspace }}/python
poetry version $(git describe --tags --abbrev=0)
- name: Build Wheel
run: |
cd ${{ github.workspace }}/python
python -m pip wheel --no-deps --wheel-dir build .
- name: Create wheel artifact
uses: actions/upload-artifact@v3
with:
name: pySpinW Wheel
path: ${{ github.workspace }}/python/build/*.whl
- uses: ncipollo/release-action@v1
if: startsWith(github.ref, 'refs/tags/v')
with:
artifacts: ${{ github.workspace }}/python/build/*.whl
prerelease: true
replacesArtifacts: true
name: "pySpinW"
bodyFile: ${{ github.workspace }}/python/release_notes.md
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@ dev/standalone/MacOS/Source/
*.xml
*.rej

**profile_results
**profile_results
python/ctf
.idea/
**/*.pyc
python/ctf
.idea/
**/*.pyc
16 changes: 16 additions & 0 deletions python/build_ctf.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
out_dir = 'ctf';
VERSION = version('-release');
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);
164 changes: 164 additions & 0 deletions python/matlab/call.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
function [varargout] = call(name, varargin)
if strcmp(name, '_call_python')
varargout = call_python_m(varargin{:});
return
end
resultsize = nargout;
try
maxresultsize = nargout(name);
if maxresultsize == -1
maxresultsize = resultsize;
end
catch
maxresultsize = resultsize;
end
if resultsize > maxresultsize
resultsize = maxresultsize;
end
if nargin == 1
args = {};
else
args = varargin;
end
for ir = 1:numel(args)
args{ir} = unwrap(args{ir});
end
if resultsize > 0
% call the function with the given number of
% output arguments:
varargout = cell(resultsize, 1);
try
[varargout{:}] = feval(name, args{:});
catch err
if (strcmp(err.identifier,'MATLAB:unassignedOutputs'))
varargout = eval_ans(name, args);
else
rethrow(err);
end
end
else
varargout = eval_ans(name, args);
end
for ir = 1:numel(varargout)
varargout{ir} = wrap(varargout{ir});
end
end

function out = unwrap(in_obj)
out = in_obj;
if isstruct(in_obj) && isfield(in_obj, 'func_ptr') && isfield(in_obj, 'converter')
out = @(varargin) call('_call_python', [in_obj.func_ptr, in_obj.converter], varargin{:});
elseif isa(in_obj, 'containers.Map') && in_obj.isKey('wrapped_oldstyle_class')
out = in_obj('wrapped_oldstyle_class');
elseif iscell(in_obj)
for ii = 1:numel(in_obj)
out{ii} = unwrap(in_obj{ii});
end
end
end

function out = wrap(obj)
out = obj;
if isobject(obj) && (isempty(metaclass(obj)) && ~isjava(obj)) || has_thin_members(obj)
out = containers.Map({'wrapped_oldstyle_class'}, {obj});
elseif iscell(obj)
for ii = 1:numel(obj)
out{ii} = wrap(obj{ii});
end
end
end

function out = has_thin_members(obj)
% Checks whether any member of a class or struct is an old-style class
% or is already a wrapped instance of such a class
out = false;
if isobject(obj) || isstruct(obj)
try
fn = fieldnames(obj);
catch
return;
end
for ifn = 1:numel(fn)
try
mem = subsref(obj, struct('type', '.', 'subs', fn{ifn}));
catch
continue;
end
if (isempty(metaclass(mem)) && ~isjava(mem))
out = true;
break;
end
end
end
end

function results = eval_ans(name, args)
% try to get output from ans:
clear('ans');
feval(name, args{:});
try
results = {ans};
catch err
results = {[]};
end
end

function [n, undetermined] = getArgOut(name, parent)
undertermined = false;
if isstring(name)
fun = str2func(name);
try
n = nargout(fun);
catch % nargout fails if fun is a method:
try
n = nargout(name);
catch
n = 1;
undetermined = true;
end
end
else
n = 1;
undetermined = true;
end
end

function out = call_python_m(varargin)
% Convert row vectors to column vectors for better conversion to numpy
for ii = 1:numel(varargin)
if size(varargin{ii}, 1) == 1
varargin{ii} = varargin{ii}';
end
end
fun_name = varargin{1};
[kw_args, remaining_args] = get_kw_args(varargin(2:end));
if ~isempty(kw_args)
remaining_args = [remaining_args {struct('pyHorace_pyKwArgs', 1, kw_args{:})}];
end
out = call_python(fun_name, remaining_args{:});
if ~iscell(out)
out = {out};
end
end

function [kw_args, remaining_args] = get_kw_args(args)
% Finds the keyword arguments (string, val) pairs, assuming that they always at the end (last 2n items)
first_kwarg_id = numel(args) + 1;
for ii = (numel(args)-1):-2:1
if ischar(args{ii}); args{ii} = string(args{ii}(:)'); end
if isstring(args{ii}) && ...
strcmp(regexp(args{ii}, '^[A-Za-z_][A-Za-z0-9_]*', 'match'), args{ii})
% Python identifiers must start with a letter or _ and can contain charaters, numbers or _
first_kwarg_id = ii;
else
break;
end
end
if first_kwarg_id < numel(args)
kw_args = args(first_kwarg_id:end);
remaining_args = args(1:(first_kwarg_id-1));
else
kw_args = {};
remaining_args = args;
end
end
Loading

0 comments on commit 80d30ff

Please sign in to comment.