├── AirQual ├── for_testing │ ├── includedSupportPackages.txt │ ├── unresolvedSymbols.txt │ ├── matlabruntimeforpython.egg-info │ │ ├── dependency_links.txt │ │ ├── top_level.txt │ │ ├── PKG-INFO │ │ └── SOURCES.txt │ ├── requiredMCRProducts.txt │ ├── AirQual │ │ ├── AirQual.ctf │ │ └── __init__.py │ ├── mccExcludedFiles.log │ ├── setup.py │ └── readme.txt ├── for_redistribution │ └── MyAppInstaller_web.exe ├── for_redistribution_files_only │ ├── AirQual │ │ ├── AirQual.ctf │ │ └── __init__.py │ ├── setup.py │ └── GettingStarted.html └── PackagingLog.html ├── cities.mat ├── airQualModel.mat ├── CallPythonFromMATLAB.mlx ├── README.md ├── predictAirQual.m ├── prepData.m ├── CallMATLABCompiledLibraryFromPython.ipynb ├── weather.py ├── AirQual.prj └── CallMATLABfromPython.ipynb /AirQual/for_testing/includedSupportPackages.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AirQual/for_testing/unresolvedSymbols.txt: -------------------------------------------------------------------------------- 1 | Path Symbol Reason 2 | -------------------------------------------------------------------------------- /AirQual/for_testing/matlabruntimeforpython.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AirQual/for_testing/matlabruntimeforpython.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | AirQual 2 | -------------------------------------------------------------------------------- /cities.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgorr/weather-matlab-python/HEAD/cities.mat -------------------------------------------------------------------------------- /airQualModel.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgorr/weather-matlab-python/HEAD/airQualModel.mat -------------------------------------------------------------------------------- /AirQual/for_testing/requiredMCRProducts.txt: -------------------------------------------------------------------------------- 1 | 35000 35003 35010 35103 35104 35106 35119 35180 35274 35055 -------------------------------------------------------------------------------- /CallPythonFromMATLAB.mlx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgorr/weather-matlab-python/HEAD/CallPythonFromMATLAB.mlx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # weather-matlab-python 2 | Example illustrating coexecution between MATLAB and Python for a weather prediction app 3 | -------------------------------------------------------------------------------- /AirQual/for_testing/AirQual/AirQual.ctf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgorr/weather-matlab-python/HEAD/AirQual/for_testing/AirQual/AirQual.ctf -------------------------------------------------------------------------------- /AirQual/for_testing/mccExcludedFiles.log: -------------------------------------------------------------------------------- 1 | The List of Excluded Files 2 | Excluded files Exclusion Message ID Reason For Exclusion Exclusion Rule 3 | -------------------------------------------------------------------------------- /AirQual/for_redistribution/MyAppInstaller_web.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgorr/weather-matlab-python/HEAD/AirQual/for_redistribution/MyAppInstaller_web.exe -------------------------------------------------------------------------------- /AirQual/for_redistribution_files_only/AirQual/AirQual.ctf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgorr/weather-matlab-python/HEAD/AirQual/for_redistribution_files_only/AirQual/AirQual.ctf -------------------------------------------------------------------------------- /AirQual/for_testing/matlabruntimeforpython.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: matlabruntimeforpython 3 | Version: R2022a 4 | Summary: A module to call MATLAB from Python 5 | Home-page: https://www.mathworks.com/ 6 | Author: MathWorks 7 | License: UNKNOWN 8 | Platform: Linux 9 | Platform: Windows 10 | Platform: MacOS 11 | 12 | UNKNOWN 13 | 14 | -------------------------------------------------------------------------------- /AirQual/for_testing/matlabruntimeforpython.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | AirQual/AirQual.ctf 3 | AirQual/__init__.py 4 | matlabruntimeforpython.egg-info/.MATLABDriveTag 5 | matlabruntimeforpython.egg-info/PKG-INFO 6 | matlabruntimeforpython.egg-info/SOURCES.txt 7 | matlabruntimeforpython.egg-info/dependency_links.txt 8 | matlabruntimeforpython.egg-info/top_level.txt 9 | predictAirQual/__init__.py 10 | predictAirQual/predictAirQual.ctf -------------------------------------------------------------------------------- /predictAirQual.m: -------------------------------------------------------------------------------- 1 | function airQual = predictAirQual(data) 2 | % PREDICTAIRQUAL Predict air quality, based on machine learning model 3 | % 4 | %#function CompactClassificationEnsemble 5 | 6 | % Convert data types 7 | currentData = prepData(data); 8 | 9 | % Load model 10 | mdl = load("airQualModel.mat"); 11 | model = mdl.model; 12 | 13 | % Determine air quality 14 | airQual = predict(model,currentData); 15 | 16 | % Convert data type for use in Python 17 | airQual = char(airQual); 18 | 19 | end 20 | 21 | -------------------------------------------------------------------------------- /AirQual/PackagingLog.html: -------------------------------------------------------------------------------- 1 |
 2 | mcc -W python:AirQual -T link:lib -d 'C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_testing' -v 'C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\predictAirQual.m' 
 3 | Compiler version: 8.4 (R2022a)
 4 | 
 5 | Analyzing file dependencies. 
 6 | 
 7 | Parsing file "C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\predictAirQual.m"
 8 | 	(referenced from command line).
 9 | Generating file "C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_testing\AirQual\__init__.py".
