Repository: JuliaInterop/MATLAB.jl
Branch: master
Commit: 11f657c7593b
Files: 23
Total size: 90.1 KB
Directory structure:
gitextract_utsjq7lg/
├── .JuliaFormatter.toml
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── CI.yml
│ ├── Format.yml
│ ├── RegisterAction.yml
│ └── TagBot.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── deps/
│ └── build.jl
├── src/
│ ├── MATLAB.jl
│ ├── engine.jl
│ ├── exceptions.jl
│ ├── init.jl
│ ├── matfile.jl
│ ├── matstr.jl
│ └── mxarray.jl
└── test/
├── engine.jl
├── matfile.jl
├── matstr.jl
├── mxarray.jl
└── runtests.jl
================================================
FILE CONTENTS
================================================
================================================
FILE: .JuliaFormatter.toml
================================================
whitespace_typedefs = false
whitespace_ops_in_indices = false
whitespace_in_kwargs = false
remove_extra_newlines = true
format_docstrings = true
annotate_untyped_fields_with_any = true
join_lines_based_on_source = true
align_assignment = true
align_pair_arrow = true
align_struct_field = true
================================================
FILE: .github/dependabot.yml
================================================
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
================================================
FILE: .github/workflows/CI.yml
================================================
name: CI
on:
push:
branches:
- master
- release-*
tags: '*'
pull_request:
jobs:
test:
name: julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1.3'
- '1'
- 'nightly'
os:
- ubuntu-latest
arch:
- x64
steps:
- uses: actions/checkout@v6
- uses: julia-actions/setup-julia@latest
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
================================================
FILE: .github/workflows/Format.yml
================================================
name: Format suggestions
on:
pull_request:
# this argument is not required if you don't use the `suggestion-label` input
types: [opened, reopened, synchronize, labeled, unlabeled]
jobs:
code-style:
runs-on: ubuntu-latest
steps:
- uses: julia-actions/julia-format@v4
with:
version: "1" # Set `version` to '1.0.54' if you need to use JuliaFormatter.jl v1.0.54 (default: '1')
suggestion-label: "format-suggest" # leave this unset or empty to show suggestions for all PRs
================================================
FILE: .github/workflows/RegisterAction.yml
================================================
name: RegisterAction
on:
workflow_dispatch:
inputs:
version:
description: Version to register or component to bump
required: true
jobs:
register:
runs-on: ubuntu-latest
steps:
- uses: julia-actions/RegisterAction@latest
with:
token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot
on:
issue_comment:
types:
- created
workflow_dispatch:
inputs:
lookback:
default: 3
permissions:
actions: read
checks: read
contents: write
deployments: read
issues: read
discussions: read
packages: read
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Edit the following line to reflect the actual name of the GitHub Secret containing your private key
ssh: ${{ secrets.DOCUMENTER_KEY }}
# ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }}
================================================
FILE: .gitignore
================================================
deps/build.log
deps/deps.jl
*.mat
Manifest.toml
================================================
FILE: LICENSE.md
================================================
Copyright (c) 2013 Dahua Lin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Project.toml
================================================
name = "MATLAB"
uuid = "10e44e05-a98a-55b3-a45b-ba969058deb6"
license = "MIT"
repo = "https://github.com/JuliaInterop/MATLAB.jl.git"
version = "0.9.0"
[deps]
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
[compat]
julia = "1"
[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test"]
================================================
FILE: README.md
================================================
## MATLAB
| ❗ If you get a 'Segmentation fault' error when calling MATLAB with version R2022 or newer, try using an [older version](https://github.com/JuliaInterop/MATLAB.jl#changing-matlab-version) of MATLAB. ❗ |
|:----:|
The `MATLAB.jl` package provides an interface for using [MATLAB®](http://www.mathworks.com/products/matlab/) from [Julia](http://julialang.org) using the MATLAB C api. In other words, this package allows users to call MATLAB functions within Julia, thus making it easy to interoperate with MATLAB from the Julia language.
You cannot use `MATLAB.jl` without having purchased and installed a copy of MATLAB® from [MathWorks](http://www.mathworks.com/). This package is available free of charge and in no way replaces or alters any functionality of MathWorks's MATLAB product.
## Overview
This package is composed of two aspects:
* Creating and manipulating mxArrays (the data structure that MATLAB used to represent arrays and other kinds of data)
* Communicating with MATLAB engine sessions
**Warning**:
* MATLAB string arrays are not supported, and will throw an error exception. This also applies if they are nested within a MATLAB struct. This is a limitation of the MATLAB C api. The MATLAB function `convertContainedStringsToChars` may be used to facilitate conversion to a compatible format for use with `MATLAB.jl`.
* Threading is also not supported within Julia when using the MATLAB.jl library.
## Installation
**Important**: The procedure to setup this package consists of the following steps.
### Windows
1. For Matlab R2020a onwards, you should be able to go directly to step 2. If you encounter issues, run `matlab -batch "comserver('register')"` in the command prompt. For earlier versions of Matlab, start a command prompt as an administrator and enter `matlab /regserver`. Alternatively, you can do the same using the MATLAB GUI. To do this, open MATLAB with administrative privileges and run the following command in the MATLAB command window: `!matlab -regserver`. Close MATLAB and restart Julia.
2. From Julia run: `Pkg.add("MATLAB")`
### Linux
1. Make sure ``matlab`` is in executable path.
2. Make sure ``csh`` is installed. (Note: MATLAB for Linux relies on ``csh`` to open an engine session.)
To install ``csh`` in Debian/Ubuntu/Linux Mint, you may type in the following command in terminal:
```bash
sudo apt-get install csh
```
3. From Julia run: `Pkg.add("MATLAB")`
If you experience problems when starting the MATLAB engine with versions R2022 or R2023, try to [update](https://se.mathworks.com/help/matlab/matlab_env/check-for-software-updates.html) your MATLAB release.
### Mac OS X
1. Ensure that MATLAB is installed in `/Applications` (for example, if you are using MATLAB R2012b, you may add the following command to `.profile`: `export MATLAB_HOME=/Applications/MATLAB_R2012b.app`).
2. From Julia run: `Pkg.add("MATLAB")`
## Changing MATLAB version
By default, `MATLAB.jl` is built using the MATLAB installation with the greatest version number. To specify that a specific MATLAB installation should be used, set the environment variable `MATLAB_ROOT`:
```julia
julia> ENV["MATLAB_ROOT"] = "/usr/local/MATLAB/R2021b" # example on a Linux machine
```
```julia
julia> ENV["MATLAB_ROOT"] = raw"C:\Program Files\MATLAB\R2021b" # example on a Windows machine
```
Replace the path string with the location of the MATLAB folder on your machine. You need to set the path to the `R20XX` folder, not the `matlab` binary.
If you had the package `MATLAB.jl` already installed and built before changing the environment variable, you will need to rebuild it to apply the change:
```julia
julia> using Pkg; Pkg.build("MATLAB")
```
### Notes for Windows Users
If you are using Windows, MATLAB needs to register its COM interface to properly work with MATLAB.jl. This should happen automatically during installation, but if you encounter issues, you can manually re-register MATLAB (with the version you want to use). To do this
1. Open a MATLAB window with administrative privileges
2. Run the following command in the MATLAB command window: `!matlab -regserver`
3. Close MATLAB and restart Julia
> [!IMPORTANT]
> The version of MATLAB that is registered must be same that `MATLAB.jl` uses.
## Usage
### MxArray class
An instance of ``MxArray`` encapsulates a MATLAB variable. This package provides a series of functions to manipulate such instances.
#### Create MATLAB variables in Julia
One can use the function ``mxarray`` to create MATLAB variables (of type ``MxArray``), as follows
```julia
mxarray(Float64, n) # creates an n-by-1 MATLAB zero array of double valued type
mxarray(Int32, m, n) # creates an m-by-n MATLAB zero array of int32 valued type
mxarray(Bool, m, n) # creates a MATLAB logical array of size m-by-n
mxarray(Float64, (n1, n2, n3)) # creates a MATLAB array of size n1-by-n2-by-n3
mxcellarray(m, n) # creates a MATLAB cell array
mxstruct("a", "b", "c") # creates a MATLAB struct with given fields
```
You may also convert a Julia variable to MATLAB variable
```julia
a = rand(m, n)
x = mxarray(a) # converts a to a MATLAB array
x = mxarray(1.2) # converts a scalar 1.2 to a MATLAB variable
a = sprand(m, n, 0.1)
x = mxarray(a) # converts a sparse matrix to a MATLAB sparse matrix
x = mxarray("abc") # converts a string to a MATLAB char array
x = mxarray(["a", 1, 2.3]) # converts a Julia array to a MATLAB cell array
x = mxarray(Dict("a"=>1, "b"=>"string", "c"=>[1,2,3])) # converts a Julia dictionary to a MATLAB struct
```
The function ``mxarray`` can also convert a compound type to a Julia struct:
```julia
struct S
x::Float64
y::Vector{Int32}
z::Bool
end
s = S(1.2, Int32[1, 2], false)
x = mxarray(s) # creates a MATLAB struct with three fields: x, y, z
xc = mxarray([s, s]) # creates a MATLAB cell array, each cell is a struct.
xs = mxstructarray([s, s]) # creates a MATLAB array of structs
```
**Note:** For safety, the conversation between MATLAB and Julia variables uses deep copy.
When you finish using a MATLAB variable, you may call ``delete`` to free the memory. But this is optional, it will be deleted when reclaimed by the garbage collector.
```julia
delete(x)
```
*Note:* if you put a MATLAB variable ``x`` to MATLAB engine session, then the MATLAB engine will take over the management of its life cylce, and you don't have to delete it explicitly.
#### Access MATLAB variables
You may access attributes and data of a MATLAB variable through the functions provided by this package.
```julia
# suppose x is of type MxArray
nrows(x) # returns number of rows in x
ncols(x) # returns number of columns in x
nelems(x) # returns number of elements in x
ndims(x) # returns number of dimensions in x
size(x) # returns the size of x as a tuple
size(x, d) # returns the size of x along a specific dimension
eltype(x) # returns element type of x (in Julia Type)
elsize(x) # return number of bytes per element
data_ptr(x) # returns pointer to data (in Ptr{T}), where T is eltype(x)
# suppose s is a MATLAB struct
mxnfields(s) # returns the number of fields in struct s
```
You may also make tests on a MATLAB variable.
```julia
is_double(x) # returns whether x is a double array
is_sparse(x) # returns whether x is sparse
is_complex(x) # returns whether x is complex
is_cell(x) # returns whether x is a cell array
is_struct(x) # returns whether x is a struct
is_empty(x) # returns whether x is empty
... # there are many more there
```
#### Convert MATLAB variables to Julia
```julia
a = jarray(x) # converts x to a Julia array
a = jvector(x) # converts x to a Julia vector (1D array) when x is a vector
a = jscalar(x) # converts x to a Julia scalar
a = jmatrix(x) # converts x to a Julia matrix
a = jstring(x) # converts x to a Julia string
a = jdict(x) # converts a MATLAB struct to a Julia dictionary (using fieldnames as keys)
a = jvalue(x) # converts x to a Julia value in default manner
```
### Read/Write MAT Files
This package provides functions to manipulate MATLAB's mat files:
```julia
mf = MatFile(filename, mode) # opens a MAT file using a specific mode, and returns a handle
mf = MatFile(filename) # opens a MAT file for reading, equivalent to MatFile(filename, "r")
close(mf) # closes a MAT file.
get_mvariable(mf, name) # gets a variable and returns an mxArray
get_variable(mf, name) # gets a variable, but converts it to a Julia value using `jvalue`
put_variable(mf, name, v) # puts a variable v to the MAT file
# v can be either an MxArray instance or normal variable
# If v is not an MxArray, it will be converted using `mxarray`
put_variables(mf; name1=v1, name2=v2, ...) # put multiple variables using keyword arguments
variable_names(mf) # get a vector of all variable names in a MAT file
```
There are also convenient functions that can get/put all variables in one call:
```julia
read_matfile(filename) # returns a dictionary that maps each variable name
# to an MxArray instance
write_matfile(filename; name1=v1, name2=v2, ...) # writes all variables given in the
# keyword argument list to a MAT file
```
Both ``read_matfile`` and ``write_matfile`` will close the MAT file handle before returning.
**Examples:**
```julia
struct S
x::Float64
y::Bool
z::Vector{Float64}
end
write_matfile("test.mat";
a = Int32[1 2 3; 4 5 6],
b = [1.2, 3.4, 5.6, 7.8],
c = [[0.0, 1.0], [1.0, 2.0], [1.0, 2.0, 3.0]],
d = Dict("name"=>"MATLAB", "score"=>100.0),
s = "abcde",
ss = [S(1.0, true, [1., 2.]), S(2.0, false, [3., 4.])] )
```
This example will create a MAT file called ``test.mat``, which contains six MATLAB variables:
* ``a``: a 2-by-3 int32 array
* ``b``: a 4-by-1 double array
* ``c``: a 3-by-1 cell array, each cell contains a double vector
* ``d``: a struct with two fields: name and score
* ``s``: a string (i.e. char array)
* ``ss``: an array of structs with two elements, and three fields: x, y, and z.
### Use MATLAB Engine
#### Basic Use
To evaluate expressions in MATLAB, one may open a MATLAB engine session and communicate with it. There are three ways to call MATLAB from Julia:
- The `mat""` custom string literal allows you to write MATLAB syntax inside Julia and use Julia variables directly from MATLAB via interpolation
- The `eval_string` evaluate a string containing MATLAB expressions (typically used with the helper macros `@mget` and `@mput`
- The `mxcall` function calls a given MATLAB function and returns the result
In general, the `mat""` custom string literal is the preferred method to interact with the MATLAB engine.
*Note:* There can be multiple (reasonable) ways to convert a MATLAB variable to Julia array. For example, MATLAB represents a scalar using a 1-by-1 matrix. Here we have two choices in terms of converting such a matrix back to Julia: (1) convert to a scalar number, or (2) convert to a matrix of size 1-by-1.
##### The `mat""` custom string literal
Text inside the `mat""` custom string literal is in MATLAB syntax. Variables from Julia can be "interpolated" into MATLAB code by prefixing them with a dollar sign as you would interpolate them into an ordinary string.
```julia
using MATLAB
x = range(-10.0, stop=10.0, length=500)
mat"plot($x, sin($x))" # evaluate a MATLAB function
y = range(2.0, stop=3.0, length=500)
mat"""
$u = $x + $y
$v = $x - $y
"""
@show u v # u and v are accessible from Julia
```
As with ordinary string literals, you can also interpolate whole Julia expressions, e.g. `mat"$(x[1]) = $(x[2]) + $(binomial(5, 2))"`.
##### `eval_string`
You may also use the `eval_string` function to evaluate MATLAB code as follows
```julia
eval_string("a = sum([1,2,3])")
```
The `eval_string` function also takes an optional argument that specifies which MATLAB session to evaluate the code in, e.g.
```julia
julia> s = MSession();
julia> eval_string(s, "a = sum([1,2,3])")
a =
6
```
##### `mxcall`
You may also directly call a MATLAB function on Julia variables using `mxcall`:
```julia
x = -10.0:0.1:10.0
y = -10.0:0.1:10.0
xx, yy = mxcall(:meshgrid, 2, x, y)
```
*Note:* Since MATLAB functions behavior depends on the number of outputs, you have to specify the number of output arguments in ``mxcall`` as the second argument.
``mxcall`` puts the input arguments to the MATLAB workspace (using mangled names), evaluates the function call in MATLAB, and retrieves the variable from the MATLAB session. This function is mainly provided for convenience. However, you should keep in mind that it may incur considerable overhead due to the communication between MATLAB and Julia domain.
##### `@mget` and `@mput`
The macro `@mget` can be used to extract the value of a MATLAB variable into Julia
```julia
julia> mat"a = 6"
julia> @mget a
6.0
```
The macro `@mput` can be used to translate a Julia variable into MATLAB
```julia
julia> x = [1,2,3]
julia> @mput x
julia> eval_string("y = sum(x)")
julia> @mget y
6.0
julia> @show y
a = 63.0
```
#### Calling custom MATLAB function
If the MATLAB function is not in the current directory, we need to first add it to the MATLAB path before calling through Julia:
```julia
mat"addpath('/path/to/folder')"
val = mat"myfunction($arg1, $arg2)"
```
For example, if there is a MATLAB file located at `/path/to/folder` with contents:
```matlab
function [r,u] = test(x, y)
r = x + y;
u = x - y;
end
```
We can call this function as follows in Julia:
```julia
using MATLAB
x = range(-10.0, stop=10.0, length=500)
y = range(2.0, stop=3.0, length=500)
mat"addpath('/path/to/folder')"
r, u = mxcall(:test,2,x,y)
```
#### Viewing the MATLAB Session (Windows only)
To open an interactive window for the MATLAB session, use the command `show_msession()` and to hide the window, use `hide_msession()`. *Warning: manually closing this window will result in an error or result in a segfault; it is advised that you only use the `hide_msession()` command to hide the interactive window.*
Note that this feature only works on Windows.
```julia
# default
show_msession() # open the default MATLAB session interactive window
get_msession_visiblity() # get the session's visibility state
hide_msession() # hide the default MATLAB session interactive window
# similarly
s = MSession()
show_msession(s)
get_msession_visiblity(a)
hide_msession(s)
```
#### Advanced use of MATLAB Engines
This package provides a series of functions for users to control the communication with MATLAB sessions.
Here is an example:
```julia
s1 = MSession() # creates a MATLAB session
s2 = MSession(0) # creates a MATLAB session without recording output
x = rand(3, 4)
put_variable(s1, :x, x) # put x to session s1
y = rand(2, 3)
put_variable(s2, :y, y) # put y to session s2
eval_string(s1, "r = sin(x)") # evaluate sin(x) in session s1
eval_string(s2, "r = sin(y)") # evaluate sin(y) in session s2
r1_mx = get_mvariable(s1, :r) # get r from s1
r2_mx = get_mvariable(s2, :r) # get r from s2
r1 = jarray(r1_mx)
r2 = jarray(r2_mx)
# ... do other stuff on r1 and r2
close(s1) # close session s1
close(s2) # close session s2
```
================================================
FILE: deps/build.jl
================================================
import Libdl
const depsfile = joinpath(@__DIR__, "deps.jl")
function find_matlab_root()
# Determine MATLAB library path and provide facilities to load libraries with this path
matlab_root = get(ENV, "MATLAB_ROOT",
get(ENV, "MATLAB_HOME", nothing))
if isnothing(matlab_root)
matlab_exe = Sys.which("matlab")
if !isnothing(matlab_exe)
matlab_exe = islink(matlab_exe) ? readlink(matlab_exe) : matlab_exe # guard against /usr/local
matlab_root = dirname(dirname(matlab_exe))
else
if Sys.isapple()
default_dir = "/Applications"
if isdir(default_dir)
dirs = readdir(default_dir)
filter!(app -> occursin(r"^MATLAB_R[0-9]+[ab]\.app$", app), dirs)
if !isempty(dirs)
matlab_root = joinpath(default_dir, maximum(dirs))
end
end
elseif Sys.iswindows()
default_dir =
Sys.WORD_SIZE == 32 ? "C:\\Program Files (x86)\\MATLAB" :
"C:\\Program Files\\MATLAB"
if isdir(default_dir)
dirs = readdir(default_dir)
filter!(dir -> occursin(r"^R[0-9]+[ab]$", dir), dirs)
if !isempty(dirs)
matlab_root = joinpath(default_dir, maximum(dirs))
end
end
elseif Sys.islinux()
default_dir = "/usr/local/MATLAB"
if isdir(default_dir)
dirs = readdir(default_dir)
filter!(dir -> occursin(r"^R[0-9]+[ab]$", dir), dirs)
if !isempty(dirs)
matlab_root = joinpath(default_dir, maximum(dirs))
end
end
end
end
end
!isnothing(matlab_root) && isdir(matlab_root) &&
@info("Detected MATLAB root folder at \"$matlab_root\"")
return matlab_root
end
function find_matlab_libpath(matlab_root)
# get path to MATLAB libraries
matlab_libdir = if Sys.islinux()
Sys.WORD_SIZE == 32 ? "glnx86" : "glnxa64"
elseif Sys.isapple()
archchar = Sys.ARCH == :aarch64 ? "a" : "i"
Sys.WORD_SIZE == 32 ? "maci" : "mac" * archchar * "64"
elseif Sys.iswindows()
Sys.WORD_SIZE == 32 ? "win32" : "win64"
end
matlab_libpath = joinpath(matlab_root, "bin", matlab_libdir)
isdir(matlab_libpath) && @info("Detected MATLAB library path at \"$matlab_libpath\"")
return matlab_libpath
end
function find_matlab_cmd(matlab_root)
if Sys.iswindows()
matlab_cmd = joinpath(
matlab_root,
"bin",
(Sys.WORD_SIZE == 32 ? "win32" : "win64"),
"matlab.exe",
)
isfile(matlab_cmd) && @info("Detected MATLAB executable at \"$matlab_cmd\"")
else
matlab_exe = joinpath(matlab_root, "bin", "matlab")
isfile(matlab_exe) && @info("Detected MATLAB executable at \"$matlab_exe\"")
matlab_cmd = "exec $(Base.shell_escape(matlab_exe))"
end
return matlab_cmd
end
matlab_root = find_matlab_root()
if !isnothing(matlab_root)
matlab_libpath = find_matlab_libpath(matlab_root)
matlab_cmd = find_matlab_cmd(matlab_root)
libmx_size = filesize(Libdl.dlpath(joinpath(matlab_libpath, "libmx")))
open(depsfile, "w") do io
println(io,
"""
# This file is automatically generated, do not edit.
function check_deps()
if libmx_size != filesize(Libdl.dlpath(joinpath(matlab_libpath, "libmx")))
error("MATLAB library has changed, re-run Pkg.build(\\\"MATLAB\\\")")
end
end
""",
)
println(io, "const matlab_libpath = \"$(escape_string(matlab_libpath))\"")
println(io, "const matlab_cmd = \"$(escape_string(matlab_cmd))\"")
println(io, "const libmx_size = $libmx_size")
end
elseif get(ENV, "JULIA_REGISTRYCI_AUTOMERGE", nothing) == "true" ||
get(ENV, "CI", nothing) == "true"
# We need to be able to install and load this package without error for
# Julia's registry AutoMerge to work, so we just use dummy values.
# Similarly we want to also be able to install and load this package for CI.
matlab_libpath = ""
matlab_cmd = ""
libmx_size = 0
open(depsfile, "w") do io
println(io,
"""
# This file is automatically generated, do not edit.
check_deps() = nothing
""",
)
println(io, "const matlab_libpath = \"$(escape_string(matlab_libpath))\"")
println(io, "const matlab_cmd = \"$(escape_string(matlab_cmd))\"")
println(io, "const libmx_size = $libmx_size")
end
else
error(
"MATLAB cannot be found. Set the \"MATLAB_ROOT\" environment variable to the MATLAB root directory and re-run Pkg.build(\"MATLAB\").",
)
end
================================================
FILE: src/MATLAB.jl
================================================
module MATLAB
using Libdl
using SparseArrays
import Base: eltype, close, size, copy, ndims, unsafe_convert
# mxarray
export MxArray,
mxClassID, mxComplexity,
mxclassid, data_ptr,
classid, nrows, ncols, nelems, elsize
export is_double, is_single,
is_int8, is_uint8, is_int16, is_uint16,
is_int32, is_uint32, is_int64, is_uint64,
is_numeric, is_complex, is_sparse, is_empty,
is_logical, is_char, is_struct, is_cell
export mxarray, mxsparse, delete,
mxcellarray, get_cell, set_cell,
mxstruct, mxstructarray, mxnfields, get_fieldname, get_field, set_field,
jvalue, jarray, jscalar, jvector, jmatrix, jsparse, jstring, jdict
# engine & matfile
export MSession, MatFile,
get_default_msession, restart_default_msession, close_default_msession,
eval_string, get_mvariable, get_variable, put_variable, put_variables,
variable_names, read_matfile, write_matfile,
mxcall,
@mput, @mget, @mat_str
if Sys.iswindows()
export show_msession, hide_msession, get_msession_visiblity
end
const depsfile = joinpath(dirname(@__DIR__), "deps", "deps.jl")
if isfile(depsfile)
include(depsfile)
else
error(
"MATLAB is not properly installed. Please run Pkg.build(\"MATLAB\") and restart Julia.",
)
end
include("exceptions.jl")
include("init.jl") # initialize Refs
include("mxarray.jl")
include("matfile.jl")
include("engine.jl")
include("matstr.jl")
if Sys.iswindows()
# workaround "primary message table for module 77" error
# creates a dummy Engine session and keeps it open so the libraries used by all other
# Engine clients are not loaded and unloaded repeatedly
# see: https://www.mathworks.com/matlabcentral/answers/305877-what-is-the-primary-message-table-for-module-77
# initialization is delayed until first call to MSession
const persistent_msession_ref = Ref{MSession}()
const persistent_msession_assigned = Ref(false)
function assign_persistent_msession()
if persistent_msession_assigned[] == false
persistent_msession_assigned[] = true
persistent_msession_ref[] = MSession(0)
end
end
end
# helper library access function
engfunc(fun::Symbol) = Libdl.dlsym(libeng[], fun)
mxfunc(fun::Symbol) = Libdl.dlsym(libmx[], fun)
matfunc(fun::Symbol) = Libdl.dlsym(libmat[], fun)
function __init__()
check_deps()
if libmx_size > 0 # non-zero size library path
# load libraries
# workaround for https://github.com/JuliaInterop/MATLAB.jl/issues/200
if Sys.iswindows()
ENV["PATH"] = string(matlab_libpath, ";", ENV["PATH"])
elseif Sys.islinux()
ENV["PATH"] = string(matlab_libpath, ":", ENV["PATH"])
end
libmx[] = Libdl.dlopen(joinpath(matlab_libpath, "libmx"), Libdl.RTLD_GLOBAL)
libmat[] = Libdl.dlopen(joinpath(matlab_libpath, "libmat"), Libdl.RTLD_GLOBAL)
libeng[] = Libdl.dlopen(joinpath(matlab_libpath, "libeng"), Libdl.RTLD_GLOBAL)
# engine functions
eng_open[] = engfunc(:engOpen)
eng_close[] = engfunc(:engClose)
eng_set_visible[] = engfunc(:engSetVisible)
eng_get_visible[] = engfunc(:engGetVisible)
eng_output_buffer[] = engfunc(:engOutputBuffer)
eng_eval_string[] = engfunc(:engEvalString)
eng_put_variable[] = engfunc(:engPutVariable)
eng_get_variable[] = engfunc(:engGetVariable)
# mxarray functions
mx_destroy_array[] = mxfunc(:mxDestroyArray)
mx_duplicate_array[] = mxfunc(:mxDuplicateArray)
# load functions to access mxarray
mx_free[] = mxfunc(:mxFree)
mx_get_classid[] = mxfunc(:mxGetClassID)
mx_get_m[] = mxfunc(:mxGetM)
mx_get_n[] = mxfunc(:mxGetN)
mx_get_nelems[] = mxfunc(:mxGetNumberOfElements)
mx_get_ndims[] = mxfunc(:mxGetNumberOfDimensions_730)
mx_get_elemsize[] = mxfunc(:mxGetElementSize)
mx_get_data[] = mxfunc(:mxGetData)
mx_get_dims[] = mxfunc(:mxGetDimensions_730)
mx_get_nfields[] = mxfunc(:mxGetNumberOfFields)
mx_get_pr[] = mxfunc(:mxGetPr)
mx_get_pi[] = mxfunc(:mxGetPi)
mx_get_ir[] = mxfunc(:mxGetIr_730)
mx_get_jc[] = mxfunc(:mxGetJc_730)
mx_is_double[] = mxfunc(:mxIsDouble)
mx_is_single[] = mxfunc(:mxIsSingle)
mx_is_int64[] = mxfunc(:mxIsInt64)
mx_is_uint64[] = mxfunc(:mxIsUint64)
mx_is_int32[] = mxfunc(:mxIsInt32)
mx_is_uint32[] = mxfunc(:mxIsUint32)
mx_is_int16[] = mxfunc(:mxIsInt16)
mx_is_uint16[] = mxfunc(:mxIsUint16)
mx_is_int8[] = mxfunc(:mxIsInt8)
mx_is_uint8[] = mxfunc(:mxIsUint8)
mx_is_char[] = mxfunc(:mxIsChar)
mx_is_numeric[] = mxfunc(:mxIsNumeric)
mx_is_logical[] = mxfunc(:mxIsLogical)
mx_is_complex[] = mxfunc(:mxIsComplex)
mx_is_sparse[] = mxfunc(:mxIsSparse)
mx_is_empty[] = mxfunc(:mxIsEmpty)
mx_is_struct[] = mxfunc(:mxIsStruct)
mx_is_cell[] = mxfunc(:mxIsCell)
# load functions to create & delete MATLAB array
mx_create_numeric_matrix[] = mxfunc(:mxCreateNumericMatrix_730)
mx_create_numeric_array[] = mxfunc(:mxCreateNumericArray_730)
mx_create_double_scalar[] = mxfunc(:mxCreateDoubleScalar)
mx_create_logical_scalar[] = mxfunc(:mxCreateLogicalScalar)
mx_create_sparse[] = mxfunc(:mxCreateSparse_730)
mx_create_sparse_logical[] = mxfunc(:mxCreateSparseLogicalMatrix_730)
mx_create_string[] = mxfunc(:mxCreateString)
mx_create_char_array[] = mxfunc(:mxCreateCharArray_730)
mx_create_cell_array[] = mxfunc(:mxCreateCellArray_730)
mx_create_struct_matrix[] = mxfunc(:mxCreateStructMatrix_730)
mx_create_struct_array[] = mxfunc(:mxCreateStructArray_730)
mx_get_cell[] = mxfunc(:mxGetCell_730)
mx_set_cell[] = mxfunc(:mxSetCell_730)
mx_get_field[] = mxfunc(:mxGetField_730)
mx_set_field[] = mxfunc(:mxSetField_730)
mx_get_field_bynum[] = mxfunc(:mxGetFieldByNumber_730)
mx_get_fieldname[] = mxfunc(:mxGetFieldNameByNumber)
mx_get_string[] = mxfunc(:mxGetString_730)
# load I/O mat functions
mat_open[] = matfunc(:matOpen)
mat_close[] = matfunc(:matClose)
mat_get_variable[] = matfunc(:matGetVariable)
mat_put_variable[] = matfunc(:matPutVariable)
mat_get_dir[] = matfunc(:matGetDir)
end
end
###########################################################
#
# deprecations
#
###########################################################
end
================================================
FILE: src/engine.jl
================================================
# operation on MATLAB engine sessions
###########################################################
#
# Session open & close
#
###########################################################
const default_startflag = "-nodisplay -nosplash -nodesktop" # no additional flags
const default_matlabcmd = matlab_cmd * " -nodisplay -nosplash -nodesktop"
# pass matlab flags directly or as a Vector of flags, i.e. "-a" or ["-a", "-b", "-c"]
startcmd(flag::AbstractString=default_startflag) =
isempty(flag) ? default_matlabcmd : default_matlabcmd * " " * flag
startcmd(flags::AbstractVector{<:AbstractString}) =
isempty(flags) ? default_matlabcmd : default_matlabcmd * " " * join(flags, " ")
# 64 K buffer should be sufficient to store the output text in most cases
const default_output_buffer_size = 64 * 1024
const windows_regserver_warning = """
Failed to start MATLAB engine. If you have/had multiple versions of MATLAB installed, this can happen if you
tried to start a different version of MATLAB in Julia compared to which MATLAB server is registered in Windows.
Steps to resolve this:
1. Register a specific MATLAB version manually as a server, open a MATLAB window as a user with administrator privileges.
In MATLAB, enter the command `!matlab -regserver`. Then close the MATLAB window. More details:
https://de.mathworks.com/help/matlab/matlab_external/registering-matlab-software-as-a-com-server.html
2. Ensure that the MATLAB.jl package is using the same MATLAB version that was registered in step 1. See the instructions on GitHub
on how to change the version that MATLAB.jl uses:
https://github.com/JuliaInterop/MATLAB.jl?tab=readme-ov-file#changing-matlab-version
"""
mutable struct MSession
ptr::Ptr{Cvoid}
buffer::Vector{UInt8}
bufptr::Ptr{UInt8}
check_exceptions::Bool
function MSession(
bufsize::Integer=default_output_buffer_size;
flags=default_startflag,
check_exceptions::Bool=true,
)
if Sys.iswindows()
assign_persistent_msession()
end
ep = ccall(eng_open[], Ptr{Cvoid}, (Ptr{UInt8},), startcmd(flags))
if ep == C_NULL
@warn(
"Confirm MATLAB is installed and discoverable.",
matlab_libpath,
maxlog = 1
)
if Sys.iswindows()
@warn(windows_regserver_warning, maxlog = 1)
elseif Sys.islinux()
@warn(
"Ensure `csh` is installed; this may require running `sudo apt-get install csh`.",
maxlog = 1
)
end
throw(MEngineError("failed to open MATLAB engine session"))
end
if Sys.iswindows()
# hide the MATLAB command window on Windows and change to current directory
ccall(eng_set_visible[], Cint, (Ptr{Cvoid}, Cint), ep, 0)
ccall(eng_eval_string[], Cint, (Ptr{Cvoid}, Ptr{UInt8}),
ep, "try cd('$(escape_string(pwd()))'); end")
end
buf = Vector{UInt8}(undef, bufsize)
if bufsize > 0
bufptr = pointer(buf)
ccall(eng_output_buffer[], Cint, (Ptr{Cvoid}, Ptr{UInt8}, Cint),
ep, bufptr, bufsize)
else
bufptr = convert(Ptr{UInt8}, C_NULL)
end
self = new(ep, buf, bufptr, check_exceptions)
finalizer(release, self)
return self
end
end
function unsafe_convert(::Type{Ptr{Cvoid}}, m::MSession)
ptr = m.ptr
ptr == C_NULL && throw(UndefRefError())
return ptr
end
function release(session::MSession)
ptr = session.ptr
if ptr != C_NULL
ccall(eng_close[], Cint, (Ptr{Cvoid},), ptr)
end
session.ptr = C_NULL
return nothing
end
function close(session::MSession)
# close a MATLAB Engine session
ret = ccall(eng_close[], Cint, (Ptr{Cvoid},), session)
ret != 0 && throw(MEngineError("failed to close MATLAB engine session (err = $ret)"))
session.ptr = C_NULL
return nothing
end
has_exception_check_enabled(session::MSession=get_default_msession()) =
session.check_exceptions
disable_exception_check!(session::MSession=get_default_msession()) =
(session.check_exceptions = false; nothing)
enable_exception_check!(session::MSession=get_default_msession()) =
(session.check_exceptions = true; nothing)
# default session
const default_msession_ref = Ref{MSession}()
# this function will start an MSession if default_msession_ref is undefined or if the
# MSession has been closed so that the engine ptr is void
function get_default_msession()
if !isassigned(default_msession_ref) || default_msession_ref[].ptr == C_NULL
default_msession_ref[] = MSession()
end
return default_msession_ref[]
end
function restart_default_msession(bufsize::Integer=default_output_buffer_size)
close_default_msession()
default_msession_ref[] = MSession(bufsize)
return nothing
end
function close_default_msession()
if isassigned(default_msession_ref) && default_msession_ref[].ptr !== C_NULL
close(default_msession_ref[])
end
return nothing
end
if Sys.iswindows()
function show_msession(m::MSession=get_default_msession())
ret = ccall(eng_set_visible[], Cint, (Ptr{Cvoid}, Cint), m, 1)
ret != 0 && throw(MEngineError("failed to show MATLAB engine session (err = $ret)"))
return nothing
end
function hide_msession(m::MSession=get_default_msession())
ret = ccall(eng_set_visible[], Cint, (Ptr{Cvoid}, Cint), m, 0)
ret != 0 && throw(MEngineError("failed to hide MATLAB engine session (err = $ret)"))
return nothing
end
function get_msession_visiblity(m::MSession=get_default_msession())
vis = Ref{Cint}(true)
ccall(eng_get_visible[], Int, (Ptr{Cvoid}, Ptr{Cint}), m, vis)
return vis[] == 1 ? true : false
end
end
###########################################################
#
# communication with MATLAB session
#
###########################################################
function _eval_string(session::MSession, stmt::String)
# evaluate a MATLAB statement in a given MATLAB session
ret = ccall(eng_eval_string[], Cint, (Ptr{Cvoid}, Ptr{UInt8}), session, stmt)
ret != 0 && throw(MEngineError("invalid engine session (err = $ret)"))
bufptr = session.bufptr
if bufptr != C_NULL
bs = unsafe_string(bufptr)
if ~isempty(bs)
print(bs)
end
end
return nothing
end
function eval_string(session::MSession, stmt::String)
_eval_string(session, stmt)
if session.check_exceptions
check_and_clear_last_exception(session)
end
end
eval_string(stmt::String) = eval_string(get_default_msession(), stmt)
function put_variable(session::MSession, name::Symbol, v::MxArray)
# put a variable into a MATLAB engine session
ret = ccall(
eng_put_variable[],
Cint,
(Ptr{Cvoid}, Ptr{UInt8}, Ptr{Cvoid}),
session,
string(name),
v,
)
ret != 0 && throw(
MEngineError("failed to put variable $(name) into MATLAB session (err = $ret)"),
)
return nothing
end
put_variable(session::MSession, name::Symbol, v) = put_variable(session, name, mxarray(v))
put_variable(name::Symbol, v) = put_variable(get_default_msession(), name, v)
function get_mvariable(session::MSession, name::Symbol)
pv = ccall(
eng_get_variable[],
Ptr{Cvoid},
(Ptr{Cvoid}, Ptr{UInt8}),
session,
string(name),
)
pv == C_NULL &&
throw(MEngineError("failed to get variable $(name) from MATLAB session"))
return MxArray(pv)
end
get_mvariable(name::Symbol) = get_mvariable(get_default_msession(), name)
get_variable(name::Symbol) = jvalue(get_mvariable(name))
get_variable(name::Symbol, kind) = jvalue(get_mvariable(name), kind)
"""
check_and_clear_last_exception(session::MSession)
Checks if an exception has been thrown in the MATLAB session by checking the `MException.last` variable.
If it is not empty, it throws a `MatlabException` with the message and identifier of the last exception.
In any case, it clears the `MException.last` variable.
"""
function check_and_clear_last_exception(session::MSession)
exception_check_code = """
matlab_exception_jl_message = MException.last.message;
matlab_exception_jl_identifier = MException.last.identifier;
MException.last('reset');
"""
_eval_string(session, exception_check_code)
message = jvalue(get_mvariable(session, :matlab_exception_jl_message))
identifier = jvalue(get_mvariable(session, :matlab_exception_jl_identifier))
if !isempty(identifier)
throw(MatlabException(identifier, message))
end
_eval_string(
session,
"clear matlab_exception_jl_message matlab_exception_jl_identifier;",
)
end
###########################################################
#
# macro to simplify syntax
#
###########################################################
function _mput_multi(vs::Symbol...)
nv = length(vs)
if nv == 1
v = vs[1]
:(MATLAB.put_variable($(Meta.quot(v)), $(v)))
else
stmts = Vector{Expr}(undef, nv)
for i = 1:nv
v = vs[i]
stmts[i] = :(MATLAB.put_variable($(Meta.quot(v)), $(v)))
end
Expr(:block, stmts...)
end
end
macro mput(vs...)
esc(_mput_multi(vs...))
end
function make_getvar_statement(v::Symbol)
:($(v) = MATLAB.get_variable($(Meta.quot(v))))
end
function make_getvar_statement(ex::Expr)
if !(ex.head == :(::))
error("Invalid expression for @mget.")
end
v::Symbol = ex.args[1]
k::Symbol = ex.args[2]
:($(v) = MATLAB.get_variable($(Meta.quot(v)), $(k)))
end
function _mget_multi(vs::Union{Symbol,Expr}...)
nv = length(vs)
if nv == 1
make_getvar_statement(vs[1])
else
stmts = Vector{Expr}(undef, nv)
for i = 1:nv
stmts[i] = make_getvar_statement(vs[i])
end
Expr(:block, stmts...)
end
end
macro mget(vs...)
esc(_mget_multi(vs...))
end
###########################################################
#
# mxcall
#
###########################################################
# MATLAB does not allow underscore as prefix of a variable name
_gen_marg_name(mfun::Symbol, prefix::String, i::Int) = "jx_$(mfun)_arg_$(prefix)_$(i)"
function mxcall(session::MSession, mfun::Symbol, nout::Integer, in_args...)
nin = length(in_args)
# generate temporary variable names
in_arg_names = Vector{String}(undef, nin)
out_arg_names = Vector{String}(undef, nout)
for i = 1:nin
in_arg_names[i] = _gen_marg_name(mfun, "in", i)
end
for i = 1:nout
out_arg_names[i] = _gen_marg_name(mfun, "out", i)
end
# generate MATLAB statement
buf = IOBuffer()
if nout > 0
if nout > 1
print(buf, "[")
end
join(buf, out_arg_names, ", ")
if nout > 1
print(buf, "]")
end
print(buf, " = ")
end
print(buf, string(mfun))
print(buf, "(")
if nin > 0
join(buf, in_arg_names, ", ")
end
print(buf, ");")
stmt = String(take!(buf))
# put variables to MATLAB
for i = 1:nin
put_variable(session, Symbol(in_arg_names[i]), in_args[i])
end
# execute MATLAB statement
eval_string(session, stmt)
# get results from MATLAB
ret = if nout == 1
jvalue(get_mvariable(session, Symbol(out_arg_names[1])))
elseif nout >= 2
results = Vector{Any}(undef, nout)
for i = 1:nout
results[i] = jvalue(get_mvariable(session, Symbol(out_arg_names[i])))
end
tuple(results...)
else
nothing
end
# clear temporaries from MATLAB workspace
for i = 1:nin
eval_string(session, string("clear ", in_arg_names[i], ";"))
end
for i = 1:nout
eval_string(session, string("clear ", out_arg_names[i], ";"))
end
return ret
end
mxcall(mfun::Symbol, nout::Integer, in_args...) =
mxcall(get_default_msession(), mfun, nout, in_args...)
================================================
FILE: src/exceptions.jl
================================================
struct MEngineError <: Exception
message::String
end
"""
MEngineError(message::String)
Exception thrown by MATLAB, e.g. due to syntax errors in the code
passed to `eval_string` or `mat"..."`.
"""
struct MatlabException <: Exception
identifier::String
message::String
end
================================================
FILE: src/init.jl
================================================
# libraries
const libeng = Ref{Ptr{Cvoid}}()
const libmx = Ref{Ptr{Cvoid}}()
const libmat = Ref{Ptr{Cvoid}}()
# matlab engine functions
const eng_open = Ref{Ptr{Cvoid}}()
const eng_close = Ref{Ptr{Cvoid}}()
const eng_set_visible = Ref{Ptr{Cvoid}}()
const eng_get_visible = Ref{Ptr{Cvoid}}()
const eng_output_buffer = Ref{Ptr{Cvoid}}()
const eng_eval_string = Ref{Ptr{Cvoid}}()
const eng_put_variable = Ref{Ptr{Cvoid}}()
const eng_get_variable = Ref{Ptr{Cvoid}}()
# mxarray functions
const mx_destroy_array = Ref{Ptr{Cvoid}}()
const mx_duplicate_array = Ref{Ptr{Cvoid}}()
# functions to access mxarray
const mx_free = Ref{Ptr{Cvoid}}()
const mx_get_classid = Ref{Ptr{Cvoid}}()
const mx_get_m = Ref{Ptr{Cvoid}}()
const mx_get_n = Ref{Ptr{Cvoid}}()
const mx_get_nelems = Ref{Ptr{Cvoid}}()
const mx_get_ndims = Ref{Ptr{Cvoid}}()
const mx_get_elemsize = Ref{Ptr{Cvoid}}()
const mx_get_data = Ref{Ptr{Cvoid}}()
const mx_get_dims = Ref{Ptr{Cvoid}}()
const mx_get_nfields = Ref{Ptr{Cvoid}}()
const mx_get_pr = Ref{Ptr{Cvoid}}()
const mx_get_pi = Ref{Ptr{Cvoid}}()
const mx_get_ir = Ref{Ptr{Cvoid}}()
const mx_get_jc = Ref{Ptr{Cvoid}}()
const mx_is_double = Ref{Ptr{Cvoid}}()
const mx_is_single = Ref{Ptr{Cvoid}}()
const mx_is_int64 = Ref{Ptr{Cvoid}}()
const mx_is_uint64 = Ref{Ptr{Cvoid}}()
const mx_is_int32 = Ref{Ptr{Cvoid}}()
const mx_is_uint32 = Ref{Ptr{Cvoid}}()
const mx_is_int16 = Ref{Ptr{Cvoid}}()
const mx_is_uint16 = Ref{Ptr{Cvoid}}()
const mx_is_int8 = Ref{Ptr{Cvoid}}()
const mx_is_uint8 = Ref{Ptr{Cvoid}}()
const mx_is_char = Ref{Ptr{Cvoid}}()
const mx_is_numeric = Ref{Ptr{Cvoid}}()
const mx_is_logical = Ref{Ptr{Cvoid}}()
const mx_is_complex = Ref{Ptr{Cvoid}}()
const mx_is_sparse = Ref{Ptr{Cvoid}}()
const mx_is_empty = Ref{Ptr{Cvoid}}()
const mx_is_struct = Ref{Ptr{Cvoid}}()
const mx_is_cell = Ref{Ptr{Cvoid}}()
# functions to create & delete MATLAB arrays
const mx_create_numeric_matrix = Ref{Ptr{Cvoid}}()
const mx_create_numeric_array = Ref{Ptr{Cvoid}}()
const mx_create_double_scalar = Ref{Ptr{Cvoid}}()
const mx_create_logical_scalar = Ref{Ptr{Cvoid}}()
const mx_create_sparse = Ref{Ptr{Cvoid}}()
const mx_create_sparse_logical = Ref{Ptr{Cvoid}}()
const mx_create_string = Ref{Ptr{Cvoid}}()
const mx_create_char_array = Ref{Ptr{Cvoid}}()
const mx_create_cell_array = Ref{Ptr{Cvoid}}()
const mx_create_struct_matrix = Ref{Ptr{Cvoid}}()
const mx_create_struct_array = Ref{Ptr{Cvoid}}()
const mx_get_cell = Ref{Ptr{Cvoid}}()
const mx_set_cell = Ref{Ptr{Cvoid}}()
const mx_get_field = Ref{Ptr{Cvoid}}()
const mx_set_field = Ref{Ptr{Cvoid}}()
const mx_get_field_bynum = Ref{Ptr{Cvoid}}()
const mx_get_fieldname = Ref{Ptr{Cvoid}}()
const mx_get_string = Ref{Ptr{Cvoid}}()
# load I/O mat functions
const mat_open = Ref{Ptr{Cvoid}}()
const mat_close = Ref{Ptr{Cvoid}}()
const mat_get_variable = Ref{Ptr{Cvoid}}()
const mat_put_variable = Ref{Ptr{Cvoid}}()
const mat_get_dir = Ref{Ptr{Cvoid}}()
================================================
FILE: src/matfile.jl
================================================
# mat file open & close
mutable struct MatFile
ptr::Ptr{Cvoid}
filename::String
function MatFile(filename::String, mode::String)
p = ccall(mat_open[], Ptr{Cvoid}, (Ptr{Cchar}, Ptr{Cchar}), filename, mode)
self = new(p, filename)
finalizer(release, self)
return self
end
end
MatFile(filename::String) = MatFile(filename, "r")
function unsafe_convert(::Type{Ptr{Cvoid}}, f::MatFile)
ptr = f.ptr
ptr == C_NULL && throw(UndefRefError())
return ptr
end
function release(f::MatFile)
ptr = f.ptr
if ptr != C_NULL
ccall(mat_close[], Cint, (Ptr{Cvoid},), ptr)
end
f.ptr = C_NULL
return nothing
end
function close(f::MatFile)
ret = ccall(mat_close[], Cint, (Ptr{Cvoid},), f)
ret != 0 && throw(MEngineError("failed to close file (err = $ret)"))
f.ptr = C_NULL
return nothing
end
# get & put variables
function get_mvariable(f::MatFile, name::String)
pm = ccall(mat_get_variable[], Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cchar}), f, name)
pm == C_NULL && error("Attempt to get variable $(name) failed.")
MxArray(pm)
end
get_mvariable(f::MatFile, name::Symbol) = get_mvariable(f, string(name))
get_variable(f::MatFile, name::String) = jvalue(get_mvariable(f, name))
get_variable(f::MatFile, name::Symbol) = jvalue(get_mvariable(f, name))
function put_variable(f::MatFile, name::String, v::MxArray)
ret = ccall(mat_put_variable[], Cint, (Ptr{Cvoid}, Ptr{Cchar}, Ptr{Cvoid}), f, name, v)
ret != 0 && error("Attempt to put variable $(name) failed.")
return nothing
end
put_variable(f::MatFile, name::Symbol, v::MxArray) = put_variable(f, string(name), v)
put_variable(f::MatFile, name::String, v) = put_variable(f, name, mxarray(v))
put_variable(f::MatFile, name::Symbol, v) = put_variable(f, name, mxarray(v))
# operation over entire file
function put_variables(f::MatFile; kwargs...)
for (name, val) in kwargs
put_variable(f, name, val)
end
end
function write_matfile(filename::String; kwargs...)
mf = MatFile(filename, "w")
put_variables(mf; kwargs...)
close(mf)
end
function variable_names(f::MatFile)
# get a list of all variable names
_n = Ref{Cint}()
_a = ccall(mat_get_dir[], Ptr{Ptr{Cchar}}, (Ptr{Cvoid}, Ref{Cint}), f, _n)
n = Int(_n[])
a = unsafe_wrap(Array, _a, (n,))
names = String[unsafe_string(s) for s in a]
ccall(mx_free[], Cvoid, (Ptr{Cvoid},), _a)
return names
end
function read_matfile(f::MatFile)
# return a dictionary of all variables
names = variable_names(f)
r = Dict{String,MxArray}()
sizehint!(r, length(names))
for nam in names
r[nam] = get_mvariable(f, nam)
end
return r
end
function read_matfile(filename::String)
f = MatFile(filename, "r")
r = read_matfile(f)
close(f)
return r
end
================================================
FILE: src/matstr.jl
================================================
# Syntax for mat"" string interpolation
# A really basic parser intended only to handle checking whether
# a variable is on the left or right hand side of an expression
mutable struct DumbParserState
paren_depth::Int
in_string::Bool
end
DumbParserState() = DumbParserState(0, false)
# Returns true if an = is encountered and updates pstate
function dumb_parse!(pstate::DumbParserState, str::String)
paren_depth = pstate.paren_depth
in_string = pstate.in_string
x = '\0'
s = firstindex(str)
while s <= ncodeunits(str)
lastx = x
x = str[s]
s = nextind(str, s)
if in_string
if x == '\''
if (s <= ncodeunits(str)) && (str[s] == '\'')
x = str[s]
s = nextind(str, s)
else
in_string = false
end
end
else
if x == '('
paren_depth += 1
elseif x == ')'
paren_depth -= 1
elseif x == '\'' && lastx in ",( \t\0;"
in_string = true
elseif x == '=' && !(lastx in "<>~")
if (s <= ncodeunits(str)) && (str[s] == '=')
x = str[s]
s = nextind(str, s)
else
return true
end
elseif x == '%'
break
end
end
end
pstate.paren_depth = paren_depth
pstate.in_string = in_string
return false
end
# Check if a given variable is assigned, used, or both. Returns the#
# assignment and use status
function check_assignment(interp, i)
# Go back to the last newline
before = String[]
for j = (i-1):-1:1
if isa(interp[j], String)
sp = split(interp[j], "\n")
pushfirst!(before, sp[end])
for k = (length(sp)-1):-1:1
match(r"\.\.\.[ \t]*\r?$", sp[k]) === nothing && @goto done_before
pushfirst!(before, sp[k])
end
end
end
@label done_before
# Check if this reference is inside parens at the start, or on the rhs of an assignment
pstate = DumbParserState()
(dumb_parse!(pstate, join(before)) || pstate.paren_depth > 1) && return (false, true)
# Go until the next newline or comment
after = String[]
both_sides = false
for j = (i+1):length(interp)
if isa(interp[j], String)
sp = split(interp[j], "\n")
push!(after, sp[1])
for k = 2:length(sp)
match(r"\.\.\.[ \t]*\r?$", sp[k-1]) === nothing && @goto done_after
push!(after, sp[k])
end
elseif interp[j] == interp[i]
both_sides = true
end
end
@label done_after
assigned = dumb_parse!(pstate, join(after))
used =
!assigned || both_sides ||
(i < length(interp) && match(r"^[ \t]*\(", interp[i+1]) != nothing)
return (assigned, used)
end
function do_mat_str(ex)
# Hack to do interpolation
interp = Meta.parse(string("\"\"\"", replace(ex, "\"\"\"" => "\\\"\"\""), "\"\"\""))
if isa(interp, String)
interp = [interp]
elseif interp.head == :string
interp = interp.args
elseif interp.head == :macrocall
interp = interp.args[2:end]
else
throw(ArgumentError("unexpected input"))
end
# Handle interpolated variables
putblock = Expr(:block)
getblock = Expr(:block)
usedvars = Set{Symbol}()
assignedvars = Set{Symbol}()
varmap = Dict{Symbol,Symbol}()
for i = 1:length(interp)
if !isa(interp[i], String)
# Don't put the same symbol to MATLAB twice
if haskey(varmap, interp[i])
var = varmap[interp[i]]
else
var = Symbol("matlab_jl_", i)
if isa(interp[i], Symbol)
varmap[interp[i]] = var
end
end
# Try to determine if variable is being used in an assignment
(assigned, used) = check_assignment(interp, i)
if used && !(var in usedvars)
push!(usedvars, var)
(var in assignedvars) || push!(
putblock.args,
:(put_variable($(Meta.quot(var)), $(esc(interp[i])))),
)
end
if assigned && !(var in assignedvars)
push!(assignedvars, var)
if isa(interp[i], Expr) && (interp[i].head == :ref)
# Assignment to a sliced variable, e.g., x[1:3], must use broadcasting in v0.7+
push!(
getblock.args,
Expr(:(.=), esc(interp[i]), :(get_variable($(Meta.quot(var))))),
)
else
push!(
getblock.args,
Expr(:(=), esc(interp[i]), :(get_variable($(Meta.quot(var))))),
)
end
end
interp[i] = var
end
end
# Clear `ans` and set `matlab_jl_has_ans` before we run the code
pushfirst!(interp, "clear ans;\nmatlab_jl_has_ans = 0;\n")
# Add a semicolon to the end of the last statement to suppress output
isa(interp[end], String) && (interp[end] = rstrip(interp[end]))
push!(interp, ";")
# Figure out if `ans` exists in code to avoid an error if it doesn't
push!(interp, "\nmatlab_jl_has_ans = exist('ans', 'var');")
quote
$(putblock)
eval_string($(join(interp)))
$(getblock)
$(
if !isempty(usedvars) || !isempty(assignedvars)
# Clear variables we created
:(eval_string(
$(string("clear ", join(union(usedvars, assignedvars), " "), ";")),
))
end
)
if get_variable(:matlab_jl_has_ans) != 0
# Return ans if it was set
get_variable(:ans)
end
end
end
macro mat_str(ex)
do_mat_str(ex)
end
================================================
FILE: src/mxarray.jl
================================================
# functions to deal with MATLAB arrays
mutable struct MxArray
ptr::Ptr{Cvoid}
own::Bool
function MxArray(p::Ptr{Cvoid}, own::Bool)
p == C_NULL && error("NULL pointer for MxArray.")
self = new(p, own)
if own
finalizer(release, self)
end
return self
end
end
MxArray(p::Ptr{Cvoid}) = MxArray(p, true)
mxarray(mx::MxArray) = mx
function release(mx::MxArray)
if mx.own && mx.ptr != C_NULL
ccall(mx_destroy_array[], Cvoid, (Ptr{Cvoid},), mx.ptr)
end
mx.ptr = C_NULL
return nothing
end
# delete & copy
function delete(mx::MxArray)
if mx.own
ccall(mx_destroy_array[], Cvoid, (Ptr{Cvoid},), mx)
end
mx.ptr = C_NULL
return nothing
end
function copy(mx::MxArray)
pm = ccall(mx_duplicate_array[], Ptr{Cvoid}, (Ptr{Cvoid},), mx)
return MxArray(pm)
end
function unsafe_convert(::Type{Ptr{Cvoid}}, mx::MxArray)
ptr = mx.ptr
ptr == C_NULL && throw(UndefRefError())
return ptr
end
# functions to create mxArray from Julia values/arrays
const MxRealNum =
Union{Float64,Float32,Int32,UInt32,Int64,UInt64,Int16,UInt16,Int8,UInt8,Bool}
const MxComplexNum = Union{ComplexF32,ComplexF64}
const MxNum = Union{MxRealNum,MxComplexNum}
###########################################################
#
# MATLAB types
#
###########################################################
const mwSize = UInt
const mwIndex = Int
@enum mxClassID::Cint begin
mxUNKNOWN_CLASS
mxCELL_CLASS
mxSTRUCT_CLASS
mxLOGICAL_CLASS
mxCHAR_CLASS
mxVOID_CLASS
mxDOUBLE_CLASS
mxSINGLE_CLASS
mxINT8_CLASS
mxUINT8_CLASS
mxINT16_CLASS
mxUINT16_CLASS
mxINT32_CLASS
mxUINT32_CLASS
mxINT64_CLASS
mxUINT64_CLASS
mxFUNCTION_CLASS
mxOPAQUE_CLASS
mxOBJECT_CLASS
end
@enum mxComplexity::Cint begin
mxREAL
mxCOMPLEX
end
mxclassid(::Type{Bool}) = mxLOGICAL_CLASS
mxclassid(::Union{Type{Float64},Type{ComplexF64}}) = mxDOUBLE_CLASS
mxclassid(::Union{Type{Float32},Type{ComplexF32}}) = mxSINGLE_CLASS
mxclassid(::Type{Int8}) = mxINT8_CLASS
mxclassid(::Type{UInt8}) = mxUINT8_CLASS
mxclassid(::Type{Int16}) = mxINT16_CLASS
mxclassid(::Type{UInt16}) = mxUINT16_CLASS
mxclassid(::Type{Int32}) = mxINT32_CLASS
mxclassid(::Type{UInt32}) = mxUINT32_CLASS
mxclassid(::Type{Int64}) = mxINT64_CLASS
mxclassid(::Type{UInt64}) = mxUINT64_CLASS
mxcomplexflag(::Type{T}) where {T<:MxRealNum} = mxREAL
mxcomplexflag(::Type{T}) where {T<:MxComplexNum} = mxCOMPLEX
const classid_type_map = Dict{mxClassID,Type}(
mxLOGICAL_CLASS => Bool,
mxCHAR_CLASS => Char,
mxDOUBLE_CLASS => Float64,
mxSINGLE_CLASS => Float32,
mxINT8_CLASS => Int8,
mxUINT8_CLASS => UInt8,
mxINT16_CLASS => Int16,
mxUINT16_CLASS => UInt16,
mxINT32_CLASS => Int32,
mxUINT32_CLASS => UInt32,
mxINT64_CLASS => Int64,
mxUINT64_CLASS => UInt64,
)
function mxclassid_to_type(cid::mxClassID)
ty = get(classid_type_map, cid, nothing)
ty === nothing && throw(ArgumentError("The input class id is not a primitive type id."))
return ty
end
###########################################################
#
# Functions to access mxArray
#
# Part of the functions (e.g. mxGetNumberOfDimensions)
# are actually a macro replacement of an internal
# function name as (xxxx_730)
#
###########################################################
macro mxget_attr(fun, ret, mx)
:(ccall($(esc(fun)), $(esc(ret)), (Ptr{Cvoid},), $(esc(mx))))
end
classid(mx::MxArray) = @mxget_attr(mx_get_classid[], mxClassID, mx)
nrows(mx::MxArray) = convert(Int, @mxget_attr(mx_get_m[], UInt, mx))
ncols(mx::MxArray) = convert(Int, @mxget_attr(mx_get_n[], UInt, mx))
nelems(mx::MxArray) = convert(Int, @mxget_attr(mx_get_nelems[], UInt, mx))
ndims(mx::MxArray) = convert(Int, @mxget_attr(mx_get_ndims[], mwSize, mx))
eltype(mx::MxArray) = mxclassid_to_type(classid(mx))
elsize(mx::MxArray) = convert(Int, @mxget_attr(mx_get_elemsize[], UInt, mx))
data_ptr(mx::MxArray) = convert(Ptr{eltype(mx)}, @mxget_attr(mx_get_data[], Ptr{Cvoid}, mx))
real_ptr(mx::MxArray) = convert(Ptr{eltype(mx)}, @mxget_attr(mx_get_pr[], Ptr{Cvoid}, mx))
imag_ptr(mx::MxArray) = convert(Ptr{eltype(mx)}, @mxget_attr(mx_get_pi[], Ptr{Cvoid}, mx))
mxnfields(mx::MxArray) = convert(Int, @mxget_attr(mx_get_nfields[], Cint, mx))
# validation functions
macro mx_test_is(fun, mx)
:((ccall($(esc(fun)), Bool, (Ptr{Cvoid},), $(esc(mx)))))
end
is_double(mx::MxArray) = @mx_test_is(mx_is_double[], mx)
is_single(mx::MxArray) = @mx_test_is(mx_is_single[], mx)
is_int64(mx::MxArray) = @mx_test_is(mx_is_int64[], mx)
is_uint64(mx::MxArray) = @mx_test_is(mx_is_uint64[], mx)
is_int32(mx::MxArray) = @mx_test_is(mx_is_int32[], mx)
is_uint32(mx::MxArray) = @mx_test_is(mx_is_uint32[], mx)
is_int16(mx::MxArray) = @mx_test_is(mx_is_int16[], mx)
is_uint16(mx::MxArray) = @mx_test_is(mx_is_uint16[], mx)
is_int8(mx::MxArray) = @mx_test_is(mx_is_int8[], mx)
is_uint8(mx::MxArray) = @mx_test_is(mx_is_uint8[], mx)
is_numeric(mx::MxArray) = @mx_test_is(mx_is_numeric[], mx)
is_logical(mx::MxArray) = @mx_test_is(mx_is_logical[], mx)
is_complex(mx::MxArray) = @mx_test_is(mx_is_complex[], mx)
is_sparse(mx::MxArray) = @mx_test_is(mx_is_sparse[], mx)
is_struct(mx::MxArray) = @mx_test_is(mx_is_struct[], mx)
is_cell(mx::MxArray) = @mx_test_is(mx_is_cell[], mx)
is_char(mx::MxArray) = @mx_test_is(mx_is_char[], mx)
is_empty(mx::MxArray) = @mx_test_is(mx_is_empty[], mx)
# size function
function size(mx::MxArray)
nd = ndims(mx)
pdims::Ptr{mwSize} = @mxget_attr(mx_get_dims[], Ptr{mwSize}, mx)
_dims = unsafe_wrap(Array, pdims, (nd,))
dims = Vector{Int}(undef, nd)
for i = 1:nd
dims[i] = Int(_dims[i])
end
tuple(dims...)
end
function size(mx::MxArray, d::Integer)
d <= 0 && throw(ArgumentError("The dimension must be a positive integer."))
nd = ndims(mx)
if nd == 2
d == 1 ? nrows(mx) :
d == 2 ? ncols(mx) : 1
else
pdims::Ptr{mwSize} = @mxget_attr(mx_get_dims[], Ptr{mwSize}, mx)
_dims = unsafe_wrap(Array, pdims, (nd,))
d <= nd ? Int(_dims[d]) : 1
end
end
###########################################################
#
# functions to create & delete MATLAB arrays
#
###########################################################
function _dims_to_mwSize(dims::Tuple{Vararg{Integer,N}}) where {N}
_dims = Vector{mwSize}(undef, N)
for i = 1:N
_dims[i] = mwSize(dims[i])
end
_dims
end
function mxarray(::Type{T}, dims::Tuple{Vararg{Integer,N}}) where {T<:MxNum,N}
pm = ccall(mx_create_numeric_array[], Ptr{Cvoid},
(mwSize, Ptr{mwSize}, mxClassID, mxComplexity),
N, _dims_to_mwSize(dims), mxclassid(T), mxcomplexflag(T))
MxArray(pm)
end
mxarray(::Type{T}, dims::Integer...) where {T<:MxNum} = mxarray(T, dims)
# create scalars
function mxarray(x::Float64)
pm = ccall(mx_create_double_scalar[], Ptr{Cvoid}, (Cdouble,), x)
MxArray(pm)
end
function mxarray(x::Bool)
pm = ccall(mx_create_logical_scalar[], Ptr{Cvoid}, (Bool,), x)
MxArray(pm)
end
function mxarray(x::T) where {T<:MxRealNum}
pm = ccall(mx_create_numeric_matrix[], Ptr{Cvoid},
(mwSize, mwSize, mxClassID, mxComplexity),
1, 1, mxclassid(T), mxcomplexflag(T))
pdat = ccall(mx_get_data[], Ptr{T}, (Ptr{Cvoid},), pm)
unsafe_wrap(Array, pdat, (1,))[1] = x
MxArray(pm)
end
mxarray(x::T) where {T<:MxComplexNum} = mxarray([x])
# conversion from Julia variables to MATLAB
# Note: the conversion is deep-copy, as there is no way to let
# mxArray use Julia array's memory
function mxarray(a::Array{T}) where {T<:MxRealNum}
mx = mxarray(T, size(a))
ccall(
:memcpy,
Ptr{Cvoid},
(Ptr{Cvoid}, Ptr{Cvoid}, UInt),
data_ptr(mx),
a,
length(a) * sizeof(T),
)
return mx
end
function mxarray(a::Array{T}) where {T<:MxComplexNum}
mx = mxarray(T, size(a))
na = length(a)
rdat = unsafe_wrap(Array, real_ptr(mx), na)
idat = unsafe_wrap(Array, imag_ptr(mx), na)
for i = 1:na
rdat[i] = real(a[i])
idat[i] = imag(a[i])
end
mx
end
function mxarray(a::AbstractArray{T}) where {T<:MxRealNum}
mx = mxarray(T, size(a))
ptr = data_ptr(mx)
na = length(a)
dat = unsafe_wrap(Array{T}, ptr, na)
for (i, ix) in enumerate(eachindex(a))
dat[i] = a[ix]
end
return mx
end
function mxarray(a::AbstractArray{T}) where {T<:MxComplexNum}
mx = mxarray(T, size(a))
na = length(a)
rdat = unsafe_wrap(Array, real_ptr(mx), na)
idat = unsafe_wrap(Array, imag_ptr(mx), na)
for (i, ix) in enumerate(eachindex(a))
rdat[i] = real(a[ix])
idat[i] = imag(a[ix])
end
return mx
end
function mxarray(a::NTuple{N,T}) where {N,T<:MxRealNum}
mx = mxarray(T, N)
pdat = ccall(mx_get_data[], Ptr{T}, (Ptr{Cvoid},), mx)
dat = unsafe_wrap(Array, pdat, N)
for i = 1:N
dat[i] = a[i]
end
return mx
end
function mxarray(a::NTuple{N,T}) where {N,T<:MxComplexNum}
mx = mxarray(T, size(a))
na = length(a)
rdat = unsafe_wrap(Array, real_ptr(mx), na)
idat = unsafe_wrap(Array, imag_ptr(mx), na)
for (i, ix) in enumerate(eachindex(a))
rdat[i] = real(a[ix])
idat[i] = imag(a[ix])
end
return mx
end
function mxarray(a::Tuple)
mx = mxcellarray(length(a))
for i = 1:length(a)
set_cell(mx, i, mxarray(a[i]))
end
return mx
end
# sparse matrix
function mxsparse(ty::Type{Float64}, m::Integer, n::Integer, nzmax::Integer)
pm = ccall(mx_create_sparse[], Ptr{Cvoid},
(mwSize, mwSize, mwSize, mxComplexity), m, n, nzmax, mxREAL)
MxArray(pm)
end
function mxsparse(ty::Type{ComplexF64}, m::Integer, n::Integer, nzmax::Integer)
pm = ccall(mx_create_sparse[], Ptr{Cvoid},
(mwSize, mwSize, mwSize, mxComplexity), m, n, nzmax, mxCOMPLEX)
MxArray(pm)
end
function mxsparse(ty::Type{Bool}, m::Integer, n::Integer, nzmax::Integer)
pm = ccall(mx_create_sparse_logical[], Ptr{Cvoid},
(mwSize, mwSize, mwSize), m, n, nzmax)
MxArray(pm)
end
function _copy_sparse_mat(
a::SparseMatrixCSC{V,I},
ir_p::Ptr{mwIndex},
jc_p::Ptr{mwIndex},
pr_p::Ptr{Float64},
pi_p::Ptr{Float64},
) where {V<:ComplexF64,I}
colptr::Vector{I} = a.colptr
rinds::Vector{I} = a.rowval
vr::Vector{Float64} = real(a.nzval)
vi::Vector{Float64} = imag(a.nzval)
n::Int = a.n
nnz::Int = length(vr)
# Note: ir and jc contain zero-based indices
ir = unsafe_wrap(Array, ir_p, (nnz,))
for i = 1:nnz
ir[i] = rinds[i] - 1
end
jc = unsafe_wrap(Array, jc_p, (n + 1,))
for i = 1:(n+1)
jc[i] = colptr[i] - 1
end
copyto!(unsafe_wrap(Array, pr_p, (nnz,)), vr)
copyto!(unsafe_wrap(Array, pi_p, (nnz,)), vi)
end
function _copy_sparse_mat(
a::SparseMatrixCSC{V,I},
ir_p::Ptr{mwIndex},
jc_p::Ptr{mwIndex},
pr_p::Ptr{V},
) where {V,I}
colptr::Vector{I} = a.colptr
rinds::Vector{I} = a.rowval
v::Vector{V} = a.nzval
n::Int = a.n
nnz::Int = length(v)
# Note: ir and jc contain zero-based indices
ir = unsafe_wrap(Array, ir_p, (nnz,))
for i = 1:nnz
ir[i] = rinds[i] - 1
end
jc = unsafe_wrap(Array, jc_p, (n + 1,))
for i = 1:(n+1)
jc[i] = colptr[i] - 1
end
copyto!(unsafe_wrap(Array, pr_p, (nnz,)), v)
end
function mxarray(a::SparseMatrixCSC{V,I}) where {V<:Union{Float64,ComplexF64,Bool},I}
m::Int = a.m
n::Int = a.n
nnz = length(a.nzval)
@assert nnz == a.colptr[n+1] - 1
mx = mxsparse(V, m, n, nnz)
ir_p = ccall(mx_get_ir[], Ptr{mwIndex}, (Ptr{Cvoid},), mx)
jc_p = ccall(mx_get_jc[], Ptr{mwIndex}, (Ptr{Cvoid},), mx)
if V <: ComplexF64
pr_p = ccall(mx_get_pr[], Ptr{Float64}, (Ptr{Cvoid},), mx)
pi_p = ccall(mx_get_pi[], Ptr{Float64}, (Ptr{Cvoid},), mx)
_copy_sparse_mat(a, ir_p, jc_p, pr_p, pi_p)
else
pr_p = ccall(mx_get_pr[], Ptr{V}, (Ptr{Cvoid},), mx)
_copy_sparse_mat(a, ir_p, jc_p, pr_p)
end
return mx
end
# char arrays and string
function mxarray(s::String)
utf16string = transcode(UInt16, s)
pm = ccall(mx_create_char_array[], Ptr{Cvoid}, (mwSize, Ptr{mwSize}), 2,
_dims_to_mwSize((1, length(utf16string))))
mx = MxArray(pm)
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt), data_ptr(mx), utf16string,
length(utf16string) * sizeof(UInt16))
return mx
end
# cell arrays
function mxcellarray(dims::Tuple{Vararg{Integer,N}}) where {N}
pm = ccall(mx_create_cell_array[], Ptr{Cvoid}, (mwSize, Ptr{mwSize}),
N, _dims_to_mwSize(dims))
MxArray(pm)
end
mxcellarray(dims::Integer...) = mxcellarray(dims)
function get_cell(mx::MxArray, i::Integer)
pm = ccall(mx_get_cell[], Ptr{Cvoid}, (Ptr{Cvoid}, mwIndex), mx, i - 1)
MxArray(pm, false)
end
function set_cell(mx::MxArray, i::Integer, v::MxArray)
v.own = false
ccall(mx_set_cell[], Cvoid, (Ptr{Cvoid}, mwIndex, Ptr{Cvoid}), mx, i - 1, v)
return nothing
end
function mxcellarray(a::Array)
pm = mxcellarray(size(a))
for i = 1:length(a)
set_cell(pm, i, mxarray(a[i]))
end
return pm
end
mxarray(a::Array) = mxcellarray(a)
# struct arrays
function _fieldname_array(fieldnames::String...)
n = length(fieldnames)
a = Vector{Ptr{UInt8}}(undef, n)
for i = 1:n
a[i] = unsafe_convert(Ptr{UInt8}, fieldnames[i])
end
return a
end
function mxstruct(fns::Vector{String})
a = _fieldname_array(fns...)
pm = ccall(mx_create_struct_matrix[], Ptr{Cvoid},
(mwSize, mwSize, Cint, Ptr{Ptr{UInt8}}), 1, 1, length(a), a)
MxArray(pm)
end
function mxstruct(fn1::String, fnr::String...)
a = _fieldname_array(fn1, fnr...)
pm = ccall(mx_create_struct_matrix[], Ptr{Cvoid},
(mwSize, mwSize, Cint, Ptr{Ptr{UInt8}}), 1, 1, length(a), a)
MxArray(pm)
end
function set_field(mx::MxArray, i::Integer, f::String, v::MxArray)
v.own = false
ccall(
mx_set_field[],
Cvoid,
(Ptr{Cvoid}, mwIndex, Ptr{UInt8}, Ptr{Cvoid}),
mx,
i - 1,
f,
v,
)
return nothing
end
set_field(mx::MxArray, f::String, v::MxArray) = set_field(mx, 1, f, v)
function get_field(mx::MxArray, i::Integer, f::String)
pm = ccall(mx_get_field[], Ptr{Cvoid}, (Ptr{Cvoid}, mwIndex, Ptr{UInt8}), mx, i - 1, f)
pm == C_NULL && throw(ArgumentError("Failed to get field."))
MxArray(pm, false)
end
get_field(mx::MxArray, f::String) = get_field(mx, 1, f)
function get_field(mx::MxArray, i::Integer, fn::Integer)
pm = ccall(
mx_get_field_bynum[],
Ptr{Cvoid},
(Ptr{Cvoid}, mwIndex, Cint),
mx,
i - 1,
fn - 1,
)
pm == C_NULL && throw(ArgumentError("Failed to get field."))
MxArray(pm, false)
end
get_field(mx::MxArray, fn::Integer) = get_field(mx, 1, fn)
function get_fieldname(mx::MxArray, i::Integer)
p = ccall(mx_get_fieldname[], Ptr{UInt8}, (Ptr{Cvoid}, Cint), mx, i - 1)
unsafe_string(p)
end
const Pairs = Union{Pair,NTuple{2}}
function mxstruct(pairs::Pairs...)
nf = length(pairs)
fieldnames = Vector{String}(undef, nf)
for i = 1:nf
fn = pairs[i][1]
fieldnames[i] = string(fn)
end
mx = mxstruct(fieldnames)
for i = 1:nf
set_field(mx, fieldnames[i], mxarray(pairs[i][2]))
end
return mx
end
function mxstruct(d::T) where {T}
names = fieldnames(T)
names_str = map(string, names)
mx = mxstruct(names_str...)
for i = 1:length(names)
set_field(mx, names_str[i], mxarray(getfield(d, names[i])))
end
return mx
end
function mxstructarray(d::Array{T}) where {T}
names = fieldnames(T)
names_str = map(string, names)
a = _fieldname_array(names_str...)
pm = ccall(mx_create_struct_array[], Ptr{Cvoid},
(mwSize, Ptr{mwSize}, Cint,
Ptr{Ptr{UInt8}}), ndims(d), _dims_to_mwSize(size(d)), length(a), a)
mx = MxArray(pm)
for i = 1:length(d), j = 1:length(names)
set_field(mx, i, names_str[j], mxarray(getfield(d[i], names[j])))
end
return mx
end
mxstruct(d::AbstractDict) = mxstruct(collect(d)...)
mxarray(d) = mxstruct(d)
###########################################################
#
# convert from MATLAB to Julia
#
###########################################################
# use deep-copy from MATLAB variable to Julia array
# in practice, MATLAB variable often has shorter life-cycle
function _jarrayx(fun::String, mx::MxArray, siz::Tuple)
if is_numeric(mx) || is_logical(mx)
@assert !is_sparse(mx)
T = eltype(mx)
if is_complex(mx)
rdat = unsafe_wrap(Array, real_ptr(mx), siz)
idat = unsafe_wrap(Array, imag_ptr(mx), siz)
a = complex.(rdat, idat)
else
a = Array{T}(undef, siz)
if !isempty(a)
ccall(
:memcpy,
Ptr{Cvoid},
(Ptr{Cvoid}, Ptr{Cvoid}, UInt),
a,
data_ptr(mx),
length(a) * sizeof(T),
)
end
end
return a
#unsafe_wrap(Array, data_ptr(mx), siz)
elseif is_cell(mx)
a = Array{Any}(undef, siz)
for i = 1:length(a)
a[i] = jvalue(get_cell(mx, i))
end
return a
else
throw(ArgumentError("$(fun) only applies to numeric, logical or cell arrays."))
end
end
jarray(mx::MxArray) = _jarrayx("jarray", mx, size(mx))
jvector(mx::MxArray) = _jarrayx("jvector", mx, (nelems(mx),))
function jmatrix(mx::MxArray)
if ndims(mx) != 2
throw(ArgumentError("jmatrix only applies to MATLAB arrays with ndims == 2."))
end
return _jarrayx("jmatrix", mx, (nrows(mx), ncols(mx)))
end
function jscalar(mx::MxArray)
if !(nelems(mx) == 1 && (is_logical(mx) || is_numeric(mx)))
throw(
ArgumentError(
"jscalar only applies to numeric or logical arrays with exactly one element.",
),
)
end
@assert !is_sparse(mx)
if is_complex(mx)
return unsafe_wrap(Array, real_ptr(mx), (1,))[1] +
im * unsafe_wrap(Array, imag_ptr(mx), (1,))[1]
else
return unsafe_wrap(Array, data_ptr(mx), (1,))[1]
end
end
function _jsparse(ty::Type{T}, mx::MxArray) where {T<:MxRealNum}
m = nrows(mx)
n = ncols(mx)
ir_ptr = ccall(mx_get_ir[], Ptr{mwIndex}, (Ptr{Cvoid},), mx)
jc_ptr = ccall(mx_get_jc[], Ptr{mwIndex}, (Ptr{Cvoid},), mx)
jc_a::Vector{mwIndex} = unsafe_wrap(Array, jc_ptr, (n + 1,))
nnz = jc_a[n+1]
ir = Vector{Int}(undef, nnz)
jc = Vector{Int}(undef, n + 1)
ir_x = unsafe_wrap(Array, ir_ptr, (nnz,))
for i = 1:nnz
ir[i] = ir_x[i] + 1
end
jc_x = unsafe_wrap(Array, jc_ptr, (n + 1,))
for i = 1:(n+1)
jc[i] = jc_x[i] + 1
end
pr_ptr = ccall(mx_get_pr[], Ptr{T}, (Ptr{Cvoid},), mx)
pr::Vector{T} = copy(unsafe_wrap(Array, pr_ptr, (nnz,)))
if is_complex(mx)
pi_ptr = ccall(mx_get_pi[], Ptr{T}, (Ptr{Cvoid},), mx)
pi::Vector{T} = copy(unsafe_wrap(Array, pi_ptr, (nnz,)))
return SparseMatrixCSC(m, n, jc, ir, pr + im .* pi)
else
return SparseMatrixCSC(m, n, jc, ir, pr)
end
end
function jsparse(mx::MxArray)
if !is_sparse(mx)
throw(ArgumentError("jsparse only applies to sparse matrices."))
end
return _jsparse(eltype(mx), mx)
end
function String(mx::MxArray)
if !(
classid(mx) == mxCHAR_CLASS && ((ndims(mx) == 2 && nrows(mx) == 1) || is_empty(mx))
)
throw(
ArgumentError(
"String(mx::MxArray) only applies to strings (i.e. char vectors)",
),
)
end
return transcode(String, unsafe_wrap(Array, Ptr{UInt16}(data_ptr(mx)), ncols(mx)))
end
function Dict(mx::MxArray)
if !(is_struct(mx) && nelems(mx) == 1)
throw(ArgumentError("Dict(mx::MxArray) only applies to a single struct"))
end
nf = mxnfields(mx)
fnames = Vector{String}(undef, nf)
fvals = Vector{Any}(undef, nf)
for i = 1:nf
fnames[i] = get_fieldname(mx, i)
pv =
ccall(
mx_get_field_bynum[],
Ptr{Cvoid},
(Ptr{Cvoid}, mwIndex, Cint),
mx,
0,
i - 1,
)
fx = MxArray(pv, false)
fvals[i] = jvalue(fx)
end
Dict(zip(fnames, fvals))
end
function jvalue(mx::MxArray)
if is_numeric(mx) || is_logical(mx)
if !is_sparse(mx)
nelems(mx) == 1 ? jscalar(mx) :
ndims(mx) == 2 ? (ncols(mx) == 1 ? jvector(mx) : jmatrix(mx)) :
jarray(mx)
else
jsparse(mx)
end
elseif is_char(mx) && (nrows(mx) == 1 || is_empty(mx))
String(mx)
elseif is_cell(mx)
ndims(mx) == 2 ? (ncols(mx) == 1 ? jvector(mx) : jmatrix(mx)) :
jarray(mx)
elseif is_struct(mx) && nelems(mx) == 1
Dict(mx)
else
throw(ArgumentError("Unsupported kind of variable."))
end
end
# deep conversion from MATLAB variable to Julia array
jvalue(mx::MxArray, ::Type{Array}) = jarray(mx)
jvalue(mx::MxArray, ::Type{Vector}) = jvector(mx)
jvalue(mx::MxArray, ::Type{Matrix}) = jmatrix(mx)
jvalue(mx::MxArray, ::Type{Number}) = jscalar(mx)::Number
jvalue(mx::MxArray, ::Type{String}) = String(mx)
jvalue(mx::MxArray, ::Type{Dict}) = Dict(mx)
jvalue(mx::MxArray, ::Type{SparseMatrixCSC}) = jsparse(mx)
# legacy support (eventually drop, when all constructors added)
jdict(mx::MxArray) = Dict(mx)
jstring(mx::MxArray) = String(mx)
================================================
FILE: test/engine.jl
================================================
using MATLAB
using Test
using SparseArrays
# test engine
restart_default_msession()
a = [1.0 2.0 3.0; 4.0 5.0 6.0]
b = [2.0 3.0 4.0; 8.0 7.0 6.0]
@mput a b
mat"""
r1 = a .* b;
r2 = a + b;
"""
@mget r1 r2
@test isequal(r1, a .* b)
@test isequal(r2, a + b)
@mget r1::Vector
@test isequal(r1, vec(a .* b))
s = sparse([1.0 0.0 0.0; 2.0 3.0 0.0; 0.0 4.0 5.0])
put_variable(:s, s)
s2 = get_variable(:s)
@test isequal(s, s2)
# mxcall
r = mxcall(:plus, 1, a, b)
@test isequal(r, a + b)
(xx, yy) = mxcall(:meshgrid, 2, [1.0, 2.0], [3.0, 4.0])
@test isequal(xx, [1.0 2.0; 1.0 2.0])
@test isequal(yy, [3.0 3.0; 4.0 4.0])
close_default_msession()
# test for segfault
s = MSession()
close(s)
@test_throws UndefRefError close(s)
# segfault on deleted references
x = mxarray(3.0)
delete(x)
@test_throws UndefRefError delete(x)
@test_throws UndefRefError nrows(x)
@test_throws UndefRefError is_numeric(x)
@test_throws UndefRefError jscalar(x)
@test_throws UndefRefError jvalue(x)
# make sure restart_default_msession() doesn't error on null references on
# default msession
s = get_default_msession()
close(s)
restart_default_msession()
================================================
FILE: test/matfile.jl
================================================
using MATLAB
using Test
# test MMAT file I/O
fn = "$(tempname()).mat"
a32 = Int32[1 2 3; 4 5 6]
a64 = Int64[1 2 3; 4 5 6]
b = [1.2, 3.4, 5.6, 7.8]
c = [[0.0, 1.0], [1.0, 2.0], [1.0, 2.0, 3.0]]
d = Dict("name" => "MATLAB", "score" => 100.0)
struct S
x::Float64
y::Bool
z::Vector{Float64}
end
ss = S[S(1.0, true, [1.0, 2.0]), S(2.0, false, [3.0, 4.0])]
write_matfile(fn; a32=a32, a64=a64, b=b, c=c, d=d, ss=mxstructarray(ss))
r = read_matfile(fn)
@test isa(r, Dict{String,MxArray})
@test length(r) == 6
ra32 = jmatrix(r["a32"])
ra64 = jmatrix(r["a64"])
rb = jvector(r["b"])
rc = jvalue(r["c"])
rd = jdict(r["d"])
rss = r["ss"]
GC.gc() # make sure that ra, rb, rc, rd remain valid
@test ra32 == a32
@test ra64 == a64
@test rb == b
@test rc == c
@test rd["name"] == d["name"]
@test rd["score"] == d["score"]
@test is_struct(rss)
@test jscalar(get_field(rss, 1, "x")) == 1.0
@test jscalar(get_field(rss, 1, "y"))
@test jvector(get_field(rss, 1, "z")) == ss[1].z
@test jscalar(get_field(rss, 2, "x")) == 2.0
@test !jscalar(get_field(rss, 2, "y"))
@test jvector(get_field(rss, 2, "z")) == ss[2].z
# segfault on deleted references
s = MatFile(fn)
close(s)
@test_throws UndefRefError close(s)
rm(fn)
================================================
FILE: test/matstr.jl
================================================
using MATLAB
using Test
@test mat"1" == 1
@test mat"[1, 2, 3]" == [1 2 3]
# Test interpolation
x = 1
@test mat"$x + 1" == 2
ret = mat"$y = $x + 2"
@test ret === nothing
@test y == 3
ret = mat"$y = $(x + 3)"
@test ret === nothing
@test y == 4
x = 5
@test mat"$x == 5"
# Test assignment
x = [1, 2, 3, 4, 5]
ret = mat"$x(1:3) = 1"
@test ret === nothing
@test x == [1, 1, 1, 4, 5]
ret = mat"$(x[1:3]) = 2"
@test ret === nothing
@test x == [2, 2, 2, 4, 5]
# Test a more complicated case with assignments on LHS and RHS
x = 20
mat"""
for i = 1:10
$x = $x + 1;
end
"""
# Test assignment then use
ret = mat"""
$z = 5;
$q = $z;
"""
@test ret === nothing
@test z == 5
@test q == 5
# Test multiple assignment
ret = mat"[$a, $b] = sort([4, 3])"
@test ret === nothing
@test a == [3 4]
@test b == [2 1]
# Test comments
a = 5
@test mat"$a + 1; % = 2" == 6
# Test indexing
c = [1, 2]
@test mat"$c($c == 2)" == 2
# Test line continuations
ret = mat"""
$d ...
= 3
"""
@test ret === nothing
@test d == 3
# Test strings with =
text = "hello = world"
@test mat"strfind($text, 'o = w')" == 5
@testset "Propagate Matlab Exceptions" begin
# Checks should be enabled by default
@test MATLAB.has_exception_check_enabled() == true
# Test invalid command
@test_throws MATLAB.MatlabException mat"invalid_command"
# Test invalid assignment
@test_throws MATLAB.MatlabException mat"1 = 2"
# Test invalid command within a block
@test_throws MATLAB.MatlabException mat"""
xyz = 1 + 2;
invalid_command;
abc = 2 * xyz;
"""
# Disable Checks
MATLAB.disable_exception_check!()
@test MATLAB.has_exception_check_enabled() == false
# Test invalid command
try
mat"invalid_command"
catch ex
@test false # should not throw an exception
end
end
================================================
FILE: test/mxarray.jl
================================================
using MATLAB
using Test
using SparseArrays
# Unit testing for MxArray
m = 5
n = 6
# test basic types in 1D & 2D
macro mx_test_basic_types(ty, testfun)
quote
a = mxarray($(ty), n)
@test elsize(a) == sizeof($(ty))
@test eltype(a) === $(ty)
@test nrows(a) == n
@test ncols(a) == 1
@test nelems(a) == n
@test ndims(a) == 2
@test size(a) == (n, 1)
@test size(a, 1) == n
@test size(a, 2) == 1
@test size(a, 3) == 1
@test !is_complex(a)
@test $(testfun)(a)
delete(a)
b = mxarray($(ty), m, n)
@test elsize(b) == sizeof($(ty))
@test eltype(b) === $(ty)
@test nrows(b) == m
@test ncols(b) == n
@test nelems(b) == m * n
@test ndims(b) == 2
@test size(b) == (m, n)
@test size(b, 1) == m
@test size(b, 2) == n
@test size(b, 3) == 1
@test !is_complex(b)
@test $(testfun)(b)
delete(b)
return nothing
end
end
# empty array
a = mxarray(Float64, 0, 0)
@test nrows(a) == 0
@test ncols(a) == 0
@test nelems(a) == 0
@test ndims(a) == 2
@test eltype(a) == Float64
@test is_empty(a)
# basic arrays
@mx_test_basic_types Float64 is_double
@mx_test_basic_types Float32 is_single
@mx_test_basic_types Int64 is_int64
@mx_test_basic_types UInt64 is_uint64
@mx_test_basic_types Int32 is_int32
@mx_test_basic_types UInt32 is_uint32
@mx_test_basic_types Int16 is_int16
@mx_test_basic_types UInt16 is_uint16
@mx_test_basic_types Int8 is_int8
@mx_test_basic_types UInt8 is_uint8
@mx_test_basic_types Bool is_logical
# complex arrays
macro mx_test_complex_type(ty, testfun)
quote
b = mxarray(Complex{$(ty)}, m, n)
@test elsize(b) == sizeof($(ty))
@test eltype(b) === $(ty)
@test nrows(b) == m
@test ncols(b) == n
@test nelems(b) == m * n
@test ndims(b) == 2
@test size(b) == (m, n)
@test size(b, 1) == m
@test size(b, 2) == n
@test size(b, 3) == 1
@test is_complex(b)
@test $(testfun)(b)
delete(b)
return nothing
end
end
@mx_test_complex_type Float64 is_double
@mx_test_complex_type Float32 is_single
# test creating multi-dimensional arrays
a = mxarray(Float64, (6, 5, 4))
@test elsize(a) == sizeof(Float64)
@test eltype(a) === Float64
@test size(a) == (6, 5, 4)
@test size(a, 1) == 6
@test size(a, 2) == 5
@test size(a, 3) == 4
@test size(a, 4) == 1
@test nelems(a) == 6 * 5 * 4
@test is_numeric(a)
@test !is_sparse(a)
a = mxarray(Bool, (6, 5, 4))
@test elsize(a) == 1
@test eltype(a) === Bool
@test size(a) == (6, 5, 4)
@test size(a, 1) == 6
@test size(a, 2) == 5
@test size(a, 3) == 4
@test size(a, 4) == 1
@test nelems(a) == 6 * 5 * 4
@test is_logical(a)
@test !is_sparse(a)
# scalars
a_mx = mxarray(3.25)
@test eltype(a_mx) == Float64
@test size(a_mx) == (1, 1)
@test jscalar(a_mx) == 3.25
delete(a_mx)
a_mx = mxarray(Int32(12))
@test eltype(a_mx) == Int32
@test size(a_mx) == (1, 1)
@test jscalar(a_mx) == Int32(12)
delete(a_mx)
a_mx = mxarray(true)
@test eltype(a_mx) == Bool
@test size(a_mx) == (1, 1)
@test jscalar(a_mx)
delete(a_mx)
a_mx = mxarray(false)
@test eltype(a_mx) == Bool
@test size(a_mx) == (1, 1)
@test !jscalar(a_mx)
delete(a_mx)
a_mx = mxarray(3.25 + 4im)
@test eltype(a_mx) == Float64
@test size(a_mx) == (1, 1)
@test jscalar(a_mx) == 3.25 + 4im
delete(a_mx)
# conversion between Julia and MATLAB numeric arrays
a = rand(5, 6)
a_mx = mxarray(a)
a2 = jarray(a_mx)
@test isequal(a, a2)
delete(a_mx)
a = rand(5)
a_mx = mxarray(a)
a2 = jvector(a_mx)
@test isequal(a, a2)
delete(a_mx)
a_t = reshape(a, 1, 5)
a_mx = mxarray(a_t)
a2 = jvector(a_mx)
@test isequal(a, a2)
delete(a_mx)
a = 1:5
a_mx = mxarray(a)
a2 = jvector(a_mx)
@test isequal([1:5;], a2)
delete(a_mx)
a = rand(5, 6) + rand(5, 6) * im
a_mx = mxarray(a)
a2 = jarray(a_mx)
@test isequal(a, a2)
delete(a_mx)
# sparse matrices
a = sprand(8, 9, 0.2)
a_mx = mxarray(a)
@test is_double(a_mx)
@test is_sparse(a_mx)
@test nrows(a_mx) == 8
@test ncols(a_mx) == 9
a2 = jsparse(a_mx)
@test size(a2) == (8, 9)
@test count(!iszero, a2) == count(!iszero, a)
@test isequal(a2, a)
delete(a_mx)
a = sparse(convert(Array{Bool}, rand(8, 9) .< 0.3))
a_mx = mxarray(a)
@test is_logical(a_mx)
@test is_sparse(a_mx)
@test nrows(a_mx) == 8
@test ncols(a_mx) == 9
a2 = jsparse(a_mx)
@test size(a2) == (8, 9)
@test count(!iszero, a2) == count(!iszero, a)
@test isequal(a2, a)
delete(a_mx)
a = sparse([1.0 1.0im])
a_mx = mxarray(a)
@test is_sparse(a_mx)
@test is_double(a_mx)
@test is_complex(a_mx)
@test nrows(a_mx) == 1
@test ncols(a_mx) == 2
delete(a_mx)
# strings
s = "MATLAB.jl"
s_mx = mxarray(s)
@test classid(s_mx) == MATLAB.mxCHAR_CLASS
@test nrows(s_mx) == 1
@test ncols(s_mx) == length(s)
@test nelems(s_mx) == length(s)
@test ndims(s_mx) == 2
@test is_char(s_mx)
s2 = jstring(s_mx)
@test s == s2
delete(s_mx)
s = ""
s_mx = mxarray(s)
@test classid(s_mx) == MATLAB.mxCHAR_CLASS
@test is_char(s_mx)
@test is_empty(s_mx)
s2 = jstring(s_mx)
@test s == s2
delete(s_mx)
# cell arrays
a = mxcellarray(10)
@test nrows(a) == 10
@test ncols(a) == 1
@test nelems(a) == 10
@test classid(a) == MATLAB.mxCELL_CLASS
@test is_cell(a)
delete(a)
a = mxcellarray(4, 5)
@test nrows(a) == 4
@test ncols(a) == 5
@test nelems(a) == 20
@test classid(a) == MATLAB.mxCELL_CLASS
@test is_cell(a)
delete(a)
a = mxcellarray((3, 4, 5))
@test size(a) == (3, 4, 5)
@test nelems(a) == 60
@test classid(a) == MATLAB.mxCELL_CLASS
@test is_cell(a)
delete(a)
s = ["abc", "efg"]
s_mx = mxcellarray(s)
@test jstring(get_cell(s_mx, 1)) == "abc"
@test jstring(get_cell(s_mx, 2)) == "efg"
delete(s_mx)
# struct
a = mxstruct("abc", "efg", "xyz")
@test is_struct(a)
@test mxnfields(a) == 3
@test nrows(a) == 1
@test ncols(a) == 1
@test nelems(a) == 1
@test ndims(a) == 2
@test get_fieldname(a, 1) == "abc"
@test get_fieldname(a, 2) == "efg"
@test get_fieldname(a, 3) == "xyz"
delete(a)
s = Dict("name" => "MATLAB", "version" => 12.0, "data" => [1, 2, 3])
a = mxstruct(s)
@test is_struct(a)
@test mxnfields(a) == 3
@test jstring(get_field(a, "name")) == "MATLAB"
@test jscalar(get_field(a, "version")) == 12.0
@test isequal(jvector(get_field(a, "data")), [1, 2, 3])
delete(a)
mutable struct TestType
name::String
version::Float64
data::Vector{Int}
end
t = TestType("MATLAB", 12.0, [1, 2, 3])
a = mxstruct(t)
@test is_struct(a)
@test mxnfields(a) == 3
@test jstring(get_field(a, "name")) == "MATLAB"
@test jscalar(get_field(a, "version")) == 12.0
@test isequal(jvector(get_field(a, "data")), [1, 2, 3])
delete(a)
a = mxstructarray([TestType("MATLAB", 12.0, [1, 2, 3]),
TestType("Julia", 0.2, [4, 5, 6])])
@test is_struct(a)
@test mxnfields(a) == 3
@test jstring(get_field(a, 1, "name")) == "MATLAB"
@test jscalar(get_field(a, 1, "version")) == 12.0
@test isequal(jvector(get_field(a, 1, "data")), [1, 2, 3])
@test jstring(get_field(a, 2, "name")) == "Julia"
@test jscalar(get_field(a, 2, "version")) == 0.2
@test isequal(jvector(get_field(a, 2, "data")), [4, 5, 6])
delete(a)
# bi-directional conversions
x = mxarray(12.0)
y = jvalue(x)
delete(x)
@test isa(y, Float64)
@test y == 12.0
a = rand(5)
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Vector{Float64})
@test isequal(y, a)
a = rand(3, 4)
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Matrix{Float64})
@test isequal(y, a)
a = rand(3, 4, 5)
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Array{Float64,3})
@test isequal(y, a)
a = sparse([1.0 2.0im; 0 -1.0im])
a_mx = mxarray(a)
a_jl = jvalue(a_mx)
delete(a_mx)
@test a == a_jl
@test isa(a_jl, SparseMatrixCSC{Complex{Float64}})
##############################
# Abstract Array Conversions
##############################
a = transpose(rand(10))
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Array{Float64,2})
@test size(y) == size(a)
@test isequal(y, a)
a = rand(10, 10)
a_ = @view a[3:7, 4:8]
x = mxarray(a_)
y = jvalue(x)
delete(x)
@test isa(y, Array{Float64,2})
@test size(y) == size(a_)
@test isequal(y, a_)
a_ = rand(ComplexF32, 10, 10, 10)
a = @view a_[3:7, 4:8, 2:5]
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Array{ComplexF32,3})
@test size(y) == size(a)
a = 1:100 # range
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Array{Int64,1})
@test isequal(y, collect(a))
a = BitArray(rand(Bool, 5, 20, 10))
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Array{Bool,3})
@test isequal(y, a)
# Issue: Tuples converted to MATLAB structs
# https://github.com/JuliaInterop/MATLAB.jl/issues/178
a = (2.5, 2.6)
x = mxarray(a)
y = jvalue(x)
@test classid(x) == MATLAB.mxDOUBLE_CLASS
@test nrows(x) == 2
@test ncols(x) == 1
delete(x)
@test isa(y, Vector{Float64})
@test isequal(y, collect(a))
# Tuple with mixed types
a = (1, 2.0, "MATLAB", [1, 2, 3])
x = mxarray(a)
y = jvalue(x)
@test nrows(x) == 4
@test ncols(x) == 1
@test classid(x) == MATLAB.mxCELL_CLASS
@test isa(y, Vector{Any})
@test length(y) == length(a)
@test isequal(y, collect(a))
##############################
# String Conversions
##############################
a = "MATLAB"
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, String)
@test y == a
a = ["abc", 3, "efg"]
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Vector{Any})
@test length(y) == 3
@test y[1] == a[1]
@test y[2] == a[2]
@test y[3] == a[3]
a = Dict("abc" => 10.0, "efg" => [1, 2, 3], "xyz" => "MATLAB")
x = mxarray(a)
y = jvalue(x)
delete(x)
@test isa(y, Dict{String,Any})
@test y["abc"] == 10.0
@test isequal(y["efg"], [1, 2, 3])
@test y["xyz"] == "MATLAB"
# Test string encoding
str = "λ α γ"
@test jstring(mxarray(str)) == str
@test mat"all($str == [955 32 945 32 947])"
GC.gc()
================================================
FILE: test/runtests.jl
================================================
using MATLAB
using Test
is_ci() = lowercase(get(ENV, "CI", "false")) == "true"
if !is_ci() # only test if not CI
include("engine.jl")
include("matfile.jl")
include("matstr.jl")
include("mxarray.jl")
end
gitextract_utsjq7lg/
├── .JuliaFormatter.toml
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── CI.yml
│ ├── Format.yml
│ ├── RegisterAction.yml
│ └── TagBot.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── deps/
│ └── build.jl
├── src/
│ ├── MATLAB.jl
│ ├── engine.jl
│ ├── exceptions.jl
│ ├── init.jl
│ ├── matfile.jl
│ ├── matstr.jl
│ └── mxarray.jl
└── test/
├── engine.jl
├── matfile.jl
├── matstr.jl
├── mxarray.jl
└── runtests.jl
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (97K chars).
[
{
"path": ".JuliaFormatter.toml",
"chars": 295,
"preview": "whitespace_typedefs = false\nwhitespace_ops_in_indices = false\nwhitespace_in_kwargs = false\nremove_extra_newlines = true\n"
},
{
"path": ".github/dependabot.yml",
"chars": 256,
"preview": "# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\nversion: 2\nupda"
},
{
"path": ".github/workflows/CI.yml",
"chars": 728,
"preview": "name: CI\r\n\r\non:\r\n push:\r\n branches:\r\n - master\r\n - release-*\r\n tags: '*'\r\n pull_request:\r\n\r\njobs:\r\n t"
},
{
"path": ".github/workflows/Format.yml",
"chars": 522,
"preview": "name: Format suggestions\non:\n pull_request:\n # this argument is not required if you don't use the `suggestion-label`"
},
{
"path": ".github/workflows/RegisterAction.yml",
"chars": 323,
"preview": "name: RegisterAction\non:\n workflow_dispatch:\n inputs:\n version:\n description: Version to register or com"
},
{
"path": ".github/workflows/TagBot.yml",
"chars": 826,
"preview": "name: TagBot\non:\n issue_comment:\n types:\n - created\n workflow_dispatch:\n inputs:\n lookback:\n de"
},
{
"path": ".gitignore",
"chars": 48,
"preview": "deps/build.log\ndeps/deps.jl\n*.mat\nManifest.toml\n"
},
{
"path": "LICENSE.md",
"chars": 1053,
"preview": "Copyright (c) 2013 Dahua Lin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this softw"
},
{
"path": "Project.toml",
"chars": 365,
"preview": "name = \"MATLAB\"\nuuid = \"10e44e05-a98a-55b3-a45b-ba969058deb6\"\nlicense = \"MIT\"\nrepo = \"https://github.com/JuliaInterop/MA"
},
{
"path": "README.md",
"chars": 15359,
"preview": "## MATLAB\n\n| ❗ If you get a 'Segmentation fault' error when calling MATLAB with version R2022 or newer, try using an [ol"
},
{
"path": "deps/build.jl",
"chars": 5027,
"preview": "import Libdl\n\nconst depsfile = joinpath(@__DIR__, \"deps.jl\")\n\nfunction find_matlab_root()\n # Determine MATLAB library"
},
{
"path": "src/MATLAB.jl",
"chars": 6780,
"preview": "module MATLAB\n\nusing Libdl\nusing SparseArrays\n\nimport Base: eltype, close, size, copy, ndims, unsafe_convert\n\n# mxarray\n"
},
{
"path": "src/engine.jl",
"chars": 12181,
"preview": "# operation on MATLAB engine sessions\n\n###########################################################\n#\n# Session open & "
},
{
"path": "src/exceptions.jl",
"chars": 289,
"preview": "struct MEngineError <: Exception\n message::String\nend\n\n\"\"\"\n MEngineError(message::String)\n\nException thrown by MAT"
},
{
"path": "src/init.jl",
"chars": 3068,
"preview": "# libraries\n\nconst libeng = Ref{Ptr{Cvoid}}()\nconst libmx = Ref{Ptr{Cvoid}}()\nconst libmat = Ref{Ptr{Cvoid}}()\n\n# matla"
},
{
"path": "src/matfile.jl",
"chars": 2846,
"preview": "# mat file open & close\n\nmutable struct MatFile\n ptr::Ptr{Cvoid}\n filename::String\n\n function MatFile(filename:"
},
{
"path": "src/matstr.jl",
"chars": 6098,
"preview": "# Syntax for mat\"\" string interpolation\n\n# A really basic parser intended only to handle checking whether\n# a variable i"
},
{
"path": "src/mxarray.jl",
"chars": 22073,
"preview": "# functions to deal with MATLAB arrays\n\nmutable struct MxArray\n ptr::Ptr{Cvoid}\n own::Bool\n\n function MxArray(p"
},
{
"path": "test/engine.jl",
"chars": 1144,
"preview": "using MATLAB\nusing Test\nusing SparseArrays\n\n# test engine\n\nrestart_default_msession()\n\na = [1.0 2.0 3.0; 4.0 5.0 6.0]\nb "
},
{
"path": "test/matfile.jl",
"chars": 1217,
"preview": "using MATLAB\nusing Test\n\n# test MMAT file I/O\nfn = \"$(tempname()).mat\"\n\na32 = Int32[1 2 3; 4 5 6]\na64 = Int64[1 2 3; 4 5"
},
{
"path": "test/matstr.jl",
"chars": 1814,
"preview": "using MATLAB\nusing Test\n\n@test mat\"1\" == 1\n@test mat\"[1, 2, 3]\" == [1 2 3]\n\n# Test interpolation\nx = 1\n@test mat\"$x + 1\""
},
{
"path": "test/mxarray.jl",
"chars": 9697,
"preview": "using MATLAB\nusing Test\nusing SparseArrays\n\n# Unit testing for MxArray\n\nm = 5\nn = 6\n\n# test basic types in 1D & 2D\n\nmacr"
},
{
"path": "test/runtests.jl",
"chars": 221,
"preview": "using MATLAB\nusing Test\n\nis_ci() = lowercase(get(ENV, \"CI\", \"false\")) == \"true\"\n\nif !is_ci() # only test if not CI\n i"
}
]
About this extraction
This page contains the full source code of the JuliaInterop/MATLAB.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (90.1 KB), approximately 27.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.