Документация Engee
Notebook

Совместное использование Julia, MATLAB и Python при решении инженерных задач

Введение

Современный инженер и учёный редко ограничивается одним языком программирования. Численные расчёты — на MATLAB, анализ данных — на Python, высокопроизводительные вычисления — на Julia. Но что если не выбирать, а объединить?

Вычислительная среда Engee позволяет запускать код на Julia, MATLAB и Python прямо в одном скрипте, обмениваясь данными между языками на лету. В данном примере мы покажем, как это работает: создадим матрицу в Julia, обработаем её в MATLAB, сохраним в .mat-файл, а затем проанализируем результат с помощью NumPy — и всё это без выхода за пределы одной среды.

Присоединение библиотек

В данном примере нам понадобятся следующие библиотеки:

  • MATLAB — для запуска MATLAB-команд и функций прямо из Julia, позволяя обмениваться переменными между средами
  • MAT — для чтения и записи файлов .mat без необходимости запускать сам MATLAB, работает как автономный конвертер данных
  • PyCall — для Python-скриптоа и библиотек из Julia с автоматической конвертацией типов между языками.
In [ ]:
using MATLAB, MAT, PyCall

Вызов MATLAB из Julia

Выполнение MATLAB-команд

Данный скрипт выполнится внутри ядра MATLAB

In [ ]:
mat"""
a = 10;
b = 20;
c = a + b;
disp(c);
"""
>> >> >> >> >> >>     30

Вызов MATLAB-функции

Имеется файл:

% function1.m\
\
function y = function1(x)\
 y = x.^2 + 1;\
end

Выполним скрипт MATLAB c помощью команды mxcall()и отобразим результат.

In [ ]:
result = mxcall(:function1, 1, 5)
println(result)
26

Передача данных Julia → MATLAB

Макрос @mput помещает переменные Julia в рабочее пространство MATLAB.

In [ ]:
x = [1,2,3,4,5]

@mput x

mat"""
y = x.^2;
disp(y)
"""
>> >> >> >>     1
    4
    9
   16
   25

Теперь MATLAB видит переменную x.

Несколько переменных
In [ ]:
a = 10
b = 20

@mput a b
mat"""
c = a + b;
disp(c)
"""
>> >> >> >>    30

Получение данных MATLAB → Julia

Макрос @mget извлекает переменные MATLAB и помещает их в рабочее пространство Julia.

In [ ]:
mat"""
x = 42;
"""

@mget x

println(x)
42.0
Получение массива
In [ ]:
mat"""
A = magic(3);
"""

@mget A

println(A)
[8.0 1.0 6.0; 3.0 5.0 7.0; 4.0 9.0 2.0]

Одновременная передача данных

In [ ]:
x = [1.0, 2.0, 3.0]

@mput x

mat"""
y = sin(x);
z = cos(x);
"""

@mget y z

println(y)
println(z)
[0.8414709848078965, 0.9092974268256817, 0.1411200080598672]
[0.5403023058681398, -0.4161468365471424, -0.9899924966004454]
Передача матриц
In [ ]:
A = rand(5,5)
@mput A
mat"""
B = inv(A);
"""
@mget B
println(B)
[-5.221370437657235 4.536689829822023 1.7853694400555051 -1.7555803059084987 2.5459703018696143; -2.656223457338815 2.6954496614608283 1.9527633720615352 -0.7726425993370551 0.3924611199505691; 1.1298133336804967 0.13971945565486266 -1.0208263947545195 -0.09726994177272896 -0.013276362895074445; 8.019040759955885 -7.849628311451011 -2.63590807356968 2.136718238044462 -1.7078887483116802; 3.268552864160139 -3.710816487781914 -1.5021042004828817 2.4835666024648373 -1.7450236032972575]

Обмен данными без использования макросов

Через put_variable() и get_variable()

In [ ]:
x = 10

put_variable(:x, x)

mat"""
y = x^2;
"""

y = get_variable(:y)

println(y) 
100

Через интерполяцию $

In [ ]:
x = 10

mat"""
y = $x^2;
"""

y = get_variable(:y)

println(y)  
100

Через mxcall()

In [ ]:
x = 10
y = mxcall(:sqrt, 1, x)
println(y)

Аналог в MATLAB:

y = sqrt(10);

Через явную сессию MSession

In [ ]:
s = MSession()
put_variable(s, :x, 10)
eval_string(s, "y = x^2;")
y = get_mvariable(s, :y)
println(y)
close(s)
MxArray(Ptr{Nothing}(0x000077b0f43c39e0), true)
mkdir: cannot create directory ‘/user/.MathWorks’: Permission denied

