# Information

## Dynamic Functions

Oct2Py will create methods for you on the fly, which correspond to
Octave functions. For example:

```pycon
>>> from oct2py import octave
>>> octave.ones(3)
array([[1.,  1.,  1.],
   [1.,  1.,  1.],
   [1.,  1.,  1.]])
```

If you pass keyword arguments to the function, they will be treated as
Octave keywords, for example, `octave.plot(x, y, linewidth=3)` becomes
`plot(x, y, 'linewidth', 3)`. Arguments that are integer type will be
converted to floats unless you set `convert_to_float=False`.

Additionally, you can look up the documentation for one of these methods
using `help()`

```pycon
>>> from oct2py import octave
>>> help(octave.ones)  # doctest: +SKIP
'ones' is a built-in function
...
```

## Interactivity

Oct2Py supports code completion in IPython, so once you have created a
method, you can recall it on the fly, so `octave.one<TAB>` would give
you `ones`. Structs (mentioned below) also support code completion for
attributes.

You can share data with an Octave session explicitly using the `push`
and `pull` methods. When using other Oct2Py methods, the variable names
in Octave start with underscores because they are temporary (you would
only see this if you were using logging).

```pycon
>>> from oct2py import octave
>>> octave.push("a", 1)
>>> octave.pull("a")
1.0
```

## Workspace Access

The `workspace` attribute provides a dict-like interface to the Octave base
workspace, mirroring the `eng.workspace` API in MATLAB's Python engine:

```pycon
>>> from oct2py import octave
>>> octave.eval("x = 5", nout=0)
>>> octave.workspace["x"]
5.0
>>> octave.workspace["y"] = [1, 2, 3]
>>> octave.pull("y")
array([[1., 2., 3.]])
>>> del octave.workspace["y"]
```

This is equivalent to using `push` and `pull` directly, but can be more
natural when porting code from MATLAB's Python engine.

## Expression Pointers

Some Octave values — such as cell arrays of function handles — cannot be
converted to Python objects. Use `get_pointer` with `expr=True` to hold a
reference to such an expression and pass it directly to Octave functions
without a round-trip through Python:

```pycon
>>> from oct2py import octave
>>> ptr = octave.get_pointer('{@cos @sin}', expr=True)
>>> type(ptr).__name__
'OctaveVariablePtr'
>>> # Pass the cell of function handles to an Octave function unchanged
>>> octave.feval('cellfun', '@(f) f(0)', ptr)  # doctest: +SKIP
array([1., 0.])
```

`get_pointer(expr_str, expr=True)` assigns the expression to a uniquely
named temporary variable in the Octave workspace and returns a pointer to
it. The pointer's `address` is the internal variable name; the temporary
variable persists for the life of the session.

This is different from `get_pointer(name)` (without `expr=True`), which
looks up an *existing* named variable or function.

## Using M-Files

In order to use an m-file in Oct2Py you must first call `addpath` for
the directory containing the script. You can then use it as a dynamic
function or use the `eval` function to call it. Alternatively, you can
call `feval` with the full path.

```pycon
>>> from oct2py import octave
>>> octave.addpath("/path/to/")  # doctest: +SKIP
>>> octave.myscript(1, 2)  # doctest: +SKIP
>>> # or
>>> octave.eval("myscript(1, 2)")  # doctest: +SKIP
>>> # as feval
>>> octave.feval("/path/to/myscript", 1, 2)  # doctest: +SKIP
```

## Running Scripts and Accessing Their Variables

Octave scripts (as opposed to functions) assign variables directly into
the caller's workspace. When you call a script through the dynamic
dispatch mechanism (e.g. `octave.myscript()`), oct2py evaluates it
inside a temporary function scope, so any variables the script creates
are discarded when it returns and are not accessible via `pull`.

Use `Oct2Py.run()` instead. It executes the script in Octave's **base
workspace**, so variables it assigns persist and can be retrieved with
`pull`:

```pycon
>>> from oct2py import Oct2Py
>>> oc = Oct2Py()
>>> # myscript.m contains: result = [1, 2, 3];
>>> oc.run("/path/to/myscript.m")  # doctest: +SKIP
>>> oc.pull("result")              # doctest: +SKIP
array([1., 2., 3.])
```

`run` accepts the same optional keyword arguments as `eval` (`verbose`,
`timeout`, `stream_handler`, etc.).

> **Note:** This limitation does not apply to Octave *functions* — a
> function's return values are passed back to Python normally via
> `feval`. If you need to share data between Python and Octave in a
> flexible way, prefer writing a function that accepts arguments and
> returns results rather than relying on side-effects in the base
> workspace.