10 | Generating file "C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_testing\setup.py".
11 | Generating file "C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_testing\readme.txt".
12 | Packaging...
13 | Creating the bundle...
14 | Creating the install agent URL file...
15 | Web based installer created at C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_redistribution\MyAppInstaller_web.exe.
16 | Packaging complete.
17 | Elapsed packaging time was: 8 seconds.
18 | 
19 | -------------------------------------------------------------------------------- /AirQual/for_testing/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2018 The MathWorks, Inc. 2 | 3 | from distutils.core import setup 4 | from distutils.command.clean import clean 5 | from distutils.command.install import install 6 | 7 | class InstallRuntime(install): 8 | # Calls the default run command, then deletes the build area 9 | # (equivalent to "setup clean --all"). 10 | def run(self): 11 | install.run(self) 12 | c = clean(self.distribution) 13 | c.all = True 14 | c.finalize_options() 15 | c.run() 16 | 17 | if __name__ == '__main__': 18 | 19 | setup( 20 | name="matlabruntimeforpython", 21 | version="R2022a", 22 | description='A module to call MATLAB from Python', 23 | author='MathWorks', 24 | url='https://www.mathworks.com/', 25 | platforms=['Linux', 'Windows', 'MacOS'], 26 | packages=[ 27 | 'AirQual' 28 | ], 29 | package_data={'AirQual': ['*.ctf']}, 30 | # Executes the custom code above in order to delete the build area. 31 | cmdclass={'install': InstallRuntime} 32 | ) 33 | 34 | 35 | -------------------------------------------------------------------------------- /AirQual/for_redistribution_files_only/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2018 The MathWorks, Inc. 2 | 3 | from distutils.core import setup 4 | from distutils.command.clean import clean 5 | from distutils.command.install import install 6 | 7 | class InstallRuntime(install): 8 | # Calls the default run command, then deletes the build area 9 | # (equivalent to "setup clean --all"). 10 | def run(self): 11 | install.run(self) 12 | c = clean(self.distribution) 13 | c.all = True 14 | c.finalize_options() 15 | c.run() 16 | 17 | if __name__ == '__main__': 18 | 19 | setup( 20 | name="matlabruntimeforpython", 21 | version="R2022a", 22 | description='A module to call MATLAB from Python', 23 | author='MathWorks', 24 | url='https://www.mathworks.com/', 25 | platforms=['Linux', 'Windows', 'MacOS'], 26 | packages=[ 27 | 'AirQual' 28 | ], 29 | package_data={'AirQual': ['*.ctf']}, 30 | # Executes the custom code above in order to delete the build area. 31 | cmdclass={'install': InstallRuntime} 32 | ) 33 | 34 | 35 | -------------------------------------------------------------------------------- /AirQual/for_testing/readme.txt: -------------------------------------------------------------------------------- 1 | AirQual MATLAB Python Package 2 | 3 | 1. Prerequisites for Deployment 4 | 5 | Verify that version 9.12 (R2022a) of the MATLAB Runtime is installed. 6 | If not, you can run the MATLAB Runtime installer. 7 | To find its location, enter 8 | 9 | >>mcrinstaller 10 | 11 | at the MATLAB prompt. 12 | NOTE: You will need administrator rights to run the MATLAB Runtime installer. 13 | 14 | Alternatively, download and install the Windows version of the MATLAB Runtime for R2022a 15 | from the following link on the MathWorks website: 16 | 17 | https://www.mathworks.com/products/compiler/mcr/index.html 18 | 19 | For more information about the MATLAB Runtime and the MATLAB Runtime installer, see 20 | "Distribute Applications" in the MATLAB Compiler SDK documentation 21 | in the MathWorks Documentation Center. 22 | 23 | Verify that a Windows version of Python 2.7, 3.8, and/or 3.9 is installed. 24 | 25 | 2. Installing the AirQual Package 26 | 27 | A. Change to the directory that contains the file setup.py and the subdirectory AirQual. 28 | If you do not have write permissions, copy all its contents to a temporary location and 29 | change to that directory. 30 | 31 | B. Execute the command: 32 | 33 | python setup.py install [options] 34 | 35 | If you have full administrator privileges, and install to the default location, you do 36 | not need to specify any options. Otherwise, use --user to install to your home folder, or 37 | --prefix="installdir" to install to "installdir". In the latter case, add "installdir" to 38 | the PYTHONPATH environment variable. For details, refer to: 39 | 40 | https://docs.python.org/2/install/index.html 41 | 42 | 43 | 3. Using the AirQual Package 44 | 45 | The AirQual package is on your Python path. To import it into a Python script or session, 46 | execute: 47 | 48 | import AirQual 49 | 50 | If a namespace must be specified for the package, modify the import statement accordingly. 51 | -------------------------------------------------------------------------------- /prepData.m: -------------------------------------------------------------------------------- 1 | function data = prepData(currentData) 2 | % Convert types, units, and organize data for machine learning 3 | 4 | % Convert data type 5 | data = convertData(currentData); 6 | 7 | % Calculate dew point from temp and relative humidity 8 | data.DP = data.T-(9/25)*(100-data.RH); 9 | 10 | % Convert Kelvin to Celsius 11 | data.T = data.T - 273.15; 12 | 13 | % Convert date 14 | if ~isa(data.DateLocal,"datetime") 15 | if strlength(data.DateLocal) > 20 16 | data.DateLocal = datetime(data.DateLocal,... 17 | "InputFormat","uuuu-MM-dd HH:mm:ss.SSS"); 18 | else 19 | data.DateLocal = datetime(data.DateLocal); 20 | end 21 | end 22 | % Split date into components for machine learning 23 | [data.yy,data.MM,data.dd] = ymd(data.DateLocal); 24 | 25 | % Figure out the US State from the city name and lat/lon using data from: 26 | % https://simplemaps.com/data/us-cities. 27 | switch data.city 28 | case "San Jose" 29 | data.StateName = "California"; 30 | case "Washington DC." 31 | data.StateName = "District of Columbia"; 32 | case "Boston" 33 | data.StateName = "Massachusetts"; 34 | case "Munich" 35 | data.StateName = "Bayern"; 36 | case "London" 37 | data.StateName = "UK"; 38 | otherwise 39 | load cities city 40 | idx = city.city == data.city & ... 41 | round(city.lat) == round(data.lat) & ... 42 | round(city.lng) == round(data.lon); 43 | data.StateName = city.state_name(idx); 44 | end 45 | % Convert state to categorical 46 | data.StateName = categorical(data.StateName); 47 | 48 | % Select data for model prediction 49 | data = data(:,["DateLocal","city","StateName","T","P",... 50 | "DP","RH","WindDir","WindSpd","yy","MM","dd"]); 51 | end 52 | 53 | function data = convertData(data) 54 | % Organize and convert data types 55 | data = struct2table(struct(data)); 56 | % Check for wind direction (deg), sometimes missing 57 | if ~any(data.Properties.VariableNames == "deg") 58 | deg = ""; 59 | data = addvars(data,deg,'After','speed'); 60 | end 61 | data = removevars(data,["temp_min","temp_max"]); 62 | data.Properties.VariableNames([1:5,end]) = ["T","P","RH","WindSpd","WindDir","DateLocal"]; 63 | data = convertvars(data,["T","P","RH","WindSpd","WindDir"],"double"); 64 | data = convertvars(data,["city","DateLocal"],"string"); 65 | 66 | % Convert date 67 | if strlength(data.DateLocal) > 20 68 | data.DateLocal = datetime(data.DateLocal,"InputFormat","uuuu-MM-dd HH:mm:ss.SSS"); 69 | else 70 | data.DateLocal = datetime(data.DateLocal); 71 | end 72 | 73 | end -------------------------------------------------------------------------------- /CallMATLABCompiledLibraryFromPython.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Predict Air Quality: Calling MATLAB from Python Using MATLAB Runtime" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Import the weather data and use the MATLAB code to predict air quality.\n", 15 | "\n", 16 | "Use the MATLAB Runtime to call the model and code, which does not require a MATLAB installation or license to use. More info here: https://www.mathworks.com/help/compiler_sdk/python_packages.html\n", 17 | "\n", 18 | "First, install the Compiled Python library by going in the for_testing folder and execute the install command class (open a terminal with Ctrl+ù from VScode or the good old Windows cmd):\n", 19 | " * `cd AirQual\\for_testing`\n", 20 | " * `python setup.py install`\n" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "#### Read the current weather data" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 1, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "import weather" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "metadata": {}, 43 | "outputs": [ 44 | { 45 | "name": "stdout", 46 | "output_type": "stream", 47 | "text": [ 48 | "{'temp': 280.32, 'pressure': 1012, 'humidity': 81, 'temp_min': 279.15, 'temp_max': 281.15, 'speed': 4.1, 'deg': 80, 'lon': -0.13, 'lat': 51.51, 'city': 'London', 'current_time': '2022-07-27 14:14:20.500642'}\n" 49 | ] 50 | } 51 | ], 52 | "source": [ 53 | "appid ='b1b15e88fa797225412429c1c50c122a1'\n", 54 | "json_data = weather.get_current_weather(\"London\",\"UK\",appid,api=\"samples\")\n", 55 | "data = weather.parse_current_json(json_data)\n", 56 | "print(data)" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "#### Use MATLAB to predict air quality\n", 64 | "Import the package created from MATLAB" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 1, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "import AirQual" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 3, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "aq = AirQual.initialize()" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 8, 88 | "metadata": {}, 89 | "outputs": [ 90 | { 91 | "name": "stdout", 92 | "output_type": "stream", 93 | "text": [ 94 | "Good\n" 95 | ] 96 | } 97 | ], 98 | "source": [ 99 | "label = aq.predictAirQual(data)\n", 100 | "print(label)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "Terminate the MATLAB instance" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 9, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "aq.terminate()" 117 | ] 118 | } 119 | ], 120 | "metadata": { 121 | "kernelspec": { 122 | "display_name": "Python 3.9.13 64-bit", 123 | "language": "python", 124 | "name": "python3" 125 | }, 126 | "language_info": { 127 | "codemirror_mode": { 128 | "name": "ipython", 129 | "version": 3 130 | }, 131 | "file_extension": ".py", 132 | "mimetype": "text/x-python", 133 | "name": "python", 134 | "nbconvert_exporter": "python", 135 | "pygments_lexer": "ipython3", 136 | "version": "3.9.13" 137 | }, 138 | "vscode": { 139 | "interpreter": { 140 | "hash": "81794d4967e6c3204c66dcd87b604927b115b27c00565d3d43f05ba2f3a2cb0d" 141 | } 142 | } 143 | }, 144 | "nbformat": 4, 145 | "nbformat_minor": 4 146 | } 147 | -------------------------------------------------------------------------------- /weather.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Created on Thu Jan 4 20:41:45 2018 4 | Modified Jan 29 2019 5 | Simplified June 16 2022 6 | 7 | ''' 8 | 9 | # weather.py 10 | import datetime 11 | import json 12 | import urllib.request 13 | 14 | BASE_URL = '/data/2.5/{}?q={},{}&appid={}' 15 | 16 | def get_current_weather(city, country, apikey,**kwargs): 17 | '''get current conditions in specified location 18 | appid='b1b15e88fa797225412429c1c50c122a1' 19 | get_current_weather('London','uk',appid,api='samples')''' 20 | 21 | # select between samples or prod api 22 | if 'api' in kwargs: 23 | if kwargs['api'] == 'samples': 24 | DOMAIN = "http://samples.openweathermap.org" 25 | else: 26 | print('not the right api arg') 27 | DOMAIN = "http://api.openweathermap.org" 28 | else: 29 | DOMAIN = "http://api.openweathermap.org" 30 | 31 | # Read current conditions 32 | try: 33 | # url = 'http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b1b15e88fa797225412429c1c50c122a1' 34 | url = DOMAIN + BASE_URL.format('weather',city,country,apikey) 35 | json_data = json.loads(urllib.request.urlopen(url).read()) 36 | return json_data 37 | except urllib.error.URLError: 38 | # If weather API doesnt work 39 | print("API not available") 40 | 41 | 42 | def parse_current_json(json_data): 43 | '''parse and extract json data from the current weather data''' 44 | 45 | try: 46 | # select data of interest from dictionary 47 | weather_info = json_data['main'] 48 | weather_info.update(json_data['wind']) 49 | weather_info.update(json_data['coord']) 50 | weather_info['city'] = json_data['name'] 51 | # add current date and time 52 | weather_info['current_time'] = str(datetime.datetime.now()) 53 | 54 | except KeyError as e: 55 | # use current dictionary (because it probably came from backup file) 56 | try: 57 | # If this fails then the json_data didn't come from backup file 58 | json_data.pop('City') 59 | weather_info = json_data 60 | except: 61 | # print('Something else went wrong while parsing current json') 62 | raise e 63 | 64 | return weather_info 65 | 66 | 67 | def get_forecast(city, country, apikey, **kwargs): 68 | '''get forecast conditions in specified location 69 | appid='b1b15e88fa797225412429c1c50c122a1' 70 | get_forecast('Muenchen','DE',appid,api='samples')''' 71 | 72 | # select between samples or prod api 73 | if 'api' in kwargs: 74 | if kwargs['api'] == 'samples': 75 | DOMAIN = "http://samples.openweathermap.org" 76 | else: 77 | print('not the right api arg') 78 | DOMAIN = "http://api.openweathermap.org" 79 | else: 80 | DOMAIN = "http://api.openweathermap.org" 81 | # get forecast 82 | try: 83 | url = DOMAIN + BASE_URL.format('forecast',city,country,apikey) 84 | json_data = json.loads(urllib.request.urlopen(url).read()) 85 | return json_data 86 | except: 87 | print("API not available") 88 | 89 | 90 | def parse_forecast_json(json_data): 91 | '''parse and extract json data from the weather forecast data''' 92 | 93 | try: 94 | # parse forecast json data 95 | data = json_data['list'] 96 | wind_keys = ['deg','speed'] 97 | weather_info = {'current_time':[], 'temp':[], 'deg':[], 98 | 'speed':[], 'humidity':[], 'pressure':[]} 99 | for data_point in data[0:40]: 100 | for k in list(weather_info.keys())[1:]: #Taking a slice so we don't add the time 101 | weather_info[k].append(float(data_point['wind' if k in wind_keys else 'main'][k])) 102 | weather_info['current_time'].append(data_point['dt_txt']) 103 | return weather_info 104 | 105 | except: 106 | print('Something went wrong while parsing forecast json') 107 | -------------------------------------------------------------------------------- /AirQual/for_redistribution_files_only/GettingStarted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Getting Started with the AirQual Python Package 5 | 14 | 15 | 16 |
17 |

