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