## Suppressing Output Capture

By default, `eval` and `feval` capture and return the Octave `ans` value.
Passing `quiet=True` executes the command but discards the result entirely,
returning `None`. This is useful in three situations:

- **Inspecting struct fields in Jupyter** — without `quiet=True`, the value is
  both printed by Octave and returned to the cell output, causing it to appear
  twice.
- **Non-serialisable types** — some Octave values (custom class objects, large
  sparse arrays, etc.) cannot be serialised to a MAT file. `quiet=True` lets
  you run the command for its side effects without triggering a serialisation
  error.
- **Fire-and-forget calls** — when you only care about the side effect (e.g.
  setting a variable, calling `disp`) and do not need the return value.

```pycon
>>> from oct2py import octave
>>> octave.push("s", {"field": [1, 2, 3]})
>>> result = octave.eval("s.field", quiet=True)  # prints once, returns None
>>> result is None
True
>>> result = octave.feval("max", [3, 1, 2], quiet=True)  # no return value
>>> result is None
True
```

## Direct Interaction

Oct2Py supports the Octave `keyboard` function which drops you into an
interactive Octave prompt in the current session. This also works in the
IPython Notebook. Note: If you use the `keyboard` command and the
session hangs, try opening an Octave session from your terminal and see
if the `keyboard` command hangs there too. You may need to update your
version of Octave.

## Logging

Oct2Py uses the standard Python `logging` module under the logger name
`"oct2py"`. Following Python library best practices, oct2py installs no
handlers and sets no log levels. This means:

- **WARNING and above** propagate to the root logger. If your application has
  not configured logging, Python's built-in `lastResort` handler will print
  them to `stderr`.
- **DEBUG and INFO** are invisible until you configure logging, since the root
  logger's default level is WARNING.

To see oct2py INFO output (e.g. commands sent to Octave), configure the
`"oct2py"` logger or the root logger in your application:

```python
import logging

# Show INFO and above from oct2py only
logging.getLogger("oct2py").setLevel(logging.INFO)
logging.getLogger("oct2py").addHandler(logging.StreamHandler())

# Or configure the root logger (affects all loggers):
logging.basicConfig(level=logging.INFO)
```

To enable DEBUG output (useful for troubleshooting), use `logging.DEBUG`:

```python
import logging
logging.basicConfig(level=logging.DEBUG)
```

When using pytest, pass `--log-cli-level=DEBUG` on the command line or add
to `pyproject.toml`:

```toml
[tool.pytest.ini_options]
log_cli = true
log_cli_level = "DEBUG"
```

You can also pass a logger directly to the `Oct2Py` constructor or replace
it at any time:

```pycon
>>> import logging
>>> from oct2py import Oct2Py, get_log
>>> oc = Oct2Py(logger=get_log())
>>> oc.logger = get_log("new_log")
```

All Oct2Py methods support a `verbose` keyword. If True, the commands
are logged at the INFO level, otherwise they are logged at the DEBUG
level.

## Warnings

Oct2Py uses `Oct2PyWarning` (a subclass of `UserWarning`) for deprecation
notices and other advisory conditions. Because it has its own type, you can
handle oct2py warnings independently of warnings from other libraries.

Catch oct2py warnings in tests with `pytest.warns`:

```python
import pytest
from oct2py import Oct2PyWarning

with pytest.warns(Oct2PyWarning):
    oc.eval("1 + 1", log=False)  # log= kwarg is deprecated
```

Silence oct2py warnings without affecting other `UserWarning`s:

```python
import warnings
from oct2py import Oct2PyWarning

warnings.filterwarnings("ignore", category=Oct2PyWarning)
```

Turn oct2py warnings into errors (useful in CI):

```python
import warnings
from oct2py import Oct2PyWarning

warnings.filterwarnings("error", category=Oct2PyWarning)
```

## Debugging

When troubleshooting an installation or unexpected behaviour, use
`oct2py.check()` to print a snapshot of your environment:

```pycon
>>> import oct2py
>>> oct2py.check()  # doctest: +SKIP
Platform:     Linux 6.8.0 (x86_64)
Python:       3.11.9 (main, Apr  2 2024, 08:55:17) [GCC 11.4.0]
Python path:  /usr/bin/python3

oct2py:       5.8.0
numpy:        2.2.6
scipy:        1.15.3
octave_kernel:0.39.0

Octave exe:   /usr/bin/octave-cli

Connecting to Octave...
Octave:       9.2.0
Graphics:     qt (available: gnuplot, qt)
Connection OK
```

The output includes:

