% file code/matlab_py/py2mat.m
function [x_mat] = py2mat(x_py)
Im = @py.importlib.import_module;
np = Im('numpy');
% Convert a native Python variable to an equivalent
% native MATLAB variable.
switch class(x_py)
% Python dictionaries
case 'py.dict'
try
% the obvious dict -> struct conversion only works
% if the key string can be a MATLAB variable
x_mat = struct(x_py);
catch EO
% A failure most likely means a Python key is not
% a proper MATLAB variable name. Go through them
% individually
key_index = int64(0);
for key = cell(py.list(x_py.keys()))
key_index = key_index + 1;
v_name = string(key);
if isvarname(v_name)
x_mat.(v_name) = x_py.get(v_name);
else
% this key can't be used; replace it
fixed = sprintf('K%06d_', key_index) + ...
regexprep(string(key),'W','_');
x_mat.(fixed) = x_py.get(v_name);
end
end
end
fields = fieldnames(x_mat);
for i = 1:length(fields)
new_var = py2mat(x_mat.(fields{i}));
x_mat = setfield(x_mat,fields{i}, new_var);
end
% NumPy arrays and typed scalars
case 'py.numpy.ndarray'
switch string(x_py.dtype.name)
case "float64"
x_mat = x_py.double;
case "float32"
x_mat = x_py.single;
case "float16"
% doesn't exist in matlab, upcast to float32
x_mat = single(x_py.astype(np.float32));
% only reals and logicals can be cast to arrays so
% have to cast the rest to either single or double
case "uint8"
x_mat = uint8(x_py.astype(np.float32));
case "int8"
x_mat = int8(x_py.astype(np.float32));
case "uint16"
x_mat = uint16(x_py.astype(np.float32));
case "int16"
x_mat = int16(x_py.astype(np.float32));
case "uint32"
x_mat = uint32(x_py.astype(np.float32));
case "int32"
x_mat = int32(x_py.astype(np.float32));
case "uint64"
x_mat = uint64(x_py.astype(np.float64));
case "int64"
x_mat = int64(x_py.astype(np.float64));
% Complex types require a math operation to coerce the
% real and imaginary components to contiguous arrays.
% Use "+0" for minimal performance impact. Without this,
% complex creation fails with
% Python Error: ValueError: ndarray is not contiguous
case "complex64"
x_mat = complex(single(x_py.real+0), single(x_py.imag+0));
case "complex128"
x_mat = complex(double(x_py.real+0), double(x_py.imag+0));
case "complex256"
fprintf('py2mat: MATLAB does not support quad precision complex
')
x_mat = [];
return
otherwise
% gets here with np.float16, custom dtypes
fprintf('py2mat: %s not recognized
', ...
string(x_py.dtype.name));
x_mat = [];
return
end
% Scipy sparse matrices
case {'py.scipy.sparse.coo.coo_matrix', ...
'py.scipy.sparse.csr.csr_matrix', ...
'py.scipy.sparse.csc.csc_matrix', ...
'py.scipy.sparse.dok.dok_matrix', ...
'py.scipy.sparse.bsr.bsr_matrix', ...
'py.scipy.sparse.dia.dia_matrix', ...
'py.scipy.sparse.lil.lil_matrix', }
ndims = x_py.get_shape();
if length(ndims) ˜= 2
fprintf('py2mat: can only convert 2D sparse matrices
')
x_mat = [];
return
end
nR = int64(ndims{1});
nC = int64(ndims{2});
x_py = x_py.tocoo();
values = py2mat(x_py.data);
if isempty(values)
% gets here if trying to convert a complex256 sparse matrix
return
end
% add 1 to row & col indices to go from 0-based to 1-based
x_mat = sparse(single(x_py.row)+1, single(x_py.col)+1, values, nR, nC);
% Python sets, tuples, and lists
case {'cell', 'py.tuple', 'py.list'}
[nR, nC] = size(x_py);
x_mat = cell(nR,nC);
for r = 1:nR
for c = 1:nC
x_mat{r,c} = py2mat(x_py{r,c});
end
end
% Python strings
case 'py.str'
x_mat = string(x_py);
% Python integers
case 'py.int'
x_mat = x_py.int64;
% Python floats (float64) -- same as matlab double
case 'double'
x_mat = x_py;
case 'py.datetime.datetime'
x_mat = datetime(int64(x_py.year), ...
int64(x_py.month), ...
int64(x_py.day), ...
int64(x_py.hour), ...
int64(x_py.minute),...
int64(x_py.second),...
int64(x_py.microsecond));
% punt
otherwise
% return the original item? nothing?
fprintf('py2mat: type "%s" not recognized
', ...
string(x_py.dtype.name));
x_mat = [];
% x_mat = x_py;
end
end