Getting Started with the AirQual Python Package

18 |
The Library Compiler in MATLAB® Compiler SDK™ creates Python® packages that can be integrated with applications written in Python. It also generates sample Python driver code that can be used to integrate and test the generated components. You can use this guide to set up your environment and run your sample driver application.
19 |
Note: Sample Python driver code is only generated if sample MATLAB code is included during the packaging phase. Samples can be found in the folder named "samples".
20 |
21 |

Location of Installed Python Package Files

22 |
View AirQual Files
23 |
24 |
25 |

Prerequisites

26 |
1. Install and Configure the MATLAB Runtime
27 |
Complete this step only if you have not installed the MATLAB Runtime while installing the package.
28 |
2. Configure the Python Environment
29 |
30 |

Set Up the Python Component

31 |
At the system terminal, type:
32 |
python setup.py install [options]
33 |
If you have full administrator privileges and install to the default location, you do not need to specify any options. Otherwise, use --user to install to your home folder, or --prefix="installdir" to install to "installdir". In the latter case, add "installdir" to the PYTHONPATH environment variable.
34 |
For details, refer to:
35 |
Installing Python Modules (Python 2)
36 |
Installing Python Modules (Python 3)
37 |
38 |

Executing Python Driver Applications

39 |
At the system terminal, type:
40 |
python driverApplication.py
41 |
42 |
43 |

Additional Resources