- **Platform / Python** — OS, architecture, Python version, and executable
  path, useful for confirming which environment is active.
- **Dependency versions** — `oct2py`, `numpy`, `scipy`, and `octave_kernel`.
  Version mismatches here are a common source of serialisation errors.
- **Octave executable** — the resolved path to the Octave binary. If this
  shows `(not found)`, Octave is not on `PATH` and you need to install it or
  set the `OCTAVE_EXECUTABLE` environment variable.
- **Live connection test** — starts an Octave session and reports the Octave
  version plus the active and available graphics toolkits. A `Connection failed` line here points to a problem with the Octave installation itself
  rather than the Python side.

You can also run it directly from the command line:

```shell
python -m oct2py.check
```

Include the full output when filing a bug report.

## Shadowed Function Names

If you'd like to call an Octave function that is also an Oct2Py method,
you must add a trailing underscore. For example:

```pycon
>>> from oct2py import octave
>>> octave.eval_("a=1")
1.0
```

The methods that shadow Octave builtins are: `exit` and `eval`.

## Timeout

Oct2Py sessions have a `timeout` attribute that determines how long to
wait for a command to complete. The default is 1e6 seconds (indefinite).
You may either set the timeout for the session, or as a keyword argument
to an individual command. The session is closed in the event of a
timeout.

```pycon
>>> from oct2py import octave
>>> octave.timeout = 3
>>> octave.sleep(2)  # doctest: +SKIP
>>> octave.sleep(2, timeout=1)  # doctest: +SKIP
Traceback (most recent call last):
...
oct2py.utils.Oct2PyError: Session timed out
```

## Configuration & Settings

All session defaults — timeout, executable, plot format, backend, and
more — can be set in one place using `Oct2PySettings` and read from
environment variables automatically. See the
[Configuration & Settings](settings.md) page for the full guide,
including env var names, `.env` file support, and common recipes for
headless, sandboxed, and CI environments.

```python
from oct2py import Oct2Py, Oct2PySettings

s = Oct2PySettings(backend="disable", timeout=30, plot_format="png")
oc = Oct2Py(settings=s)
```

Use `oct2py.configure()` to reconfigure the default global instance:

```python
import oct2py
oct2py.configure(backend="disable", timeout=60)
```

## Octave Executable

By default, oct2py uses `octave` as the Octave executable. To use a
different binary, pass it directly or set the `OCTAVE_EXECUTABLE`
environment variable:

```python
oc = Oct2Py(executable="/path/to/octave")
```

```shell
export OCTAVE_EXECUTABLE=/path/to/octave
```

When using IPython or Jupyter, you can also change it at runtime via the
`OctaveMagics` config trait without restarting the kernel:

```python
%config OctaveMagics.executable = "/path/to/octave"
```

## Graphics Toolkit

In some cases, the `qt` graphics toolkit is only available when running
with a display enabled.

On a remote system without a display, you can use `xvfb-run` to provide
a virtual framebuffer. For example:

```shell
export OCTAVE_EXECUTABLE="xvfb-run octave"
```

Or in the config file:

```python
c.OctaveMagics.executable = "xvfb-run octave"
```

To inspect or change the active toolkit at runtime:

```pycon
>>> from oct2py import octave
>>> octave.available_graphics_toolkits()  # doctest: +SKIP
['qt', 'gnuplot']
>>> octave.graphics_toolkit("gnuplot")  # doctest: +SKIP
'gnuplot'
```

## PyCharm and IDE Integration

PyCharm's interactive console (and similar IDEs) displays matplotlib figures
inline but cannot capture Octave's native figure windows. As a result,
`octave.plot([1, 2, 3])` produces no visible output even though the figure
exists in Octave.

Oct2Py bridges this gap with two mechanisms:

**Automatic (`auto_show`):** When `PYCHARM_HOSTED` is set in the environment
(i.e. when running inside PyCharm), oct2py automatically renders open Octave
figures as PNG images and displays them via `matplotlib.pyplot.imshow()` after
every `eval` or `feval` call. No code changes are required — just install
matplotlib and run your plotting code as normal:

```pycon
>>> from oct2py import octave
>>> octave.plot([1, 2, 3])  # figure appears inline automatically in PyCharm
```

**Manual (`show()`):** In other environments, or to trigger display on demand,
call `show()` explicitly:

```pycon
>>> from oct2py import Oct2Py
>>> oc = Oct2Py()
>>> oc.plot([1, 2, 3])
>>> oc.show()  # renders and displays the figure via matplotlib
```

You can also enable or disable `auto_show` explicitly regardless of the
environment:

