├── .github
└── workflows
│ ├── python-publish.yml
│ └── qualify_25a.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── SECURITY.md
├── pyproject.toml
├── python
├── setup.py
├── src
└── matlab
│ ├── __init__.py
│ └── engine
│ ├── __init__.py
│ ├── _arch.txt
│ ├── basefuture.py
│ ├── engineerror.py
│ ├── enginehelper.py
│ ├── enginesession.py
│ ├── fevalfuture.py
│ ├── futureresult.py
│ ├── matlabengine.py
│ └── matlabfuture.py
└── test
└── tInstall.m
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | name: Upload Python Package
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | deploy:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Set up Python
18 | uses: actions/setup-python@v3
19 | with:
20 | python-version: 3.9
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | python -m pip install setuptools twine
25 |
26 | - name: Build package
27 | run: python setup.py sdist
28 |
29 | - name: Publish package to TestPyPI since the "prerelease" checkbox is checked
30 | if: "github.event.release.prerelease"
31 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
32 | with:
33 | user: __token__
34 | password: ${{ secrets.MATLABENGINE_TEST_PYPI_TOKEN }}
35 | repository_url: https://test.pypi.org/legacy/
36 |
37 | - name: Publish package to PyPI since the "prerelease" checkbox is unchecked
38 | if: "!github.event.release.prerelease"
39 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
40 | with:
41 | user: __token__
42 | password: ${{ secrets.MATLABENGINE_PYPI_TOKEN }}
43 |
--------------------------------------------------------------------------------
/.github/workflows/qualify_25a.yml:
--------------------------------------------------------------------------------
1 | # Run tInstall on Ubuntu against python versions 3.12, 3.11, 3.10 and 3.9
2 |
3 | name: Test R2025a
4 |
5 | on:
6 | push:
7 | branches:
8 | - R2025a
9 |
10 | pull_request:
11 | branches:
12 | - R2025a
13 |
14 | # Allows you to run this workflow manually from the Actions tab
15 | workflow_dispatch:
16 |
17 |
18 |
19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
20 | jobs:
21 | test-python-engine:
22 | strategy:
23 | matrix:
24 | python: ["3.12", "3.11", "3.10", "3.9"]
25 |
26 | runs-on: ubuntu-latest
27 |
28 | steps:
29 | - name: Set up Python
30 | uses: actions/setup-python@v3.1.3
31 | with:
32 | python-version: ${{ matrix.python }}
33 |
34 | - name: Set up MATLAB
35 | uses: matlab-actions/setup-matlab@v2
36 | with:
37 | release: R2025a
38 |
39 | - uses: actions/checkout@v3
40 |
41 | - name: Run tests
42 | uses: matlab-actions/run-tests@v1
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | build
3 | *.egg-info
4 | dist
5 | matlabengine-*
6 | matlabengine-test*
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2022, The MathWorks, Inc.
2 | All rights reserved.
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
5 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
6 | 3. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings.
7 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MATLAB Engine API for Python
2 |
3 | The MATLAB® Engine API for Python® provides a package to integrate MATLAB functionality directly with a Python application, creating an interface to call functions from your MATLAB installation from Python code.
4 |
5 | ---
6 | ## Requirements
7 | ### Required MathWorks Products
8 |
9 | * MATLAB release R2025a
10 |
11 | ### Required 3rd Party Products
12 |
13 | * Python 3.9, 3.10, 3.11, or 3.12
14 | * Supported Python versions by MATLAB release can be found [here](https://www.mathworks.com/support/requirements/python-compatibility.html).
15 |
16 | ---
17 |
18 | ## Install
19 |
20 | ### Windows
21 | MATLAB Engine API for Python can be installed directly from the Python Package Index.
22 |
23 | ```bash
24 | $ python -m pip install matlabengine==25.1.2
25 | ```
26 |
27 |
28 |
29 | ### Linux®
30 | Prior to installation, check the default install location of MATLAB by calling ```matlabroot``` in a MATLAB Command Window. By default, Linux installs MATLAB at:
31 |
32 | ```/usr/local/MATLAB/R2025a```
33 |
34 | When MATLAB is not installed in the default location, the bin/*architecture* directory within the MATLAB root directory must be added to the environment variable LD_LIBRARY_PATH. The path can be added to the environment variable within the shell startup configuration file (for example, .bashrc for bash shell or .tcshrc for tcsh).
35 |
36 | ```bash
37 | # in .bashrc
38 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/bin/glnxa64
39 | ```
40 |
41 | ```bash
42 | # in .tcshrc
43 | setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:/bin/glnxa64
44 | ```
45 |
46 | MATLAB Engine API for Python can be installed directly from the Python Package Index.
47 |
48 | ```bash
49 | $ python -m pip install matlabengine==25.1.2
50 | ```
51 |
52 | ### macOS
53 | Prior to installation, check the default install location of MATLAB by calling ```matlabroot``` in a MATLAB Command Window. By default, macOS installs MATLAB at:
54 |
55 |
56 | ```/Applications/MATLAB_R2025a.app```
57 |
58 | When MATLAB is not installed in the default location, the bin/*architecture* directory within the MATLAB root directory must be added to the environment variable DYLD_LIBRARY_PATH. The path can be added to the environment variable within the shell startup configuration file (for example, .bashrc for bash shell or .tcshrc for tcsh).
59 |
60 | ```bash
61 | # in .bashrc
62 | export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/bin/maci64
63 | ```
64 |
65 | ```bash
66 | # in .tcshrc
67 | setenv DYLD_LIBRARY_PATH ${DYLD_LIBRARY_PATH}:/bin/maci64
68 | ```
69 |
70 | MATLAB Engine API for Python can be installed directly from the Python Package Index.
71 |
72 | ```bash
73 | $ python -m pip install matlabengine==25.1.2
74 | ```
75 |
76 | ---
77 |
78 | ## Getting Started
79 | * Start Python.
80 | * Import the ```matlab.engine``` package into the Python session.
81 | * Start a new MATLAB process by calling ```start_matlab```. The ```start_matlab``` function returns a Python object which enables you to pass data and call functions executed by MATLAB.
82 |
83 | ```python
84 | >>> import matlab.engine
85 | >>> eng = matlab.engine.start_matlab()
86 | >>> eng.sqrt(4.0)
87 | 2.0
88 | ```
89 |
90 | * Call either the ```exit``` or ```quit``` function to stop the engine. Exiting Python with an engine running stops the engine and its MATLAB processes.
91 |
92 | ```python
93 | >>> eng.quit()
94 | ```
95 |
96 | See [Start and Stop MATLAB Engine for Python](https://www.mathworks.com/help/matlab/matlab_external/start-the-matlab-engine-for-python.html) for advanced startup examples.
97 |
98 | ---
99 |
100 | ## Examples
101 | You can call any MATLAB function directly and return the results to Python.
102 | ```python
103 | >>> eng.plus(2, 3)
104 | 5
105 | >>> eng.isprime(37)
106 | True
107 | >>> eng.gcd(100.0, 80.0, nargout=3)
108 | (20.0, 1.0, -1.0)
109 | ```
110 | See [Call MATLAB Functions from Python](https://www.mathworks.com/help/matlab/matlab_external/call-matlab-functions-from-python.html) for more usage examples.
111 |
112 | ---
113 |
114 | ## Limitations
115 | Limitations of the MATLAB Engine API for Python can be found [here](https://www.mathworks.com/help/matlab/matlab_external/limitations-to-the-matlab-engine-for-python.html).
116 |
117 | ---
118 |
119 | ## Troubleshooting
120 | See [Troubleshoot MATLAB Errors in Python](https://www.mathworks.com/help/matlab/matlab_external/troubleshoot-matlab-errors-in-python.html) for troubleshooting assistance.
121 |
122 | ---
123 |
124 | ## License
125 | The license is available in the LICENSE.txt file within this repository.
126 |
127 | ---
128 |
129 | ## Support
130 | Technical issues or enhancement requests can be submitted [here](https://github.com/mathworks/matlab-engine-for-python/issues).
131 |
132 | ---
133 |
134 | Copyright © 2022 MathWorks, Inc. All rights reserved.
135 |
136 | Linux® is the registered trademark of Linus Torvalds in the U.S. and other countries.
137 |
138 | Mac OS is a trademark of Apple Inc., registered in the U.S. and other countries.
139 |
140 | "Python" and the Python logos are trademarks or registered trademarks of the Python Software Foundation, used by MathWorks with permission from the Foundation.
141 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Vulnerabilities
2 |
3 | If you believe you have discovered a security vulnerability, please report it to
4 | [security@mathworks.com](mailto:security@mathworks.com). Please see
5 | [MathWorks Vulnerability Disclosure Policy for Security Researchers](https://www.mathworks.com/company/aboutus/policies_statements/vulnerability-disclosure-policy.html)
6 | for additional information.
7 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools >= 42", "wheel"]
3 | build-backend = "setuptools.build_meta"
--------------------------------------------------------------------------------
/python:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mathworks/matlab-engine-for-python/65cb554db27232c380005edc3e085015e607cf8b/python
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Mathworks, Inc.
2 |
3 | from setuptools import setup, find_packages
4 | from setuptools.command.build_py import build_py
5 | import os
6 | import re
7 | import sys
8 | import platform
9 | import xml.etree.ElementTree as xml
10 | if platform.system() == 'Windows':
11 | import winreg
12 |
13 | class _MatlabFinder(build_py):
14 | """
15 | Private class that finds MATLAB on user's computer prior to package installation.
16 | """
17 | PLATFORM_DICT = {
18 | 'Windows': 'PATH',
19 | 'Linux': 'LD_LIBRARY_PATH',
20 | 'Darwin': 'DYLD_LIBRARY_PATH'
21 | }
22 |
23 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
24 | MATLAB_REL = 'R2025a'
25 |
26 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
27 | MATLAB_VER = '25.1.2'
28 |
29 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
30 | SUPPORTED_PYTHON_VERSIONS = set(['3.9', '3.10', '3.11', '3.12'])
31 |
32 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
33 | VER_TO_REL = {
34 | "9.9": "R2020b",
35 | "9.10": "R2021a",
36 | "9.11": "R2021b",
37 | "9.12": "R2022a",
38 | "9.13": "R2022b",
39 | "9.14": "R2023a",
40 | "23.2": "R2023b",
41 | "24.1": "R2024a",
42 | "24.2": "R2024b",
43 | "25.1": "R2025a"
44 | }
45 |
46 | DEFAULT_INSTALLS = {
47 | 'Darwin': f"/Applications/MATLAB_{MATLAB_REL}.app",
48 | 'Linux': f"/usr/local/MATLAB/{MATLAB_REL}"
49 | }
50 |
51 | arch = ''
52 | path_env_var_name = ''
53 | python_ver = ''
54 | platform = ''
55 | found_matlab_release = ''
56 | found_matlab_version = ''
57 | found_matlab_with_wrong_arch_in_default_install = ''
58 | found_matlab_with_wrong_arch_in_path = ''
59 | verbose = False
60 |
61 | # ERROR MESSAGES
62 | minimum_maximum = "No compatible version of MATLAB was found. " + \
63 | "Version {this_v:s} was found, but this feature only supports MATLAB {min_v:s} ({min_r:s}) through {max_v:s} ({max_r:s}), inclusive."
64 | dir_not_found = "Directory not found: "
65 | no_windows_install = "MATLAB installation not found in Windows Registry:"
66 | unsupported_platform = "{platform:s} is not a supported platform."
67 | unsupported_python = "{python:s} is not supported. The supported Python versions are {supported:s}."
68 | unset_env = "Environment variable {path1:s} has not been set. Add /bin/{arch:s} to {path2:s}, where is the root of a valid MATLAB installation."
69 | install_or_set_path = "MATLAB {ver:s} installation not found. Install to default location, or add /bin/{arch:s} to {path:s}, where is the root of a MATLAB {ver:s} installation."
70 | no_compatible_matlab = "No compatible MATLAB installation found in Windows Registry. This release of " + \
71 | "MATLAB Engine API for Python is compatible with version {ver:s}. The found versions were"
72 | no_matlab = "No compatible MATLAB installation found in Windows Registry."
73 | incompatible_ver = "MATLAB version {ver:s} ({rel:s}) was found, but this release of MATLAB Engine API for Python is not compatible with it. " + \
74 | "To install a compatible version, call 'python -m pip install matlabengine=={ver:s}'."
75 | invalid_version_from_matlab_ver = "Format of MATLAB version '{ver:s}' is invalid."
76 | invalid_version_from_eng = "Format of MATLAB Engine API version '{ver:s}' is invalid."
77 | next_steps = "Reinstall MATLAB, use DYLD_LIBRARY_PATH to specify a different MATLAB installation, or use a different Python interpreter."
78 | wrong_arch_in_default_install = "MATLAB installation in {path1:s} is {matlab_arch:s}, but Python interpreter is {python_arch:s}. {next_steps:s}."
79 | wrong_arch_in_path = "MATLAB installation in {path1:s}, listed in DYLD_LIBRARY_PATH, is {matlab_arch:s}, but Python interpreter is {python_arch:s}. {next_steps:s}."
80 |
81 | def _print_if_verbose(self, msg):
82 | if self.verbose:
83 | print(msg)
84 |
85 | def set_platform_and_arch(self):
86 | """
87 | Sets the platform and architecture.
88 | """
89 | self.platform = platform.system()
90 | if self.platform not in self.PLATFORM_DICT:
91 | raise RuntimeError(self.unsupported_platform.format(platform=self.platform))
92 | else:
93 | self.path_env_var_name = self.PLATFORM_DICT[self.platform]
94 |
95 | if self.platform == 'Windows':
96 | self.arch = 'win64'
97 | elif self.platform == 'Linux':
98 | self.arch = 'glnxa64'
99 | elif self.platform == 'Darwin':
100 | if platform.mac_ver()[-1] == 'arm64':
101 | self.arch = 'maca64'
102 | else:
103 | self.arch = 'maci64'
104 | else:
105 | raise RuntimeError(self.unsupported_platform.format(platform=self.platform))
106 |
107 | def set_python_version(self):
108 | """
109 | Gets Python version and ensures it is supported.
110 | """
111 | ver = sys.version_info
112 | self.python_ver = f"{ver.major}.{ver.minor}"
113 |
114 | if self.python_ver not in self.SUPPORTED_PYTHON_VERSIONS:
115 | raise RuntimeError(self.unsupported_python.format(python=self.python_ver, supported=str(self.SUPPORTED_PYTHON_VERSIONS)))
116 |
117 | def unix_default_install_exists(self):
118 | """
119 | Determines whether MATLAB is installed in default UNIX location.
120 | """
121 | path = self.DEFAULT_INSTALLS[self.platform]
122 | if not os.path.exists(path):
123 | return False
124 |
125 | if self.platform == 'Darwin':
126 | # On Mac, we need to further verify that there is a 'bin/maci64' subdir if the Python is maci64
127 | # or a 'bin/maca64' subdir if the Python is maca64.
128 | path_to_bin = os.path.join(path, 'bin', self.arch)
129 | if os.path.exists(path_to_bin):
130 | # The path exists, and we don't need to do anything further.
131 | return True
132 |
133 | if self.arch == 'maci64':
134 | alternate_arch = 'maca64'
135 | else:
136 | alternate_arch = 'maci64'
137 |
138 | if os.path.exists(os.path.join(path, 'bin', alternate_arch)):
139 | # There is a default install, but its arch doesn't match the Python arch. Save this info
140 | # so that if we don't find an install with a valid arch in DYLD_LIBRARY_PATH, we can
141 | # issue an error message that says that there is a Mac installation in the default
142 | # location that has the wrong arch. The user can choose whether to change the
143 | # Python interpreter or the MATLAB installation so that the arch will match.
144 | self.found_matlab_with_wrong_arch_in_default_install = path
145 | return False
146 |
147 | return True
148 |
149 | def _create_path_list(self):
150 | """
151 | Creates a list of directories on the path to be searched.
152 | """
153 | path_dirs = []
154 | path_string = ''
155 | if self.path_env_var_name in os.environ:
156 | path_string = os.environ[self.path_env_var_name]
157 | path_dirs.extend(path_string.split(os.pathsep))
158 |
159 | if not path_dirs:
160 | raise RuntimeError(self.install_or_set_path.format(
161 | ver=self.MATLAB_REL, arch=self.arch,
162 | path=self.path_env_var_name))
163 |
164 | self._print_if_verbose(f'_create_path_list returned: {path_dirs}')
165 | return path_dirs
166 |
167 | def _get_alternate_arch(self):
168 | if self.arch == 'maci64':
169 | return 'maca64'
170 | if self.arch == 'maca64':
171 | return 'maci64'
172 | return self.arch
173 |
174 | def _arch_in_mac_dir_is_correct(self, dir):
175 | ARCH_LEN = 6 # == len('maci64') or len('maca64')
176 | BIN_ARCH_LEN = ARCH_LEN + 4 # == len('bin/maci64') or len('bin/maca64')
177 |
178 | if len(dir) < BIN_ARCH_LEN:
179 | return False
180 |
181 | if dir[-1] == os.sep:
182 | # It's safe to look at dir[[-1 * (ARCH_LEN+1)] because BIN_ARCH_LEN > ARCH_LEN + 1.
183 | possible_arch = dir[-1 * (ARCH_LEN+1) : -1]
184 | else:
185 | possible_arch = dir[-1 * ARCH_LEN :]
186 |
187 | self._print_if_verbose(f'possible_arch: {possible_arch}; self.arch: {self.arch}')
188 | if possible_arch == self.arch:
189 | return True
190 | else:
191 | return False
192 |
193 | def _get_matlab_root_from_unix_bin(self, dir):
194 | """
195 | Searches bin directory for presence of MATLAB file. Used only for
196 | UNIX systems.
197 | """
198 | matlab_path = os.path.join(dir, 'MATLAB')
199 | possible_root = os.path.normpath(os.path.join(dir, os.pardir, os.pardir))
200 | matlab_root = ''
201 | if os.path.isfile(matlab_path) and self.verify_matlab_release(possible_root):
202 | if self.platform == 'Darwin' and not self._arch_in_mac_dir_is_correct(dir):
203 | self.found_matlab_with_wrong_arch_in_path = possible_root
204 | self._print_if_verbose(f'self.found_matlab_with_wrong_arch_in_path: {self.found_matlab_with_wrong_arch_in_path}')
205 | else:
206 | matlab_root = possible_root
207 | self._print_if_verbose(f'_get_matlab_root_from_unix_bin returned: {matlab_root}')
208 | return matlab_root
209 |
210 | def get_matlab_root_from_windows_reg(self):
211 | """
212 | Searches Windows Registry for MATLAB installs and gets the root directory of MATLAB.
213 | """
214 | try:
215 | reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
216 | key = winreg.OpenKey(reg, "SOFTWARE\\MathWorks\\MATLAB")
217 | except OSError as err:
218 | raise RuntimeError(f"{self.no_windows_install} {err}")
219 |
220 | matlab_ver_key = self._find_matlab_key_from_windows_registry(key)
221 | ret = self._get_root_from_version_key(reg, matlab_ver_key)
222 | self._print_if_verbose(f'get_matlab_root_from_windows_reg returned: {ret}')
223 | return ret
224 |
225 | def _get_root_from_version_key(self, reg, ver_key):
226 | """
227 | Opens a registry corresponding to the version of MATLAB specified and queries for
228 | MATLABROOT.
229 | """
230 | try:
231 | key = winreg.OpenKey(reg, "SOFTWARE\\MathWorks\\MATLAB\\" + ver_key)
232 | matlab_root = winreg.QueryValueEx(key, "MATLABROOT")[0]
233 | except (OSError, FileNotFoundError) as err:
234 | raise RuntimeError(f"{self.no_windows_install} {err}")
235 |
236 | self._print_if_verbose(f'_get_root_from_version_key returned: {matlab_root}')
237 | return matlab_root
238 |
239 | def _find_matlab_key_from_windows_registry(self, key):
240 | """
241 | Searches the MATLAB folder in Windows registry for the specified version of MATLAB. When found,
242 | the MATLAB root directory will be returned.
243 | """
244 | # QueryInfoKey returns a tuple, index 0 is the number of sub keys we need to search
245 | num_keys = winreg.QueryInfoKey(key)[0]
246 | key_value = ''
247 | found_vers = []
248 | for idx in range(num_keys):
249 | sub_key = winreg.EnumKey(key, idx)
250 | if sub_key in self.VER_TO_REL:
251 | found_vers.append(sub_key)
252 | # Example: the version in the registry could be "9.X" whereas the version in this file could be "9.X.Y".
253 | # We want to allow this.
254 | if self._check_matlab_ver_against_engine(sub_key):
255 | key_value = sub_key
256 | break
257 |
258 | if not key_value:
259 | if found_vers:
260 | vers = ', '.join(found_vers)
261 | eng_ver_major_minor = self._get_engine_ver_major_minor(self.MATLAB_VER)
262 | eng_ver_major_minor_as_str = '{}.{}'.format(eng_ver_major_minor[0], eng_ver_major_minor[1])
263 | raise RuntimeError(f"{self.no_compatible_matlab.format(ver=eng_ver_major_minor_as_str)} {vers}.")
264 | else:
265 | raise RuntimeError(f"{self.no_matlab}")
266 |
267 | self._print_if_verbose(f'_find_matlab_key_from_windows_registry returned: {key_value}')
268 | return key_value
269 |
270 | def _get_engine_ver_major_minor(self, id):
271 | re_major_minor = "^(\d+)\.(\d+)"
272 | eng_match = re.match(re_major_minor, id)
273 | if not eng_match:
274 | raise RuntimeError(f"{self.invalid_version_from_eng.format(ver=self.MATLAB_VER)}")
275 | ret = (eng_match.group(1), eng_match.group(2))
276 | self._print_if_verbose(f'_get_engine_ver_major_minor returned: {ret}')
277 | return ret
278 |
279 | def _check_matlab_ver_against_engine(self, matlab_ver):
280 | re_major_minor = "^(\d+)\.(\d+)"
281 | matlab_ver_match = re.match(re_major_minor, matlab_ver)
282 | if not matlab_ver_match:
283 | raise RuntimeError(f"{self.invalid_version_from_matlab_ver.format(ver=matlab_ver)}")
284 | eng_major_minor = self._get_engine_ver_major_minor(self.MATLAB_VER)
285 | matlab_ver_major_minor = (matlab_ver_match.group(1), matlab_ver_match.group(2))
286 | return (matlab_ver_major_minor == eng_major_minor)
287 |
288 | def verify_matlab_release(self, root):
289 | """
290 | Parses VersionInfo.xml to verify that the MATLAB release matches the supported release
291 | for the Python Engine.
292 | """
293 | version_info = os.path.join(root, 'VersionInfo.xml')
294 | if not os.path.isfile(version_info):
295 | return False
296 |
297 | tree = xml.parse(version_info)
298 | tree_root = tree.getroot()
299 |
300 | matlab_release = ''
301 | for child in tree_root:
302 | if child.tag == 'release':
303 | matlab_release = self.found_matlab_release = child.text
304 | elif child.tag == 'version':
305 | major, minor = self._get_engine_ver_major_minor(child.text)
306 | self.found_matlab_version = f'{major}.{minor}'
307 | return matlab_release == self.MATLAB_REL
308 |
309 | def search_path_for_directory_unix(self, arch, path_dirs):
310 | """
311 | Used for finding MATLAB root in UNIX systems. Searches all paths ending in
312 | /bin/ for the presence of MATLAB file to ensure the path is within
313 | the MATLAB tree.
314 | """
315 | dir_to_find = os.path.join('bin', arch)
316 | # directory could end with slashes
317 | endings = [dir_to_find, dir_to_find + os.sep]
318 |
319 | matlab_root = ''
320 | dir_idx = 0
321 | while not matlab_root and dir_idx < len(path_dirs):
322 | path = path_dirs[dir_idx]
323 | ending_idx = 0
324 | while not matlab_root and ending_idx < len(endings):
325 | ending = endings[ending_idx]
326 | if path.endswith(ending):
327 | # _get_matlab_root_from_unix_bin will return an empty string if MATLAB is not found.
328 | # Non-empty string (MATLAB found) will break both loops.
329 | matlab_root = self._get_matlab_root_from_unix_bin(path)
330 | ending_idx += 1
331 | dir_idx += 1
332 | self._print_if_verbose(f'search_path_for_directory_unix returned: {matlab_root}')
333 | return matlab_root
334 |
335 | def _err_msg_if_bad_matlab_root(self, matlab_root):
336 | if not matlab_root:
337 | if self.found_matlab_version:
338 | self._print_if_verbose(f'self.found_matlab_version: {self.found_matlab_version}; self.VER_TO_REL: {self.VER_TO_REL}')
339 | if self.found_matlab_version in self.VER_TO_REL:
340 | return self.incompatible_ver.format(ver=self.found_matlab_version, rel=self.found_matlab_release)
341 | # Found a MATLAB release but it is older than the oldest version supported,
342 | # or newer than the newest version supported.
343 | else:
344 | v_to_r_keys = list(self.VER_TO_REL.keys())
345 | self._print_if_verbose(f'v_to_r_keys: {v_to_r_keys}')
346 | min_v = v_to_r_keys[0]
347 | min_r = self.VER_TO_REL[min_v]
348 | max_v = v_to_r_keys[-1]
349 | max_r = self.VER_TO_REL[max_v]
350 | return self.minimum_maximum.format(this_v=self.found_matlab_release, min_v=min_v, min_r=min_r, max_v=max_v, max_r=max_r)
351 | else:
352 | # If we reach this line, we assume that the default location has already been checked for an
353 | # appropriate MATLAB installation but none was found.
354 | return self.install_or_set_path.format(ver=self.MATLAB_REL, arch=self.arch,
355 | path=self.path_env_var_name)
356 |
357 | if not os.path.isdir(matlab_root):
358 | return f"{self.dir_not_found} {matlab_root}"
359 |
360 | return ''
361 |
362 | def write_text_file(self, matlab_root):
363 | """
364 | Writes root.txt for use at import time.
365 | """
366 | file_location = os.path.join(os.getcwd(), 'src', 'matlab', 'engine', '_arch.txt')
367 | bin_arch = os.path.join(matlab_root, 'bin', self.arch)
368 | engine_arch = os.path.join(matlab_root, 'extern', 'engines', 'python', 'dist', 'matlab', 'engine', self.arch)
369 | extern_bin = os.path.join(matlab_root, 'extern', 'bin', self.arch)
370 | with open(file_location, 'w') as root_file:
371 | root_file.write(self.arch + '\n')
372 | root_file.write(bin_arch + '\n')
373 | root_file.write(engine_arch + '\n')
374 | root_file.write(extern_bin)
375 |
376 | def run(self):
377 | """
378 | Logic that runs prior to installation.
379 | """
380 | self.set_platform_and_arch()
381 | self.set_python_version()
382 |
383 | if self.platform == 'Windows':
384 | matlab_root = self.get_matlab_root_from_windows_reg()
385 | else:
386 | if self.unix_default_install_exists():
387 | matlab_root = self.DEFAULT_INSTALLS[self.platform]
388 | else:
389 | path_dirs = self._create_path_list()
390 | matlab_root = self.search_path_for_directory_unix(self.arch, path_dirs)
391 | err_msg = self._err_msg_if_bad_matlab_root(matlab_root)
392 | if err_msg:
393 | if self.platform == 'Darwin':
394 | if self.found_matlab_with_wrong_arch_in_default_install:
395 | raise RuntimeError(
396 | self.wrong_arch_in_default_install.format(
397 | path1=self.found_matlab_with_wrong_arch_in_default_install,
398 | matlab_arch=self._get_alternate_arch(),
399 | python_arch=self.arch,
400 | next_steps=self.next_steps))
401 | if self.found_matlab_with_wrong_arch_in_path:
402 | raise RuntimeError(
403 | self.wrong_arch_in_path.format(
404 | path1=self.found_matlab_with_wrong_arch_in_path,
405 | matlab_arch=self._get_alternate_arch(),
406 | python_arch=self.arch,
407 | next_steps=self.next_steps))
408 | raise RuntimeError(err_msg)
409 |
410 | self.write_text_file(matlab_root)
411 | build_py.run(self)
412 |
413 |
414 | if __name__ == '__main__':
415 | with open('README.md', 'r', encoding='utf-8') as rm:
416 | long_description = rm.read()
417 |
418 | setup(
419 | name="matlabengine",
420 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
421 | version="25.1.2",
422 | description='A module to call MATLAB from Python',
423 | author='MathWorks',
424 | license="LICENSE.txt, located in this repository",
425 | url='https://github.com/mathworks/matlab-engine-for-python/',
426 | long_description=long_description,
427 | long_description_content_type="text/markdown",
428 | package_dir={'': 'src'},
429 | packages=find_packages(where="src"),
430 | cmdclass={'build_py': _MatlabFinder},
431 | package_data={'': ['_arch.txt']},
432 | zip_safe=False,
433 | project_urls={
434 | 'Documentation': 'https://www.mathworks.com/help/matlab/matlab-engine-for-python.html',
435 | 'Source': 'https://github.com/mathworks/matlab-engine-for-python',
436 | 'Tracker': 'https://github.com/mathworks/matlab-engine-for-python/issues'
437 | },
438 | keywords=[
439 | "MATLAB",
440 | "MATLAB Engine for Python"
441 | ],
442 | classifiers=[
443 | "Natural Language :: English",
444 | "Intended Audience :: Developers",
445 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
446 | "Programming Language :: Python :: 3.9",
447 | "Programming Language :: Python :: 3.10",
448 | "Programming Language :: Python :: 3.11",
449 | "Programming Language :: Python :: 3.12"
450 | ],
451 | # MUST_BE_UPDATED_EACH_RELEASE (Search repo for this string)
452 | python_requires=">=3.9, <3.13"
453 | )
454 |
--------------------------------------------------------------------------------
/src/matlab/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2024 MathWorks, Inc.
2 |
3 | import os
4 | import platform
5 | import sys
6 | import pkgutil
7 |
8 | __path__ = pkgutil.extend_path(__path__, __name__)
9 | package_folder = os.path.dirname(os.path.realpath(__file__))
10 | sys.path.append(package_folder)
11 |
12 | def add_dirs_to_path(bin_dir, engine_dir, extern_dir):
13 | """
14 | Adds MATLAB engine and extern/bin directories to sys.path.
15 | """
16 | path = 'PATH'
17 |
18 | if not os.path.isdir(engine_dir):
19 | raise RuntimeError("Could not find directory: {0}".format(engine_dir))
20 |
21 | if not os.path.isdir(extern_dir):
22 | raise RuntimeError("Could not find directory: {0}".format(extern_dir))
23 |
24 | if platform.system() == 'Windows':
25 | if not os.path.isdir(bin_dir):
26 | raise RuntimeError("Could not find directory: {0}".format(bin_dir))
27 | if path in os.environ:
28 | paths = os.environ[path]
29 | os.environ[path] = bin_dir + os.pathsep + paths
30 | else:
31 | os.environ[path] = bin_dir
32 | if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
33 | os.add_dll_directory(bin_dir)
34 |
35 | sys.path.insert(0, engine_dir)
36 | sys.path.insert(0, extern_dir)
37 |
38 | arch_file = os.path.join(package_folder, 'engine', '_arch.txt')
39 | if not os.path.isfile(arch_file):
40 | raise RuntimeError("The MATLAB Engine for Python install is corrupted. Please try to re-install.")
41 |
42 | with open(arch_file, 'r') as root:
43 | [arch, bin_folder, engine_folder, extern_bin] = [line.strip() for line in root.readlines()]
44 |
45 |
46 | add_dirs_to_path(bin_folder, engine_folder, extern_bin)
47 |
48 | from matlabmultidimarrayforpython import double, single, uint8, int8, uint16, \
49 | int16, uint32, int32, uint64, int64, logical, ShapeError, SizeError
50 |
--------------------------------------------------------------------------------
/src/matlab/engine/__init__.py:
--------------------------------------------------------------------------------
1 | #Copyright 2014-2024 MathWorks, Inc.
2 |
3 | """
4 | The MATLAB Engine enables you to call any MATLAB statement either synchronously
5 | or asynchronously. With synchronous execution, the invocation of a MATLAB
6 | statement returns the result after the call finishes. With asynchronous
7 | execution, the invocation of a MATLAB statement is performed in the background
8 | and a FutureResult object is returned immediately. You can call its "done"
9 | function to check if the call has finished, and its "result" function to obtain
10 | the actual result of the MATLAB statement.
11 |
12 | This example shows how to call a MATLAB function:
13 |
14 | >>> import matlab.engine
15 | >>> eng = matlab.engine.start_matlab()
16 | >>> eng.sqrt(4.0)
17 | 2.0
18 | >>> eng.exit()
19 | """
20 |
21 |
22 | import os
23 | import sys
24 | import importlib
25 | import atexit
26 | import weakref
27 | import threading
28 | import warnings
29 |
30 | # UPDATE_IF_PYTHON_VERSION_ADDED_OR_REMOVED : search for this string in codebase
31 | # when support for a Python version must be added or removed
32 |
33 | _supported_versions = ['3_9', '3_10', '3_11', '3_12']
34 | _ver = sys.version_info
35 | _version = '{0}_{1}'.format(_ver[0], _ver[1])
36 | _newer_than_supported = _ver[1] > 12
37 |
38 | _PYTHONVERSION = None
39 |
40 | if _version in _supported_versions:
41 | _PYTHONVERSION = _version
42 | elif _newer_than_supported:
43 | warnings.warn('MATLAB Engine for Python supports Python version'
44 | ' 3.9, 3.10, 3.11, and 3.12, but your version of Python '
45 | 'is %s' % _version)
46 | _PYTHONVERSION = _version
47 | else:
48 | raise EnvironmentError('MATLAB Engine for Python supports Python version'
49 | ' 3.9, 3.10, 3.11, and 3.12, but your version of Python '
50 | 'is %s' % _version)
51 |
52 |
53 | _module_folder = os.path.dirname(os.path.realpath(__file__))
54 | _arch_filename = os.path.join(_module_folder, "_arch.txt")
55 | success = False
56 | firstExceptionMessage = ''
57 | secondExceptionMessage = ''
58 | try:
59 | if _PYTHONVERSION != '3_9' and _PYTHONVERSION != '3_10':
60 | pythonengine = importlib.import_module("matlabengineforpython_abi3")
61 | else:
62 | pythonengine = importlib.import_module("matlabengineforpython" + _PYTHONVERSION)
63 | except Exception as firstE:
64 | firstExceptionMessage = str(firstE)
65 |
66 | if firstExceptionMessage:
67 | try:
68 | _arch_file = open(_arch_filename,'r')
69 | _lines = _arch_file.readlines()
70 | [_arch, _bin_dir,_engine_dir, _extern_bin_dir] = [x.rstrip() for x in _lines if x.rstrip() != ""]
71 | _arch_file.close()
72 | sys.path.insert(0,_engine_dir)
73 | sys.path.insert(0,_extern_bin_dir)
74 |
75 | _envs = {'win32': 'PATH', 'win64': 'PATH'}
76 | if _arch in _envs:
77 | if _envs[_arch] in os.environ:
78 | _env = os.environ[_envs[_arch]]
79 | os.environ[_envs[_arch]] = _bin_dir + os.pathsep + os.environ[_envs[_arch]]
80 | else:
81 | os.environ[_envs[_arch]] = _bin_dir
82 | os.add_dll_directory(_bin_dir)
83 | if _PYTHONVERSION != '3_9' or _PYTHONVERSION != '3_10':
84 | pythonengine = importlib.import_module("matlabengineforpython_abi3")
85 | else:
86 | pythonengine = importlib.import_module("matlabengineforpython" + _PYTHONVERSION)
87 | except Exception as secondE:
88 | str1 = 'Please reinstall MATLAB Engine for Python or contact '
89 | str2 = 'MathWorks Technical Support for assistance:\nFirst issue: {}\nSecond issue: {}'.format(
90 | firstExceptionMessage, secondE)
91 | secondExceptionMessage = str1 + str2
92 |
93 | if secondExceptionMessage:
94 | raise EnvironmentError(secondExceptionMessage)
95 |
96 |
97 | """
98 | This lock can make sure the global variable _engines is updated correctly in
99 | multi-thread use case. Also, it guarantees that only one MATLAB is launched
100 | when connect_matlab() is called if there is no shared MATLAB session.
101 | """
102 | _engine_lock = threading.RLock()
103 | _engines = []
104 |
105 | from matlab.engine.engineerror import RejectedExecutionError
106 | from matlab.engine.basefuture import BaseFuture
107 | from matlab.engine.matlabfuture import MatlabFuture
108 | from matlab.engine.fevalfuture import FevalFuture
109 | from matlab.engine.futureresult import FutureResult
110 | from matlab.engine.enginesession import EngineSession
111 | from matlab.engine.matlabengine import MatlabEngine
112 | from matlab.engine.matlabengine import enginehelper
113 |
114 | _session = EngineSession()
115 |
116 | def start_matlab(option="-nodesktop", **kwargs):
117 | """
118 | Start the MATLAB Engine. This function creates an instance of the
119 | MatlabEngine class. The local version of MATLAB will be launched
120 | with the "-nodesktop" argument.
121 |
122 | Please note the invocation of this function is synchronous, which
123 | means it only returns after MATLAB launches.
124 |
125 | Parameters
126 | option - MATLAB startup option.
127 | background: bool - start MATLAB asynchronously or not. This parameter
128 | is optional and false by default.
129 |
130 | Returns
131 | MatlabEngine - if aync or background is false. This object can be used to evaluate
132 | MATLAB statements.
133 | FutureResult - if async or background is true. This object can be used to obtain the
134 | real MatlabEngine instance.
135 |
136 | Raises
137 | EngineError - if MATLAB can't be started.
138 | """
139 | if not isinstance(option, str):
140 | raise TypeError(pythonengine.getMessage('StartupOptionShouldBeStr'))
141 |
142 | background = enginehelper._get_async_or_background_argument(kwargs)
143 | future = FutureResult(option=option)
144 | if not background:
145 | #multi-threads cannot launch MATLAB simultaneously
146 | eng = future.result()
147 | return eng
148 | else:
149 | return future
150 |
151 | def find_matlab():
152 | """
153 | Discover all shared MATLAB sessions on the local machine. This function
154 | returns the names of all shared MATLAB sessions.
155 |
156 | Returns
157 | tuple - the names of all shared MATLAB sessions running locally.
158 | """
159 | engines = pythonengine.findMATLAB()
160 | return engines
161 |
162 | def connect_matlab(name=None, **kwargs):
163 | """
164 | Connect to a shared MATLAB session. This function creates an instance
165 | of the MatlabEngine class and connects it to a MATLAB session. The MATLAB
166 | session must be a shared session on the local machine.
167 |
168 | If name is not specified and there is no shared MATLAB available, this
169 | function launches a shared MATLAB session with default options. If name
170 | is not specified and there are shared MATLAB sessions available, the first
171 | shared MATLAB created is connected. If name is specified and there are no
172 | shared MATLAB sessions with that name, an exception is raised.
173 |
174 | Parameters
175 | name: str - the name of the shared MATLAB session, which is optional.
176 | By default it is None.
177 | async, background: bool - connect to the shared MATLAB session asynchronously or
178 | not. This is optional and false by default. "async" is a synonym for
179 | "background" that will be removed in a future release.
180 |
181 | Returns
182 | MatlabEngine - if async or background is false. This object can be used to evaluate
183 | MATLAB functions.
184 | FutureResult - if async or background is true. This object can be used to obtain the
185 | real MatlabEngine instance.
186 |
187 | Raises
188 | EngineError - if the MATLAB cannot be connected.
189 | """
190 |
191 | #multi-threads cannot run this function simultaneously
192 |
193 | background = enginehelper._get_async_or_background_argument(kwargs)
194 | if name is None:
195 | with _engine_lock:
196 | #if there is no shareable or more than one shareable MATLAB
197 | engines = find_matlab()
198 |
199 | if len(engines) == 0:
200 | future = FutureResult(option="-r matlab.engine.shareEngine")
201 | else:
202 | #if there are shareable MATLAB sessions available
203 | future = FutureResult(name=engines[0], attach=True)
204 |
205 | if not background:
206 | eng = future.result()
207 | return eng
208 | else:
209 | return future
210 | else:
211 | future = FutureResult(name=name, attach=True)
212 | if not background:
213 | eng = future.result()
214 | return eng
215 | else:
216 | return future
217 |
218 | @atexit.register
219 | def __exit_engines():
220 | for eng in _engines:
221 | if eng() is not None:
222 | eng().exit()
223 | _session.release()
224 |
--------------------------------------------------------------------------------
/src/matlab/engine/_arch.txt:
--------------------------------------------------------------------------------
1 | win64
2 | C:\UserAddedPrograms\MATLAB\R2022b\bin\win64
3 | C:\UserAddedPrograms\MATLAB\R2022b\extern\engines\python\dist\matlab\engine\win64
4 | C:\UserAddedPrograms\MATLAB\R2022b\extern\bin\win64
--------------------------------------------------------------------------------
/src/matlab/engine/basefuture.py:
--------------------------------------------------------------------------------
1 | #Copyright 2015 MathWorks, Inc.
2 |
3 | """
4 | BaseFuture: The base class of FevalFuture and MatlabFuture.
5 |
6 | """
7 |
8 | import time
9 |
10 |
11 | class BaseFuture(object):
12 |
13 | def wait(self, timeout, wait_for_func):
14 | """
15 | Wait for the execution of a function.
16 |
17 | Parameter
18 | timeout: int
19 | Number of seconds to wait before returning.
20 |
21 | Returns
22 | The result is ready or not.
23 | """
24 | time_slice = 1
25 | if timeout is None:
26 | result_ready = self.done()
27 | while not result_ready:
28 | result_ready = wait_for_func(self._future, time_slice)
29 | else:
30 | result_ready = self.done()
31 | current_time = time.time()
32 | sleep_until = current_time + timeout
33 | while (not result_ready) and (current_time < sleep_until):
34 | if (sleep_until - current_time) >= time_slice:
35 | result_ready = wait_for_func(self._future, time_slice)
36 | else:
37 | time_to_sleep = sleep_until - current_time
38 | result_ready = wait_for_func(self._future, time_to_sleep)
39 | current_time = time.time()
40 | return result_ready
--------------------------------------------------------------------------------
/src/matlab/engine/engineerror.py:
--------------------------------------------------------------------------------
1 | #Copyright 2014 MathWorks, Inc.
2 |
3 | class RejectedExecutionError(Exception):
4 | """Exception raised from MATLAB engine"""
5 |
6 | def __init__(self, message):
7 | self.message = message
8 | def __repr__(self):
9 | return self.message
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/matlab/engine/enginehelper.py:
--------------------------------------------------------------------------------
1 | #Copyright 2017-2022 MathWorks, Inc.
2 | import warnings
3 | from matlab.engine import pythonengine
4 | import sys
5 |
6 | def _get_async_or_background_argument(kwargs):
7 | if 'async' in kwargs and 'background' in kwargs:
8 | raise KeyError(pythonengine.getMessage('EitherAsyncOrBackground'))
9 | background = False
10 | if 'async' in kwargs:
11 | background = kwargs.pop('async', False)
12 | if not isinstance(background, bool):
13 | raise TypeError(pythonengine.getMessage('AsyncMustBeBool'))
14 | # No test should be passing "async" with Python 3.7 or higher, so throw an exception
15 | # if a test tries to do it.
16 | raise SyntaxError(pythonengine.getMessage('AsyncWillDeprecate'))
17 |
18 | if 'background' in kwargs:
19 | background = kwargs.pop('background', False)
20 | if not isinstance(background, bool):
21 | raise TypeError(pythonengine.getMessage('BackgroundMustBeBool'))
22 |
23 | if kwargs:
24 | raise TypeError((pythonengine.getMessage('InvalidKwargs', list(kwargs.keys())[0].__repr__())))
25 |
26 | return background
--------------------------------------------------------------------------------
/src/matlab/engine/enginesession.py:
--------------------------------------------------------------------------------
1 | #Copyright 2014 MathWorks, Inc.
2 |
3 | from matlab.engine import pythonengine
4 |
5 | class EngineSession():
6 | def __init__(self):
7 | try:
8 | pythonengine.createProcess()
9 | self._process_created = True
10 | except:
11 | raise
12 |
13 | def __del__(self):
14 | self.release()
15 |
16 | def release(self):
17 | if self._process_created:
18 | try:
19 | pythonengine.closeProcess()
20 | self._process_created = False
21 | except:
22 | pass
23 |
--------------------------------------------------------------------------------
/src/matlab/engine/fevalfuture.py:
--------------------------------------------------------------------------------
1 | #Copyright 2015 MathWorks, Inc.
2 |
3 | """
4 | FevalFuture: The class name of a future result returned by the MATLAB Engine.
5 |
6 | An instance of FevalFuture is returned from each asynchronous invocation of a
7 | MATLAB statement or function. The future result serves as a placeholder of the
8 | actual result, so the future result can be returned immediately. MATLAB puts
9 | the actual result into the placeholder when the MATLAB function finishes its
10 | evaluation. The future result can be used to interrupt the execution, check the
11 | completion status, and get the result of a MATLAB statement.
12 |
13 | """
14 |
15 | from matlab.engine import pythonengine
16 | from matlab.engine import RejectedExecutionError
17 | from matlab.engine import TimeoutError
18 | from matlab.engine import BaseFuture
19 | import time
20 | import weakref
21 |
22 | class FevalFuture(BaseFuture):
23 | """
24 | A FevalFuture object is used to hold the future result of a MATLAB
25 | statement. The FevalFuture object should be only created by MatlabEngine
26 | after submitting a MATLAB command for evaluation.
27 | """
28 |
29 | def __init__(self, eng, handle, nout, stdout, stderr):
30 | self._engine = weakref.ref(eng)
31 | self._future = handle
32 | self._nargout = nout
33 | self._out = stdout
34 | self._err = stderr
35 | self._retrieved = False
36 | self._result = None
37 |
38 | def result(self, timeout=None):
39 | """
40 | Get the result of a MATLAB statement.
41 |
42 | Parameter
43 | timeout: int
44 | Number of seconds to wait before returning. By default,
45 | this function will wait until the result is generated.
46 |
47 | Returns
48 | The result of MATLAB statement. A tuple is returned if multiple
49 | outputs are returned.
50 |
51 | Raises
52 | SyntaxError - if there is an error in the MATLAB statement.
53 | InterruptedError - if the task is interrupted.
54 | CancelledError - if the evaluation of MATLAB function is cancelled already.
55 | TimeoutError - if this method fails to get the result in timeout seconds.
56 | MatlabExecutionError - if the MATLAB statement fails in execution.
57 | TypeError - if the data type of return value is not supported.
58 | RejectedExecutionError - an error occurs if the engine is terminated.
59 | """
60 | self.__validate_engine()
61 |
62 | if self._retrieved:
63 | return self._result
64 |
65 | """
66 | Following code is used to poll the Ctrl+C every second from keyboard in
67 | order to cancel a MATLAB function.
68 | """
69 |
70 | try:
71 | result_ready = self.wait(timeout, pythonengine.waitForFEval)
72 |
73 | if not result_ready:
74 | raise TimeoutError(pythonengine.getMessage('MatlabFunctionTimeout'))
75 |
76 | self._result = pythonengine.getFEvalResult(self._future,self._nargout, None, out=self._out, err=self._err)
77 | self._retrieved = True
78 | return self._result
79 |
80 | except KeyboardInterrupt:
81 | self.cancel()
82 | if self.cancelled():
83 | print(pythonengine.getMessage('MatlabFunctionCancelled'))
84 | except:
85 | raise
86 |
87 | def cancel(self):
88 | """
89 | Cancel the execution of an evaluation of a MATLAB statement.
90 |
91 | Returns
92 | bool - True if the corresponding MATLAB statement can be cancelled;
93 | False otherwise.
94 |
95 | Raises
96 | RejectedExecutionError - an error occurs if the engine is terminated.
97 | """
98 | self.__validate_engine()
99 | return pythonengine.cancelFEval(self._future)
100 |
101 | def cancelled(self):
102 | """
103 | Obtain the cancellation status of the asynchronous execution of a MATLAB
104 | command.
105 |
106 | Returns
107 | bool - True if the execution is cancelled; False otherwise.
108 |
109 | Raises
110 | RejectedExecutionError - an error occurs if the engine is terminated.
111 | """
112 | self.__validate_engine()
113 | return pythonengine.isCancelledFEval(self._future)
114 |
115 | def done(self):
116 | """
117 | Obtain the completion status of the asynchronous invocation of a MATLAB
118 | command.
119 |
120 | Returns
121 | bool - True if the execution is finished; False otherwise. It
122 | returns True even if there is an error generated from the MATLAB
123 | statement or it is cancelled.
124 |
125 | Raises
126 | RejectedExecutionError - an error occurs if the engine is terminated.
127 | """
128 | self.__validate_engine()
129 | return pythonengine.isDoneFEval(self._future)
130 |
131 | def __del__(self):
132 | if self._future is not None:
133 | pythonengine.destroyFEvalResult(self._future)
134 | self._future = None
135 | self._result = None
136 |
137 | def __validate_engine(self):
138 | if self._engine() is None or not self._engine()._check_matlab():
139 | raise RejectedExecutionError(pythonengine.getMessage('MatlabTerminated'))
--------------------------------------------------------------------------------
/src/matlab/engine/futureresult.py:
--------------------------------------------------------------------------------
1 | #Copyright 2014-2017 MathWorks, Inc.
2 |
3 | """
4 | FutureResult: The class name of a future result returned by the MATLAB Engine.
5 |
6 | An instance of FutureResult is returned from each asynchronous invocation of a
7 | function call: start_matlab, connect_matlab, or MatlabEngine.. The
8 | future result serves as a placeholder of the actual result, so the future
9 | result can be returned immediately. The actual result is placed into the
10 | placeholder when the function finishes its evaluation. The future result can
11 | be used to interrupt the execution, check the completion status, and get the
12 | actual result.
13 | """
14 |
15 | from matlab.engine import pythonengine
16 | from matlab.engine import MatlabFuture
17 | from matlab.engine import FevalFuture
18 |
19 | class FutureResult():
20 | """
21 | A FutureResult object is used to hold the future result of a function call.
22 | The FutureResult object can be created by start_matlab(async=True),
23 | connect_matlab(async=True), and MaltabEngine.(async=True).
24 | """
25 |
26 | def __init__(self, *args, **kwargs):
27 | feval = kwargs.pop("feval", False)
28 | if feval:
29 | self.__future = FevalFuture(*args)
30 | else:
31 | self.__future = MatlabFuture(*args, **kwargs)
32 |
33 | def result(self, timeout=None):
34 | """
35 | Get the MatlabEngine object or the result of a MATLAB statement.
36 |
37 | Parameter
38 | timeout: int
39 | Number of seconds to wait before returning. By default,
40 | this function will wait until the result is generated.
41 |
42 | Returns
43 | The MatlabEngine object or the result of MATLAB statement. A tuple
44 | is returned if multiple outputs are returned.
45 |
46 | Raises
47 | SyntaxError - if there is an error in the MATLAB statement.
48 | InterruptedError - if the task is interrupted.
49 | CancelledError - if the evaluation of MATLAB function is cancelled already.
50 | TimeoutError - if this method fails to get the result in timeout seconds.
51 | MatlabExecutionError - if the MATLAB statement fails in execution.
52 | TypeError - if the data type of return value is not supported.
53 | RejectedExecutionError - an error occurs if the engine is terminated.
54 | """
55 | if timeout is not None:
56 | if not isinstance(timeout, (int, float)):
57 | raise TypeError(pythonengine.getMessage('TimeoutMustBeNumeric', type(timeout).__name__))
58 |
59 | if timeout < 0:
60 | raise TypeError(pythonengine.getMessage('TimeoutCannotBeNegative'))
61 |
62 | return self.__future.result(timeout)
63 |
64 | def cancel(self):
65 | """
66 | Cancel the launch/connection of MATLAB or evaluation of a MATLAB
67 | task.
68 |
69 | Returns
70 | bool - True if the corresponding task can be cancelled;
71 | False otherwise.
72 |
73 | Raises
74 | RejectedExecutionError - an error occurs if the Engine is terminated.
75 | """
76 | return self.__future.cancel()
77 |
78 | def cancelled(self):
79 | """
80 | Obtain the cancellation status of the asynchronous execution of the
81 | function.
82 |
83 | Returns
84 | bool - True if the execution is cancelled; False otherwise.
85 |
86 | Raises
87 | RejectedExecutionError - an error occurs if the engine is terminated.
88 | """
89 | return self.__future.cancelled()
90 |
91 | def done(self):
92 | """
93 | Obtain the completion status of the asynchronous invocation of the
94 | task.
95 |
96 | Returns
97 | bool - True if the execution is finished; False otherwise. It
98 | returns True even if there is an error generated from the task
99 | or the task is cancelled.
100 |
101 | Raises
102 | RejectedExecutionError - an error occurs if the engine is terminated.
103 | """
104 | return self.__future.done()
105 |
106 | def __del__(self):
107 | if self.__future is not None:
108 | del self.__future
--------------------------------------------------------------------------------
/src/matlab/engine/matlabengine.py:
--------------------------------------------------------------------------------
1 | #Copyright 2014-2017 MathWorks, Inc.
2 |
3 | """
4 | MatlabEngine: The class name of MATLAB Engine. You can call MATLAB software as
5 | a computational engine using the MatlabEngine class.
6 | """
7 |
8 |
9 | from matlab.engine import pythonengine
10 | from matlab.engine import FutureResult
11 | from matlab.engine import RejectedExecutionError
12 | from matlab.engine import MatlabExecutionError
13 | import weakref
14 | import shlex
15 | from matlab.engine import enginehelper
16 |
17 | try:
18 | import StringIO as sIO
19 | except ImportError:
20 | import io as sIO
21 |
22 | class MatlabFunc(object):
23 | """
24 | Reference to a MATLAB function, where "matlabfunc" is replaced by the
25 | function called by the user. *args are passed to MATLAB. **kwargs are
26 | only passed to the engine.
27 | """
28 |
29 | def __init__(self, eng, name):
30 | self.__dict__["_engine"] = weakref.ref(eng)
31 | self.__dict__["_name"] = name
32 |
33 | def __getattr__(self, name):
34 | return MatlabFunc(self._engine(), "%s.%s" % (self._name, name))
35 |
36 | def __setattr__(self, kw, value):
37 | raise AttributeError(pythonengine.getMessage('AttrCannotBeAddedToM'))
38 |
39 | def __call__(self, *args, **kwargs):
40 | self.__validate_engine()
41 |
42 | nargs = kwargs.pop('nargout', 1)
43 |
44 | if not isinstance(nargs, int):
45 | raise TypeError(pythonengine.getMessage('NargoutMustBeInt', type(nargs).__name__))
46 |
47 | if nargs < 0:
48 | raise ValueError(pythonengine.getMessage('NargoutCannotBeLessThanZero'))
49 |
50 | _stdout = kwargs.pop('stdout', None)
51 | _stderr = kwargs.pop('stderr', None)
52 |
53 | background = enginehelper._get_async_or_background_argument(kwargs)
54 |
55 | _sIO_info = '{0}.{1}'.format(sIO.__name__, sIO.StringIO.__name__);
56 | if (_stdout is not None) and (not isinstance(_stdout, sIO.StringIO)):
57 | _stdout_info = '{0}.{1}'.format(_stdout.__class__.__module__, _stdout.__class__.__name__);
58 | raise TypeError(pythonengine.getMessage('StdoutMustBeStringIO', _sIO_info, _stdout_info))
59 |
60 | if (_stderr is not None) and (not isinstance(_stderr, sIO.StringIO)):
61 | _stderr_info = '{0}.{1}'.format(_stderr.__class__.__module__, _stderr.__class__.__name__);
62 | raise TypeError(pythonengine.getMessage('StderrMustBeStringIO', _sIO_info, _stderr_info))
63 |
64 | future = pythonengine.evaluateFunction(self._engine()._matlab,
65 | self._name, nargs,args,
66 | out=_stdout, err=_stderr)
67 | if background:
68 | return FutureResult(self._engine(), future, nargs, _stdout, _stderr, feval=True)
69 | else:
70 | return FutureResult(self._engine(), future, nargs, _stdout,
71 | _stderr, feval=True).result()
72 |
73 | def __validate_engine(self):
74 | if self._engine() is None or not self._engine()._check_matlab():
75 | raise RejectedExecutionError(pythonengine.getMessage('MatlabTerminated'))
76 |
77 | class MatlabWorkSpace(object):
78 | """
79 | ['']
80 | ['']=vardata
81 |
82 | Pass a variable into the MATLAB workspace and copy a
83 | variable from the MATLAB workspace.
84 |
85 | Parameters
86 | : str
87 | Variable name to be used in the MATLAB workspace.
88 |
89 | vardata: object
90 | A Python variable to be passed into the MATLAB workspace.
91 |
92 | Returns
93 | [''] returns the variable copied from the
94 | MATLAB workspace.
95 |
96 | ['']=vardata returns None.
97 |
98 | Raises
99 | NameError - if there is no such variable in the MATLAB
100 | workspace.
101 |
102 | SyntaxError - if the data is passed to the MATLAB
103 | workspace with an illegal variable name.
104 |
105 | TypeError - if is not a string, or if
106 | the data type of vardata is not supported.
107 |
108 | ValueError - if is empty.
109 |
110 | RejectedExecutionError - if the Engine is terminated.
111 | """
112 |
113 | def __init__(self,eng):
114 | self.__dict__["_engine"] = weakref.ref(eng)
115 |
116 | def __getitem__(self,attr):
117 | self.__validate_engine()
118 | self.__validate_identity(attr)
119 | _method=MatlabFunc(self._engine(), "matlab.internal.engine.getVariable")
120 | future = _method(attr)
121 | return future
122 |
123 | def __setitem__(self,attr,value):
124 | self.__validate_engine()
125 | self.__validate_identity(attr)
126 |
127 | _method=MatlabFunc(self._engine(), "assignin")
128 | return _method("base", attr, value, nargout=0)
129 |
130 | def __repr__(self):
131 | _method = MatlabFunc(self._engine(), "whos")
132 | _method(nargout=0)
133 | return ""
134 |
135 | def __setattr__(self, kw, value):
136 | raise AttributeError(pythonengine.getMessage('AttrCannotBeAddedToMWS'))
137 |
138 | def __validate_engine(self):
139 | if self._engine() is None or not self._engine()._check_matlab():
140 | raise RejectedExecutionError(pythonengine.getMessage('MatlabTerminated'))
141 |
142 | def __validate_identity(self, attr):
143 | if not isinstance(attr, str):
144 | raise TypeError(pythonengine.getMessage('VarNameMustBeStr', type(attr).__name__))
145 | if not pythonengine.validateIdentity(attr):
146 | raise ValueError(pythonengine.getMessage('VarNameNotValid'))
147 |
148 | class MatlabEngine(object):
149 | """
150 | By default, the MATLAB Engine starts a MATLAB instance in a separate
151 | process without the desktop on the local machine. The MATLAB version
152 | used by the engine application is the version of MATLAB specified in PATH.
153 |
154 | The MATLAB Engine supports calling MATLAB functions directly. MATLAB
155 | functions are dynamically added to a MatlabEngine object as callable
156 | attributes. The function name is a replaceable MATLAB
157 | function name (for example, sqrt). The function signature is the same as in
158 | MATLAB, with optional named arguments nargout, async|background, stdout, and stderr.
159 |
160 | workspace['']
161 | A property to represent the MATLAB workspace. Variables in
162 | the MATLAB workspace can be accessed through . The
163 | type of this property is MatlabWorkSpace.
164 |
165 |
166 | (*args, nargout=1, async=False, stdout=sys.stdout, stderr=sys.stderr)
167 | (*args, nargout=1, background=False, stdout=sys.stdout, stderr=sys.stderr)
168 |
169 | The invocation of a MATLAB statement can be either synchronous
170 | or asynchronous. While a synchronous function call returns
171 | the result after it finishes executing, an asynchronous
172 | function call is performed in the background and returns a FutureResult
173 | immediately. This FutureResult object can be used to retrieve the
174 | actual result later. If there are any output or error messages
175 | generated from , by default they will be redirected to the
176 | standard output or standard error of the Python console.
177 |
178 | Please note that you can call an arbitrary MATLAB function
179 | available in the MATLAB path using feval and eval.
180 |
181 | Parameters
182 | args:
183 | Arguments accepted by the MATLAB function to be called.
184 | nargout: int
185 | By default, the number of output is 1. If the number of output
186 | is more than 1, a tuple is returned.
187 | async, background: bool
188 | This parameter is used to specify how the MATLAB command is
189 | evaluated: asynchronously or synchronously. By default, async|background
190 | is chosen to be False so the MATLAB command is evaluated synchronously.
191 | "async" is a synonym for "background" that will be removed in a future release.
192 | stdout: StringIO.StringIO (Python 2.7), io.StringIO (Python 3)
193 | Stream used to capture the output of the MATLAB command. By
194 | default, the system standard output sys.stdout is used.
195 | stderr: StringIO.StringIO (Python 2.7), io.StringIO (Python 3)
196 | Stream used to capture the error message of the MATLAB command.
197 | By default, the system standard error output sys.stderr is used.
198 |
199 | Returns
200 | The type of the return value of this function varies based on the
201 | value of parameter async or background. For the case of synchronously invocation,
202 | the result of the MATLAB command is returned directly. For the case
203 | of asynchronous invocation, a FutureResult is returned which can be
204 | used to retrieve the actual result, check completion status, and
205 | interrupt the execution of the MATLAB function.
206 |
207 | Raises
208 | RejectedExecutionError - if the engine is terminated.
209 | SyntaxError - if there is an error in parsing the MATLAB statement.
210 | MatlabExecutionError - if the MATLAB statement fails in execution.
211 | TypeError - if the data types of *args are not supported by
212 | MATLABEngine; or if the data type of a return value is not supported.
213 |
214 | """
215 |
216 | def __init__(self, matlab):
217 | self.__dict__["_matlab"] = matlab
218 | self.__dict__["workspace"] = MatlabWorkSpace(self)
219 |
220 | def __enter__(self):
221 | return self
222 |
223 | def __exit__(self, exc_type, exc_value, traceback):
224 | self.exit()
225 |
226 | def exit(self):
227 | """
228 | Stop the MATLAB session. Calling this method will terminate the
229 | MatlabEngine instance immediately.
230 | """
231 | if self._check_matlab():
232 | pythonengine.closeMATLAB(self.__dict__["_matlab"])
233 | self.__dict__.pop("_matlab")
234 |
235 | def quit(self):
236 | """
237 | Stop the MATLAB session. Calling this method will terminate the
238 | MatlabEngine instance immediately.
239 | """
240 | self.exit()
241 |
242 | def __getattr__(self,name):
243 | """Dynamic attribute of MatlabEngine"""
244 | return MatlabFunc(self, name)
245 |
246 | def __setattr__(self, kw, value):
247 | raise AttributeError(pythonengine.getMessage('AttrCannotBeAddedToM'))
248 |
249 | def __del__(self):
250 | self.exit()
251 |
252 | def _check_matlab(self):
253 | return "_matlab" in self.__dict__
254 |
--------------------------------------------------------------------------------
/src/matlab/engine/matlabfuture.py:
--------------------------------------------------------------------------------
1 | #Copyright 2015 MathWorks, Inc.
2 |
3 |
4 | """
5 | MatlabFuture: The class name of a future handle returned by starting MATLAB
6 | asynchronously.
7 |
8 | An instance of MatlabFuture is returned from the invocation of starting MATLAB
9 | asyncronously. The future handle serves as a placeholder of the actual MATLAB
10 | instance, the the future handle can be returned immediately. The future handle
11 | can be used to interrupt the launch of MATLAB, check the completion status and
12 | get the real MATLAB Engine object.
13 |
14 | """
15 |
16 | from matlab.engine import pythonengine
17 | from matlab.engine import TimeoutError
18 | from matlab.engine import CancelledError
19 | from matlab.engine import BaseFuture
20 | from matlab.engine import _engines
21 | from matlab.engine import _engine_lock
22 | import shlex
23 | import weakref
24 |
25 |
26 | class MatlabFuture(BaseFuture):
27 | """
28 | A MatlabFuture object is used to hold the future handle of a MATLAB
29 | instance. The MatlabFuture object should be only created by calling
30 | start_matlab or connect_matlab asynchronously.
31 | """
32 |
33 | def __init__(self, **kargs):
34 | tokens = []
35 | option = kargs.pop("option", None)
36 | name_ = kargs.pop("name", None)
37 | attach = kargs.pop("attach", False)
38 | self._matlab = None
39 | self._cancelled = False
40 | self._done = False
41 | if option is not None:
42 | tokens = shlex.split(option)
43 | try:
44 | if attach:
45 | self._future = pythonengine.attachMATLABAsync(name_)
46 | self._attach = True
47 | else:
48 | self._future = pythonengine.createMATLABAsync(tokens)
49 | self._attach = False
50 | except:
51 | raise
52 |
53 | def result(self, timeout=None):
54 | """
55 | Get the MatlabEngine instance.
56 |
57 | Parameter
58 | timeout: Number of seconds to wait before returning. By default,
59 | this function will wait until the MatlabEngine instance is ready.
60 |
61 | Returns
62 | An object of MatlabEngine class.
63 |
64 | Raises
65 | CancelledError - if the launch or connection of MATLAB is cancelled already.
66 | TimeoutError - if the MATLAB instance is not ready in timeout seconds.
67 | """
68 | from matlab.engine import MatlabEngine
69 | if self._cancelled:
70 | if self._attach:
71 | raise CancelledError(pythonengine.getMessage('ConnectMatlabCancelled'))
72 | else:
73 | raise CancelledError(pythonengine.getMessage('LaunchMatlabCancelled'))
74 |
75 | if self._matlab is not None:
76 | return self._matlab
77 |
78 | try:
79 | result_ready = self.wait(timeout, pythonengine.waitForMATLAB)
80 |
81 | if not result_ready:
82 | if self._attach:
83 | raise TimeoutError(pythonengine.getMessage('ConnectMatlabTimeout'))
84 | else:
85 | raise TimeoutError(pythonengine.getMessage('LaunchMatlabTimeout'))
86 |
87 | handle = pythonengine.getMATLAB(self._future)
88 | eng = MatlabEngine(handle)
89 | self._matlab = eng
90 | with _engine_lock:
91 | _engines.append(weakref.ref(eng))
92 | return eng
93 |
94 | except KeyboardInterrupt:
95 | self.cancel()
96 | print(pythonengine.getMessage('MatlabCancelled'))
97 | except:
98 | raise
99 |
100 |
101 | def cancel(self):
102 | """
103 | Cancel the launch of or connection to a MATLAB instance.
104 |
105 | Returns
106 | bool - True if the action can be cancelled; False otherwise.
107 | """
108 | if self._matlab is None and not self._cancelled and not self._done:
109 | pythonengine.cancelMATLAB(self._future)
110 | self._cancelled = True
111 | return True
112 | else:
113 | return False
114 |
115 | def cancelled(self):
116 | """
117 | Obtain the cancellation status of the asynchronous launch or connection
118 | to a MATLAB instance.
119 |
120 | Returns
121 | bool - True if the execution was cancelled; False otherwise.
122 | """
123 | if self._cancelled:
124 | return True
125 | return False
126 |
127 | def done(self):
128 | """
129 | Obtain the completion status of the asynchronous launch or connection
130 | to a MATLAB instance.
131 |
132 | Returns
133 | bool - True if the action is finished; False otherwise.
134 | """
135 | if self._done or self._cancelled or self._matlab is not None:
136 | return True
137 | self._done = pythonengine.isDoneMATLAB(self._future)
138 | return self._done
139 |
140 | def __del__(self):
141 | if self._future is not None:
142 | pythonengine.destroyMATLAB(self._future)
143 | self._future = None
144 | self._matlab = None
--------------------------------------------------------------------------------
/test/tInstall.m:
--------------------------------------------------------------------------------
1 | classdef tInstall < matlab.unittest.TestCase
2 | % Verify installation of matlab engine
3 |
4 | % Copyright 2023 Mathworks, Inc.
5 |
6 | properties (Constant)
7 | MATLABVersion = string(ver('MATLAB').Version) % Example: 23.2
8 | end
9 |
10 | methods (Test)
11 | function installNoVersionSpecified(testCase)
12 | [status, out] = system("pip install matlabengine");
13 | addTeardown(testCase, @system, "pip uninstall -y matlabengine");
14 | verifyEqual(testCase, status, 0, out)
15 | verifyInstallation(testCase)
16 | end
17 |
18 | function installMatchingEngine(testCase)
19 | [status, out] = system("pip install matlabengine==" + testCase.MATLABVersion + ".*");
20 | addTeardown(testCase, @system, "pip uninstall -y matlabengine");
21 | verifyEqual(testCase, status, 0, out)
22 | verifyInstallation(testCase)
23 | end
24 | end
25 |
26 | methods
27 | function verifyInstallation(testCase)
28 | % Verify installation by calling functions in matlab engine
29 | % Share this session and see if find_matlab can find it.
30 | sharedEngineName = matlab.engine.engineName;
31 | if isempty(sharedEngineName)
32 | sharedEngineName = 'MATLAB_tInstall';
33 | matlab.engine.shareEngine(sharedEngineName)
34 | end
35 | pySharedEngineName = char(py.matlab.engine.find_matlab());
36 | verifySubstring(testCase, pySharedEngineName, sharedEngineName)
37 | end
38 | end
39 | end
--------------------------------------------------------------------------------