44 |
Python Package Integration
45 |
46 |
MATLAB Arrays as Python Variables
47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /AirQual.prj: -------------------------------------------------------------------------------- 1 | 2 | 3 | AirQual 4 | 5 | 6 | 1.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | \AirQual\ 15 | option.installpath.programfiles 16 | 17 | 18 | You must have Python 2.7, 3.8, and/or 3.9 on your machine to install this package. Go to the directory containing your package and the file setup.py and execute: 19 | 20 | python setup.py install [options] 21 | 22 | For details, see GettingStarted.html, distributed with this package, or the official Python documentation at: 23 | 24 | https://docs.python.org/2/install/index.html 25 | ${PROJECT_ROOT}\AirQual\for_testing 26 | ${PROJECT_ROOT}\AirQual\for_redistribution_files_only 27 | ${PROJECT_ROOT}\AirQual\for_redistribution 28 | ${PROJECT_ROOT}\AirQual 29 | false 30 | 31 | subtarget.python.module 32 | 33 | 34 | 35 | true 36 | false 37 | false 38 | MyAppInstaller_web 39 | MyAppInstaller_mcr 40 | MyAppInstaller_app 41 | true 42 | false 43 | 44 | false 45 | option.net.version.four 46 | false 47 | false 48 | 49 | false 50 | 51 | 52 | 53 | Class1 54 | 55 | C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar 56 | 57 | option.cpp.all 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ${PROJECT_ROOT}\predictAirQual.m 108 | 109 | 110 | 111 | 112 | ${PROJECT_ROOT}\predictAirQual.m 113 | 114 | 115 | 116 | 117 | 118 | ${PROJECT_ROOT}\airQualModel.mat 119 | ${PROJECT_ROOT}\cities.mat 120 | ${PROJECT_ROOT}\prepData.m 121 | 122 | 123 | 124 | 125 | 126 | C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_testing\setup.py 127 | C:\Users\ydebray\MATLAB Drive\PythonMunichWebinar\AirQual\for_testing\AirQual 128 | 129 | 130 | 131 | C:\Program Files\MATLAB\R2022a 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | true 143 | 144 | 145 | 146 | 147 | true 148 | 149 | 150 | 151 | 152 | true 153 | 154 | 155 | 156 | 157 | true 158 | 159 | 160 | 161 | 162 | true 163 | 164 | 165 | 166 | 167 | true 168 | 169 | 170 | 171 | 172 | false 173 | false 174 | true 175 | false 176 | false 177 | false 178 | false 179 | false 180 | 10.0 181 | false 182 | true 183 | win64 184 | true 185 | 186 | 187 | -------------------------------------------------------------------------------- /AirQual/for_testing/AirQual/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2021 MathWorks, Inc. 2 | 3 | 4 | """ Package for executing deployed MATLAB functions """ 5 | 6 | from __future__ import print_function 7 | import atexit 8 | import glob 9 | import importlib 10 | import os 11 | import os.path 12 | import pdb 13 | import platform 14 | import re 15 | import sys 16 | import weakref 17 | 18 | class _PathInitializer(object): 19 | PLATFORM_DICT = {'Windows': ['PATH','dll',''], 'Linux': ['LD_LIBRARY_PATH','so','libmw'], 'Darwin': ['DYLD_LIBRARY_PATH','dylib','libmw']} 20 | SUPPORTED_PYTHON_VERSIONS = ['2_7', '3_8', '3_9'] 21 | RUNTIME_VERSION_W_DOTS = '9.12' 22 | RUNTIME_VERSION_W_UNDERSCORES = '9_12' 23 | PACKAGE_NAME = 'AirQual' 24 | 25 | def set_interpreter_version(self): 26 | """Make sure the interpreter version is supported.""" 27 | ver = sys.version_info 28 | version = '{0}_{1}'.format(ver[0], ver[1]) 29 | 30 | if version in _PathInitializer.SUPPORTED_PYTHON_VERSIONS: 31 | self.interpreter_version = version 32 | else: 33 | version_with_dot = version.replace("_", ".") 34 | raise EnvironmentError("Python {0} is not supported.".format(version_with_dot)) 35 | 36 | def __init__(self): 37 | """Initialize the variables.""" 38 | self.arch = '' 39 | self.is_linux = False 40 | self.is_mac = False 41 | self.is_windows = False 42 | self.mr_handle = None 43 | self.ml_handle = None 44 | self.system = '' 45 | self.cppext_handle = None 46 | 47 | # path to the folder that stores the mcpyarray Python extension 48 | self.extern_bin_dir = '' 49 | 50 | # path to the folder that stores pure Python matlab_pysdk.runtime code (_runtime_dir) 51 | self.pysdk_py_runtime_dir = '' 52 | 53 | # path to the folder that stores the __init__file for the matlab module 54 | self.matlab_mod_dist_dir = '' 55 | 56 | # path to the folder that stores Python extensions and shared libraries 57 | self.bin_dir = '' 58 | 59 | self.set_interpreter_version() 60 | self.get_platform_info() 61 | 62 | this_folder = os.path.dirname(os.path.realpath(__file__)) 63 | self.path_file_name = os.path.join(this_folder, 'paths.{0}.txt'.format(self.arch)) 64 | 65 | self.instances_of_this_package = set([]) 66 | 67 | def get_platform_info(self): 68 | """Ask Python for the platform and architecture.""" 69 | 70 | # This will return 'Windows', 'Linux', or 'Darwin' (for Mac). 71 | self.system = platform.system() 72 | if not self.system in _PathInitializer.PLATFORM_DICT: 73 | raise RuntimeError('{0} is not a supported platform.'.format(self.system)) 74 | else: 75 | # path_var is the OS-dependent name of the path variable ('PATH', 'LD_LIBRARY_PATH', "DYLD_LIBRARY_PATH') 76 | (self.path_var, self.ext, self.lib_prefix) = _PathInitializer.PLATFORM_DICT[self.system] 77 | 78 | if self.system == 'Windows': 79 | self.is_windows = True 80 | bit_str = platform.architecture()[0] 81 | if bit_str == '64bit': 82 | self.arch = 'win64' 83 | elif bit_str == '32bit': 84 | self.arch = 'win32' 85 | else: 86 | raise RuntimeError('{0} is not supported.'.format(bit_str)) 87 | elif self.system == 'Linux': 88 | self.is_linux = True 89 | self.arch = 'glnxa64' 90 | elif self.system == 'Darwin': 91 | self.is_mac = True 92 | self.arch = 'maci64' 93 | else: 94 | raise RuntimeError('Operating system {0} is not supported.'.format(self.system)) 95 | 96 | def get_paths_from_os(self): 97 | """ 98 | Look through the system path for a file whose name contains a runtime version 99 | corresponding to the one with which this package was produced. 100 | """ 101 | 102 | # Concatenates the pieces into a string. The double parentheses are necessary. 103 | if self.system == 'Windows': 104 | file_to_find = ''.join((self.lib_prefix, 'mclmcrrt', 105 | _PathInitializer.RUNTIME_VERSION_W_UNDERSCORES, '.', self.ext)) 106 | elif self.system == 'Linux': 107 | file_to_find = ''.join((self.lib_prefix, 'mclmcrrt', '.', self.ext, '.', 108 | _PathInitializer.RUNTIME_VERSION_W_DOTS)) 109 | elif self.system == 'Darwin': 110 | file_to_find = ''.join((self.lib_prefix, 'mclmcrrt', '.', 111 | _PathInitializer.RUNTIME_VERSION_W_DOTS, 112 | '.', self.ext)) 113 | else: 114 | raise RuntimeError('Operating system {0} is not supported.'.format(self.system)) 115 | 116 | path_elements = [] 117 | if self.path_var in os.environ: 118 | path_elements = os.environ[self.path_var].split(os.pathsep) 119 | if not path_elements: 120 | if self.system == 'Darwin': 121 | raise RuntimeError('On the Mac, you must run mwpython rather than python ' + 122 | 'to start a session or script that imports your package. ' + 123 | 'For more details, execute "mwpython -help" or see the package documentation.') 124 | else: 125 | raise RuntimeError('On {0}, you must set the environment variable "{1}" to a non-empty string. {2}'.format( 126 | self.system, self.path_var, 127 | 'For more details, see the package documentation.')) 128 | 129 | path_found = '' 130 | for elem in path_elements: 131 | filename = os.path.join(elem, file_to_find) 132 | if (os.path.isfile(filename)): 133 | path_found = elem 134 | break 135 | if not path_found: 136 | msg = '{0} {1}. Details: file not found: {2}; {1}: {3}'.format( 137 | 'Could not find an appropriate directory for MATLAB or the MATLAB runtime in', 138 | self.path_var, file_to_find, os.environ[self.path_var]) 139 | raise RuntimeError(msg) 140 | 141 | path_components = re.split(r'\\|/', path_found) 142 | 143 | if path_components[-1]: 144 | last_path_component = path_components[-1] 145 | else: 146 | # The directory name ended with a slash, so the last item in the list was an empty string. Go back one more. 147 | last_path_component = path_components[-2] 148 | 149 | if last_path_component != self.arch: 150 | output_str = ''.join(('To call deployed MATLAB code on a {0} machine, you must run a {0} version of Python, ', 151 | 'and your {1} variable must contain an element pointing to "{2}runtime{2}{0}", ', 152 | 'where "" indicates a MATLAB or MATLAB Runtime root. ', 153 | 'Instead, the value found was as follows: {3}')) 154 | raise RuntimeError(output_str.format(self.arch, self.path_var, os.sep, path_found)) 155 | 156 | matlabroot = os.path.dirname(os.path.dirname(os.path.normpath(path_found))) 157 | extern_bin_dir = os.path.join(matlabroot, 'extern', 'bin', self.arch) 158 | pysdk_py_runtime_dir = os.path.join(matlabroot, 'toolbox', 'compiler_sdk', 'pysdk_py') 159 | matlab_mod_dist_dir = os.path.join(pysdk_py_runtime_dir, 'matlab_mod_dist') 160 | bin_dir = os.path.join(matlabroot, 'bin', self.arch) 161 | if not os.path.isdir(extern_bin_dir): 162 | raise RuntimeError('Could not find the directory {0}'.format(extern_bin_dir)) 163 | if not os.path.isdir(pysdk_py_runtime_dir): 164 | raise RuntimeError('Could not find the directory {0}'.format(pysdk_py_runtime_dir)) 165 | if not os.path.isdir(matlab_mod_dist_dir): 166 | raise RuntimeError('Could not find the directory {0}'.format(matlab_mod_dist_dir)) 167 | if not os.path.isdir(bin_dir): 168 | raise RuntimeError('Could not find the directory {0}'.format(bin_dir)) 169 | (self.extern_bin_dir, self.pysdk_py_runtime_dir, self.matlab_mod_dist_dir, self.bin_dir) = ( 170 | extern_bin_dir, pysdk_py_runtime_dir, matlab_mod_dist_dir, bin_dir) 171 | 172 | def update_paths(self): 173 | """Update the OS and Python paths.""" 174 | 175 | #For Windows, add the extern_bin_dir and bin_dir to the OS path. This is unnecessary 176 | #for Linux and Mac, where the OS can find this information via rpath. 177 | if self.is_windows: 178 | os.environ[self.path_var] = self.extern_bin_dir + os.pathsep + self.bin_dir + os.pathsep + os.environ[self.path_var] 179 | 180 | #Add all paths to the Python path. 181 | sys.path.insert(0, self.bin_dir) 182 | sys.path.insert(0, self.matlab_mod_dist_dir) 183 | sys.path.insert(0, self.pysdk_py_runtime_dir) 184 | sys.path.insert(0, self.extern_bin_dir) 185 | 186 | def import_matlab_pysdk_runtime(self): 187 | """Import matlab_pysdk.runtime. Must be done after update_paths() and import_cppext() are called.""" 188 | try: 189 | self.mr_handle = importlib.import_module('matlab_pysdk.runtime') 190 | except Exception as e: 191 | raise e 192 | 193 | if not hasattr(self.mr_handle, '_runtime_version_w_dots'): 194 | raise RuntimeError('Runtime version of package ({0}) does not match runtime version of previously loaded package'.format( 195 | _PathInitializer.RUNTIME_VERSION_W_DOTS)) 196 | elif self.mr_handle._runtime_version_w_dots and (self.mr_handle._runtime_version_w_dots != _PathInitializer.RUNTIME_VERSION_W_DOTS): 197 | raise RuntimeError('Runtime version of package ({0}) does not match runtime version of previously loaded package ({1})'.format( 198 | _PathInitializer.RUNTIME_VERSION_W_DOTS, 199 | self.mr_handle._runtime_version_w_dots)) 200 | else: 201 | self.mr_handle._runtime_version_w_dots = _PathInitializer.RUNTIME_VERSION_W_DOTS 202 | 203 | self.mr_handle._cppext_handle = self.cppext_handle 204 | 205 | def import_matlab(self): 206 | """Import the matlab package. Must be done after Python system path contains what it needs to.""" 207 | try: 208 | self.ml_handle = importlib.import_module('matlab') 209 | except Exception as e: 210 | raise e 211 | 212 | def initialize_package(self): 213 | package_handle = self.mr_handle.DeployablePackage(self, self.PACKAGE_NAME, __file__) 214 | self.instances_of_this_package.add(weakref.ref(package_handle)) 215 | package_handle.initialize() 216 | return package_handle 217 | 218 | def initialize_runtime(self, option_list): 219 | if not self.cppext_handle: 220 | raise RuntimeError('Cannot call initialize_application before import_cppext.') 221 | if self.is_mac: 222 | ignored_option_found = False 223 | for option in option_list: 224 | if option in ('-nodisplay', '-nojvm'): 225 | ignored_option_found = True 226 | break 227 | if ignored_option_found: 228 | print('WARNING: Options "-nodisplay" and "-nojvm" are ignored on Mac.') 229 | print('They must be passed to mwpython in order to take effect.') 230 | self.cppext_handle.initializeApplication(option_list) 231 | 232 | def terminate_runtime(self): 233 | if not self.cppext_handle: 234 | raise RuntimeError('Cannot call terminate_application before import_cppext.') 235 | self.cppext_handle.terminateApplication() 236 | 237 | def import_cppext(self): 238 | module_name = "matlabruntimeforpython" + self.interpreter_version 239 | self.cppext_handle = importlib.import_module(module_name) 240 | 241 | try: 242 | _pir = _PathInitializer() 243 | _pir.get_paths_from_os() 244 | _pir.update_paths() 245 | _pir.import_cppext() 246 | _pir.import_matlab_pysdk_runtime() 247 | _pir.import_matlab() 248 | except Exception as e: 249 | print("Exception caught during initialization of Python interface. Details: {0}".format(e)) 250 | raise 251 | # We let the program exit normally. 252 | 253 | def initialize(): 254 | """ 255 | Initialize package and return a handle. 256 | 257 | Initialize a package consisting of one or more deployed MATLAB functions. The return 258 | value is used as a handle on which any of the functions can be executed. To wait 259 | for all graphical figures to close before continuing, call wait_for_figures_to_close() 260 | on the handle. To close the package, call terminate(), quit() or exit() (which are 261 | synonymous) on the handle. The terminate() function is executed automatically when the 262 | script or session ends. 263 | 264 | Returns 265 | handle - used to execute deployed MATLAB functions and to call terminate() 266 | """ 267 | return _pir.initialize_package() 268 | 269 | def initialize_runtime(option_list): 270 | """ 271 | Initialize runtime with a list of startup options. 272 | 273 | Initialize the MATLAB Runtime with a list of startup options that will affect 274 | all packages opened within the script or session. If it is not called 275 | explicitly, it will be executed automatically, with an empty list of options, 276 | by the first call to initialize(). Do not call initialize_runtime() after 277 | calling initialize(). 278 | 279 | There is no corresponding terminate_runtime() call. The runtime is terminated 280 | automatically when the script or session ends. 281 | 282 | Parameters 283 | option_list - Python list of options; valid options are: 284 | -nodisplay (suppresses display functionality; Linux only) 285 | -nojvm (disables the Java Virtual Machine) 286 | """ 287 | if option_list: 288 | if not isinstance(option_list, list) and not isinstance(option_list, tuple): 289 | raise SyntaxError('initialize_runtime takes a list or tuple of strings.') 290 | _pir.initialize_runtime(option_list) 291 | 292 | # Before terminating the process, call terminate_runtime() once on any package. This will 293 | # ensure graceful MATLAB runtime shutdown. After this call, the user should not use 294 | # any MATLAB-related function. 295 | # When running interactively, the user should call exit() after done using the package. 296 | # When running a script, the runtime will automatically be terminated when the script ends. 297 | def terminate_runtime(): 298 | _pir.terminate_runtime(); 299 | 300 | @atexit.register 301 | def __exit_packages(): 302 | for package in _pir.instances_of_this_package: 303 | if package() is not None: 304 | package().terminate() 305 | -------------------------------------------------------------------------------- /AirQual/for_redistribution_files_only/AirQual/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2021 MathWorks, Inc. 2 | 3 | 4 | """ Package for executing deployed MATLAB functions """ 5 | 6 | from __future__ import print_function 7 | import atexit 8 | import glob 9 | import importlib 10 | import os 11 | import os.path 12 | import pdb 13 | import platform 14 | import re 15 | import sys 16 | import weakref 17 | 18 | class _PathInitializer(object): 19 | PLATFORM_DICT = {'Windows': ['PATH','dll',''], 'Linux': ['LD_LIBRARY_PATH','so','libmw'], 'Darwin': ['DYLD_LIBRARY_PATH','dylib','libmw']} 20 | SUPPORTED_PYTHON_VERSIONS = ['2_7', '3_8', '3_9'] 21 | RUNTIME_VERSION_W_DOTS = '9.12' 22 | RUNTIME_VERSION_W_UNDERSCORES = '9_12' 23 | PACKAGE_NAME = 'AirQual' 24 | 25 | def set_interpreter_version(self): 26 | """Make sure the interpreter version is supported.""" 27 | ver = sys.version_info 28 | version = '{0}_{1}'.format(ver[0], ver[1]) 29 | 30 | if version in _PathInitializer.SUPPORTED_PYTHON_VERSIONS: 31 | self.interpreter_version = version 32 | else: 33 | version_with_dot = version.replace("_", ".") 34 | raise EnvironmentError("Python {0} is not supported.".format(version_with_dot)) 35 | 36 | def __init__(self): 37 | """Initialize the variables.""" 38 | self.arch = '' 39 | self.is_linux = False 40 | self.is_mac = False 41 | self.is_windows = False 42 | self.mr_handle = None 43 | self.ml_handle = None 44 | self.system = '' 45 | self.cppext_handle = None 46 | 47 | # path to the folder that stores the mcpyarray Python extension 48 | self.extern_bin_dir = '' 49 | 50 | # path to the folder that stores pure Python matlab_pysdk.runtime code (_runtime_dir) 51 | self.pysdk_py_runtime_dir = '' 52 | 53 | # path to the folder that stores the __init__file for the matlab module 54 | self.matlab_mod_dist_dir = '' 55 | 56 | # path to the folder that stores Python extensions and shared libraries 57 | self.bin_dir = '' 58 | 59 | self.set_interpreter_version() 60 | self.get_platform_info() 61 | 62 | this_folder = os.path.dirname(os.path.realpath(__file__)) 63 | self.path_file_name = os.path.join(this_folder, 'paths.{0}.txt'.format(self.arch)) 64 | 65 | self.instances_of_this_package = set([]) 66 | 67 | def get_platform_info(self): 68 | """Ask Python for the platform and architecture.""" 69 | 70 | # This will return 'Windows', 'Linux', or 'Darwin' (for Mac). 71 | self.system = platform.system() 72 | if not self.system in _PathInitializer.PLATFORM_DICT: 73 | raise RuntimeError('{0} is not a supported platform.'.format(self.system)) 74 | else: 75 | # path_var is the OS-dependent name of the path variable ('PATH', 'LD_LIBRARY_PATH', "DYLD_LIBRARY_PATH') 76 | (self.path_var, self.ext, self.lib_prefix) = _PathInitializer.PLATFORM_DICT[self.system] 77 | 78 | if self.system == 'Windows': 79 | self.is_windows = True 80 | bit_str = platform.architecture()[0] 81 | if bit_str == '64bit': 82 | self.arch = 'win64' 83 | elif bit_str == '32bit': 84 | self.arch = 'win32' 85 | else: 86 | raise RuntimeError('{0} is not supported.'.format(bit_str)) 87 | elif self.system == 'Linux': 88 | self.is_linux = True 89 | self.arch = 'glnxa64' 90 | elif self.system == 'Darwin': 91 | self.is_mac = True 92 | self.arch = 'maci64' 93 | else: 94 | raise RuntimeError('Operating system {0} is not supported.'.format(self.system)) 95 | 96 | def get_paths_from_os(self): 97 | """ 98 | Look through the system path for a file whose name contains a runtime version 99 | corresponding to the one with which this package was produced. 100 | """ 101 | 102 | # Concatenates the pieces into a string. The double parentheses are necessary. 103 | if self.system == 'Windows': 104 | file_to_find = ''.join((self.lib_prefix, 'mclmcrrt', 105 | _PathInitializer.RUNTIME_VERSION_W_UNDERSCORES, '.', self.ext)) 106 | elif self.system == 'Linux': 107 | file_to_find = ''.join((self.lib_prefix, 'mclmcrrt', '.', self.ext, '.', 108 | _PathInitializer.RUNTIME_VERSION_W_DOTS)) 109 | elif self.system == 'Darwin': 110 | file_to_find = ''.join((self.lib_prefix, 'mclmcrrt', '.', 111 | _PathInitializer.RUNTIME_VERSION_W_DOTS, 112 | '.', self.ext)) 113 | else: 114 | raise RuntimeError('Operating system {0} is not supported.'.format(self.system)) 115 | 116 | path_elements = [] 117 | if self.path_var in os.environ: 118 | path_elements = os.environ[self.path_var].split(os.pathsep) 119 | if not path_elements: 120 | if self.system == 'Darwin': 121 | raise RuntimeError('On the Mac, you must run mwpython rather than python ' + 122 | 'to start a session or script that imports your package. ' + 123 | 'For more details, execute "mwpython -help" or see the package documentation.') 124 | else: 125 | raise RuntimeError('On {0}, you must set the environment variable "{1}" to a non-empty string. {2}'.format( 126 | self.system, self.path_var, 127 | 'For more details, see the package documentation.')) 128 | 129 | path_found = '' 130 | for elem in path_elements: 131 | filename = os.path.join(elem, file_to_find) 132 | if (os.path.isfile(filename)): 133 | path_found = elem 134 | break 135 | if not path_found: 136 | msg = '{0} {1}. Details: file not found: {2}; {1}: {3}'.format( 137 | 'Could not find an appropriate directory for MATLAB or the MATLAB runtime in', 138 | self.path_var, file_to_find, os.environ[self.path_var]) 139 | raise RuntimeError(msg) 140 | 141 | path_components = re.split(r'\\|/', path_found) 142 | 143 | if path_components[-1]: 144 | last_path_component = path_components[-1] 145 | else: 146 | # The directory name ended with a slash, so the last item in the list was an empty string. Go back one more. 147 | last_path_component = path_components[-2] 148 | 149 | if last_path_component != self.arch: 150 | output_str = ''.join(('To call deployed MATLAB code on a {0} machine, you must run a {0} version of Python, ', 151 | 'and your {1} variable must contain an element pointing to "{2}runtime{2}{0}", ', 152 | 'where "" indicates a MATLAB or MATLAB Runtime root. ', 153 | 'Instead, the value found was as follows: {3}')) 154 | raise RuntimeError(output_str.format(self.arch, self.path_var, os.sep, path_found)) 155 | 156 | matlabroot = os.path.dirname(os.path.dirname(os.path.normpath(path_found))) 157 | extern_bin_dir = os.path.join(matlabroot, 'extern', 'bin', self.arch) 158 | pysdk_py_runtime_dir = os.path.join(matlabroot, 'toolbox', 'compiler_sdk', 'pysdk_py') 159 | matlab_mod_dist_dir = os.path.join(pysdk_py_runtime_dir, 'matlab_mod_dist') 160 | bin_dir = os.path.join(matlabroot, 'bin', self.arch) 161 | if not os.path.isdir(extern_bin_dir): 162 | raise RuntimeError('Could not find the directory {0}'.format(extern_bin_dir)) 163 | if not os.path.isdir(pysdk_py_runtime_dir): 164 | raise RuntimeError('Could not find the directory {0}'.format(pysdk_py_runtime_dir)) 165 | if not os.path.isdir(matlab_mod_dist_dir): 166 | raise RuntimeError('Could not find the directory {0}'.format(matlab_mod_dist_dir)) 167 | if not os.path.isdir(bin_dir): 168 | raise RuntimeError('Could not find the directory {0}'.format(bin_dir)) 169 | (self.extern_bin_dir, self.pysdk_py_runtime_dir, self.matlab_mod_dist_dir, self.bin_dir) = ( 170 | extern_bin_dir, pysdk_py_runtime_dir, matlab_mod_dist_dir, bin_dir) 171 | 172 | def update_paths(self): 173 | """Update the OS and Python paths.""" 174 | 175 | #For Windows, add the extern_bin_dir and bin_dir to the OS path. This is unnecessary 176 | #for Linux and Mac, where the OS can find this information via rpath. 177 | if self.is_windows: 178 | os.environ[self.path_var] = self.extern_bin_dir + os.pathsep + self.bin_dir + os.pathsep + os.environ[self.path_var] 179 | 180 | #Add all paths to the Python path. 181 | sys.path.insert(0, self.bin_dir) 182 | sys.path.insert(0, self.matlab_mod_dist_dir) 183 | sys.path.insert(0, self.pysdk_py_runtime_dir) 184 | sys.path.insert(0, self.extern_bin_dir) 185 | 186 | def import_matlab_pysdk_runtime(self): 187 | """Import matlab_pysdk.runtime. Must be done after update_paths() and import_cppext() are called.""" 188 | try: 189 | self.mr_handle = importlib.import_module('matlab_pysdk.runtime') 190 | except Exception as e: 191 | raise e 192 | 193 | if not hasattr(self.mr_handle, '_runtime_version_w_dots'): 194 | raise RuntimeError('Runtime version of package ({0}) does not match runtime version of previously loaded package'.format( 195 | _PathInitializer.RUNTIME_VERSION_W_DOTS)) 196 | elif self.mr_handle._runtime_version_w_dots and (self.mr_handle._runtime_version_w_dots != _PathInitializer.RUNTIME_VERSION_W_DOTS): 197 | raise RuntimeError('Runtime version of package ({0}) does not match runtime version of previously loaded package ({1})'.format( 198 | _PathInitializer.RUNTIME_VERSION_W_DOTS, 199 | self.mr_handle._runtime_version_w_dots)) 200 | else: 201 | self.mr_handle._runtime_version_w_dots = _PathInitializer.RUNTIME_VERSION_W_DOTS 202 | 203 | self.mr_handle._cppext_handle = self.cppext_handle 204 | 205 | def import_matlab(self): 206 | """Import the matlab package. Must be done after Python system path contains what it needs to.""" 207 | try: 208 | self.ml_handle = importlib.import_module('matlab') 209 | except Exception as e: 210 | raise e 211 | 212 | def initialize_package(self): 213 | package_handle = self.mr_handle.DeployablePackage(self, self.PACKAGE_NAME, __file__) 214 | self.instances_of_this_package.add(weakref.ref(package_handle)) 215 | package_handle.initialize() 216 | return package_handle 217 | 218 | def initialize_runtime(self, option_list): 219 | if not self.cppext_handle: 220 | raise RuntimeError('Cannot call initialize_application before import_cppext.') 221 | if self.is_mac: 222 | ignored_option_found = False 223 | for option in option_list: 224 | if option in ('-nodisplay', '-nojvm'): 225 | ignored_option_found = True 226 | break 227 | if ignored_option_found: 228 | print('WARNING: Options "-nodisplay" and "-nojvm" are ignored on Mac.') 229 | print('They must be passed to mwpython in order to take effect.') 230 | self.cppext_handle.initializeApplication(option_list) 231 | 232 | def terminate_runtime(self): 233 | if not self.cppext_handle: 234 | raise RuntimeError('Cannot call terminate_application before import_cppext.') 235 | self.cppext_handle.terminateApplication() 236 | 237 | def import_cppext(self): 238 | module_name = "matlabruntimeforpython" + self.interpreter_version 239 | self.cppext_handle = importlib.import_module(module_name) 240 | 241 | try: 242 | _pir = _PathInitializer() 243 | _pir.get_paths_from_os() 244 | _pir.update_paths() 245 | _pir.import_cppext() 246 | _pir.import_matlab_pysdk_runtime() 247 | _pir.import_matlab() 248 | except Exception as e: 249 | print("Exception caught during initialization of Python interface. Details: {0}".format(e)) 250 | raise 251 | # We let the program exit normally. 252 | 253 | def initialize(): 254 | """ 255 | Initialize package and return a handle. 256 | 257 | Initialize a package consisting of one or more deployed MATLAB functions. The return 258 | value is used as a handle on which any of the functions can be executed. To wait 259 | for all graphical figures to close before continuing, call wait_for_figures_to_close() 260 | on the handle. To close the package, call terminate(), quit() or exit() (which are 261 | synonymous) on the handle. The terminate() function is executed automatically when the 262 | script or session ends. 263 | 264 | Returns 265 | handle - used to execute deployed MATLAB functions and to call terminate() 266 | """ 267 | return _pir.initialize_package() 268 | 269 | def initialize_runtime(option_list): 270 | """ 271 | Initialize runtime with a list of startup options. 272 | 273 | Initialize the MATLAB Runtime with a list of startup options that will affect 274 | all packages opened within the script or session. If it is not called 275 | explicitly, it will be executed automatically, with an empty list of options, 276 | by the first call to initialize(). Do not call initialize_runtime() after 277 | calling initialize(). 278 | 279 | There is no corresponding terminate_runtime() call. The runtime is terminated 280 | automatically when the script or session ends. 281 | 282 | Parameters 283 | option_list - Python list of options; valid options are: 284 | -nodisplay (suppresses display functionality; Linux only) 285 | -nojvm (disables the Java Virtual Machine) 286 | """ 287 | if option_list: 288 | if not isinstance(option_list, list) and not isinstance(option_list, tuple): 289 | raise SyntaxError('initialize_runtime takes a list or tuple of strings.') 290 | _pir.initialize_runtime(option_list) 291 | 292 | # Before terminating the process, call terminate_runtime() once on any package. This will 293 | # ensure graceful MATLAB runtime shutdown. After this call, the user should not use 294 | # any MATLAB-related function. 295 | # When running interactively, the user should call exit() after done using the package. 296 | # When running a script, the runtime will automatically be terminated when the script ends. 297 | def terminate_runtime(): 298 | _pir.terminate_runtime(); 299 | 300 | @atexit.register 301 | def __exit_packages(): 302 | for package in _pir.instances_of_this_package: 303 | if package() is not None: 304 | package().terminate() 305 | -------------------------------------------------------------------------------- /CallMATLABfromPython.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Predict Air Quality: Calling MATLAB from Python Using MATLAB Engine API" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Import the weather data and use the MATLAB code to predict air quality.\n", 15 | "\n", 16 | "This example uses the MATLAB Engine API, which runs a MATLAB session. More info and set up instructions are included here: https://www.mathworks.com/help/matlab/matlab-engine-for-python.html\n", 17 | "\n", 18 | "First, import and start MATLAB through the MATLAB Engine API." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 9, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "import matlab.engine" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 10, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "m = matlab.engine.start_matlab()\n", 37 | "# m = matlab.engine.start_matlab(\"-desktop\")" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "Test a simple function " 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 11, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "6.48074069840786\n" 57 | ] 58 | } 59 | ], 60 | "source": [ 61 | "x = m.sqrt(42.0)\n", 62 | "print(x)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Specify number of output arguments (also for zero outputs)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 12, 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "name": "stdout", 79 | "output_type": "stream", 80 | "text": [ 81 | "(5, 0)\n" 82 | ] 83 | } 84 | ], 85 | "source": [ 86 | "x = m.gcd(10,5,nargout=2)\n", 87 | "print(x)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 13, 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "data": { 97 | "text/plain": [ 98 | "0" 99 | ] 100 | }, 101 | "execution_count": 13, 102 | "metadata": {}, 103 | "output_type": "execute_result" 104 | } 105 | ], 106 | "source": [ 107 | "x[1]" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "Exchange data between workspaces" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 14, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "m.workspace['a'] = 42" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 15, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "m.desktop(nargout=0) # reveals MATLAB desktop if started in default headless mode" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "## Use MATLAB to predict temperature" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "Read the current weather data" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 16, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "import weather" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 17, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "name": "stdout", 165 | "output_type": "stream", 166 | "text": [ 167 | "{'temp': 280.32, 'pressure': 1012, 'humidity': 81, 'temp_min': 279.15, 'temp_max': 281.15, 'speed': 4.1, 'deg': 80, 'lon': -0.13, 'lat': 51.51, 'city': 'London', 'current_time': '2022-08-25 14:59:37.352525'}\n" 168 | ] 169 | } 170 | ], 171 | "source": [ 172 | "appid ='b1b15e88fa797225412429c1c50c122a1'\n", 173 | "json_data = weather.get_current_weather(\"London\",\"UK\",appid,api=\"samples\")\n", 174 | "data = weather.parse_current_json(json_data)\n", 175 | "print(data)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "Predict air quality from user-defined function." 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 18, 188 | "metadata": {}, 189 | "outputs": [ 190 | { 191 | "name": "stdout", 192 | "output_type": "stream", 193 | "text": [ 194 | "Good\n" 195 | ] 196 | } 197 | ], 198 | "source": [ 199 | "aq = m.predictAirQual(data)\n", 200 | "print(aq)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "Going further in data preprocessing" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 19, 213 | "metadata": {}, 214 | "outputs": [ 215 | { 216 | "name": "stdout", 217 | "output_type": "stream", 218 | "text": [ 219 | "{'current_time': ['2017-02-16 12:00:00', '2017-02-16 15:00:00', '2017-02-16 18:00:00', '2017-02-16 21:00:00', '2017-02-17 00:00:00', '2017-02-17 03:00:00', '2017-02-17 06:00:00', '2017-02-17 09:00:00', '2017-02-17 12:00:00', '2017-02-17 15:00:00', '2017-02-17 18:00:00', '2017-02-17 21:00:00', '2017-02-18 00:00:00', '2017-02-18 03:00:00', '2017-02-18 06:00:00', '2017-02-18 09:00:00', '2017-02-18 12:00:00', '2017-02-18 15:00:00', '2017-02-18 18:00:00', '2017-02-18 21:00:00', '2017-02-19 00:00:00', '2017-02-19 03:00:00', '2017-02-19 06:00:00', '2017-02-19 09:00:00', '2017-02-19 12:00:00', '2017-02-19 15:00:00', '2017-02-19 18:00:00', '2017-02-19 21:00:00', '2017-02-20 00:00:00', '2017-02-20 03:00:00', '2017-02-20 06:00:00', '2017-02-20 09:00:00', '2017-02-20 12:00:00', '2017-02-20 15:00:00', '2017-02-20 18:00:00', '2017-02-20 21:00:00'], 'temp': [286.67, 285.66, 277.05, 272.78, 273.341, 275.568, 276.478, 276.67, 278.253, 276.455, 275.639, 275.459, 275.035, 274.965, 274.562, 275.648, 277.927, 278.367, 273.797, 271.239, 269.553, 268.198, 267.295, 272.956, 277.422, 277.984, 272.459, 269.473, 268.793, 268.106, 267.655, 273.75, 279.302, 279.343, 274.443, 272.424], 'deg': [247.501, 290.501, 263.5, 205.502, 224.003, 237.002, 268.005, 266.504, 261.501, 268.001, 258.001, 265.503, 273.5, 285.502, 276.5, 251.008, 244.004, 79.5024, 77.0026, 95.5017, 101.004, 121.5, 155.005, 195.002, 357.003, 48.5031, 75.5042, 174.002, 207.502, 191.001, 194.001, 208.5, 252.001, 268.001, 257.501, 255.503], 'speed': [1.81, 1.59, 1.41, 2.24, 3.59, 3.77, 3.81, 2.6, 3.17, 3.21, 3.17, 3.71, 3.56, 2.66, 1.46, 1.5, 0.86, 1.62, 2.42, 2.42, 1.96, 1.06, 1.17, 1.66, 1.32, 1.58, 1.16, 1.12, 2.11, 1.67, 1.61, 2.49, 2.46, 3.21, 3.27, 3.57], 'humidity': [75.0, 70.0, 90.0, 80.0, 85.0, 89.0, 97.0, 100.0, 95.0, 99.0, 95.0, 96.0, 99.0, 97.0, 98.0, 99.0, 95.0, 89.0, 91.0, 93.0, 92.0, 84.0, 86.0, 84.0, 89.0, 87.0, 90.0, 83.0, 80.0, 85.0, 84.0, 83.0, 83.0, 81.0, 88.0, 85.0], 'pressure': [972.73, 970.91, 970.44, 969.32, 968.14, 966.6, 966.45, 967.41, 966.98, 966.38, 966.39, 966.3, 966.43, 966.36, 966.75, 967.21, 966.06, 964.57, 964.13, 963.39, 962.39, 961.28, 961.16, 962.03, 962.23, 962.15, 963.31, 964.65, 965.92, 966.4, 967.4, 968.84, 968.37, 967.9, 968.19, 968.38]}\n" 220 | ] 221 | } 222 | ], 223 | "source": [ 224 | "json_data = weather.get_forecast(\"Muenchen\",\"DE\",appid,api=\"samples\")\n", 225 | "data = weather.parse_forecast_json(json_data)\n", 226 | "print(data)" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "Send to the MATLAB workspace for further processing" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 20, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "t = matlab.double(data['temp'])\n", 243 | "m.workspace['t'] = t\n", 244 | "m.signalAnalyzer(nargout=0)" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": 21, 250 | "metadata": {}, 251 | "outputs": [], 252 | "source": [ 253 | "m.workspace['data'] = data\n", 254 | "m.eval(\"TT = timetable(datetime(string(data.current_time))',cell2mat(data.temp)','VariableNames',{'Temp'})\",nargout=0)" 255 | ] 256 | }, 257 | { 258 | "cell_type": "markdown", 259 | "metadata": {}, 260 | "source": [ 261 | "On the MATLAB side, creates a timetable:\n", 262 | "```matlab\n", 263 | "TT = timetable(datetime(string(data.current_time))',cell2mat(data.temp)','VariableNames',{'Temp'})\n", 264 | "```\n", 265 | "\n", 266 | "Another alternative is to create a csv file to import from MATLAB" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 22, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "import csv\n", 276 | "with open(\"data.csv\",\"w\") as f:\n", 277 | " write = csv.writer(f,lineterminator = '\\n')\n", 278 | " write.writerow(['Time','Temp'])\n", 279 | " write.writerows([[data['current_time'][i],data['temp'][i]] for i in range(36)])" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 23, 285 | "metadata": {}, 286 | "outputs": [], 287 | "source": [ 288 | "m.dataCleaner(nargout=0)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "Export the processing steps as a function `preprocess.m`" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 1, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "# TT = m.workspace['TT']\n", 305 | "# TT2 = m.preprocess(TT)\n", 306 | "# m.parquetwrite(\"data.parquet\",TT2,nargout=0)" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": null, 312 | "metadata": {}, 313 | "outputs": [ 314 | { 315 | "data": { 316 | "text/plain": [ 317 | "" 318 | ] 319 | }, 320 | "execution_count": 23, 321 | "metadata": {}, 322 | "output_type": "execute_result" 323 | }, 324 | { 325 | "data": { 326 | "image/png": "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", 327 | "text/plain": [ 328 | "
" 329 | ] 330 | }, 331 | "metadata": { 332 | "needs_background": "light" 333 | }, 334 | "output_type": "display_data" 335 | } 336 | ], 337 | "source": [ 338 | "# import pandas as pd\n", 339 | "# pd.read_parquet('data.parquet').plot(y='Temp')" 340 | ] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "metadata": {}, 345 | "source": [ 346 | "Exit the MATLAB engine" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 25, 352 | "metadata": {}, 353 | "outputs": [], 354 | "source": [ 355 | "m.exit()" 356 | ] 357 | } 358 | ], 359 | "metadata": { 360 | "kernelspec": { 361 | "display_name": "Python 3.9.13 64-bit", 362 | "language": "python", 363 | "name": "python3" 364 | }, 365 | "language_info": { 366 | "codemirror_mode": { 367 | "name": "ipython", 368 | "version": 3 369 | }, 370 | "file_extension": ".py", 371 | "mimetype": "text/x-python", 372 | "name": "python", 373 | "nbconvert_exporter": "python", 374 | "pygments_lexer": "ipython3", 375 | "version": "3.9.13" 376 | }, 377 | "vscode": { 378 | "interpreter": { 379 | "hash": "81794d4967e6c3204c66dcd87b604927b115b27c00565d3d43f05ba2f3a2cb0d" 380 | } 381 | } 382 | }, 383 | "nbformat": 4, 384 | "nbformat_minor": 4 385 | } 386 | --------------------------------------------------------------------------------