```pycon
>>> oc = Oct2Py(auto_show=True)   # always auto-display figures
>>> oc = Oct2Py(auto_show=False)  # never auto-display figures
```

`show()` requires `matplotlib` to be installed. If it is not available the
method returns silently, so it is safe to call unconditionally.

## Context Manager

Oct2Py can be used as a Context Manager. The session will be closed and
the temporary m-files will be deleted when the Context Manager exits.

```pycon
>>> from oct2py import Oct2Py
>>> with Oct2Py() as oc:  # doctest:+ELLIPSIS
...     oc.ones(10)
...
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
...
```

## Pandas

Oct2Py supports `pandas.Series` and `pandas.DataFrame` objects directly.
They are converted to their underlying NumPy array via `.values` before
being sent to Octave, so the Octave side always receives a plain numeric
array or matrix. The round-trip type is `ndarray`, not the original pandas
type.

```pycon
>>> import numpy as np
>>> import pandas as pd
>>> from oct2py import Oct2Py
>>> oc = Oct2Py()
>>> series = pd.Series([1.0, 2.0, 3.0])
>>> oc.push("s", series)
>>> oc.pull("s")
array([[1., 2., 3.]])
>>> data = np.array([[1.0, 2.0], [3.0, 4.0]])
>>> df = pd.DataFrame(data, columns=["a", "b"])
>>> oc.push("df", df)
>>> oc.pull("df")
array([[1., 2.],
       [3., 4.]])
>>> oc.exit()

```

## Structs

Struct is a convenience class that mimics an Octave structure variable
type. It is a dictionary with attribute lookup, and it creates
sub-structures on the fly of arbitrary nesting depth. It can be pickled.
You can also use tab completion for attributes when in IPython.

```pycon
>>> from oct2py import Struct
>>> test = Struct()
>>> test["foo"] = 1
>>> test.bizz["buzz"] = "bar"
>>> test
{'foo': 1, 'bizz': {'buzz': 'bar'}}
>>> import pickle
>>> p = pickle.dumps(test)
```

## Unicode

Oct2Py supports Unicode characters, so you may feel free to use m-files
that contain them.

## Speed

There is a performance penalty for passing information using MAT files.
If you have a lot of calculations, it is probably better to make an
m-file that does the looping and data aggregation, and pass that back to
Python for further processing. To see an example of the speed penalty on
your machine, run:

```pycon
>>> import oct2py
>>> oct2py.speed_check()  # doctest:+ELLIPSIS
Oct2Py speed test
...
```

### Reducing MAT-file overhead with a RAM-backed temp directory

Oct2py exchanges data with Octave by writing and reading MAT files in a
temporary directory. Keeping that directory in RAM eliminates disk I/O
entirely and can meaningfully improve throughput for workloads that make
many short calls.

**Linux** — `/dev/shm` (a kernel-managed tmpfs) is used automatically
whenever it is available and writable. No configuration is required.

**macOS — opt-in RAM disk (managed by oct2py)**

Pass `ramdisk_size_mb` to have oct2py create, use, and destroy a HFS+
RAM disk for the duration of the session:

```python
from oct2py import Oct2Py

oc = Oct2Py(ramdisk_size_mb=256)   # 256 MiB RAM disk
```

You can also set it via the environment variable `OCT2PY_RAMDISK_SIZE_MB`.
Choose a size large enough to hold the largest single argument or return
value you expect to exchange. The disk is unmounted automatically when
`oc.exit()` is called or when the Python process exits.

**macOS — existing RAM disk (user-managed)**

If you already have a RAM disk mounted (for example via a login-item
script), pass its path directly as `temp_dir`:

```bash
# Create a 256 MiB RAM disk and mount it at /Volumes/ramdisk
diskutil erasevolume HFS+ "ramdisk" $(hdiutil attach -nomount ram://524288)
```

```python
from oct2py import Oct2Py

oc = Oct2Py(temp_dir="/Volumes/ramdisk")
```

oct2py will create a subdirectory inside that path and will *not* unmount
the disk when the session exits — lifetime management is left to you.

## Threading

If you want to use threading, you *must* create a new `Oct2Py` instance
for each thread. The `octave` convenience instance is in itself *not*
threadsafe. Each `Oct2Py` instance has its own dedicated Octave session
and will not interfere with any other session.

## IPython Notebook

Oct2Py provides
[OctaveMagic](https://nbviewer.org/github/blink1073/oct2py/blob/main/example/octavemagic_extension.ipynb?create=1)
for IPython, including inline plotting in notebooks. This requires
IPython >= 1.0.0.
