motopy 2.1.0
The tool of converting Matlab/Octave code TO PYthon.
Requires Python: >=3.7

# Motopy

`Motopy` is a tool used to translate Matlab/Octave code TO PYthon.

[点此查看中文说明](https://github.com/falwat/motopy/blob/main/readme_zh.md).

## Introduce

`Motopy` is a powerful tool used to translate `Matlab`/`Octave` code to `PYthon`. In the process of translation, the python statement generated by `mopy` will be executed to ensure the translated correctness of subsequent code. For example, the following `Matlab`/`Octave` code:

```m
a = ones(1, 3);
b = a';
c = a * b;
```
will be translated to `python` code:

```py
import numpy as np
a = np.ones((1, 3))
b = a.T
c = a @ b
```

The type of the variables `a` and `b` are array. So the third statement `c = a * b` will be translate to: `c = a @ b`.

## install

Please use `pip` install `motopy`:

```bash
pip install motopy
```

## Quick Start

`Motopy` is very easy to use. First please prepare your `Matlab`/`Octave` files, put the script file and the function files with extetion ".m" in a folder, and ensure that your `Matlab`/`Octave` script can be run without exception. And meet [Code Preprocessing](#Code_Preprocessing).Here's a simple example:
- Create a folder named "demo".
- In the "demo" folder, create two ".m" files whose names are "fun.m" and "func_test.m". Input the folowing text:
	```m
	% file: func.m
	function s = func(a, b)
	    s = sqrt(a.^2 + b.^2);
	end
	```

	```m
	% file: func_test.m
	a = 3;
	b = 4;
	s = func(a, b);
	disp(s)
	```
- In the "demo" folder, create a `python` script file. Import `motopy` and use `motopy.make()` translate your mfile:
  ```py
  import motopy
  motopy.make(entry_basename='func_test')
  ```
  The `entryfile` parameter specifies the mfile script to be translated(***!!Note that there is no extension!! ***).
  >  You can also execute the above code directly from the python command line. Ensure that the current directory is the "demo" folder.

### Specify the input/output folder

The `python` script file may not be placed in the same folder as the mfile, and the input mfile and the output `python` file may be in a different folder. You can use the `input_path` parameter to specify the location of the input mfile and the `output_path` parameter to specify the output path of the generated `python` file.
```py
import motopy

motopy.make(
    entry_basename='<the script filename without extension(*.m)>',
    input_path='<the input path of *.m files>', 
    output_path='<the output path of *.py files>' 
)
```

### Specify replaced function

If you have already translated a function, you can specify an replaced function to that function using the `replaced_functions` argument in the `motopy.make()` function.

```py
import motopy
motopy.make(
    entry_basename='func_test', # no extension
    input_path='The path that input ".m" files loaded', 
    output_path='The path that output ".py" files stored',
    replaced_functions={
        'func': ('func', 'func') # 
    }
)
```

The `replaced_functions` parameter is a dictionary, the key is the function name that appears in the mfile, and the value is a tuple (`module_name`, `function_name`). In the example above, the `func` function file will not be translated again.

When do you use `replaced_functions`:

- The `.py` file generated by `motopy` has been modified manually, and do not want `motopy` to regenerate it.

- The `.m` function that `motpy` does not support translated yet. You can implement it by yourself.

### Code Annotation

The code line started with "`%%>`" in mfile is a python statment. And will be inserted to generated `python` file. And the next statment will be skiped. For example, the mfile:

```m
%%> print('this is a code annotation.')
disp('this statment will be skiped.')
```
will be translated to:
```py
print('this is a code annotation.')
```



### Output Log

By default, `motopy` generate a log file named "motopy.log" under the `output_path` folder. You can use the 'logging_file' parameter to specify the output location and name of the log file. Using `logging_level` set log level: `WARN|INFO|DEBUG`

```py
import motopy
motopy.make(.., logging_level=motopy.DEBUG, ..)
```

### Indent

By default, the generated `.py` file uses 4 Spaces for indentation. You can use `indent` parameter specifies the number of Spaces required for indentation.

## Code Preprocessing

The translation will failed if your `Matlab`/`Octave` code don't satisfy the folowing requirements:

- Do not use blank spaces to separate elements in arrays and cells. The following code will make failed:

    ```m
    a = [1 2 3; 4 5 6];
    c = {1 2 'abc'};
    ```

- The first function name in a function file must be same as the filename.

- Arrays and cells should be defined before used and allocated enough space. The following code will make failed:

    ```m
    for k=1:5
        A(k) = 2*k; % The variable A is not defined before used.
    end
    ```

    ```m
    A = []; % The variable A has not enough space.
    for k=1:5
        A(k) = 2*k; % the size of variable A will grow in iteration.
    end
    ```

- Do not use "`[]`" to define empty array. The folowing mcode will translate failed:
    ```m
    A = [];
    for k=1:5
        B= rand(2,2);
        A = [A;B];
    end
    disp(A)
    ```
    The expression `[A;B]` translate failed. because the empty array A with size of `0x0` cannot concatenate with array B with size of `2x2`.

    An easy way to resolve this problem is to define the array `A` as an empty array with size of `0x2`:

    ```m
    A = zeros(0,2);
    for k=1:5
        B= rand(2,2);
        A = [A;B];
    end
    disp(A)
    ```

## Implemented Translation

### Creation of Array, Matrix and Cell 

Matlab/Octave|Python|Note
-|-|-
`a = [1,2,3,4]` | `a = np.array([1, 2, 3, 4])` | The array in `matlab` will be translated to `np.array`
`a = [1,2;3,4]` | `a = np.array([[1, 2], [3, 4]])`
`a = [1;2;3;4]` | `a = np.array([[1], [2], [3], [4]])`
`C = {1,2,3;'4',[5,6],{7,8,9}}` | `C = [[1, 2, 3], ['4', np.array([5, 6]), [7, 8, 9]]]` | The cell in `matlab` will be translated to `list`
`r1 = 1:10;` | `r1 = arange(1, 11)` | `low_bound:high_bound` in `matlab` will be translated to `arange(low_bound, high_boud + 1)`
`N = 10;`<br>`r2 = 1:N;` | `N = 10`<br>`r2 = arange(1, N + 1)`
`zeros(3)` | `np.zeros((3, 3))`
`zeros(2,3)` | `np.zeros((2, 3))`
`ones(3)` | `np.ones((3, 3))`
`ones((2, 3))` | `np.ones((2, 3))`
`C = cell(2,3)` | `C = [[None for _c in range(3)] for _r in range(2)]`

### Slice of Array, Matrix and Cell 
Matlab/Octave|Python|Note
-|-|-
`a(1,1)` | `a[0, 0]` |  The value of `index` will decrease 1, if the `index` is a number.
`a(1,:)` | `a[0, :]`
`a(:,1)` | `a[:, 0]`
`a(1, 1:2)` | `a[0, 0:2]`
`a(1:2, 1)` | `a[0:2, 0]`
`a(1,2:end)` | `a[0, 1:]`
`m = 1;`<br>`n = 1;`<br>`a(m, n*2)` | `m = 1`<br>`n = 1`<br>`a[m - 1, n * 2 - 1]` | The `index` will be replaced with `index - 1`, if `index` is a variable.

### Functions

Matlab/Octave|Python|Note
-|-|-
`abs` | `np.abs`
`acos` | `np.arccos`
`asin` | `np.arcsin`
`atan` | `np.arctan`
`[y,Fs] = audioread(filename)` | `Fs, y = wavfile.read(filename)`
`ceil` | `np.ceil`
`cos` | `np.cos`
`diag` | `np.diag`
`d = dir(name)` | `d = [{'name':e.name, 'folder':e.path, 'isdir':e.is_dir()} for e in scandir(name)]`
`disp` | `print`
`eye` | `np.eye`
`exp` | `np.exp`
`fft` | `np.fft`
`fix` | `np.fix`
`floor` | `np.floor`
`fprintf` | 
`ifft` | `np.ifft`
`inv` | `linalg.inv`
`linspace` | `np.linspace`
`S = load('data.mat')`| `S = loadmat('data.mat')` | the Variable `S` is a dict 
`A = load('data.txt')` | `A = np.loadtxt('data.txt')` | the file "data.txt" is a ASCII data.
`load('data.mat')` | `_mat = loadmat('data.mat');`<br>`a = _mat['a'];`<br>`b = _mat['b']` | assume there are two variable `a` and `b` in "data.mat"
`load('data.txt')` | `data = np.loadtxt('data.txt')` | the file "data.txt" is a ASCII data.
`log` | `np.log`
`log10` | `np.log10`
`log2` | `np.log2`
`mod` | `np.mod`
`ndims` | `np.ndim`
`numel` | `np.size`
`pinv` | `linalg.pinv`
`rand` | `random.rand`
`rank` | `linalg.matrix_rank`
`round` | `np.round`
`sin` | `np.sin`
`sort` | `np.sort`
`sprintf('%d%s',a, b)` | `f'{a}{b}'`
`sqrt` | `np.sqrt`
`s = strcat(s1,...,sN)` | `s = ''.join([s1,...,sN])`
`unique` | `np.unique`

## Change Log

See [changelog.md](https://github.com/falwat/motopy/blob/main/changelog.md) for more information.