Через встроенное выражение MATLAB

In [ ]:
y = mxcall(:power, 1, 10, 2)
println(y) 

Аналог в MATLAB:

power(10,2)

Вызов MATLAB-скрипта

Имеется файл:

% script1.m

x = 100;
y = x^2;

disp(y)

In [ ]:
mat"""
run('script1.m');
"""
>> >> >>        10000

или

In [ ]:
mat"""
script1
"""
>> >> >>        10000

Сохранение MAT-файлов

Julia → MAT

In [ ]:
A = rand(100,100)
matwrite("data.mat", Dict("A" => A))

Затем в MATLAB можно выполнить:

In [ ]:
mat"""
load('data.mat')
"""

MATLAB → Julia

In [ ]:
data = matread("data.mat")
A = data["A"]
Out[0]:
100×100 Matrix{Float64}:
 0.187665    0.536775   0.559974   …  0.497759   0.370136   0.217892
 0.498748    0.802852   0.447108      0.137034   0.986267   0.0492607
 0.692961    0.585268   0.0409884     0.675473   0.352056   0.832218
 0.16713     0.523383   0.488701      0.64212    0.339589   0.0828162
 0.994572    0.0283767  0.932887      0.29104    0.573378   0.11291
 0.287387    0.787492   0.92626    …  0.312808   0.13772    0.796877
 0.930736    0.39265    0.133021      0.536968   0.665237   0.768994
 0.185316    0.0128372  0.846072      0.494333   0.36337    0.151129
 0.137108    0.0811613  0.612712      0.913359   0.0901814  0.639056
 0.257916    0.267885   0.0811315     0.309257   0.806105   0.982611
 0.153683    0.480328   0.330206   …  0.0230829  0.440528   0.584395
 0.147242    0.830841   0.611354      0.913737   0.175778   0.294328
 0.141859    0.259777   0.473815      0.391229   0.448802   0.783275
 ⋮                                 ⋱                        
 0.491116    0.22047    0.914381      0.403631   0.572556   0.680288
 0.31416     0.0558817  0.119846      0.58611    0.976843   0.402179
 0.523465    0.549084   0.292965   …  0.947264   0.593057   0.0757108
 0.505689    0.569463   0.271353      0.840848   0.613332   0.429195
 0.00443699  0.777516   0.928007      0.125124   0.702318   0.947103
 0.506735    0.841525   0.632436      0.389265   0.0218632  0.830129
 0.540411    0.701873   0.806918      0.588761   0.540034   0.473754
 0.643525    0.979437   0.540921   …  0.850907   0.661718   0.224936
 0.225163    0.693345   0.953538      0.0294944  0.0242852  0.983359
 0.80467     0.069913   0.223371      0.771978   0.675066   0.482565
 0.174844    0.123621   0.549536      0.146337   0.701713   0.917493
 0.962306    0.950703   0.678788      0.394894   0.0613499  0.605355

Вызов Python из Julia

Выполнение Python-скрипта

In [ ]:
py"""
x = 10
y = 20
z = x + y
print(z)
"""
30

Получение результата

In [ ]:
result = py"z"
println(result)

Вызов Python-функции

Имеется файл:

# mymodule.py

def square(x):
return x*x

In [ ]:
pushfirst!(PyVector(pyimport("sys")."path"), ".")
mod = pyimport("mymodule")
result = mod.square(5)
println(result)
25

Передача данных Julia → Python

In [ ]:
a = [1,2,3,4]
py"""
a = $a
"""

Теперь Python видит вектор x.

Передача матрицы NumPy

In [ ]:
A = rand(3,3)
py"""
A = $A
"""

PyCall автоматически конвертирует массив.

Получение данных Python → Julia

In [ ]:
py"""
import numpy as np
A = np.random.rand(3,3)
"""

A = py"A"
println(A)
[0.6654948909047511 0.3864146000126685 0.6323793133576465; 0.44635742775821485 0.751622525464147 0.12916692842936617; 0.2232885178400401 0.5449276484095866 0.18403058713001075]

Использование NumPy напрямую

In [ ]:
np = pyimport("numpy")
x = np.linspace(0, 10, 100)
println(x)
[0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091, 1.0101010101010102, 1.1111111111111112, 1.2121212121212122, 1.3131313131313131, 1.4141414141414141, 1.5151515151515151, 1.6161616161616161, 1.7171717171717171, 1.8181818181818181, 1.9191919191919191, 2.0202020202020203, 2.121212121212121, 2.2222222222222223, 2.323232323232323, 2.4242424242424243, 2.525252525252525, 2.6262626262626263, 2.727272727272727, 2.8282828282828283, 2.929292929292929, 3.0303030303030303, 3.131313131313131, 3.2323232323232323, 3.3333333333333335, 3.4343434343434343, 3.5353535353535355, 3.6363636363636362, 3.7373737373737375, 3.8383838383838382, 3.9393939393939394, 4.040404040404041, 4.141414141414141, 4.242424242424242, 4.343434343434343, 4.444444444444445, 4.545454545454545, 4.646464646464646, 4.747474747474747, 4.848484848484849, 4.94949494949495, 5.05050505050505, 5.151515151515151, 5.252525252525253, 5.353535353535354, 5.454545454545454, 5.555555555555555, 5.656565656565657, 5.757575757575758, 5.858585858585858, 5.959595959595959, 6.0606060606060606, 6.161616161616162, 6.262626262626262, 6.363636363636363, 6.4646464646464645, 6.565656565656566, 6.666666666666667, 6.767676767676767, 6.8686868686868685, 6.96969696969697, 7.070707070707071, 7.171717171717171, 7.2727272727272725, 7.373737373737374, 7.474747474747475, 7.575757575757575, 7.6767676767676765, 7.777777777777778, 7.878787878787879, 7.979797979797979, 8.080808080808081, 8.181818181818182, 8.282828282828282, 8.383838383838384, 8.484848484848484, 8.585858585858587, 8.686868686868687, 8.787878787878787, 8.88888888888889, 8.98989898989899, 9.09090909090909, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0]

NumPy + Julia-массив

In [ ]:
A = rand(100)
np.mean(A)
Out[0]:
0.4975610444322162

Передача данных между всеми тремя языками

Julia ↔ MATLAB ↔ Python

Julia:

In [ ]:
x = rand(100)
@mput x

MATLAB:

In [ ]:
mat"""
y = fft(x);
save('fft.mat','y')
"""

Julia:

In [ ]:
d = matread("fft.mat")
y = d["y"]
Out[0]:
100×1 Matrix{ComplexF64}:
  50.313741254263505 + 0.0im
 -1.5771407887055269 + 2.124727170787205im
 -1.2688118432157607 + 0.05402836822868018im
   2.004984078494711 - 1.3179735416202203im
 -0.8416332361392718 + 1.717682921499197im
   -0.92602810124836 + 3.5138264917159967im
   1.593016731074605 - 0.7439815251896407im
 0.46384142685195917 + 3.8454495764527im
  1.0924993653124653 - 3.905545719219579im
  0.7162821343272152 + 1.4136618648658554im
 -0.6796169943349781 + 0.7628501552265539im
 -3.4342798941532124 - 0.5686610086095321im
 -2.3822899937223627 + 0.03900956219344409im
                     ⋮
 -2.3822899937223627 - 0.03900956219344409im
 -3.4342798941532124 + 0.5686610086095321im
 -0.6796169943349781 - 0.7628501552265539im
  0.7162821343272152 - 1.4136618648658554im
  1.0924993653124653 + 3.905545719219579im
 0.46384142685195917 - 3.8454495764527im
   1.593016731074605 + 0.7439815251896407im
   -0.92602810124836 - 3.5138264917159967im
 -0.8416332361392718 - 1.717682921499197im
   2.004984078494711 + 1.3179735416202203im
 -1.2688118432157607 - 0.05402836822868018im
 -1.5771407887055269 - 2.124727170787205im

Python:

In [ ]:
np = pyimport("numpy")
py_y = PyObject(y)
meanval = np.mean(abs.(y))
Out[0]:
2.972701599598033

Вызов MATLAB и Python в одном Julia-скрипте

In [ ]:
x = 1:10
@mput x

mat"""
y = x.^2;
"""

@mget y

np = pyimport("numpy")
avg = np.mean(y)

println(avg)
38.5

Здесь данные проходят по цепочке: Julia → MATLAB → Julia → Python → Julia, что является одним из типичных сценариев научных вычислений и анализа данных.

Заключение

Мы убедились, что вычислительная среда Engee способна органично интегрировать три языковые вселенные в едином вычислительном процессе, что позволяет объединить инженерные сообщества, использующие разные языки программирования, предоставляя готовую платформу, где MATLAB-совместимые расчёты, скрипты на Python и высокопроизводительный код на Julia работают совместно без необходимости настраивать межъязыковые мосты вручную.