├── .coveragerc ├── .git-blame-ignore-revs ├── .github ├── dependabot.yml └── workflows │ ├── enforce-label.yml │ ├── prep-release.yml │ ├── publish-changelog.yml │ ├── publish-release.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGELOG.md ├── CITATION.cff ├── CONTRIBUTING.rst ├── CONTRIBUTORS.rst ├── LICENSE.txt ├── README.rst ├── RELEASE.md ├── codecov.yml ├── docs ├── Makefile ├── _templates │ └── layout.html ├── make.bat └── source │ ├── api.rst │ ├── conf.py │ ├── conversions.rst │ ├── demo.rst │ ├── examples.rst │ ├── index.rst │ ├── info.rst │ ├── installation.rst │ └── static │ ├── plot.png │ └── surf.png ├── example ├── octavemagic_extension.ipynb ├── roundtrip.m ├── roundtrip.py ├── test_datatypes.m ├── test_datatypes.py └── test_nodocstring.m ├── licenses ├── mlabwrap.txt ├── octavemagic.txt └── ompc.txt ├── oct2py ├── __init__.py ├── _pyeval.m ├── _version.py ├── core.py ├── demo.py ├── dynamic.py ├── io.py ├── ipython │ ├── __init__.py │ ├── ipython-COPYING.txt │ └── octavemagic.py ├── speed_check.py ├── thread_check.py └── utils.py ├── pyproject.toml └── tests ├── @polynomial ├── display.m ├── get.m ├── polynomial.m └── set.m ├── __init__.py ├── ipython ├── __init__.py └── test_octavemagic.py ├── pyeval_like_error0.m ├── pyeval_like_error1.m ├── pyeval_like_error2.m ├── pyeval_like_error3.m ├── roundtrip.m ├── script_error.m ├── test_conversions.py ├── test_datatypes.m ├── test_keep_matlab_shapes.py ├── test_misc.py ├── test_nodocstring.m ├── test_numpy.py ├── test_roundtrip.py └── test_usage.py /.coveragerc: -------------------------------------------------------------------------------- 1 | # Configuration for coverage.py 2 | 3 | [run] 4 | branch = True 5 | omit = 6 | oct2py/tests/* 7 | oct2py/ipython/tests/* 8 | 9 | [report] 10 | exclude_lines = 11 | def __repr__ 12 | if __name__ == .__main__.: 13 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Run AutoFormatters: https://github.com/blink1073/oct2py/pull/232 2 | 00192f2cd97f2395cfce8af69ba3671ae0cb70b1 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | actions: 9 | patterns: 10 | - "*" 11 | - package-ecosystem: "pip" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | groups: 16 | actions: 17 | patterns: 18 | - "*" 19 | -------------------------------------------------------------------------------- /.github/workflows/enforce-label.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: enforce-triage-label 11 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 12 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 1: Prep Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version_spec: 6 | description: "New Version Specifier" 7 | default: "next" 8 | required: false 9 | branch: 10 | description: "The branch to target" 11 | required: false 12 | post_version_spec: 13 | description: "Post Version Specifier" 14 | required: false 15 | silent: 16 | description: "Set a placeholder in the changelog and don't publish the release." 17 | required: false 18 | type: boolean 19 | since: 20 | description: "Use PRs with activity since this date or git reference" 21 | required: false 22 | since_last_stable: 23 | description: "Use PRs with activity since the last stable git tag" 24 | required: false 25 | type: boolean 26 | jobs: 27 | prep_release: 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: write 31 | steps: 32 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 33 | 34 | - name: Prep Release 35 | id: prep-release 36 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | version_spec: ${{ github.event.inputs.version_spec }} 40 | silent: ${{ github.event.inputs.silent }} 41 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 42 | target: ${{ github.event.inputs.target }} 43 | branch: ${{ github.event.inputs.branch }} 44 | since: ${{ github.event.inputs.since }} 45 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 46 | 47 | - name: "** Next Step **" 48 | run: | 49 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 50 | -------------------------------------------------------------------------------- /.github/workflows/publish-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Publish Changelog" 2 | on: 3 | release: 4 | types: [published] 5 | 6 | workflow_dispatch: 7 | inputs: 8 | branch: 9 | description: "The branch to target" 10 | required: false 11 | 12 | jobs: 13 | publish_changelog: 14 | runs-on: ubuntu-latest 15 | environment: release 16 | steps: 17 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 18 | 19 | - uses: actions/create-github-app-token@v1 20 | id: app-token 21 | with: 22 | app-id: ${{ vars.APP_ID }} 23 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 24 | 25 | - name: Publish changelog 26 | id: publish-changelog 27 | uses: jupyter-server/jupyter_releaser/.github/actions/publish-changelog@v2 28 | with: 29 | token: ${{ steps.app-token.outputs.token }} 30 | branch: ${{ github.event.inputs.branch }} 31 | 32 | - name: "** Next Step **" 33 | run: | 34 | echo "Merge the changelog update PR: ${{ steps.publish-changelog.outputs.pr_url }}" 35 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 2: Publish Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | branch: 6 | description: "The target branch" 7 | required: false 8 | release_url: 9 | description: "The URL of the draft GitHub release" 10 | required: false 11 | steps_to_skip: 12 | description: "Comma separated list of steps to skip" 13 | required: false 14 | 15 | jobs: 16 | publish_release: 17 | runs-on: ubuntu-latest 18 | environment: release 19 | permissions: 20 | id-token: write 21 | steps: 22 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 23 | 24 | - uses: actions/create-github-app-token@v1 25 | id: app-token 26 | with: 27 | app-id: ${{ vars.APP_ID }} 28 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 29 | 30 | - name: Populate Release 31 | id: populate-release 32 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 33 | with: 34 | token: ${{ steps.app-token.outputs.token }} 35 | branch: ${{ github.event.inputs.branch }} 36 | release_url: ${{ github.event.inputs.release_url }} 37 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 38 | 39 | - name: Finalize Release 40 | id: finalize-release 41 | uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 42 | with: 43 | token: ${{ steps.app-token.outputs.token }} 44 | release_url: ${{ steps.populate-release.outputs.release_url }} 45 | 46 | - name: "** Next Step **" 47 | if: ${{ success() }} 48 | run: | 49 | echo "Verify the final release" 50 | echo ${{ steps.finalize-release.outputs.release_url }} 51 | 52 | - name: "** Failure Message **" 53 | if: ${{ failure() }} 54 | run: | 55 | echo "Failed to Publish the Draft Release Url:" 56 | echo ${{ steps.populate-release.outputs.release_url }} 57 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: oct2py 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | workflow_dispatch: 8 | schedule: 9 | # Run weekly 10 | # * is a special character in YAML so you have to quote this string 11 | - cron: "0 0 * * 0" 12 | 13 | concurrency: 14 | group: tests-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] 23 | fail-fast: false 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 28 | - name: Install Octave 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get install -qq octave octave-signal liboctave-dev 32 | - name: Generate coverage report 33 | run: | 34 | pip install -q hatch 35 | hatch run cover:test 36 | 37 | check_release: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 42 | - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 43 | with: 44 | token: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | docs: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 51 | - run: hatch run docs:build 52 | 53 | test_lint: 54 | name: Test Lint 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 59 | - name: Run Linters 60 | run: | 61 | hatch run typing:test 62 | hatch run lint:build 63 | pipx run interrogate -v . 64 | pipx run doc8 --max-line-length=200 docs/source *.md 65 | 66 | check_links: 67 | runs-on: ubuntu-latest 68 | timeout-minutes: 10 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 72 | - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 73 | 74 | tests_check: # This job does nothing and is only used for the branch protection 75 | if: always() 76 | needs: 77 | - build 78 | - test_lint 79 | - docs 80 | - check_links 81 | - check_release 82 | runs-on: ubuntu-latest 83 | steps: 84 | - name: Decide whether the needed jobs succeeded or failed 85 | uses: re-actors/alls-green@release/v1 86 | with: 87 | jobs: ${{ toJSON(needs) }} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .coverage 3 | MANIFEST 4 | .spyderproject 5 | .coveralls.yml 6 | oct2py.egg-info/* 7 | build/* 8 | dist/* 9 | docs/_build/* 10 | .cache/* 11 | htmlcov/* 12 | example/.ipynb_checkpoints/ 13 | .ipynb_checkpoints/ 14 | .DS_Store 15 | .pytest_cache 16 | coverage.xml 17 | .vscode/ 18 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_schedule: monthly 3 | autoupdate_commit_msg: "chore: update pre-commit hooks" 4 | skip: [mypy] 5 | 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v4.5.0 9 | hooks: 10 | - id: check-case-conflict 11 | - id: check-ast 12 | - id: check-docstring-first 13 | - id: check-executables-have-shebangs 14 | - id: check-added-large-files 15 | - id: check-case-conflict 16 | - id: check-merge-conflict 17 | - id: check-json 18 | - id: check-toml 19 | - id: check-yaml 20 | - id: debug-statements 21 | exclude: ipykernel/kernelapp.py 22 | - id: end-of-file-fixer 23 | - id: trailing-whitespace 24 | 25 | - repo: https://github.com/python-jsonschema/check-jsonschema 26 | rev: 0.27.4 27 | hooks: 28 | - id: check-github-workflows 29 | 30 | - repo: https://github.com/executablebooks/mdformat 31 | rev: 0.7.17 32 | hooks: 33 | - id: mdformat 34 | 35 | - repo: https://github.com/pre-commit/mirrors-prettier 36 | rev: "v4.0.0-alpha.8" 37 | hooks: 38 | - id: prettier 39 | types_or: [yaml, html, json] 40 | 41 | - repo: https://github.com/adamchainz/blacken-docs 42 | rev: "1.16.0" 43 | hooks: 44 | - id: blacken-docs 45 | additional_dependencies: [black==23.7.0] 46 | 47 | - repo: https://github.com/codespell-project/codespell 48 | rev: "v2.2.6" 49 | hooks: 50 | - id: codespell 51 | args: ["-L", "ans,te,manuel"] 52 | 53 | - repo: https://github.com/pre-commit/mirrors-mypy 54 | rev: "v1.8.0" 55 | hooks: 56 | - id: mypy 57 | files: "^oct2py" 58 | stages: [manual] 59 | args: ["--install-types", "--non-interactive"] 60 | additional_dependencies: 61 | ["ipython", "metakernel", "octave_kernel", "scipy"] 62 | 63 | - repo: https://github.com/pre-commit/pygrep-hooks 64 | rev: "v1.10.0" 65 | hooks: 66 | - id: rst-backticks 67 | - id: rst-directive-colons 68 | - id: rst-inline-touching-normal 69 | 70 | - repo: https://github.com/astral-sh/ruff-pre-commit 71 | rev: v0.2.0 72 | hooks: 73 | - id: ruff 74 | types_or: [python, jupyter] 75 | args: ["--fix", "--show-fixes"] 76 | - id: ruff-format 77 | types_or: [python, jupyter] 78 | 79 | - repo: https://github.com/scientific-python/cookie 80 | rev: "2024.01.24" 81 | hooks: 82 | - id: sp-repo-review 83 | additional_dependencies: ["repo-review[cli]"] 84 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | python: 3 | install: 4 | # install itself with pip install . 5 | - method: pip 6 | path: . 7 | extra_requirements: 8 | - docs 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.9" 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | 4 | 5 | ## 5.8.0 6 | 7 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.7.2...e4b82b9c77e7730b56f2f0a0af0f695da7707635)) 8 | 9 | ### Enhancements made 10 | 11 | - add keep_matlab_shapes to oct2py [#334](https://github.com/blink1073/oct2py/pull/334) ([@tylerbjackson](https://github.com/tylerbjackson)) 12 | 13 | ### Maintenance and upkeep improvements 14 | 15 | - add citation [#330](https://github.com/blink1073/oct2py/pull/330) ([@yasirroni](https://github.com/yasirroni)) 16 | 17 | ### Contributors to this release 18 | 19 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2024-08-19&to=2024-12-11&type=c)) 20 | 21 | [@tylerbjackson](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Atylerbjackson+updated%3A2024-08-19..2024-12-11&type=Issues) | [@yasirroni](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ayasirroni+updated%3A2024-08-19..2024-12-11&type=Issues) 22 | 23 | 24 | 25 | ## 5.7.2 26 | 27 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.7.1...7b9400f72a7cf61a31ce0296ca73bec6a523996d)) 28 | 29 | ### Bugs fixed 30 | 31 | - add id warning on save_safe_struct [#327](https://github.com/blink1073/oct2py/pull/327) ([@yasirroni](https://github.com/yasirroni)) 32 | 33 | ### Maintenance and upkeep improvements 34 | 35 | - Fix CI failures [#329](https://github.com/blink1073/oct2py/pull/329) ([@blink1073](https://github.com/blink1073)) 36 | 37 | ### Contributors to this release 38 | 39 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2024-08-12&to=2024-08-19&type=c)) 40 | 41 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2024-08-12..2024-08-19&type=Issues) | [@yasirroni](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ayasirroni+updated%3A2024-08-12..2024-08-19&type=Issues) 42 | 43 | ## 5.7.1 44 | 45 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.7.0)) 46 | 47 | ### Contributors to this release 48 | 49 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2024-06-11&to=2024-08-12&type=c)) 50 | 51 | ## 5.7.0 52 | 53 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.6.1...e27d7cc8f58034874a1222d5792afb63706efbdb)) 54 | 55 | ### Enhancements made 56 | 57 | - Skip unsavable [#321](https://github.com/blink1073/oct2py/pull/321) ([@yasirroni](https://github.com/yasirroni)) 58 | 59 | ### Contributors to this release 60 | 61 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2024-03-12&to=2024-06-11&type=c)) 62 | 63 | [@yasirroni](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ayasirroni+updated%3A2024-03-12..2024-06-11&type=Issues) 64 | 65 | ## 5.6.1 66 | 67 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.6.0...e1573c7f699f5dee9c0bbd3bbf0a8c94cd8f47d3)) 68 | 69 | ### Maintenance and upkeep improvements 70 | 71 | - Add Release Scripts [#318](https://github.com/blink1073/oct2py/pull/318) ([@blink1073](https://github.com/blink1073)) 72 | - chore: update pre-commit hooks [#313](https://github.com/blink1073/oct2py/pull/313) ([@pre-commit-ci](https://github.com/pre-commit-ci)) 73 | - Handle pandas deprecation [#312](https://github.com/blink1073/oct2py/pull/312) ([@blink1073](https://github.com/blink1073)) 74 | - chore: update pre-commit hooks [#311](https://github.com/blink1073/oct2py/pull/311) ([@pre-commit-ci](https://github.com/pre-commit-ci)) 75 | - chore: update pre-commit hooks [#309](https://github.com/blink1073/oct2py/pull/309) ([@pre-commit-ci](https://github.com/pre-commit-ci)) 76 | - Update ruff config [#307](https://github.com/blink1073/oct2py/pull/307) ([@blink1073](https://github.com/blink1073)) 77 | - chore: update pre-commit hooks [#306](https://github.com/blink1073/oct2py/pull/306) ([@pre-commit-ci](https://github.com/pre-commit-ci)) 78 | - Clean up lint handling and mypy config [#305](https://github.com/blink1073/oct2py/pull/305) ([@blink1073](https://github.com/blink1073)) 79 | - Adopt ruff format [#304](https://github.com/blink1073/oct2py/pull/304) ([@blink1073](https://github.com/blink1073)) 80 | - Update typings for mypy 1.6.0 [#302](https://github.com/blink1073/oct2py/pull/302) ([@blink1073](https://github.com/blink1073)) 81 | - chore: update pre-commit hooks [#301](https://github.com/blink1073/oct2py/pull/301) ([@pre-commit-ci](https://github.com/pre-commit-ci)) 82 | - Adopt sp-repo-review [#299](https://github.com/blink1073/oct2py/pull/299) ([@blink1073](https://github.com/blink1073)) 83 | - Bump actions/checkout from 3 to 4 [#297](https://github.com/blink1073/oct2py/pull/297) ([@dependabot](https://github.com/dependabot)) 84 | - Fix numpy warnings [#293](https://github.com/blink1073/oct2py/pull/293) ([@blink1073](https://github.com/blink1073)) 85 | - Fix typings [#288](https://github.com/blink1073/oct2py/pull/288) ([@blink1073](https://github.com/blink1073)) 86 | - Add more lint checks [#282](https://github.com/blink1073/oct2py/pull/282) ([@blink1073](https://github.com/blink1073)) 87 | - Add spelling and docstring enforcement [#280](https://github.com/blink1073/oct2py/pull/280) ([@blink1073](https://github.com/blink1073)) 88 | - Adopt ruff and typing [#279](https://github.com/blink1073/oct2py/pull/279) ([@blink1073](https://github.com/blink1073)) 89 | 90 | ### Contributors to this release 91 | 92 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-12-06&to=2024-03-12&type=c)) 93 | 94 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2022-12-06..2024-03-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Adependabot+updated%3A2022-12-06..2024-03-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Apre-commit-ci+updated%3A2022-12-06..2024-03-12&type=Issues) 95 | 96 | ## 5.6.0 97 | 98 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.5.1...6debc3e927329d4b540258f83b7a5d49225eb43a)) 99 | 100 | ### Maintenance and upkeep improvements 101 | 102 | - Bump actions/checkout from 2 to 3 [#273](https://github.com/blink1073/oct2py/pull/273) ([@dependabot](https://github.com/dependabot)) 103 | - Bump codecov/codecov-action from 1 to 3 [#272](https://github.com/blink1073/oct2py/pull/272) ([@dependabot](https://github.com/dependabot)) 104 | - Add dependabot [#271](https://github.com/blink1073/oct2py/pull/271) ([@blink1073](https://github.com/blink1073)) 105 | - Bump to Python 3.8+ [#270](https://github.com/blink1073/oct2py/pull/270) ([@blink1073](https://github.com/blink1073)) 106 | - Maintenance cleanup [#267](https://github.com/blink1073/oct2py/pull/267) ([@blink1073](https://github.com/blink1073)) 107 | - Clean up precommit [#265](https://github.com/blink1073/oct2py/pull/265) ([@blink1073](https://github.com/blink1073)) 108 | - Maintenance cleanup [#263](https://github.com/blink1073/oct2py/pull/263) ([@blink1073](https://github.com/blink1073)) 109 | - Clean up pyproject and ci [#261](https://github.com/blink1073/oct2py/pull/261) ([@blink1073](https://github.com/blink1073)) 110 | - Clean up handling of flake8 [#258](https://github.com/blink1073/oct2py/pull/258) ([@blink1073](https://github.com/blink1073)) 111 | - Switch to flit backend [#234](https://github.com/blink1073/oct2py/pull/234) ([@blink1073](https://github.com/blink1073)) 112 | - Add flake8 and remove py2 compat [#233](https://github.com/blink1073/oct2py/pull/233) ([@blink1073](https://github.com/blink1073)) 113 | 114 | ### Contributors to this release 115 | 116 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-03-30&to=2022-12-06&type=c)) 117 | 118 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2022-03-30..2022-12-06&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Adependabot+updated%3A2022-03-30..2022-12-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Apre-commit-ci+updated%3A2022-03-30..2022-12-06&type=Issues) 119 | 120 | ## 5.5.1 121 | 122 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.5.0...d1a6e0e27bcb999668f4bb2da44358e36688fd04)) 123 | 124 | ### Bugs fixed 125 | 126 | - Properly Close Octave Sessions [#230](https://github.com/blink1073/oct2py/pull/230) ([@yasirroni](https://github.com/yasirroni)) 127 | 128 | ### Contributors to this release 129 | 130 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-03-30&to=2022-03-30&type=c)) 131 | 132 | [@yasirroni](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ayasirroni+updated%3A2022-03-30..2022-03-30&type=Issues) 133 | 134 | ## 5.5.0 135 | 136 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.4.3...bf2d0ea55b24005781d6d6a15e7b956e37f8456d)) 137 | 138 | ### Bugs fixed 139 | 140 | - Fix column vector [#225](https://github.com/blink1073/oct2py/pull/225) ([@yasirroni](https://github.com/yasirroni)) 141 | 142 | ### Maintenance and upkeep improvements 143 | 144 | - Handle warnings [#226](https://github.com/blink1073/oct2py/pull/226) ([@blink1073](https://github.com/blink1073)) 145 | - Test with warning and fix resourcewarning [#224](https://github.com/blink1073/oct2py/pull/224) ([@blink1073](https://github.com/blink1073)) 146 | 147 | ### Contributors to this release 148 | 149 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-01-24&to=2022-03-30&type=c)) 150 | 151 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2022-01-24..2022-03-30&type=Issues) | [@yasirroni](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ayasirroni+updated%3A2022-01-24..2022-03-30&type=Issues) 152 | 153 | ## 5.4.3 154 | 155 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.4.2...793fb0f3ad49f25e70ed7d6ab95d7e1f29941403)) 156 | 157 | ### Bugs fixed 158 | 159 | - Clean up plot dir handling [#216](https://github.com/blink1073/oct2py/pull/216) ([@blink1073](https://github.com/blink1073)) 160 | 161 | ### Maintenance and upkeep improvements 162 | 163 | - Clean up CI [#213](https://github.com/blink1073/oct2py/pull/213) ([@blink1073](https://github.com/blink1073)) 164 | 165 | ### Contributors to this release 166 | 167 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-01-10&to=2022-01-24&type=c)) 168 | 169 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2022-01-10..2022-01-24&type=Issues) 170 | 171 | ## 5.4.2 172 | 173 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.4.1...09d3ee077bd62c2d0d279b311a8f74bedf97429d)) 174 | 175 | ### Enhancements made 176 | 177 | - Add \_print_doc convenience function [#209](https://github.com/blink1073/oct2py/pull/209) ([@yasirroni](https://github.com/yasirroni)) 178 | 179 | ### Bugs fixed 180 | 181 | - Clean up handling of nout in feval [#208](https://github.com/blink1073/oct2py/pull/208) ([@yasirroni](https://github.com/yasirroni)) 182 | 183 | ### Contributors to this release 184 | 185 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-01-04&to=2022-01-10&type=c)) 186 | 187 | [@yasirroni](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ayasirroni+updated%3A2022-01-04..2022-01-10&type=Issues) 188 | 189 | ## 5.4.1 190 | 191 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.4.0...9246b150c0682e1a89abbd99f8e811294a901877)) 192 | 193 | ### Bugs fixed 194 | 195 | - Fix setup fields [#205](https://github.com/blink1073/oct2py/pull/205) ([@blink1073](https://github.com/blink1073)) 196 | 197 | ### Contributors to this release 198 | 199 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2022-01-04&to=2022-01-04&type=c)) 200 | 201 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2022-01-04..2022-01-04&type=Issues) 202 | 203 | ## 5.4.0 204 | 205 | ([Full Changelog](https://github.com/blink1073/oct2py/compare/v5.3.0...e71a6fa46f17ab80f8e52be75d86d90317f14ee4)) 206 | 207 | ### Enhancements made 208 | 209 | - Update for latest octave_kernel [#202](https://github.com/blink1073/oct2py/pull/202) ([@blink1073](https://github.com/blink1073)) 210 | 211 | ### Contributors to this release 212 | 213 | ([GitHub contributors page for this release](https://github.com/blink1073/oct2py/graphs/contributors?from=2021-11-15&to=2022-01-04&type=c)) 214 | 215 | [@blink1073](https://github.com/search?q=repo%3Ablink1073%2Foct2py+involves%3Ablink1073+updated%3A2021-11-15..2022-01-04&type=Issues) 216 | 217 | ## 5.3.0 (2021-11-15) 218 | 219 | - Update README.rst by @pooyaEst in 220 | \[#181\[(https://github.com/blink1073/oct2py/pull/181) 221 | - Added the option to specify temp_dir by @adityaapte in 222 | [#192](https://github.com/blink1073/oct2py/pull/192) 223 | - Update CI by @blink1073 in 224 | [#193](https://github.com/blink1073/oct2py/pull/193) 225 | - Update docs to fix build by @blink1073 in 226 | [#194](https://github.com/blink1073/oct2py/pull/194) 227 | 228 | ## 5.2.0 (2020-08-05) 229 | 230 | - Add \_get_max_nout and alternative nout mode using 'max_nout' 231 | (#170) 232 | 233 | ## 5.1.0 (2020-04-25) 234 | 235 | - FIX: no-OUT-args check was destroying original error location and 236 | was hiding similar user-errors (#161) 237 | 238 | ## 5.0.0 (2019-05-03) 239 | 240 | \- Removed the `executable` argument to `Oct2Py` in favor of using 241 | environment variables because we need to instantiate the `octave` 242 | convenience instance at startup. 243 | 244 | ## 4.3.0 (2019-05-02) 245 | 246 | - Add a plot_backend kwarg for eval and clean up default backend 247 | selection. 248 | 249 | ## 4.2.0 (2019-04-28) 250 | 251 | - Add support for Pandas DataFrames and Series (#125) 252 | - Remove unwanted octave-workspace files at exit (#133) 253 | 254 | ## 4.1.0 (2019-04-23) 255 | 256 | - Add a backend property to specify the graphics toolkit 257 | 258 | ## 4.0.5 (2017-04-08) 259 | 260 | - Fixed a thread safety bug when writing matlab files. 261 | 262 | ## 4.0.0 (2017-03-07) 263 | 264 | ### Features 265 | 266 | - Added an `feval` method, which can be used to call Octave functions 267 | without creating a dynamic function. The function also supports 268 | calling a function by path. 269 | - The new `feval` method and the dynamic functions both now support a 270 | `store_as` argument, which saves the result of the call to the 271 | Octave workspace rather than returning it. 272 | - Added `get_pointer` method that can be used to retrieve a pointer to 273 | a value in the Octave namespace, including Octave functions. 274 | Pointers can be passed to `feval` or dynamic functions as function 275 | arguments. A pointer passed as a nested value will be passed by 276 | value instead. 277 | - Added an Oct2Py `Cell` ndarray subclass used for Octave cell arrays. 278 | - Added an Oct2PY `StructArray` numpy `recarray` subclass used for 279 | Octave structure arrays. 280 | - Added a `stream_handler` argument to `eval` and the new `feval` 281 | method that can be used to capture streaming output using a simple 282 | callback. 283 | 284 | ### Breaking Changes 285 | 286 | - Removed inferred `nout` for Octave function calls; it must be 287 | explicitly given if not `1`. The old behavior was too surprising and 288 | relied on internal logic of the CPython interpreter. 289 | - Any code that received Cell or Struct Array values will need to be 290 | updated. 291 | - Numpy booleans are now equivalent to Octave literals. They were 292 | previously handled as uint8 when sending to Octave. 293 | - Deprecated the use of keyword arguments to Octave function calls, 294 | use standard Octave calling conventions. 295 | - Deprecated the `log` and \[return_both\]{.title-ref} keyword arguments 296 | to `eval()`. See docs on `Oct2Py.eval()` for more information. 297 | - Oct2Py will no longer create dynamic functions for values that are 298 | not Octave functions - use \[get_pointer\]{.title-ref} or `pull` 299 | instead. 300 | 301 | ## 3.9.0 (2017-01-28) 302 | 303 | - Added support for Python 3.6 and Octave \[input()\]{.title-ref} 304 | functions. 305 | 306 | ## 3.8.0 (2016-12-25) 307 | 308 | - Added support for Octave class objects and clean up repr() and 309 | help() for dynamic Octave methods, (PR #104) 310 | 311 | ## 3.7.0 (2016-12-24) 312 | 313 | - Fixed error that caused the session to crash on Windows when Octave 314 | encountered a syntax error. 315 | - Added separate width and height specifiers to the 316 | \[%%octave\]{.title-ref} magic so the image can be constrained in one 317 | dimension while maintaining its aspect ratio. 318 | - Added an \[extract_figures\]{.title-ref} method to the 319 | \[Oct2Py\]{.title-ref} class which gives back a list of IPython Image 320 | or SVG objects for the created figures. 321 | - Completely rewrote the internal communication to Octave on top of 322 | the \[octave_kernel\]{.title-ref}, which enabled the Windows crash 323 | fix. 324 | - Removed the internal \[\_make_figs.m\]{.title-ref} file, since that 325 | functionality is now in \[octave_kernel\]{.title-ref}. 326 | 327 | ## 3.6.1 (2016-11-20) 328 | 329 | - More plot creation cleanup - fault tolerance for svg files. 330 | 331 | ## 3.6.0 (2016-11-20) 332 | 333 | - Cleanup of plot creation - separate \_make_figs.m file 334 | 335 | ## 3.5.0 (2016-01-23) 336 | 337 | - Disable --braindead Octave argument. 338 | 339 | ## 3.4.0 (2016-01-09) 340 | 341 | - Improved handling of Octave executable 342 | 343 | ## 3.3.0 (2015-07-16) 344 | 345 | - Support for Octave 4.0 346 | - Fixes for corner cases on structs 347 | 348 | ## 3.2.0 (2015-06-18) 349 | 350 | - Better handling of returned empty values 351 | - Allow OCTAVE environment variable 352 | 353 | ## 3.1.0 (2015-02-13) 354 | 355 | - Fix handling of temporary files for multiprocessing 356 | - Clean up handling of plot settings 357 | 358 | ## 3.0.0 (2015-01-10) 359 | 360 | - Add \[convert_to_float\]{.title-ref} property that is True by default. 361 | - Suppress output in dynamic function calls (using ';') 362 | 363 | ## 2.4.2 (2014-12-19) 364 | 365 | - Add support for Octave 3.8 on Windows 366 | 367 | ## 2.4.1 (2014-10-22) 368 | 369 | - Prevent zombie octave processes. 370 | 371 | ## 2.4.0 (2014-09-27) 372 | 373 | - Make \[eval\]{.title-ref} output match Octave session output. If 374 | verbose=True, print all Octave output. Return the last "ans" from 375 | Octave, if available. If you need the response, use 376 | \[return_both\]{.title-ref} to get the \[(resp, ans)\]{.title-ref} pair 377 | back 378 | - As a result of the previous, Syntax Errors in Octave code will now 379 | result in a closed session on Windows. 380 | - Fix sizing of plots when in inline mode. 381 | - Numerous corner case bug fixes. 382 | 383 | ## 2.3.0 (2014-09-14) 384 | 385 | - Allow library to install without meeting explicit dependencies 386 | - Fix handling of cell magic with inline comments. 387 | 388 | ## 2.2.0 (2014-09-14) 389 | 390 | - Fix IPython notebook support in Ubuntu 14.04 391 | - Fix toggling of inline plotting 392 | 393 | ## 2.1.0 (2014-08-23) 394 | 395 | - Allow keyword arguments in functions: \[octave.plot(\[1,2,3\], 396 | linewidth=2))\]{.title-ref} These are translated to ("prop", value) 397 | arguments to the function. 398 | - Add option to show plotting gui with \[-g\]{.title-ref} flag in 399 | OctaveMagic. 400 | - Add ability to specify the Octave executable as a keyword argument 401 | to the Oct2Py object. 402 | - Add specifications for plot saving instead of displaying plots 403 | to \[eval\]{.title-ref} and dynamic functions. 404 | 405 | ## 2.0.0 (2014-08-14) 406 | 407 | \- **Breaking changes** 408 | 409 | : -- Removed methods: \[run\]{.title-ref}, \[call\]{.title-ref}, 410 | \[lookfor\]{.title-ref} -- Renamed methods: \[\_eval\]{.title-ref} -> 411 | \[eval\]{.title-ref}, \[get\]{.title-ref} -> \[pull\]{.title-ref}, 412 | \[put\]{.title-ref} -> \[push\]{.title-ref}, \[close\]{.title-ref} -> 413 | \[exit\]{.title-ref} -- Removed run and call in favor of using eval 414 | dynamic functions. -- Renamed methods to avoid overshadowing Octave 415 | builtins and for clarity. -- When a command results in "ans", the 416 | value of "ans" is returned instead of the printed string. 417 | 418 | - Syntax Errors on Windows no longer crash the session. 419 | - Added ability to interrupt commands with CTRL+C. 420 | - Fixed Octavemagic not following current working directory. 421 | 422 | ## 1.6.0 (2014-07-26) 423 | 424 | - Added 'temp_dir' argument to Oct2Py constructor (#50) 425 | - Added 'kill_octave' convenience method to kill zombies (#46) 426 | - Improved Octave shutdown handling (#45, #46) 427 | - Added 'oned_as' argument to Oct2Py constructor (#49) 428 | 429 | ## 1.5.0 (2014-07-01) 430 | 431 | - Removed optional pexpect dependency 432 | - Brought back support for Python 2.6 433 | 434 | ## 1.4.0 (2014-06-28) 435 | 436 | - Added support for Python 3.4 and Octave 3.8 437 | - Support long_field names 438 | - Dropped support for Python 3.2 439 | 440 | ## 1.3.0 (2014-01-20) 441 | 442 | - Added support for Octave keyboard function (requires pexpect on 443 | Linux). 444 | - Improved error messages when things go wrong in the Octave session 445 | - (Linux) When pexpect is installed, Octave no longer closes session 446 | when a Syntax Error is encountered. 447 | - Fixed: M-files with no docstrings are now supported. 448 | 449 | ## 1.2.0 (2013-12-14) 450 | 451 | - OctaveMagic is now part of Oct2Py: `%load_ext oct2py.ipython` 452 | - Enhanced Struct behavior - supports REPL completion and pickling 453 | - Fixed: Oct2Py will install on Python3 when using setup.py 454 | 455 | ## 1.1.1 (2013-11-14) 456 | 457 | - Added support for wheels. 458 | - Fixed: Put docs back in the manifest. 459 | - Fixed: Oct2py will install when there is no Octave available. 460 | 461 | ## 1.1.0 (2013-10-27) 462 | 463 | - Full support for plotting with no changes to user code 464 | - Support for Nargout = 0 465 | - Overhaul of front end documentation 466 | - Improved test coverage and added badge. 467 | - Supports Python 2 and 3 from a single code base. 468 | - Fixed: Allow help(Oct2Py()) and tab completion on REPL 469 | - Fixed: Allow tab completion for Oct2Py().\ in REPL 470 | 471 | ## 1.0.0 (2013-10-4) 472 | 473 | - Support for Python 3.3 474 | - Added logging to Oct2Py class with optional logger keyword 475 | - Added context manager 476 | - Added support for unicode characters 477 | - Improved support for cell array and sparse matrices 478 | - Fixed: Changes to user .m files are now refreshed during a session 479 | - Fixed: Remove popup console window on Windows 480 | 481 | ## 0.4.0 (2013-01-05) 482 | 483 | - Singleton elements within a cell array treated as a singleton list 484 | - Added testing on 64 bit architecture 485 | - Fixed: Incorrect Octave commands give a more sensible error message 486 | 487 | ## 0.3.6 (2012-10-08) 488 | 489 | - Default Octave working directory set to same as OS working dir 490 | - Fixed: Plot rending on older Octave versions 491 | 492 | ## 0.3.4 (2012-08-17) 493 | 494 | - Improved speed for larger matrices, better handling of singleton 495 | dimensions 496 | 497 | ## 0.3.0 (2012-06-16) 498 | 499 | - Added Python 3 support 500 | - Added support for numpy object type 501 | 502 | ## 0.2.1 (2011-11-25) 503 | 504 | - Added Sphinx documentation 505 | 506 | ## 0.1.4 (2011-11-15) 507 | 508 | - Added support for pip 509 | 510 | ## 0.1.0 (2011-11-11) 511 | 512 | - Initial Release 513 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: "Oct2Py: Python to GNU Octave Bridge" 3 | message: "If you use this software, please cite it as below." 4 | authors: 5 | - family-names: "Silvester" 6 | given-names: "Steven" 7 | keywords: 8 | - "Python" 9 | - "Octave" 10 | - "Oct2Py" 11 | repository-code: "https://github.com/blink1073/oct2py" 12 | license: "MIT" 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing to Oct2Py 2 | ======================= 3 | 4 | Installation and Test 5 | --------------------- 6 | 7 | To install and run tests locally, run:: 8 | 9 | pip install -e ".[test]" 10 | pytest . 11 | 12 | Linters 13 | ------- 14 | 15 | Oct2Py uses `pre-commit `_ 16 | for managing linting of the codebase. 17 | ``pre-commit`` performs various checks on all files in Oct2Py and uses tools 18 | that help follow a consistent code style within the codebase. 19 | 20 | To set up ``pre-commit`` locally, run:: 21 | 22 | pip install pre-commit 23 | pre-commit install 24 | 25 | To run ``pre-commit`` manually, run:: 26 | 27 | pre-commit run --all-files 28 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | Oct2py is written and maintained by Steven Silvester and 2 | various contributors: 3 | 4 | Development Lead 5 | ```````````````` 6 | 7 | - Steven Silvester 8 | 9 | 10 | Patches and Suggestions 11 | ``````````````````````` 12 | 13 | - Stefan van der Walt @stefanv 14 | - Jens H Nielsen @jenshnielsen 15 | - @idella 16 | - @klonuo 17 | - Juan Luis Cano Rodríguez @Juanlu001 18 | - @bj0 19 | - Thomas Grainger @graingert 20 | - @jordigh 21 | - @wannesm 22 | - Konstantin Markov 23 | - Benedikt Sauer @filmore 24 | - Jason Moore @moorepants 25 | - @andrewbolster 26 | - @zimoun 27 | - @takluyver 28 | - @Arfrever 29 | - @cel4 30 | - Michael Hirsh @scienceopen 31 | - Andrew Clegg @andrewclegg 32 | - Min RK @minrk 33 | - @zimoun 34 | - Andrew Bolster @andrewbolster 35 | - @schlichtanders 36 | - Nicolas Mendoza 37 | - Jonathan Suever 38 | - Manuel Osdoba 39 | - Josue Ortega 40 | - Muhammad Yasirroni @yasirroni 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, oct2py developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Oct2Py: Python to GNU Octave Bridge 2 | =================================== 3 | 4 | .. image:: https://badge.fury.io/py/oct2py.png/ 5 | :target: http://badge.fury.io/py/oct2py 6 | 7 | .. image:: https://codecov.io/github/blink1073/oct2py/coverage.svg?branch=main 8 | :target: https://codecov.io/github/blink1073/oct2py?branch=main 9 | 10 | .. image:: http://pepy.tech/badge/oct2py 11 | :target: http://pepy.tech/project/oct2py 12 | :alt: PyPi Download stats 13 | 14 | Oct2Py allows you to seamlessly call M-files and Octave functions from Python. 15 | It manages the Octave session for you, sharing data behind the scenes using 16 | MAT files. Usage is as simple as: 17 | 18 | .. code-block:: pycon 19 | 20 | >>> import oct2py 21 | >>> oc = oct2py.Oct2Py() 22 | >>> x = oc.zeros(3, 3) 23 | >>> print(x, x.dtype) 24 | [[0. 0. 0.] 25 | [0. 0. 0.] 26 | [0. 0. 0.]] float64 27 | 28 | To run .m function, you need to explicitly add the path to .m file using: 29 | 30 | .. code-block:: pycon 31 | 32 | >>> from oct2py import octave 33 | >>> # to add a folder use: 34 | >>> octave.addpath("/path/to/directory") # doctest: +SKIP 35 | >>> # to add folder with all subfolder in it use: 36 | >>> octave.addpath(octave.genpath("/path/to/directory")) # doctest: +SKIP 37 | >>> # to run the .m file : 38 | >>> octave.run("fileName.m") # doctest: +SKIP 39 | 40 | To get the output of .m file after setting the path, use: 41 | 42 | .. code-block:: pycon 43 | 44 | >>> import numpy as np 45 | >>> from oct2py import octave 46 | >>> x = np.array([[1, 2], [3, 4]], dtype=float) 47 | >>> # use nout='max_nout' to automatically choose max possible nout 48 | >>> octave.addpath("./example") # doctest: +SKIP 49 | >>> out, oclass = octave.roundtrip(x, nout=2) # doctest: +SKIP 50 | >>> import pprint # doctest: +SKIP 51 | >>> pprint.pprint([x, x.dtype, out, oclass, out.dtype]) # doctest: +SKIP 52 | [array([[1., 2.], 53 | [3., 4.]]), 54 | dtype('float64'), 55 | array([[1., 2.], 56 | [3., 4.]]), 57 | 'double', 58 | dtype('Fork me on GitHub 10 | {% endif %} {% endblock %} 11 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . 7 | if NOT "%PAPER%" == "" ( 8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 9 | ) 10 | 11 | if "%1" == "" goto help 12 | 13 | if "%1" == "help" ( 14 | :help 15 | echo.Please use `make ^` where ^ is one of 16 | echo. html to make standalone HTML files 17 | echo. dirhtml to make HTML files named index.html in directories 18 | echo. pickle to make pickle files 19 | echo. json to make JSON files 20 | echo. htmlhelp to make HTML files and a HTML help project 21 | echo. qthelp to make HTML files and a qthelp project 22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 23 | echo. changes to make an overview over all changed/added/deprecated items 24 | echo. linkcheck to check all external links for integrity 25 | echo. doctest to run all doctests embedded in the documentation if enabled 26 | goto end 27 | ) 28 | 29 | if "%1" == "clean" ( 30 | for /d %%i in (_build\*) do rmdir /q /s %%i 31 | del /q /s _build\* 32 | goto end 33 | ) 34 | 35 | if "%1" == "html" ( 36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html 37 | echo. 38 | echo.Build finished. The HTML pages are in _build/html. 39 | goto end 40 | ) 41 | 42 | if "%1" == "dirhtml" ( 43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml 44 | echo. 45 | echo.Build finished. The HTML pages are in _build/dirhtml. 46 | goto end 47 | ) 48 | 49 | if "%1" == "pickle" ( 50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle 51 | echo. 52 | echo.Build finished; now you can process the pickle files. 53 | goto end 54 | ) 55 | 56 | if "%1" == "json" ( 57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json 58 | echo. 59 | echo.Build finished; now you can process the JSON files. 60 | goto end 61 | ) 62 | 63 | if "%1" == "htmlhelp" ( 64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp 65 | echo. 66 | echo.Build finished; now you can run HTML Help Workshop with the ^ 67 | .hhp project file in _build/htmlhelp. 68 | goto end 69 | ) 70 | 71 | if "%1" == "qthelp" ( 72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp 73 | echo. 74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 75 | .qhcp project file in _build/qthelp, like this: 76 | echo.^> qcollectiongenerator _build\qthelp\oct2py.qhcp 77 | echo.To view the help file: 78 | echo.^> assistant -collectionFile _build\qthelp\oct2py.ghc 79 | goto end 80 | ) 81 | 82 | if "%1" == "latex" ( 83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex 84 | echo. 85 | echo.Build finished; the LaTeX files are in _build/latex. 86 | goto end 87 | ) 88 | 89 | if "%1" == "changes" ( 90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes 91 | echo. 92 | echo.The overview file is in _build/changes. 93 | goto end 94 | ) 95 | 96 | if "%1" == "linkcheck" ( 97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck 98 | echo. 99 | echo.Link check complete; look for any errors in the above output ^ 100 | or in _build/linkcheck/output.txt. 101 | goto end 102 | ) 103 | 104 | if "%1" == "doctest" ( 105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest 106 | echo. 107 | echo.Testing of doctests in the sources finished, look at the ^ 108 | results in _build/doctest/output.txt. 109 | goto end 110 | ) 111 | 112 | :end 113 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | ******************** 2 | API Reference 3 | ******************** 4 | 5 | Oct2Py 6 | ------ 7 | .. module:: oct2py 8 | .. autoclass:: Oct2Py 9 | :members: 10 | 11 | Struct 12 | ------ 13 | .. autoclass:: Struct 14 | 15 | Cell 16 | ---- 17 | .. autoclass:: Cell 18 | 19 | StructArray 20 | ----------- 21 | .. autoclass:: StructArray 22 | 23 | Oct2PyError 24 | ----------- 25 | .. autoclass:: Oct2PyError 26 | 27 | get_log 28 | ------- 29 | .. autofunction:: get_log 30 | 31 | kill_octave 32 | ----------- 33 | .. autofunction:: kill_octave 34 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Oct2Py documentation build configuration file, created by 3 | # sphinx-quickstart on Wed Dec 26 00:11:34 2012. 4 | # 5 | # This file is execfile()d with the current directory set to its containing dir. 6 | # 7 | # Note that not all possible configuration values are present in this 8 | # autogenerated file. 9 | # 10 | # All configuration values have a default; values that are commented out 11 | # serve to show the default. 12 | 13 | import datetime 14 | import os 15 | from pathlib import Path 16 | from typing import Any, Dict 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | # needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = [ 30 | "myst_parser", 31 | "sphinx.ext.napoleon", 32 | "sphinx.ext.autodoc", 33 | "sphinx.ext.viewcode", 34 | "sphinx.ext.todo", 35 | "sphinx.ext.intersphinx", 36 | "sphinx.ext.inheritance_diagram", 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ["_templates"] 41 | 42 | # The suffix of source filenames. 43 | source_suffix = ".rst" 44 | 45 | # The encoding of source files. 46 | source_encoding = "utf-8" 47 | 48 | # General information about the project. 49 | project = "Oct2Py" 50 | copyright = f"2011 - {datetime.date.today().year}, Oct2Py contributors" # noqa 51 | 52 | version_ns: Dict[str, Any] = {} 53 | root = Path(__file__).parent.parent.parent 54 | version_py = os.path.join(root, "oct2py", "_version.py") 55 | with open(version_py) as f: 56 | exec(compile(f.read(), version_py, "exec"), version_ns) # noqa 57 | 58 | # The short X.Y version. 59 | version = ".".join(version_py.split(".")[:2]) 60 | # The full version, including alpha/beta/rc tags. 61 | release = version 62 | 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # language = None 67 | 68 | # There are two options for replacing |today|: either, you set today to some 69 | # non-false value, then it is used: 70 | # today = '' 71 | # Else, today_fmt is used as the format for a strftime call. 72 | # today_fmt = '%B %d, %Y' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | exclude_patterns = [] 77 | 78 | # The reST default role (used for this markup: `text`) to use for all documents. 79 | # default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | # add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | # add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | # show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = "sphinx" 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | # modindex_common_prefix = [] 97 | 98 | 99 | # -- Options for HTML output --------------------------------------------------- 100 | 101 | # The theme to use for HTML and HTML Help pages. See the documentation for 102 | # a list of builtin themes. 103 | html_theme = "pydata_sphinx_theme" 104 | html_theme_options = {"navigation_with_keys": False} 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | # html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | # html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | # html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | # html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ["static"] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | # html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | # html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | html_sidebars = { 137 | "**": [ 138 | "localtoc.html", 139 | ] 140 | } 141 | 142 | # Additional templates that should be rendered to pages, maps page names to 143 | # template names. 144 | # html_additional_pages = {} 145 | 146 | # If false, no module index is generated. 147 | # html_domain_indices = True 148 | 149 | # If false, no index is generated. 150 | # html_use_index = True 151 | 152 | # If true, the index is split into individual pages for each letter. 153 | # html_split_index = False 154 | 155 | # If true, links to the reST sources are added to the pages. 156 | # html_show_sourcelink = True 157 | 158 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 159 | # html_show_sphinx = True 160 | 161 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 162 | # html_show_copyright = True 163 | 164 | # If true, an OpenSearch description file will be output, and all pages will 165 | # contain a tag referring to it. The value of this option must be the 166 | # base URL from which the finished HTML is served. 167 | # html_use_opensearch = '' 168 | 169 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 170 | # html_file_suffix = None 171 | 172 | # Output file base name for HTML help builder. 173 | htmlhelp_basename = "Oct2Pydoc" 174 | 175 | # html_style = 'default.css' # Force usage of default template on RTD 176 | 177 | 178 | # -- Options for LaTeX output -------------------------------------------------- 179 | 180 | latex_elements = {} 181 | 182 | # Grouping the document tree into LaTeX files. List of tuples 183 | # (source start file, target name, title, author, documentclass [howto/manual]). 184 | latex_documents = [ 185 | ("index", "Oct2Py.tex", "Oct2Py Documentation", "Oct2Py contributors", "manual"), 186 | ] 187 | 188 | # The name of an image file (relative to this directory) to place at the top of 189 | # the title page. 190 | # latex_logo = None 191 | 192 | # For "manual" documents, if this is true, then toplevel headings are parts, 193 | # not chapters. 194 | # latex_use_parts = False 195 | 196 | # If true, show page references after internal links. 197 | # latex_show_pagerefs = False 198 | 199 | # If true, show URL addresses after external links. 200 | # latex_show_urls = False 201 | 202 | # Documents to append as an appendix to all manuals. 203 | # latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | # latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [("index", "Oct2Py", "Oct2Py Documentation", ["Oct2Py contributors"], 1)] 214 | 215 | # If true, show URL addresses after external links. 216 | # man_show_urls = False 217 | 218 | 219 | # -- Options for Texinfo output ------------------------------------------------ 220 | 221 | # Grouping the document tree into Texinfo files. List of tuples 222 | # (source start file, target name, title, author, 223 | # dir menu entry, description, category) 224 | texinfo_documents = [ 225 | ( 226 | "index", 227 | "Oct2Py", 228 | "Oct2Py Documentation", 229 | "Oct2Py contributors", 230 | "Oct2Py", 231 | "GNU Octave to Python Bridge.", 232 | "Miscellaneous", 233 | ), 234 | ] 235 | 236 | # Documents to append as an appendix to all manuals. 237 | # texinfo_appendices = [] 238 | 239 | # If false, no module index is generated. 240 | # texinfo_domain_indices = True 241 | 242 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 243 | # texinfo_show_urls = 'footnote' 244 | 245 | # -- Options for todo module --------------------------------------------------- 246 | 247 | todo_include_todos = False 248 | 249 | # -- Options for autodoc module ------------------------------------------------ 250 | 251 | autoclass_content = "both" 252 | autodoc_member_order = "bysource" 253 | autodoc_default_flags = [] 254 | 255 | # autodoc_default_flags = ['members', 'undoc-members'] 256 | 257 | 258 | # ----------------------------------------------------------------------------- 259 | # Numpy extensions 260 | # ----------------------------------------------------------------------------- 261 | numpydoc_show_class_members = False 262 | -------------------------------------------------------------------------------- /docs/source/conversions.rst: -------------------------------------------------------------------------------- 1 | *********************** 2 | Conversions 3 | *********************** 4 | 5 | Python to Octave Types 6 | ---------------------- 7 | 8 | Shows the round-trip data types, originating in Python. 9 | Lists and sets will be converted to a numeric array if possible, falling back 10 | on cells. If an Octave cell consisting of numbers is desired, use a tuple. 11 | Cell, Struct, StructArray are Oct2Py convenience classes. 12 | 13 | ============= =============== =============== 14 | Python Octave Python 15 | ============= =============== =============== 16 | int int32 np.int32 17 | long int64 np.int64 18 | float double np.float64 19 | complex double np.complex128 20 | str char unicode 21 | unicode cell unicode 22 | bool logical np.bool 23 | None nan np.nan 24 | dict struct Struct 25 | tuple cell Cell 26 | list array or cell ndarray or Cell 27 | set array or cell ndarray or Cell 28 | Struct struct Struct 29 | StructArray struct array StructArray 30 | ============= =============== =============== 31 | 32 | 33 | Numpy to Octave Types 34 | --------------------- 35 | 36 | Note that when ``convert_to_float`` is set (default is True), 37 | integer types are converted to floating point before sending them 38 | to Octave. 39 | 40 | ============= ============ ============= 41 | Numpy Octave Numpy 42 | ============= ============ ============= 43 | np.int8 int8 np.int8 44 | np.int16 int16 np.int16 45 | np.int32 int32 np.int32 46 | np.int64 int64 np.int64 47 | np.uint8 uint8 np.uint8 48 | np.uint16 uint16 np.uint16 49 | np.uint32 uint32 np.uint32 50 | np.uint64 uint64 np.uint64 51 | np.float16 double np.float64 52 | np.float32 single np.float32 53 | np.float64 double np.float64 54 | np.float128 double np.float64 55 | np.double double np.float64 56 | np.complex64 double np.complex64 57 | np.complex128 double np.complex128 58 | np.complex256 double np.complex128 59 | np.bool logical bool 60 | np.str cell list 61 | np.object cell list 62 | sparse sparse sparse 63 | recarray struct array StructArray 64 | ============= ============ ============= 65 | 66 | 67 | Octave to Python Types 68 | ---------------------- 69 | 70 | These are handled unambiguously. The only known data type that 71 | is not transferable is a function pointer, since Octave cannot 72 | save them to the v6 MAT file format. 73 | 74 | =================== ====================== 75 | Octave Python 76 | =================== ====================== 77 | array ndarray 78 | cell Cell 79 | struct Struct 80 | struct array StructArray 81 | logical ndarray (of uint8) 82 | sparse sparse 83 | user defined object Oct2Py object pointer 84 | =================== ====================== 85 | -------------------------------------------------------------------------------- /docs/source/demo.rst: -------------------------------------------------------------------------------- 1 | 2 | *********** 3 | Demo 4 | *********** 5 | 6 | Output of Oct2Py demo script, showing most of the features of the library. Note that the two 7 | plot commands will generate an interactive plot in the actual demo. 8 | To run interactively: 9 | 10 | 11 | .. code-block:: pycon 12 | 13 | >>> ######################### 14 | >>> # Oct2Py demo 15 | >>> ######################### 16 | >>> import numpy as np 17 | >>> from oct2py import Oct2Py 18 | >>> oc = Oct2Py() 19 | >>> # basic commands 20 | >>> print(oc.abs(-1)) 21 | 1.0 22 | >>> print(oc.upper("xyz")) 23 | XYZ 24 | >>> # plotting 25 | >>> oc.plot([1, 2, 3], "-o", "linewidth", 2) # doctest: +SKIP 26 | Press Enter to continue... 27 | 28 | .. image:: static/plot.png 29 | 30 | .. code-block:: pycon 31 | 32 | >>> oc.close() 33 | 1.0 34 | >>> xx = np.arange(-2 * np.pi, 2 * np.pi, 0.2) 35 | >>> oc.surf(np.subtract.outer(np.sin(xx), np.cos(xx))) # doctest: +SKIP 36 | Press Enter to continue... 37 | 38 | .. image:: static/surf.png 39 | 40 | .. code-block:: pycon 41 | 42 | >>> oc.close() 43 | 1.0 44 | >>> # getting help 45 | >>> help(oc.svd) # doctest: +SKIP 46 | 47 | Help on function svd in module oct2py.session: 48 | 49 | svd(*args, **kwargs) 50 | `svd' is a function from the file c:\Program Files\Octave-3.6.2\lib\octave\3.6.2\oct\i686-pc-mingw32\svd.oct 51 | 52 | -- Loadable Function: S = svd (A) 53 | -- Loadable Function: [U, S, V] = svd (A) 54 | -- Loadable Function: [U, S, V] = svd (A, ECON) 55 | Compute the singular value decomposition of A 56 | 57 | A = U*S*V' 58 | 59 | The function `svd' normally returns only the vector of singular 60 | values. When called with three return values, it computes U, S, 61 | and V. For example, 62 | 63 | svd (hilb (3)) 64 | 65 | returns 66 | 67 | ans = 68 | 69 | 1.4083189 70 | 0.1223271 71 | 0.0026873 72 | 73 | and 74 | 75 | [u, s, v] = svd (hilb (3)) 76 | 77 | returns 78 | 79 | u = 80 | 81 | -0.82704 0.54745 0.12766 82 | -0.45986 -0.52829 -0.71375 83 | -0.32330 -0.64901 0.68867 84 | 85 | s = 86 | 87 | 1.40832 0.00000 0.00000 88 | 0.00000 0.12233 0.00000 89 | 0.00000 0.00000 0.00269 90 | 91 | v = 92 | 93 | -0.82704 0.54745 0.12766 94 | -0.45986 -0.52829 -0.71375 95 | -0.32330 -0.64901 0.68867 96 | 97 | If given a second argument, `svd' returns an economy-sized 98 | decomposition, eliminating the unnecessary rows or columns of U or 99 | V. 100 | 101 | See also: svd_driver, svds, eig 102 | 103 | 104 | 105 | Additional help for built-in functions and operators is 106 | available in the on-line version of the manual. Use the command 107 | `doc ' to search the manual index. 108 | 109 | Help and information about Octave is also available on the WWW 110 | at http://www.octave.org and via the help@octave.org 111 | mailing list. 112 | 113 | >>> # single vs. multiple return values 114 | >>> print(oc.svd(np.array([[1, 2], [1, 3]]))) 115 | [[3.86432845] 116 | [0.25877718]] 117 | >>> U, S, V = oc.svd([[1, 2], [1, 3]], nout=3) 118 | >>> print(U, S, V) 119 | [[-0.57604844 -0.81741556] 120 | [-0.81741556 0.57604844]] [[3.86432845 0. ] 121 | [0. 0.25877718]] [[-0.36059668 -0.93272184] 122 | [-0.93272184 0.36059668]] 123 | >>> # low level constructs 124 | >>> oc.eval("y=ones(3,3)") 125 | y = 126 | 1 1 1 127 | 1 1 1 128 | 1 1 1 129 | >>> print(oc.pull("y")) 130 | [[1. 1. 1.] 131 | [1. 1. 1.] 132 | [1. 1. 1.]] 133 | >>> oc.eval("x=zeros(3,3)", verbose=True) 134 | x = 135 | 0 0 0 136 | 0 0 0 137 | 0 0 0 138 | >>> t = oc.eval("rand(1, 2)", verbose=True) # doctest: +SKIP 139 | ans = 140 | 0.2764 0.9381 141 | >>> y = np.zeros((3, 3)) 142 | >>> oc.push("y", y) 143 | >>> print(oc.pull("y")) 144 | [[0. 0. 0.] 145 | [0. 0. 0.] 146 | [0. 0. 0.]] 147 | >>> from oct2py import Struct 148 | >>> y = Struct() 149 | >>> y.b = "spam" 150 | >>> y.c.d = "eggs" 151 | >>> print(y.c["d"]) 152 | eggs 153 | >>> print(y) 154 | {'b': 'spam', 'c': {'d': 'eggs'}} 155 | >>> ######################### 156 | >>> # Demo Complete! 157 | >>> ######################### 158 | -------------------------------------------------------------------------------- /docs/source/examples.rst: -------------------------------------------------------------------------------- 1 | *********************** 2 | Examples 3 | *********************** 4 | 5 | OctaveMagic 6 | ========================== 7 | Oct2Py provides a plugin for IPython to bring Octave to the IPython prompt or the 8 | IPython Notebook_. 9 | 10 | .. _Notebook: http://nbviewer.jupyter.org/github/blink1073/oct2py/blob/main/example/octavemagic_extension.ipynb?create=1 11 | 12 | 13 | M-File Examples 14 | =============== 15 | 16 | 17 | M-files in the directory where oct2py was initialized, or those in the 18 | Octave path, can be called like any other Octave function. 19 | To explicitly add to the path, use:: 20 | 21 | >>> from oct2py import octave 22 | >>> import os 23 | >>> _ = octave.addpath(os.path.expanduser('~')) 24 | 25 | to add the directory in which your m-file is located to Octave's path. 26 | 27 | 28 | Roundtrip 29 | --------- 30 | 31 | roundtrip.m 32 | +++++++++++ 33 | 34 | :: 35 | 36 | function [x, class] = roundtrip(y) 37 | % returns the input variable and its class 38 | x = y 39 | class = class(x) 40 | 41 | 42 | Python Session 43 | ++++++++++++++ 44 | 45 | :: 46 | 47 | >>> from oct2py import octave 48 | >>> import numpy as np 49 | >>> x = np.array([[1, 2], [3, 4]], dtype=float) 50 | >>> #use nout='max_nout' to automatically choose max possible nout 51 | >>> octave.eval("""function [x, class] = roundtrip(y) 52 | ... % returns the input variable and its class 53 | ... x = y 54 | ... class = class(x)""") 55 | >>> out, oclass = octave.roundtrip(x,nout=2) 56 | x = 57 | 1 2 58 | 3 4 59 | class = double 60 | >>> import pprint 61 | >>> pprint.pprint([x, x.dtype, out, oclass, out.dtype]) 62 | [array([[1., 2.], 63 | [3., 4.]]), 64 | dtype('float64'), 65 | array([[1., 2.], 66 | [3., 4.]]), 67 | 'double', 68 | dtype(' 30; 115 | 116 | 117 | %%%%%%%%%%%%%%%% 118 | % sparse type 119 | test.sparse = speye(10); 120 | 121 | %%%%%%%%%%%%%%% 122 | % string types 123 | test.string.basic = 'spam'; 124 | test.string.cell = {'1'}; 125 | test.string.char_array = ['Thomas R. Lee'; ... 126 | 'Sr. Developer'; ... 127 | 'SFTware Corp.']; 128 | test.string.cell_array = {'spam', 'eggs'}; 129 | 130 | %%%%%%%%%%%%%%%% 131 | % User defined object 132 | test.object = polynomial([1,2,3]); 133 | 134 | %%%%%%%%%%%%%%% 135 | % struct array of shape 3x1 136 | test.struct_vector = [struct('key','a'); struct('key','b'); struct('key','c')]; 137 | 138 | %%%%%%%%%%%%%%% 139 | % struct array of shape 1x2 140 | test.struct_array(1).name = 'Sharon'; 141 | test.struct_array(1).age = 31; 142 | test.struct_array(2).name = 'Bill'; 143 | test.struct_array(2).age = 42; 144 | 145 | %%%%%%%%%%%%%%% 146 | % cell array types 147 | test.cell.vector = {'spam', 4.0, [1 2 3]}; 148 | test.cell.matrix = {'Bob', 40; 'Pam', 41}; 149 | test.cell.scalar = {1.8}; 150 | test.cell.string = {'1'}; 151 | test.cell.string_array = {'1', '2'}; 152 | test.cell.empty = cell(3,4,2); 153 | test.cell.array = {[0.4194 0.3629 -0.0000; 154 | 0.0376 0.3306 0.0000; 155 | 0 0 1.0000], 156 | [0.5645 -0.2903 0; 157 | 0.0699 0.1855 0.0000; 158 | 0.8500 0.8250 1.0000]}; 159 | 160 | %%%%%%%%%%%%%% 161 | % nest all of the above. 162 | test.nested = test; 163 | 164 | end 165 | 166 | 167 | Python Session 168 | +++++++++++++++ 169 | 170 | :: 171 | 172 | >>> from oct2py import octave, __file__ as octave_path 173 | >>> import os 174 | >>> _ = octave.addpath(os.path.join(os.path.dirname(octave_path), 'tests')) 175 | >>> out = octave.test_datatypes() 176 | >>> import pprint 177 | >>> pprint.pprint(out) # doctest:+ELLIPSIS 178 | {'cell': {'array': Cell([array([[ 0.4194, 0.3629, -0. ], 179 | ... 180 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: ../../README.rst 3 | :end-before: Installation 4 | 5 | .. toctree:: 6 | :hidden: 7 | :maxdepth: 1 8 | 9 | installation 10 | demo 11 | examples 12 | conversions 13 | api 14 | info 15 | 16 | :doc:`API Reference <./api>` 17 | ------------------------------------------------ 18 | 19 | Documentation for the functions included in Oct2Py. 20 | 21 | 22 | :doc:`Installation <./installation>` 23 | ------------------------------------------------ 24 | 25 | How to install Oct2Py. 26 | 27 | 28 | :doc:`Examples <./examples>` 29 | ------------------------------------------------ 30 | 31 | Introductory examples. 32 | 33 | 34 | :doc:`Type Conversions <./conversions>` 35 | ------------------------------------------------ 36 | 37 | Oct2Py data type conversions. 38 | 39 | 40 | :doc:`Information <./info>` 41 | ----------------------------------------- 42 | 43 | Other information about Oct2Py. 44 | -------------------------------------------------------------------------------- /docs/source/info.rst: -------------------------------------------------------------------------------- 1 | 2 | ****************** 3 | Information 4 | ****************** 5 | 6 | Dynamic Functions 7 | ================= 8 | Oct2Py will create methods for you on the fly, which correspond to Octave 9 | functions. For example: 10 | 11 | .. code-block:: pycon 12 | 13 | >>> from oct2py import octave 14 | >>> octave.ones(3) 15 | array([[1., 1., 1.], 16 | [1., 1., 1.], 17 | [1., 1., 1.]]) 18 | 19 | 20 | If you pass keyword arguments to the function, they will be treated as 21 | Octave keywords, for example, ``octave.plot(x, y, linewidth=3)`` becomes 22 | ``plot(x, y, 'linewidth', 3)``. Arguments that are integer type will be 23 | converted to floats unless you set ``convert_to_float=False``. 24 | 25 | Additionally, you can look up the documentation for one of these methods using 26 | ``help()`` 27 | 28 | .. code-block:: pycon 29 | 30 | >>> from oct2py import octave 31 | >>> help(octave.ones) # doctest: +SKIP 32 | 'ones' is a built-in function 33 | ... 34 | 35 | Interactivity 36 | ============= 37 | Oct2Py supports code completion in IPython, so once you have created a method, 38 | you can recall it on the fly, so octave.one would give you ones. 39 | Structs (mentioned below) also support code completion for attributes. 40 | 41 | You can share data with an Octave session explicitly using the ``push`` and 42 | ``pull`` methods. When using other Oct2Py methods, the variable names in Octave 43 | start with underscores because they are temporary (you would only see this if 44 | you were using logging). 45 | 46 | .. code-block:: pycon 47 | 48 | >>> from oct2py import octave 49 | >>> octave.push("a", 1) 50 | >>> octave.pull("a") 51 | 1.0 52 | 53 | 54 | Using M-Files 55 | ============= 56 | In order to use an m-file in Oct2Py you must first call ``addpath`` 57 | for the directory containing the script. You can then use it as 58 | a dynamic function or use the ``eval`` function to call it. 59 | Alternatively, you can call ``feval`` with the full path. 60 | 61 | .. code-block:: pycon 62 | 63 | >>> from oct2py import octave 64 | >>> octave.addpath("/path/to/") # doctest: +SKIP 65 | >>> octave.myscript(1, 2) # doctest: +SKIP 66 | >>> # or 67 | >>> octave.eval("myscript(1, 2)") # doctest: +SKIP 68 | >>> # as feval 69 | >>> octave.feval("/path/to/myscript", 1, 2) # doctest: +SKIP 70 | 71 | 72 | Direct Interaction 73 | ================== 74 | Oct2Py supports the Octave ``keyboard``` function 75 | which drops you into an interactive Octave prompt in the current session. 76 | This also works in the IPython Notebook. Note: If you use the ``keyboard`` 77 | command and the session hangs, try opening an Octave session from your terminal 78 | and see if the ``keyboard`` command hangs there too. You may need to update your version of Octave. 79 | 80 | 81 | Logging 82 | ======= 83 | Oct2Py supports logging of session interaction. You can provide a logger 84 | to the constructor or set one at any time. 85 | 86 | .. code-block:: pycon 87 | 88 | >>> import logging 89 | >>> from oct2py import Oct2Py, get_log 90 | >>> oc = Oct2Py(logger=get_log()) 91 | >>> oc.logger = get_log("new_log") 92 | >>> oc.logger.setLevel(logging.INFO) 93 | 94 | All Oct2Py methods support a ``verbose`` keyword. If True, the commands are 95 | logged at the INFO level, otherwise they are logged at the DEBUG level. 96 | 97 | 98 | Shadowed Function Names 99 | ======================= 100 | If you'd like to call an Octave function that is also an Oct2Py method, 101 | you must add a trailing underscore. For example: 102 | 103 | .. code-block:: pycon 104 | 105 | >>> from oct2py import octave 106 | >>> octave.eval_("a=1") 107 | 1.0 108 | 109 | The methods that shadow Octave builtins are: ``exit`` and ``eval``. 110 | 111 | 112 | Timeout 113 | ======= 114 | Oct2Py sessions have a ``timeout`` attribute that determines how long to wait 115 | for a command to complete. The default is 1e6 seconds (indefinite). 116 | You may either set the timeout for the session, or as a keyword 117 | argument to an individual command. The session is closed in the event of a 118 | timeout. 119 | 120 | 121 | .. code-block:: pycon 122 | 123 | >>> from oct2py import octave 124 | >>> octave.timeout = 3 125 | >>> octave.sleep(2) # doctest: +SKIP 126 | >>> octave.sleep(2, timeout=1) # doctest: +SKIP 127 | Traceback (most recent call last): 128 | ... 129 | oct2py.utils.Oct2PyError: Session timed out 130 | 131 | 132 | Graphics Toolkit 133 | ================ 134 | Oct2Py uses the ``qt`` graphics toolkit by default. To change toolkits: 135 | 136 | .. code-block:: pycon 137 | 138 | >>> from oct2py import octave 139 | >>> octave.available_graphics_toolkits() # doctest: +SKIP 140 | ['qt', 'gnuplot'] 141 | >>> octave.graphics_toolkit("gnuplot") # doctest: +SKIP 142 | 'gnuplot' 143 | 144 | Context Manager 145 | =============== 146 | Oct2Py can be used as a Context Manager. The session will be closed and the 147 | temporary m-files will be deleted when the Context Manager exits. 148 | 149 | .. code-block:: pycon 150 | 151 | >>> from oct2py import Oct2Py 152 | >>> with Oct2Py() as oc: # doctest:+ELLIPSIS 153 | ... oc.ones(10) 154 | ... 155 | array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], 156 | ... 157 | 158 | Structs 159 | ======= 160 | Struct is a convenience class that mimics an Octave structure variable type. 161 | It is a dictionary with attribute lookup, and it creates sub-structures on the 162 | fly of arbitrary nesting depth. It can be pickled. You can also use tab 163 | completion for attributes when in IPython. 164 | 165 | .. code-block:: pycon 166 | 167 | >>> from oct2py import Struct 168 | >>> test = Struct() 169 | >>> test["foo"] = 1 170 | >>> test.bizz["buzz"] = "bar" 171 | >>> test 172 | {'foo': 1, 'bizz': {'buzz': 'bar'}} 173 | >>> import pickle 174 | >>> p = pickle.dumps(test) 175 | 176 | 177 | Unicode 178 | ======= 179 | Oct2Py supports Unicode characters, so you may feel free to use m-files that 180 | contain them. 181 | 182 | 183 | Speed 184 | ===== 185 | There is a performance penalty for passing information using MAT files. 186 | If you have a lot of calculations, it is probably better to make an m-file 187 | that does the looping and data aggregation, and pass that back to Python 188 | for further processing. To see an example of the speed penalty on your 189 | machine, run: 190 | 191 | .. code-block:: pycon 192 | 193 | >>> import oct2py 194 | >>> oct2py.speed_check() # doctest:+ELLIPSIS 195 | Oct2Py speed test 196 | ... 197 | 198 | 199 | Threading 200 | ========= 201 | If you want to use threading, you *must* create a new ``Oct2Py`` instance for 202 | each thread. The ``octave`` convenience instance is in itself *not* threadsafe. 203 | Each ``Oct2Py`` instance has its own dedicated Octave session and will not 204 | interfere with any other session. 205 | 206 | 207 | IPython Notebook 208 | ================ 209 | Oct2Py provides OctaveMagic_ for IPython, including inline plotting in 210 | notebooks. This requires IPython >= 1.0.0. 211 | 212 | .. _OctaveMagic: http://nbviewer.jupyter.org/github/blink1073/oct2py/blob/main/example/octavemagic_extension.ipynb?create=1 213 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ************************ 3 | 4 | Library Installation 5 | -------------------- 6 | You must have GNU Octave installed and in your PATH 7 | (see instructions below). The library is only known to work with 8 | Octave 4.0+. 9 | Additionally, you must have the Numpy and Scipy libraries for Python 10 | installed. 11 | The simplest way to get them is to use the Anaconda_ distribution. 12 | 13 | Once the dependencies have been installed, run: 14 | 15 | .. code-block:: bash 16 | 17 | $ pip install oct2py 18 | 19 | If using conda, it is available on conda-forge: 20 | 21 | .. code-block:: bash 22 | 23 | $ conda install -c conda-forge oct2py 24 | 25 | 26 | GNU Octave Installation 27 | ----------------------- 28 | - On Linux platforms, try your package manager, or follow the 29 | instructions from Octave_. You must also have gnuplot installed, and 30 | gnuplot-x11 or gnuplot-qt, if available. 31 | 32 | - On OSX, the recommended methods are listed on this wiki_. 33 | 34 | - On Windows, download the latest MinGW or .NET version_. Cygwin 35 | is *NOT* supported. 36 | The MinGW version requires the 7zip_ program for installation. 37 | Make sure to install gnuplot if prompted. 38 | Finally, to add Octave to your path. You can do so from the Environmental 39 | Variables dialog for your version of Windows, or set from the command prompt:: 40 | 41 | setx PATH "%PATH%; 42 | 43 | Where the folder has the file "octave.exe". 44 | If you see the message: "WARNINGS: The data being saved is truncated to 1024 characters" 45 | It means your PATH variable is too long. You'll have to manually trim in in the Windows 46 | Environmental Variables editor. 47 | 48 | - To test, open a command window (or terminal) and type: ``octave``. 49 | If Octave starts, you should be good to go. 50 | 51 | - Alternatively, you can specify the path to your Octave executable by 52 | creating an ``OCTAVE_EXECUTABLE`` environmental variable. 53 | 54 | .. _Anaconda: https://conda.io/projects/conda/en/latest/user-guide/install/index.html 55 | .. _pip: http://www.pip-installer.org/en/latest/installing.html 56 | .. _Octave: https://octave.org/doc/interpreter/Installation.html 57 | .. _wiki: http://wiki.octave.org/Octave_for_MacOS_X 58 | .. _version: https://sourceforge.net/projects/octave/files/Octave%20Windows%20binaries/ 59 | .. _7zip: https://portableapps.com/apps/utilities/7-zip_portable 60 | -------------------------------------------------------------------------------- /docs/source/static/plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blink1073/oct2py/92acaf882c574afc9df4f6b4fbb28fe75e97b2a5/docs/source/static/plot.png -------------------------------------------------------------------------------- /docs/source/static/surf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blink1073/oct2py/92acaf882c574afc9df4f6b4fbb28fe75e97b2a5/docs/source/static/surf.png -------------------------------------------------------------------------------- /example/roundtrip.m: -------------------------------------------------------------------------------- 1 | 2 | function [x, cls] = roundtrip(y) 3 | 4 | % returns the variable it was given, and optionally the class 5 | 6 | x = y; 7 | 8 | if nargout == 2 9 | 10 | cls = class(x); 11 | 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /example/roundtrip.py: -------------------------------------------------------------------------------- 1 | """Send a numpy array roundtrip to Octave using an m-file. 2 | """ 3 | import numpy as np 4 | 5 | from oct2py import octave 6 | 7 | if __name__ == "__main__": 8 | x = np.array([[1, 2], [3, 4]], dtype=float) 9 | # use nout='max_nout' to automatically choose max possible nout 10 | out, oclass = octave.roundtrip(x, nout=2) 11 | import pprint 12 | 13 | pprint.pprint([x, x.dtype, out, oclass, out.dtype]) # noqa 14 | -------------------------------------------------------------------------------- /example/test_datatypes.m: -------------------------------------------------------------------------------- 1 | 2 | function test = test_datatypes() 3 | % Test of returning a structure with multiple 4 | % nesting and multiple return types 5 | % Add a UTF char for test: 猫 6 | 7 | %%%%%%%%%%%%%%% 8 | % numeric types 9 | % integers 10 | test.num.int.int8 = int8(-2^7); 11 | test.num.int.int16 = int16(-2^15); 12 | test.num.int.int32 = int32(-2^31); 13 | test.num.int.int64 = int64(-2^63); 14 | test.num.int.uint8 = uint8(2^8-1); 15 | test.num.int.uint16 = uint16(2^16-1); 16 | test.num.int.uint32 = uint32(2^32-1); 17 | test.num.int.uint64 = uint64(2^64-1); 18 | 19 | %floats 20 | test.num.float32 = single(pi); 21 | test.num.float64 = double(pi); 22 | test.num.complex = 3 + 1j; 23 | test.num.complex_matrix = (1.2 + 1.1j) * magic(3); 24 | 25 | % misc 26 | test.num.inf = inf; 27 | test.num.NaN = NaN; 28 | test.num.matrix = [1 2; 3 4]; 29 | test.num.vector = [1 2 3 4]; 30 | test.num.column_vector = [1;2;3;4]; 31 | test.num.matrix3d = ones([2 3 4]) * pi; 32 | test.num.matrix5d = ones(1,2,3,4,5) * pi; 33 | 34 | %%%%%%%%%%%%%%% 35 | % logical type 36 | test.logical = [10 20 30 40 50] > 30; 37 | 38 | 39 | %%%%%%%%%%%%%%%% 40 | % sparse type 41 | test.sparse = speye(10); 42 | 43 | %%%%%%%%%%%%%%% 44 | % string types 45 | test.string.basic = 'spam'; 46 | test.string.cell = {'1'}; 47 | test.string.char_array = ['Thomas R. Lee'; ... 48 | 'Sr. Developer'; ... 49 | 'SFTware Corp.']; 50 | test.string.cell_array = {'spam', 'eggs'}; 51 | 52 | %%%%%%%%%%%%%%%% 53 | % User defined object 54 | test.object = polynomial([1,2,3]); 55 | 56 | %%%%%%%%%%%%%%% 57 | % struct array of shape 3x1 58 | test.struct_vector = [struct('key','a'); struct('key','b'); struct('key','c')]; 59 | 60 | %%%%%%%%%%%%%%% 61 | % struct array of shape 1x2 62 | test.struct_array(1).name = 'Sharon'; 63 | test.struct_array(1).age = 31; 64 | test.struct_array(2).name = 'Bill'; 65 | test.struct_array(2).age = 42; 66 | 67 | %%%%%%%%%%%%%%% 68 | % cell array types 69 | test.cell.vector = {'spam', 4.0, [1 2 3]}; 70 | test.cell.matrix = {'Bob', 40; 'Pam', 41}; 71 | test.cell.scalar = {1.8}; 72 | test.cell.string = {'1'}; 73 | test.cell.string_array = {'1', '2'}; 74 | test.cell.empty = cell(3,4,2); 75 | test.cell.array = {[0.4194 0.3629 -0.0000; 76 | 0.0376 0.3306 0.0000; 77 | 0 0 1.0000], 78 | [0.5645 -0.2903 0; 79 | 0.0699 0.1855 0.0000; 80 | 0.8500 0.8250 1.0000]}; 81 | 82 | %%%%%%%%%%%%%% 83 | % nest all of the above. 84 | test.nested = test; 85 | 86 | end 87 | -------------------------------------------------------------------------------- /example/test_datatypes.py: -------------------------------------------------------------------------------- 1 | """ Get a sample of all datatypes from Octave and print the result 2 | """ 3 | from oct2py import octave 4 | 5 | if __name__ == "__main__": 6 | out = octave.test_datatypes() 7 | import pprint 8 | 9 | pprint.pprint(out) # noqa 10 | -------------------------------------------------------------------------------- /example/test_nodocstring.m: -------------------------------------------------------------------------------- 1 | function [outp] = test_nodocstring(inp) 2 | outp = inp; 3 | end 4 | -------------------------------------------------------------------------------- /licenses/mlabwrap.txt: -------------------------------------------------------------------------------- 1 | mlabwrap v1.1-pre 2 | ================= 3 | Copyright (C) 2003-2009 Alexander Schmolck and Vivek Rathod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /licenses/octavemagic.txt: -------------------------------------------------------------------------------- 1 | IPython is licensed under the terms of the Modified BSD License 2 | (also known as New or Revised or 3-Clause BSD), as follows: 3 | 4 | Copyright (c) 2008-2014, IPython Development Team 5 | Copyright (c) 2001-2007, Fernando Perez 6 | Copyright (c) 2001, Janko Hauser 7 | Copyright (c) 2001, Nathaniel Gray 8 | 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | Redistributions of source code must retain the above copyright notice, 14 | this list of conditions and the following disclaimer. 15 | 16 | Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | Neither the name of the IPython Development Team nor the names of its 21 | contributors may be used to endorse or promote products derived from this 22 | software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /licenses/ompc.txt: -------------------------------------------------------------------------------- 1 | ompc 1.0-beta 2 | ============= 3 | Copyright (c) 2008, Peter Jurica (juricap.pip.verisignlabs.com), RIKEN Brain Science Institute. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of the RIKEN Brain Science Institute nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY Peter Jurica ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Peter Jurica NOR RIKEN Brain Science Institute BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /oct2py/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) oct2py developers. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """ 5 | Oct2Py is a means to seamlessly call M-files and GNU Octave functions from 6 | Python. 7 | It manages the Octave session for you, sharing data behind the scenes using 8 | MAT files. Usage is as simple as: 9 | 10 | .. code-block:: pycon 11 | 12 | >>> import oct2py 13 | >>> oc = oct2py.Oct2Py() 14 | >>> x = oc.zeros(3, 3) 15 | >>> print(x, x.dtype.str) # doctest: +SKIP 16 | [[ 0. 0. 0.] 17 | [ 0. 0. 0.] 18 | [ 0. 0. 0.]] \d+).(?P\d+).(?P\d+)(?P.*?)(?P\d*)" 13 | match = re.match(pattern, __version__) 14 | assert match is not None # noqa 15 | parts: List[object] = [int(match[part]) for part in ["major", "minor", "micro"]] 16 | parts.append(match["releaselevel"] or "") 17 | parts.append(match["serial"] or "") 18 | 19 | version_info = VersionInfo(*parts) 20 | -------------------------------------------------------------------------------- /oct2py/demo.py: -------------------------------------------------------------------------------- 1 | """oct2py demo.""" 2 | # Copyright (c) oct2py developers. 3 | # Distributed under the terms of the MIT License. 4 | 5 | 6 | import time 7 | 8 | 9 | def demo(delay=1, interactive=True): 10 | """ 11 | Play a demo script showing most of the oct2py api features. 12 | 13 | Parameters 14 | ========== 15 | delay : float 16 | Time between each command in seconds. 17 | 18 | """ 19 | script = """ 20 | ######################### 21 | # Oct2Py demo 22 | ######################### 23 | import numpy as np 24 | from oct2py import Oct2Py 25 | oc = Oct2Py() 26 | # basic commands 27 | print(oc.abs(-1)) 28 | print(oc.upper('xyz')) 29 | # plotting 30 | oc.plot([1,2,3],'-o', 'linewidth', 2) 31 | raw_input('Press Enter to continue...') 32 | oc.close() 33 | xx = np.arange(-2*np.pi, 2*np.pi, 0.2) 34 | oc.surf(np.subtract.outer(np.sin(xx), np.cos(xx))) 35 | raw_input('Press Enter to continue...') 36 | oc.close() 37 | # getting help 38 | help(oc.svd) 39 | # single vs. multiple return values 40 | print(oc.svd(np.array([[1,2], [1,3]]))) 41 | U, S, V = oc.svd([[1,2], [1,3]], nout=3) 42 | print(U, S, V) 43 | # low level constructs 44 | oc.eval("y=ones(3,3)") 45 | print(oc.pull("y")) 46 | oc.eval("x=zeros(3,3)", verbose=True) 47 | t = oc.eval('rand(1, 2)', verbose=True) 48 | y = np.zeros((3,3)) 49 | oc.push('y', y) 50 | print(oc.pull('y')) 51 | from oct2py import Struct 52 | y = Struct() 53 | y.b = 'spam' 54 | y.c.d = 'eggs' 55 | print(y.c['d']) 56 | print(y) 57 | ######################### 58 | # Demo Complete! 59 | ######################### 60 | """ 61 | script = script.replace("raw_input", "input") 62 | 63 | for line in script.strip().split("\n"): 64 | line = line.strip() # noqa 65 | if "input(" not in line: 66 | time.sleep(delay) 67 | print(f">>> {line}") # noqa 68 | time.sleep(delay) 69 | if not interactive and ("plot" in line or "surf" in line or "input(" in line): 70 | line = "print()" # noqa 71 | exec(line) # noqa 72 | 73 | 74 | if __name__ == "__main__": # pragma: no cover 75 | demo(delay=0.25) 76 | -------------------------------------------------------------------------------- /oct2py/dynamic.py: -------------------------------------------------------------------------------- 1 | """dynamic value handling.""" 2 | # Copyright (c) oct2py developers. 3 | # Distributed under the terms of the MIT License. 4 | 5 | 6 | import types 7 | import warnings 8 | import weakref 9 | from typing import Any, Dict 10 | 11 | import numpy as np 12 | 13 | try: 14 | from scipy.io.matlab import MatlabObject # type:ignore[import-untyped] 15 | except ImportError: 16 | try: # noqa 17 | from scipy.io.matlab.mio5 import MatlabObject # type:ignore[import-untyped] 18 | except ImportError: 19 | pass 20 | 21 | 22 | class OctavePtr: 23 | """A pointer to an Octave workspace value.""" 24 | 25 | def __init__(self, session_weakref, name, address): 26 | """Initialize the pointer.""" 27 | self._name = name 28 | self._address = address 29 | self._ref = session_weakref 30 | self.__module__ = "oct2py.dynamic" 31 | self.__name__ = name 32 | 33 | @property 34 | def name(self): 35 | return self._name 36 | 37 | @property 38 | def address(self): 39 | return self._address 40 | 41 | 42 | class _DocDescriptor: 43 | """An object that dynamically fetches the documentation 44 | for an Octave value. 45 | """ 46 | 47 | def __init__(self, session_weakref, name): 48 | """Initialize the descriptor.""" 49 | self.ref = session_weakref 50 | self.name = name 51 | self.doc = None 52 | 53 | def __get__(self, instance, owner=None): 54 | if self.doc: 55 | return self.doc 56 | self.doc = self.ref()._get_doc(self.name) 57 | return self.doc 58 | 59 | 60 | class OctaveVariablePtr(OctavePtr): 61 | """An object that acts as a pointer to an Octave value.""" 62 | 63 | @property 64 | def __doc__(self): 65 | return "%s is a variable" % self.name 66 | 67 | @property 68 | def value(self): 69 | return self._ref().pull(self.address) 70 | 71 | @value.setter 72 | def value(self, obj): 73 | self._ref().push(self.address, obj) 74 | 75 | 76 | class OctaveFunctionPtr(OctavePtr): 77 | """An object that acts as a pointer to an Octave function.""" 78 | 79 | def __init__(self, session_weakref, name): 80 | """Initialize the pointer.""" 81 | address = "@%s" % name 82 | super().__init__(session_weakref, name, address) 83 | 84 | def __call__(self, *inputs, **kwargs): 85 | """Call the function.""" 86 | # Check for allowed keyword arguments 87 | allowed = [ 88 | "verbose", 89 | "store_as", 90 | "timeout", 91 | "stream_handler", 92 | "plot_dir", 93 | "plot_name", 94 | "plot_format", 95 | "plot_width", 96 | "plot_height", 97 | "plot_res", 98 | "nout", 99 | ] 100 | 101 | extras = {} 102 | for key, _ in kwargs.copy().items(): 103 | if key not in allowed: 104 | extras[key] = kwargs.pop(key) 105 | 106 | if extras: 107 | warnings.warn("Key - value pairs are deprecated, use `func_args`", stacklevel=2) 108 | 109 | inputs += tuple(item for pair in extras.items() for item in pair) 110 | 111 | return self._ref().feval(self.name, *inputs, **kwargs) 112 | 113 | def __repr__(self): 114 | """A string repr of the pointer.""" 115 | return '"%s" Octave function' % self.name 116 | 117 | 118 | class OctaveUserClassAttr(OctavePtr): 119 | """An attribute associated with an Octave user class instance.""" 120 | 121 | def __get__(self, instance, owner=None): 122 | """Get a method or property on the class.""" 123 | if instance is None: 124 | return "dynamic attribute" 125 | pointer = OctaveUserClass.to_pointer(instance) 126 | return instance._ref().feval("get", pointer, self.address) 127 | 128 | def __set__(self, instance, value): 129 | """Set a method or property on the class.""" 130 | if instance is None: 131 | return 132 | pointer = OctaveUserClass.to_pointer(instance) 133 | # The set function returns a new struct, so we have to store_as. 134 | instance._ref().feval("set", pointer, self.address, value, store_as=pointer.address) 135 | 136 | 137 | class _MethodDocDescriptor: 138 | """An object that dynamically fetches the documentation 139 | for an Octave user class method. 140 | """ 141 | 142 | def __init__(self, session_weakref, class_name, name): 143 | """Initialize the descriptor.""" 144 | self.ref = session_weakref 145 | self.class_name = class_name 146 | self.name = name 147 | self.doc = None 148 | 149 | def __get__(self, instance, owner=None): 150 | """Get the documentation.""" 151 | if self.doc is not None: 152 | return self.doc 153 | session = self.ref() 154 | class_name = self.class_name 155 | method = self.name 156 | doc = session._get_doc(f"@{class_name}/{method}") 157 | self.doc = doc or session._get_doc(method) 158 | return self.doc 159 | 160 | 161 | class OctaveUserClassMethod(OctaveFunctionPtr): 162 | """A method for a user defined Octave class.""" 163 | 164 | def __init__(self, session_weakref, name, class_name): 165 | """Initialize the pointer.""" 166 | OctaveFunctionPtr.__init__(self, session_weakref, name) 167 | self.class_name = class_name 168 | 169 | def __get__(self, instance, owner=None): 170 | """Bind to the instance.""" 171 | return types.MethodType(self, instance) 172 | 173 | def __call__(self, instance: "OctaveUserClass", *inputs: Any, **kwargs: Any) -> Any: 174 | """Call the class method.""" 175 | pointer = OctaveUserClass.to_pointer(instance) 176 | inputs = (pointer, *inputs) 177 | self._ref().feval(self.name, *inputs, **kwargs) 178 | 179 | def __repr__(self): 180 | """Str repr of the pointer.""" 181 | return f'"{self.name}" Octave method for object' 182 | 183 | 184 | class OctaveUserClass: 185 | """A wrapper for an Octave user class.""" 186 | 187 | _name: str 188 | _attrs: Dict[str, OctaveUserClassAttr] 189 | _ref: Any 190 | 191 | def __init__(self, *inputs, **kwargs): 192 | """Create a new instance with the user class constructor.""" 193 | addr = self._address = f"{self._name}_{id(self)}" 194 | self._ref().feval(self._name, *inputs, store_as=addr, **kwargs) 195 | 196 | @classmethod 197 | def from_value(cls, value): 198 | """This is how an instance is created when we read a 199 | MatlabObject from a MAT file. 200 | """ 201 | instance = OctaveUserClass.__new__(cls) 202 | instance._address = f"{instance._name}_{id(instance)}" 203 | instance._ref().push(instance._address, value) 204 | return instance 205 | 206 | @classmethod 207 | def to_value(cls, instance: "OctaveUserClass") -> MatlabObject: 208 | """Convert to a value to send to Octave.""" 209 | if ( 210 | not isinstance(instance, OctaveUserClass) # type:ignore[redundant-expr] 211 | or not instance._attrs 212 | ): 213 | return {} 214 | # Bootstrap a MatlabObject from scipy.io 215 | # From https://github.com/scipy/scipy/blob/93a0ea9e5d4aba1f661b6bb0e18f9c2d1fce436a/scipy/io/matlab/mio5.py#L435-L443 216 | # and https://github.com/scipy/scipy/blob/93a0ea9e5d4aba1f661b6bb0e18f9c2d1fce436a/scipy/io/matlab/mio5_params.py#L224 217 | dtype = [] 218 | values = [] 219 | for attr in instance._attrs: 220 | dtype.append((str(attr), object)) 221 | values.append(getattr(instance, attr)) 222 | struct = np.array([tuple(values)], dtype) 223 | return MatlabObject(struct, instance._name) 224 | 225 | @classmethod 226 | def to_pointer(cls, instance): 227 | """Get a pointer to the private object.""" 228 | return OctavePtr(instance._ref, instance._name, instance._address) 229 | 230 | 231 | def _make_user_class(session, name): 232 | """Make an Octave class for a given class name""" 233 | attrs = session.eval("fieldnames(%s);" % name, nout=1).ravel().tolist() 234 | methods = session.eval("methods(%s);" % name, nout=1).ravel().tolist() 235 | ref = weakref.ref(session) 236 | 237 | doc = _DocDescriptor(ref, name) 238 | values = dict(__doc__=doc, _name=name, _ref=ref, _attrs=attrs, __module__="oct2py.dynamic") 239 | 240 | for method in methods: 241 | doc = _MethodDocDescriptor(ref, name, method) # type:ignore[assignment] 242 | cls_name = f"{name}_{method}" 243 | method_values = dict(__doc__=doc) 244 | method_cls = type(str(cls_name), (OctaveUserClassMethod,), method_values) 245 | values[method] = method_cls(ref, method, name) 246 | 247 | for attr in attrs: 248 | values[attr] = OctaveUserClassAttr(ref, attr, attr) 249 | 250 | return type(str(name), (OctaveUserClass,), values) 251 | 252 | 253 | def _make_function_ptr_instance(session, name): 254 | ref = weakref.ref(session) 255 | doc = _DocDescriptor(ref, name) 256 | custom = type(str(name), (OctaveFunctionPtr,), dict(__doc__=doc)) 257 | return custom(ref, name) 258 | 259 | 260 | def _make_variable_ptr_instance(session, name): 261 | """Make a pointer instance for a given variable by name.""" 262 | return OctaveVariablePtr(weakref.ref(session), name, name) 263 | -------------------------------------------------------------------------------- /oct2py/io.py: -------------------------------------------------------------------------------- 1 | """io handling.""" 2 | # Copyright (c) oct2py developers. 3 | # Distributed under the terms of the MIT License. 4 | 5 | 6 | import dis 7 | import inspect 8 | import threading 9 | 10 | import numpy as np 11 | 12 | try: 13 | from scipy.io import loadmat, savemat # type:ignore[import-untyped] 14 | from scipy.io.matlab import MatlabFunction, MatlabObject # type:ignore[import-untyped] 15 | from scipy.sparse import spmatrix # type:ignore[import-untyped] 16 | except ImportError: 17 | try: # noqa 18 | from scipy.io.matlab.mio5 import MatlabFunction, MatlabObject # type:ignore[import-untyped] 19 | except ImportError: 20 | pass 21 | 22 | 23 | try: 24 | from pandas import DataFrame, Series 25 | except Exception: 26 | 27 | class Series: # type:ignore[no-redef] 28 | """placeholder.""" 29 | 30 | pass 31 | 32 | class DataFrame: # type:ignore[no-redef] 33 | """placeholder.""" 34 | 35 | pass 36 | 37 | 38 | from .dynamic import OctaveFunctionPtr, OctaveUserClass, OctaveVariablePtr 39 | from .utils import Oct2PyError 40 | 41 | _WRITE_LOCK = threading.Lock() 42 | 43 | 44 | def read_file(path, session=None, keep_matlab_shapes=False): 45 | """Read the data from the given file path.""" 46 | if session: 47 | keep_matlab_shapes = keep_matlab_shapes or session.keep_matlab_shapes 48 | try: 49 | data = loadmat(path, struct_as_record=True) 50 | except UnicodeDecodeError as e: 51 | raise Oct2PyError(str(e)) from None 52 | out = {} 53 | for key, value in data.items(): 54 | out[key] = _extract(value, session, keep_matlab_shapes) 55 | return out 56 | 57 | 58 | def write_file(obj, path, oned_as="row", convert_to_float=True): 59 | """Save a Python object to an Octave file on the given path.""" 60 | data = _encode(obj, convert_to_float) 61 | try: 62 | # scipy.io.savemat is not thread-save. 63 | # See https://github.com/scipy/scipy/issues/7260 64 | with _WRITE_LOCK: 65 | savemat(path, data, appendmat=False, oned_as=oned_as, long_field_names=True) 66 | except KeyError: # pragma: no cover 67 | msg = "could not save mat file" 68 | raise Exception(msg) from None 69 | 70 | 71 | class Struct(dict): # type:ignore[type-arg] 72 | """ 73 | Octave style struct, enhanced. 74 | 75 | Notes 76 | ===== 77 | Supports dictionary and attribute style access. Can be pickled, 78 | and supports code completion in a REPL. 79 | 80 | Examples 81 | ======== 82 | >>> from pprint import pprint 83 | >>> from oct2py import Struct 84 | >>> a = Struct() 85 | >>> a.b = 'spam' # a["b"] == 'spam' 86 | >>> a.c["d"] = 'eggs' # a.c.d == 'eggs' 87 | >>> pprint(a) 88 | {'b': 'spam', 'c': {'d': 'eggs'}} 89 | """ 90 | 91 | def __getattr__(self, attr): 92 | """Access the dictionary keys for unknown attributes.""" 93 | try: 94 | return self[attr] 95 | except KeyError: 96 | msg = "'Struct' object has no attribute %s" % attr 97 | raise AttributeError(msg) from None 98 | 99 | def __getitem__(self, attr): 100 | """Get a dict value; create a Struct if requesting a Struct member.""" 101 | # Do not create a key if the attribute starts with an underscore. 102 | if attr in self or attr.startswith("_"): 103 | return dict.__getitem__(self, attr) 104 | frame = inspect.currentframe() 105 | if frame is None or frame.f_back is None: 106 | return None 107 | # step into the function that called us 108 | if frame.f_back.f_back and self._is_allowed(frame.f_back.f_back): # noqa 109 | dict.__setitem__(self, attr, Struct()) 110 | elif self._is_allowed(frame.f_back): 111 | dict.__setitem__(self, attr, Struct()) 112 | return dict.__getitem__(self, attr) 113 | 114 | def _is_allowed(self, frame): 115 | # Check for allowed op code in the calling frame. 116 | allowed = [dis.opmap["STORE_ATTR"], dis.opmap["LOAD_CONST"], dis.opmap.get("STOP_CODE", 0)] 117 | bytecode = frame.f_code.co_code 118 | instruction = bytecode[frame.f_lasti + 3] 119 | return instruction in allowed 120 | 121 | __setattr__ = dict.__setitem__ # type:ignore[assignment] 122 | __delattr__ = dict.__delitem__ # type:ignore[assignment] 123 | 124 | @property 125 | def __dict__(self): 126 | # Allow for code completion in a REPL. 127 | return self.copy() 128 | 129 | 130 | class StructArray(np.recarray): # type:ignore[type-arg] 131 | """A Python representation of an Octave structure array. 132 | 133 | Notes 134 | ===== 135 | Accessing a record returns a Cell containing the values. 136 | This class is not meant to be directly created by the user. It is 137 | created automatically for structure array values received from Octave. 138 | The last axis is squeezed if it is of size 1 to simplify element access. 139 | 140 | Examples 141 | ======== 142 | >>> from oct2py import octave 143 | >>> # generate the struct array 144 | >>> octave.eval('x = struct("y", {1, 2}, "z", {3, 4});') 145 | >>> x = octave.pull('x') 146 | >>> x.y # attribute access -> oct2py Cell 147 | Cell([[1.0, 2.0]]) 148 | >>> x['z'] # item access -> oct2py Cell 149 | Cell([[3.0, 4.0]]) 150 | >>> x[0, 0] # index access -> numpy record 151 | (1.0, 3.0) 152 | >>> x[0, 1].z 153 | 4.0 154 | """ 155 | 156 | def __new__(cls, value, session=None, keep_matlab_shapes=False): 157 | """Create a struct array from a value and optional Octave session.""" 158 | value = np.asarray(value) 159 | # Squeeze the last element if it is 1 160 | if value.shape[value.ndim - 1] == 1 and not keep_matlab_shapes: 161 | value = value.squeeze(axis=value.ndim - 1) 162 | value = np.atleast_1d(value) 163 | 164 | # Extract the values. 165 | obj = np.empty(value.size, dtype=value.dtype).view(cls) 166 | for i, item in enumerate(value.ravel()): 167 | for name in value.dtype.names: 168 | obj[i][name] = _extract(item[name], session, keep_matlab_shapes) 169 | return obj.reshape(value.shape) 170 | 171 | @property 172 | def fieldnames(self): 173 | """The field names of the struct array.""" 174 | return self.dtype.names 175 | 176 | def __getattribute__(self, attr): 177 | """Return object arrays as cells and all other values unchanged.""" 178 | attr = np.recarray.__getattribute__(self, attr) 179 | if isinstance(attr, np.ndarray) and attr.dtype.kind == "O": 180 | return Cell(attr) 181 | return attr 182 | 183 | def __getitem__(self, item): 184 | """Return object arrays as cells and all other values unchanged.""" 185 | item = np.recarray.__getitem__(self, item) 186 | if isinstance(item, np.ndarray) and item.dtype.kind == "O": 187 | return Cell(item) 188 | return item 189 | 190 | def __repr__(self): 191 | """A str repr for the struct array.""" 192 | shape = self.shape 193 | if len(shape) == 1: 194 | shape = (shape[0], 1) 195 | msg = "x".join(str(i) for i in shape) 196 | msg += " StructArray containing the fields:" 197 | for key in self.fieldnames: 198 | msg += "\n %s" % key 199 | return msg 200 | 201 | 202 | class Cell(np.ndarray): # type:ignore[type-arg] 203 | """A Python representation of an Octave cell array. 204 | 205 | Notes 206 | ===== 207 | This class is not meant to be directly created by the user. It is 208 | created automatically for cell array values received from Octave. 209 | The last axis is squeezed if it is of size 1 to simplify element access. 210 | 211 | Examples 212 | ======== 213 | >>> from oct2py import octave 214 | >>> # generate the struct array 215 | >>> octave.eval("x = cell(2,2); x(:) = 1.0;") 216 | >>> x = octave.pull('x') 217 | >>> x 218 | Cell([[1.0, 1.0], 219 | [1.0, 1.0]]) 220 | >>> x[0] 221 | Cell([1.0, 1.0]) 222 | >>> x[0].tolist() 223 | [1.0, 1.0] 224 | """ 225 | 226 | def __new__(cls, value, session=None, keep_matlab_shapes=False): 227 | """Create a cell array from a value and optional Octave session.""" 228 | # Use atleast_2d to preserve Octave size() 229 | value = np.atleast_2d(np.asarray(value, dtype=object)) 230 | 231 | # Extract the values. 232 | obj = np.empty(value.size, dtype=object).view(cls) 233 | for i, item in enumerate(value.ravel()): 234 | obj[i] = _extract(item, session, keep_matlab_shapes) 235 | obj = obj.reshape(value.shape) 236 | return obj 237 | 238 | def __repr__(self): 239 | """A string repr for the cell array.""" 240 | shape = self.shape 241 | if len(shape) == 1: 242 | shape = (shape[0], 1) 243 | msg = self.view(np.ndarray).__repr__() 244 | msg = msg.replace("array", "Cell", 1) 245 | return msg.replace(", dtype=object", "", 1) 246 | 247 | def __getitem__(self, key): 248 | """Get an element of the array.""" 249 | if key == 0 and self.size == 1: 250 | # Note: 251 | # Can't use `return super().ravel()[0]` here 252 | key = tuple([0] * self.ndim) 253 | return super().__getitem__(key) 254 | 255 | 256 | def _extract(data, session=None, keep_matlab_shapes=False): # noqa 257 | """Convert the Octave values to values suitable for Python.""" 258 | # Extract each item of a list. 259 | if isinstance(data, list): 260 | return [_extract(d, session, keep_matlab_shapes) for d in data] 261 | 262 | # Ignore leaf objects. 263 | if not isinstance(data, np.ndarray): 264 | return data 265 | 266 | # Extract user defined classes. 267 | if isinstance(data, MatlabObject): 268 | cls = session._get_user_class(data.classname) 269 | return cls.from_value(data) 270 | 271 | # Extract struct data. 272 | if data.dtype.names: 273 | # Singular struct 274 | if data.size == 1: 275 | return _create_struct(data, session, keep_matlab_shapes) 276 | # Struct array 277 | return StructArray(data, session, keep_matlab_shapes) 278 | 279 | # Extract cells. 280 | if data.dtype.kind == "O": 281 | return Cell(data, session, keep_matlab_shapes) 282 | 283 | # Compress singleton values. 284 | if data.size == 1: 285 | if not keep_matlab_shapes: 286 | return data.item() 287 | else: 288 | if data.dtype.kind in "US": 289 | return data.item() 290 | return data 291 | 292 | # Compress empty values. 293 | if data.shape in ((0,), (0, 0)): 294 | if data.dtype.kind in "US": 295 | return "" 296 | return [] 297 | 298 | # Return standard array. 299 | return data 300 | 301 | 302 | def _create_struct(data, session, keep_matlab_shapes=False): 303 | """Create a struct from session data.""" 304 | out = Struct() 305 | for name in data.dtype.names: 306 | item = data[name] 307 | # Extract values that are cells (they are doubly wrapped). 308 | if isinstance(item, np.ndarray) and item.dtype.kind == "O": 309 | item = item.squeeze().tolist() 310 | out[name] = _extract(item, session, keep_matlab_shapes) 311 | return out 312 | 313 | 314 | def _encode(data, convert_to_float): # noqa 315 | """Convert the Python values to values suitable to send to Octave.""" 316 | ctf = convert_to_float 317 | 318 | # Handle variable pointer. 319 | if isinstance(data, (OctaveVariablePtr)): 320 | return _encode(data.value, ctf) 321 | 322 | # Handle a user defined object. 323 | if isinstance(data, OctaveUserClass): 324 | return _encode(OctaveUserClass.to_value(data), ctf) 325 | 326 | # Handle a function pointer. 327 | if isinstance(data, (OctaveFunctionPtr, MatlabFunction)): 328 | msg = "Cannot write Octave functions" 329 | raise Oct2PyError(msg) 330 | 331 | # Handle matlab objects. 332 | if isinstance(data, MatlabObject): # type:ignore[unreachable] 333 | view = data.view(np.ndarray) 334 | out = MatlabObject(data, data.classname) 335 | for name in out.dtype.names: 336 | out[name] = _encode(view[name], ctf) 337 | return out 338 | 339 | # Integer objects should be converted to floats 340 | if isinstance(data, int): 341 | return float(data) 342 | 343 | # Handle pandas series and dataframes 344 | if isinstance(data, (DataFrame, Series)): 345 | return _encode(data.values, ctf) 346 | 347 | # Extract and encode values from dict-like objects. 348 | if isinstance(data, dict): 349 | out = {} 350 | for key, value in data.items(): 351 | out[key] = _encode(value, ctf) 352 | return out 353 | 354 | # Send None as nan. 355 | if data is None: 356 | return np.nan 357 | 358 | # Sets are treated like lists. 359 | if isinstance(data, set): 360 | return _encode(list(data), ctf) 361 | 362 | # Lists can be interpreted as numeric arrays or cell arrays. 363 | if isinstance(data, list): 364 | if _is_simple_numeric(data): 365 | return _encode(np.array(data), ctf) 366 | return _encode(tuple(data), ctf) 367 | 368 | # Tuples are handled as cells. 369 | if isinstance(data, tuple): 370 | obj = np.empty(len(data), dtype=object) 371 | for i, item in enumerate(data): 372 | obj[i] = _encode(item, ctf) 373 | return obj 374 | 375 | # Sparse data must be floating type. 376 | if isinstance(data, spmatrix): 377 | return data.astype(np.float64) 378 | 379 | # Return other data types unchanged. 380 | if not isinstance(data, np.ndarray): 381 | return data 382 | 383 | # Extract and encode data from object-like arrays. 384 | if data.dtype.kind in "OV": 385 | out = np.empty(data.size, dtype=data.dtype) 386 | for i, item in enumerate(data.ravel()): 387 | if data.dtype.names: 388 | for name in data.dtype.names: 389 | out[i][name] = _encode(item[name], ctf) 390 | else: 391 | out[i] = _encode(item, ctf) 392 | return out.reshape(data.shape) 393 | 394 | # Complex 128 is the highest supported by savemat. 395 | if data.dtype.name == "complex256": 396 | return data.astype(np.complex128) 397 | 398 | # Convert to float if applicable. 399 | if ctf and data.dtype.kind in "ui": 400 | return data.astype(np.float64) 401 | 402 | # Return standard array. 403 | return data 404 | 405 | 406 | def _is_simple_numeric(data): 407 | """Test if a list contains simple numeric data.""" 408 | item_len = None 409 | for item in data: 410 | if isinstance(item, set): 411 | item = list(item) # noqa 412 | if isinstance(item, list): 413 | if not _is_simple_numeric(item): 414 | return False 415 | # Numpy does not support creating an ndarray from 416 | # ragged nested sequences 417 | # (which is a list-or-tuple of lists-or-tuples-or ndarrays with 418 | # different lengths or shapes 419 | if item_len is None: 420 | item_len = len(item) 421 | if len(item) != item_len: 422 | return False 423 | elif not isinstance(item, (int, float, complex)): 424 | return False 425 | return True 426 | -------------------------------------------------------------------------------- /oct2py/ipython/__init__.py: -------------------------------------------------------------------------------- 1 | from .octavemagic import load_ipython_extension # noqa 2 | -------------------------------------------------------------------------------- /oct2py/ipython/ipython-COPYING.txt: -------------------------------------------------------------------------------- 1 | ============================= 2 | The IPython licensing terms 3 | ============================= 4 | 5 | IPython is licensed under the terms of the Modified BSD License (also known as 6 | New or Revised BSD), as follows: 7 | 8 | Copyright (c) 2008-2010, IPython Development Team 9 | Copyright (c) 2001-2007, Fernando Perez. 10 | Copyright (c) 2001, Janko Hauser 11 | Copyright (c) 2001, Nathaniel Gray 12 | 13 | All rights reserved. 14 | 15 | Redistribution and use in source and binary forms, with or without 16 | modification, are permitted provided that the following conditions are met: 17 | 18 | Redistributions of source code must retain the above copyright notice, this 19 | list of conditions and the following disclaimer. 20 | 21 | Redistributions in binary form must reproduce the above copyright notice, this 22 | list of conditions and the following disclaimer in the documentation and/or 23 | other materials provided with the distribution. 24 | 25 | Neither the name of the IPython Development Team nor the names of its 26 | contributors may be used to endorse or promote products derived from this 27 | software without specific prior written permission. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 30 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 31 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 32 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 33 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 36 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 37 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 38 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 | 40 | About the IPython Development Team 41 | ---------------------------------- 42 | 43 | Fernando Perez began IPython in 2001 based on code from Janko Hauser 44 | and Nathaniel Gray . Fernando is still 45 | the project lead. 46 | 47 | The IPython Development Team is the set of all contributors to the IPython 48 | project. This includes all of the IPython subprojects. A full list with 49 | details is kept in the documentation directory, in the file 50 | ``about/credits.txt``. 51 | 52 | The core team that coordinates development on GitHub can be found here: 53 | http://github.com/ipython. As of late 2010, it consists of: 54 | 55 | * Brian E. Granger 56 | * Jonathan March 57 | * Evan Patterson 58 | * Fernando Perez 59 | * Min Ragan-Kelley 60 | * Robert Kern 61 | 62 | 63 | Our Copyright Policy 64 | -------------------- 65 | 66 | IPython uses a shared copyright model. Each contributor maintains copyright 67 | over their contributions to IPython. But, it is important to note that these 68 | contributions are typically only changes to the repositories. Thus, the IPython 69 | source code, in its entirety is not the copyright of any single person or 70 | institution. Instead, it is the collective copyright of the entire IPython 71 | Development Team. If individual contributors want to maintain a record of what 72 | changes/contributions they have specific copyright on, they should indicate 73 | their copyright in the commit message of the change, when they commit the 74 | change to one of the IPython repositories. 75 | 76 | With this in mind, the following banner should be used in any source code file 77 | to indicate the copyright and license terms: 78 | 79 | #----------------------------------------------------------------------------- 80 | # Copyright (c) 2010, IPython Development Team. 81 | # 82 | # Distributed under the terms of the Modified BSD License. 83 | # 84 | # The full license is in the file COPYING.txt, distributed with this software. 85 | #----------------------------------------------------------------------------- 86 | -------------------------------------------------------------------------------- /oct2py/ipython/octavemagic.py: -------------------------------------------------------------------------------- 1 | """ 2 | =========== 3 | octavemagic 4 | =========== 5 | 6 | Magics for interacting with Octave via oct2py. 7 | 8 | .. note:: 9 | 10 | The ``oct2py`` module needs to be installed separately and 11 | can be obtained using ``easy_install`` or ``pip``. 12 | 13 | You will also need a working copy of GNU Octave. 14 | 15 | Usage 16 | ===== 17 | 18 | To enable the magics below, execute ``%load_ext octavemagic``. 19 | 20 | ``%octave`` 21 | 22 | {OCTAVE_DOC} 23 | 24 | ``%octave_push`` 25 | 26 | {OCTAVE_PUSH_DOC} 27 | 28 | ``%octave_pull`` 29 | 30 | {OCTAVE_PULL_DOC} 31 | 32 | """ 33 | 34 | # ----------------------------------------------------------------------------- 35 | # Copyright (C) 2012 The IPython Development Team 36 | # 37 | # Distributed under the terms of the BSD License. The full license is in 38 | # the file COPYING, distributed as part of this software. 39 | # ----------------------------------------------------------------------------- 40 | 41 | import os 42 | import shutil 43 | 44 | from IPython.core.magic import ( 45 | Magics, 46 | line_cell_magic, 47 | line_magic, 48 | magics_class, 49 | needs_local_scope, 50 | ) 51 | from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring 52 | from IPython.display import display, publish_display_data 53 | from IPython.testing.skipdoctest import skip_doctest 54 | from IPython.utils.text import dedent 55 | 56 | import oct2py 57 | 58 | 59 | @magics_class 60 | class OctaveMagics(Magics): 61 | 62 | """A set of magics useful for interactive work with Octave via oct2py.""" 63 | 64 | def __init__(self, shell): 65 | """ 66 | Parameters 67 | ---------- 68 | shell : IPython shell 69 | 70 | """ 71 | super().__init__(shell) 72 | self._oct = oct2py.octave 73 | 74 | # Allow display to be overridden for 75 | # testing purposes. 76 | self._display = display 77 | 78 | @skip_doctest 79 | @line_magic 80 | def octave_push(self, line): 81 | """ 82 | Line-level magic that pushes a variable to Octave. 83 | 84 | `line` should be made up of whitespace separated variable names in the 85 | IPython namespace:: 86 | 87 | In [7]: import numpy as np 88 | 89 | In [8]: X = np.arange(5) 90 | 91 | In [9]: X.mean() 92 | Out[9]: 2.0 93 | 94 | In [10]: %octave_push X 95 | 96 | In [11]: %octave mean(X) 97 | Out[11]: 2.0 98 | 99 | """ 100 | inputs = line.split(" ") 101 | assert self.shell is not None # noqa: S101 102 | for _input in inputs: 103 | self._oct.push(_input, self.shell.user_ns[_input]) 104 | 105 | @skip_doctest 106 | @line_magic 107 | def octave_pull(self, line): 108 | """ 109 | Line-level magic that pulls a variable from Octave. 110 | 111 | :: 112 | 113 | In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello' 114 | 115 | In [19]: %octave_pull x y 116 | 117 | In [20]: x 118 | Out[20]: 119 | array([[ 1., 2.], 120 | [ 3., 4.]]) 121 | 122 | In [21]: y 123 | Out[21]: 'hello' 124 | 125 | """ 126 | outputs = line.split(" ") 127 | assert self.shell is not None # noqa: S101 128 | for output in outputs: 129 | self.shell.push({output: self._oct.pull(output)}) 130 | 131 | @skip_doctest 132 | @magic_arguments() 133 | @argument( 134 | "-i", 135 | "--input", 136 | action="append", 137 | help="Names of input variables to be pushed to Octave. Multiple names " 138 | "can be passed, separated by commas with no whitespace.", 139 | ) 140 | @argument( 141 | "-o", 142 | "--output", 143 | action="append", 144 | help="Names of variables to be pulled from Octave after executing cell " 145 | "body. Multiple names can be passed, separated by commas with no " 146 | "whitespace.", 147 | ) 148 | @argument("-s", "--size", action="store", help='Pixel size of plots, "width,height".') 149 | @argument("-f", "--format", action="store", help="Plot format (png, svg or jpg).") 150 | @argument("-w", "--width", type=int, action="store", help="The width of the plot in pixels") 151 | @argument("-h", "--height", type=int, action="store", help="The height of the plot in pixels") 152 | @argument( 153 | "-r", 154 | "--resolution", 155 | type=int, 156 | action="store", 157 | help="The resolution of the plot in pixels per inch", 158 | ) 159 | @argument( 160 | "-t", 161 | "--temp_dir", 162 | type=str, 163 | action="store", 164 | help="The directory to write variables for conversion between Octave and Python", 165 | ) 166 | @needs_local_scope 167 | @argument( 168 | "code", 169 | nargs="*", 170 | ) 171 | @line_cell_magic 172 | def octave(self, line, cell=None, local_ns=None): # noqa 173 | """ 174 | Execute code in Octave, and pull some of the results back into the 175 | Python namespace:: 176 | 177 | In [9]: %octave X = [1 2; 3 4]; mean(X) 178 | Out[9]: array([[ 2., 3.]]) 179 | 180 | As a cell, this will run a block of Octave code, without returning any 181 | value:: 182 | 183 | In [10]: %%octave 184 | ....: p = [-2, -1, 0, 1, 2] 185 | ....: polyout(p, 'x') 186 | 187 | -2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2 188 | 189 | In the notebook, plots are published as the output of the cell, e.g.:: 190 | 191 | %octave plot([1 2 3], [4 5 6]) 192 | 193 | will create a line plot. 194 | 195 | Objects can be passed back and forth between Octave and IPython via the 196 | -i and -o flags in line:: 197 | 198 | In [14]: Z = np.array([1, 4, 5, 10]) 199 | 200 | In [15]: %octave -i Z mean(Z) 201 | Out[15]: array([ 5.]) 202 | 203 | 204 | In [16]: %octave -o W W = Z * mean(Z) 205 | Out[16]: array([ 5., 20., 25., 50.]) 206 | 207 | In [17]: W 208 | Out[17]: array([ 5., 20., 25., 50.]) 209 | 210 | The size and format of output plots can be specified:: 211 | 212 | In [18]: %%octave -s 600,800 -f svg 213 | ...: plot([1, 2, 3]); 214 | 215 | """ 216 | args = parse_argstring(self.octave, line) 217 | 218 | # arguments 'code' in line are prepended to the cell lines 219 | if cell is None: 220 | code = "" 221 | return_output = True 222 | else: 223 | code = cell 224 | return_output = False 225 | 226 | code = " ".join(args.code) + code 227 | 228 | # if there is no local namespace then default to an empty dict 229 | if local_ns is None: 230 | local_ns = {} 231 | 232 | assert self.shell is not None # noqa: S101 233 | if args.input: 234 | for _input in ",".join(args.input).split(","): 235 | try: 236 | val = local_ns[_input] 237 | except KeyError: 238 | val = self.shell.user_ns[_input] 239 | self._oct.push(_input, val) 240 | 241 | width = args.width 242 | height = args.height 243 | 244 | if args.size is not None: 245 | width, height = (int(s) for s in args.size.split(",")) 246 | 247 | # Handle the temporary directory, defaulting to the Oct2Py instance's 248 | # temp dir. 249 | temp_dir = args.temp_dir 250 | if temp_dir and not os.path.isdir(temp_dir): 251 | temp_dir = None 252 | temp_dir = temp_dir or self._oct.temp_dir 253 | 254 | # Put the plots in the temp directory so we don't have to make another 255 | # temporary directory. 256 | plot_dir = os.path.join(temp_dir, "plots") 257 | if os.path.exists(plot_dir): 258 | shutil.rmtree(plot_dir) 259 | os.makedirs(plot_dir) 260 | 261 | # Match current working directory. 262 | self._oct.cd(os.getcwd().replace(os.path.sep, "/")) 263 | value = self._oct.eval( 264 | code, 265 | stream_handler=self._publish, 266 | plot_dir=plot_dir, 267 | plot_width=width, 268 | plot_height=height, 269 | plot_format=args.format, 270 | plot_name="__ipy_oct_fig_", 271 | resolution=args.resolution, 272 | temp_dir=temp_dir, 273 | ) 274 | 275 | # Publish output 276 | if args.output: 277 | for output in ",".join(args.output).split(","): 278 | self.shell.push({output: self._oct.pull(output)}) 279 | 280 | # Publish images 281 | if plot_dir: 282 | for img in self._oct.extract_figures(plot_dir, True): 283 | self._display(img) 284 | 285 | if return_output: 286 | return value 287 | 288 | def _publish(self, line): 289 | publish_display_data({"text/plain": line}) 290 | 291 | 292 | __doc__ = __doc__.format( # noqa 293 | OCTAVE_DOC=dedent(OctaveMagics.octave.__doc__), 294 | OCTAVE_PUSH_DOC=dedent(OctaveMagics.octave_push.__doc__), 295 | OCTAVE_PULL_DOC=dedent(OctaveMagics.octave_pull.__doc__), 296 | ) 297 | 298 | 299 | def load_ipython_extension(ip): 300 | """Load the extension in IPython.""" 301 | ip.register_magics(OctaveMagics) 302 | -------------------------------------------------------------------------------- /oct2py/speed_check.py: -------------------------------------------------------------------------------- 1 | """oct2py speed check.""" 2 | # Copyright (c) oct2py developers. 3 | # Distributed under the terms of the MIT License. 4 | 5 | 6 | import time 7 | import timeit 8 | 9 | import numpy as np 10 | 11 | from . import Oct2Py, get_log 12 | 13 | 14 | class SpeedCheck: 15 | """Checks the speed penalty of the Python to Octave bridge. 16 | 17 | Uses timeit to test the raw execution of a Octave command, 18 | Then tests progressively larger array passing. 19 | 20 | """ 21 | 22 | def __init__(self): 23 | """Create our Octave instance and initialize the data array""" 24 | self.octave = Oct2Py() 25 | self.array = [] 26 | 27 | def raw_speed(self): 28 | """Run a fast Octave command and see how long it takes.""" 29 | self.octave.eval("x = 1") 30 | 31 | def large_array_put(self): 32 | """Create a large matrix and load it into the octave session.""" 33 | self.octave.push("x", self.array) 34 | 35 | def large_array_get(self): 36 | """Retrieve the large matrix from the octave session""" 37 | self.octave.pull("x") 38 | 39 | def run(self): 40 | """Perform the Oct2Py speed analysis. 41 | 42 | Uses timeit to test the raw execution of an Octave command, 43 | Then tests progressively larger array passing. 44 | 45 | """ 46 | log = get_log() 47 | log.info("Oct2Py speed test") 48 | log.info("*" * 20) 49 | time.sleep(1) 50 | 51 | log.info("Raw speed: ") 52 | avg = timeit.timeit(self.raw_speed, number=10) / 10 53 | log.info(f" {avg * 1e6:0.01f} usec per loop") 54 | sides = [1, 10, 100, 1000] 55 | runs = [10, 10, 10, 5] 56 | for side, nruns in zip(sides, runs): 57 | self.array = np.reshape(np.arange(side**2), (-1)) # type:ignore[assignment] 58 | log.info(f"Put {side}x{side}: ") 59 | avg = timeit.timeit(self.large_array_put, number=nruns) / nruns 60 | log.info(f" {avg * 1e3:0.01f} msec") 61 | 62 | log.info(f"Get {side}x{side}: ") 63 | avg = timeit.timeit(self.large_array_get, number=nruns) / nruns 64 | log.info(f" {avg * 1e3:0.01f} msec") 65 | 66 | self.octave.exit() 67 | log.info("*" * 20) 68 | log.info("Test complete!") 69 | 70 | 71 | def speed_check(): 72 | """Checks the speed penalty of the Python to Octave bridge. 73 | 74 | Uses timeit to test the raw execution of a Octave command, 75 | Then tests progressively larger array passing. 76 | 77 | """ 78 | test = SpeedCheck() 79 | test.run() 80 | 81 | 82 | if __name__ == "__main__": 83 | speed_check() 84 | -------------------------------------------------------------------------------- /oct2py/thread_check.py: -------------------------------------------------------------------------------- 1 | """oct2py thread check.""" 2 | # Copyright (c) oct2py developers. 3 | # Distributed under the terms of the MIT License. 4 | 5 | 6 | import datetime 7 | import threading 8 | 9 | from . import Oct2Py, Oct2PyError, get_log 10 | 11 | 12 | class ThreadClass(threading.Thread): 13 | """Octave instance thread""" 14 | 15 | def run(self): 16 | """ 17 | Create a unique instance of Octave and verify namespace uniqueness. 18 | 19 | Raises 20 | ====== 21 | Oct2PyError 22 | If the thread does not successfully demonstrate independence 23 | 24 | """ 25 | octave = Oct2Py() 26 | # write the same variable name in each thread and read it back 27 | octave.push("name", self.name) 28 | name = octave.pull("name") 29 | now = datetime.datetime.now() # noqa 30 | logger = get_log() 31 | logger.info(f"{self.name} got '{name}' at {now}") 32 | octave.exit() 33 | if self.name != name: 34 | msg = "Thread collision detected" 35 | raise Oct2PyError(msg) 36 | 37 | 38 | def thread_check(nthreads=3): 39 | """ 40 | Start a number of threads and verify each has a unique Octave session. 41 | 42 | Parameters 43 | ========== 44 | nthreads : int 45 | Number of threads to use. 46 | 47 | Raises 48 | ====== 49 | Oct2PyError 50 | If the thread does not successfully demonstrate independence. 51 | 52 | """ 53 | logger = get_log() 54 | logger.info(f"Starting {nthreads} threads at {datetime.datetime.now()}") # noqa 55 | threads = [] 56 | for _ in range(nthreads): 57 | thread = ThreadClass() 58 | thread.daemon = True 59 | thread.start() 60 | threads.append(thread) 61 | for thread in threads: 62 | thread.join() 63 | logger.info(f"All threads closed at {datetime.datetime.now()}") # noqa 64 | 65 | 66 | if __name__ == "__main__": # pragma: no cover 67 | thread_check() 68 | -------------------------------------------------------------------------------- /oct2py/utils.py: -------------------------------------------------------------------------------- 1 | """oct2py general utils.""" 2 | # Copyright (c) oct2py developers. 3 | # Distributed under the terms of the MIT License. 4 | 5 | import logging 6 | import sys 7 | 8 | 9 | class Oct2PyError(Exception): 10 | """Called when we can't open Octave or Octave throws an error""" 11 | 12 | pass 13 | 14 | 15 | def get_log(name=None): 16 | """Return a console logger. 17 | 18 | Output may be sent to the logger using the `debug`, `info`, `warning`, 19 | `error` and `critical` methods. 20 | 21 | Parameters 22 | ---------- 23 | name : str 24 | Name of the log. 25 | """ 26 | name = "oct2py" if name is None else "oct2py." + name 27 | 28 | log = logging.getLogger(name) 29 | log.setLevel(logging.INFO) 30 | return log 31 | 32 | 33 | def _setup_log(): 34 | """Configure root logger.""" 35 | try: 36 | handler = logging.StreamHandler(stream=sys.stdout) 37 | except TypeError: # pragma: no cover 38 | handler = logging.StreamHandler(strm=sys.stdout) # type:ignore[call-overload] 39 | 40 | log = get_log() 41 | log.addHandler(handler) 42 | log.setLevel(logging.INFO) 43 | log.propagate = False 44 | 45 | 46 | _setup_log() 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.5"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "oct2py" 7 | dynamic = ["version"] 8 | description = "'Python to GNU Octave bridge --> run m-files from python.'" 9 | license = {text = "MIT"} 10 | authors = [{name = "Steven Silvester", email = "steven.silvester@ieee.org"}] 11 | classifiers = [ 12 | "Development Status :: 5 - Production/Stable", 13 | "Intended Audience :: Developers", 14 | "Intended Audience :: Science/Research", 15 | "License :: OSI Approved :: MIT License", 16 | "Operating System :: OS Independent", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Topic :: Scientific/Engineering", 20 | "Topic :: Software Development", 21 | ] 22 | requires-python = ">=3.8" 23 | dependencies = [ 24 | "numpy >=1.12", 25 | "scipy >=0.17", 26 | "octave_kernel >= 0.34.0", 27 | ] 28 | readme = "README.rst" 29 | 30 | [project.urls] 31 | homepage = "https://github.com/blink1073/oct2py" 32 | documentation = "https://blink1073.github.io/oct2py" 33 | 34 | [project.optional-dependencies] 35 | test = [ 36 | "pytest<8", 37 | "pandas", 38 | "nbconvert", 39 | "pytest-timeout", 40 | "pre-commit", 41 | ] 42 | docs = [ 43 | "sphinx", 44 | "pydata_sphinx_theme", 45 | "myst_parser", 46 | "sphinxcontrib_spelling", 47 | ] 48 | 49 | [tool.jupyter-releaser.hooks] 50 | before-build-python = ["sudo apt-get update", "sudo apt-get install -qq octave octave-signal liboctave-dev"] 51 | before-check-links = ["sudo apt-get update", "sudo apt-get install -qq octave octave-signal liboctave-dev"] 52 | 53 | [tool.hatch.version] 54 | path = "oct2py/_version.py" 55 | 56 | [tool.hatch.envs.docs] 57 | features = ["docs"] 58 | [tool.hatch.envs.docs.scripts] 59 | build = "make -C docs html SPHINXOPTS='-W'" 60 | 61 | [tool.hatch.envs.test] 62 | features = ["test"] 63 | [tool.hatch.envs.test.scripts] 64 | test = "python -m pytest -vv {args}" 65 | nowarn = "python -m pytest -vv -W default {args}" 66 | 67 | [tool.hatch.envs.cover] 68 | features = ["test"] 69 | dependencies = ["coverage", "pytest-cov"] 70 | [tool.hatch.envs.cover.env-vars] 71 | ARGS = "--doctest-modules -l --cov-report html --cov-report=xml --cov=oct2py -vv" 72 | [tool.hatch.envs.cover.scripts] 73 | test = "python -m pytest $ARGS --cov-fail-under 85 {args}" 74 | 75 | [tool.hatch.envs.lint] 76 | detached = true 77 | dependencies = ["pre-commit"] 78 | [tool.hatch.envs.lint.scripts] 79 | build = [ 80 | "pre-commit run --all-files ruff", 81 | "pre-commit run --all-files ruff-format" 82 | ] 83 | 84 | [tool.hatch.envs.typing] 85 | dependencies = [ "pre-commit"] 86 | detached = true 87 | [tool.hatch.envs.typing.scripts] 88 | test = "pre-commit run --all-files --hook-stage manual mypy" 89 | 90 | [tool.pytest.ini_options] 91 | minversion = "6.0" 92 | xfail_strict = true 93 | log_cli_level = "info" 94 | addopts = [ 95 | "-ra", "--durations=10", "--color=yes", "--doctest-modules", 96 | "--showlocals", "--strict-markers", "--strict-config" 97 | ] 98 | testpaths = ["tests", "tests/ipython"] 99 | doctest_optionflags = "NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL" 100 | timeout = 300 101 | # Restore this setting to debug failures 102 | # timeout_method = "thread" 103 | filterwarnings= [ 104 | # Fail on warnings 105 | "error", 106 | # Ignore our own user warnings 107 | "ignore:Using deprecated:UserWarning:tests", 108 | "ignore:Key - value pairs:UserWarning:tests", 109 | "module:Jupyter is migrating its paths:DeprecationWarning", 110 | "module:datetime.datetime.utcf:DeprecationWarning", 111 | "ignore:(?s).*Pyarrow will become a required dependency of pandas:DeprecationWarning", # pandas pyarrow (pandas<3.0), 112 | ] 113 | 114 | [tool.coverage.run] 115 | relative_files = true 116 | source = ["oct2py"] 117 | omit = [ 118 | "tests/*", 119 | ] 120 | 121 | [tool.mypy] 122 | strict = true 123 | disable_error_code = ["no-untyped-call", "no-untyped-def"] 124 | enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] 125 | pretty = true 126 | show_error_context = true 127 | warn_unreachable = true 128 | 129 | [tool.ruff] 130 | line-length = 100 131 | 132 | [tool.ruff.lint] 133 | select = [ 134 | "A", "B", "C", "DTZ", "E", "EM", "F", "FBT", "I", "ICN", "N", 135 | "PLC", "PLE", "PLR", "PLW", "Q", "RUF", "S", "SIM", "T", "TID", "UP", 136 | "W", "YTT", 137 | ] 138 | ignore = [ 139 | # Q000 Single quotes found but double quotes preferred 140 | "Q000", 141 | # FBT001 Boolean positional arg in function definition 142 | "FBT001", "FBT002", "FBT003", 143 | # C901 `async_setup_kernel` is too complex (12) 144 | "C901", 145 | # C408 Unnecessary `dict` call (rewrite as a literal) 146 | "C408", "C409" 147 | ] 148 | 149 | [tool.ruff.lint.per-file-ignores] 150 | # S101 Use of `assert` detected 151 | # N806 Variable `V` in function should be lowercase 152 | # PLR2004 Magic value used in comparison 153 | # SIM114 Combine `if` branches 154 | # PLR0912 Too many branches 155 | "tests/*" = ["S101", "N806", "PLR2004", "SIM114", "PLR0912"] 156 | "*.ipynb" = ["B018", "T201", "F821"] 157 | 158 | [tool.interrogate] 159 | ignore-init-module=true 160 | ignore-private=true 161 | ignore-semiprivate=true 162 | ignore-property-decorators=true 163 | ignore-nested-functions=true 164 | ignore-nested-classes=true 165 | fail-under=100 166 | exclude = ["tests", "docs"] 167 | 168 | [tool.repo-review] 169 | ignore = ["PY007", "GH102"] 170 | -------------------------------------------------------------------------------- /tests/@polynomial/display.m: -------------------------------------------------------------------------------- 1 | function display (p) 2 | %% Display a polynomial object 3 | disp('in poly display') 4 | endfunction 5 | -------------------------------------------------------------------------------- /tests/@polynomial/get.m: -------------------------------------------------------------------------------- 1 | function s = get (p, f) 2 | if (nargin == 1) 3 | s.poly = p.poly; 4 | elseif (nargin == 2) 5 | if (ischar (f)) 6 | switch (f) 7 | case "poly" 8 | s = p.poly; 9 | otherwise 10 | error ("get: invalid property %s", f); 11 | endswitch 12 | else 13 | error ("get: expecting the property to be a string"); 14 | endif 15 | else 16 | print_usage (); 17 | endif 18 | endfunction 19 | -------------------------------------------------------------------------------- /tests/@polynomial/polynomial.m: -------------------------------------------------------------------------------- 1 | ## -*- texinfo -*- 2 | ## @deftypefn {Function File} {} polynomial () 3 | ## @deftypefnx {Function File} {} polynomial (@var{a}) 4 | ## Create a polynomial object representing the polynomial 5 | ## 6 | ## @example 7 | ## a0 + a1 * x + a2 * x^2 + @dots{} + an * x^n 8 | ## @end example 9 | ## 10 | ## @noindent 11 | ## from a vector of coefficients [a0 a1 a2 @dots{} an]. 12 | ## @end deftypefn 13 | 14 | function p = polynomial (a) 15 | if (nargin == 0) 16 | p.poly = [0]; 17 | p = class (p, "polynomial"); 18 | elseif (nargin == 1) 19 | if (strcmp (class (a), "polynomial")) 20 | p = a; 21 | elseif (isvector (a) && isreal (a)) 22 | p.poly = a(:).'; 23 | p = class (p, "polynomial"); 24 | else 25 | error ("polynomial: expecting real vector"); 26 | endif 27 | else 28 | print_usage (); 29 | endif 30 | endfunction 31 | -------------------------------------------------------------------------------- /tests/@polynomial/set.m: -------------------------------------------------------------------------------- 1 | function s = set (p, varargin) 2 | s = p; 3 | if (length (varargin) < 2 || rem (length (varargin), 2) != 0) 4 | error ("set: expecting property/value pairs"); 5 | endif 6 | while (length (varargin) > 1) 7 | prop = varargin{1}; 8 | val = varargin{2}; 9 | varargin(1:2) = []; 10 | if (ischar (prop) && strcmp (prop, "poly")) 11 | if (isvector (val) && isreal (val)) 12 | s.poly = val(:).'; 13 | else 14 | error ("set: expecting the value to be a real vector"); 15 | endif 16 | else 17 | error ("set: invalid property of polynomial class"); 18 | endif 19 | endwhile 20 | endfunction 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blink1073/oct2py/92acaf882c574afc9df4f6b4fbb28fe75e97b2a5/tests/__init__.py -------------------------------------------------------------------------------- /tests/ipython/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blink1073/oct2py/92acaf882c574afc9df4f6b4fbb28fe75e97b2a5/tests/ipython/__init__.py -------------------------------------------------------------------------------- /tests/ipython/test_octavemagic.py: -------------------------------------------------------------------------------- 1 | """Tests for Octave magics extension.""" 2 | import codecs 3 | import sys 4 | import unittest 5 | from typing import Any 6 | 7 | import numpy as np 8 | from IPython.display import SVG 9 | from IPython.testing.globalipapp import get_ipython 10 | 11 | from oct2py import Oct2PyError 12 | 13 | 14 | class OctaveMagicTest(unittest.TestCase): 15 | ip: Any 16 | 17 | @classmethod 18 | def setUpClass(cls): 19 | """Set up an IPython session just once. 20 | It'd be safer to set it up for each test, but for now, 21 | I'm mimicking the IPython team's logic. 22 | """ 23 | if not sys.stdin.encoding: 24 | # needed for py.test 25 | sys.stdin = codecs.getreader("utf-8")(sys.stdin) # type:ignore 26 | cls.ip = get_ipython() 27 | # This is just to get a minimally modified version of the changes 28 | # working 29 | cls.ip.run_line_magic("load_ext", "oct2py.ipython") 30 | cls.ip.ex("import numpy as np") 31 | cls.svgs_generated = 0 # type:ignore 32 | 33 | def test_octave_inline(self): 34 | result = self.ip.run_line_magic("octave", "[1, 2, 3] + 1;") 35 | assert np.allclose(result, [[2, 3, 4]]) 36 | 37 | def test_octave_roundtrip(self): 38 | ip = self.ip 39 | ip.ex("x = np.arange(3); y = 4.5") 40 | ip.run_line_magic("octave_push", "x y") 41 | ip.run_line_magic("octave", "x = x + 1; y = y + 1;") 42 | ip.run_line_magic("octave_pull", "x y") 43 | 44 | assert np.allclose(ip.user_ns["x"], [[1, 2, 3]]) 45 | assert np.allclose(ip.user_ns["y"], 5.5) 46 | 47 | def test_octave_cell_magic(self): 48 | ip = self.ip 49 | ip.ex("x = 3; y = [1, 2]") 50 | ip.run_cell_magic("octave", "-f png -s 400,400 -i x,y -o z", "z = x + y;") 51 | assert np.allclose(ip.user_ns["z"], [[4, 5]]) 52 | 53 | def test_octave_plot(self): 54 | magic = self.ip.find_cell_magic("octave").__self__ 55 | magic._display = self._verify_display 56 | self.ip.run_cell_magic( 57 | "octave", "-f svg -s 400,500", "plot([1, 2, 3]); figure; plot([4, 5, 6]);" 58 | ) 59 | assert self.svgs_generated == 2 # type:ignore 60 | 61 | def _verify_display(self, obj): 62 | if isinstance(obj, SVG): 63 | svg = obj.data 64 | assert 'height="500px"' in svg, svg 65 | assert 'width="400px"' in svg, svg 66 | 67 | self.svgs_generated += 1 # type:ignore 68 | 69 | def test_octave_syntax_error(self): 70 | try: 71 | self.ip.run_cell_magic("octave", "", "a='1") 72 | except Oct2PyError: 73 | self.ip.run_line_magic("reload_ext", "oct2py.ipython") 74 | 75 | def test_octave_error(self): 76 | self.assertRaises(Oct2PyError, self.ip.run_cell_magic, "octave", "", "a = ones2(1)") 77 | -------------------------------------------------------------------------------- /tests/pyeval_like_error0.m: -------------------------------------------------------------------------------- 1 | function pyeval_like_error1() 2 | error('element number 1 undefined in return list') 3 | end 4 | -------------------------------------------------------------------------------- /tests/pyeval_like_error1.m: -------------------------------------------------------------------------------- 1 | function x = pyeval_like_error2() 2 | error('element number 1 undefined in return list') 3 | end 4 | -------------------------------------------------------------------------------- /tests/pyeval_like_error2.m: -------------------------------------------------------------------------------- 1 | function x = pyeval_like_error3(y) 2 | error('element number 1 undefined in return list') 3 | end 4 | -------------------------------------------------------------------------------- /tests/pyeval_like_error3.m: -------------------------------------------------------------------------------- 1 | function pyeval_like_error4(y) 2 | error('element number 1 undefined in return list') 3 | end 4 | -------------------------------------------------------------------------------- /tests/roundtrip.m: -------------------------------------------------------------------------------- 1 | 2 | function [x, cls] = roundtrip(y = 1) 3 | 4 | % returns the variable it was given, and optionally the class 5 | 6 | x = y; 7 | 8 | if nargout == 2 9 | 10 | cls = class(x); 11 | 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /tests/script_error.m: -------------------------------------------------------------------------------- 1 | disp('hi') 2 | a=b+c 3 | disp('hi again') 4 | -------------------------------------------------------------------------------- /tests/test_conversions.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any 3 | 4 | import numpy as np 5 | 6 | from oct2py import Cell, Oct2Py, Struct 7 | 8 | TYPE_CONVERSIONS = [ 9 | (int, "int32", np.int32), 10 | (int, "int64", np.int64), 11 | (float, "double", np.float64), 12 | (complex, "double", np.complex128), 13 | (str, "char", str), 14 | (bool, "logical", bool), 15 | (None, "double", np.nan), 16 | (dict, "struct", Struct), 17 | (np.int8, "int8", np.int8), 18 | (np.int16, "int16", np.int16), 19 | (np.int32, "int32", np.int32), 20 | (np.int64, "int64", np.int64), 21 | (np.uint8, "uint8", np.uint8), 22 | (np.uint16, "uint16", np.uint16), 23 | (np.uint32, "uint32", np.uint32), 24 | (np.uint64, "uint64", np.uint64), 25 | (np.float16, "double", np.float64), 26 | (np.float32, "double", np.float64), 27 | (np.float64, "double", np.float64), 28 | (str, "char", str), 29 | (np.double, "double", np.float64), 30 | (np.complex64, "double", np.complex128), 31 | (np.complex128, "double", np.complex128), 32 | ] 33 | 34 | 35 | class TestConversions: 36 | """Test the importing of all Octave data types, checking their type 37 | 38 | Uses test_datatypes.m to read in a dictionary with all Octave types 39 | Tests the types of all the values to make sure they were 40 | brought in properly. 41 | 42 | """ 43 | 44 | oc: Oct2Py 45 | data: Any 46 | 47 | @classmethod 48 | def setup_class(cls): 49 | cls.oc = Oct2Py() 50 | cls.oc.addpath(os.path.dirname(__file__)) 51 | cls.data = cls.oc.test_datatypes() 52 | 53 | @classmethod 54 | def teardown_class(cls): 55 | cls.oc.exit() 56 | 57 | def helper(self, base, keys, types): 58 | """ 59 | Perform type checking of the values 60 | 61 | Parameters 62 | ========== 63 | base : dict 64 | Sub-dictionary we are accessing. 65 | keys : array-like 66 | List of keys to test in base. 67 | types : array-like 68 | List of expected return types for the keys. 69 | 70 | """ 71 | for key, type_ in zip(keys, types): 72 | if type(base[key]) != type_: 73 | try: 74 | assert type_(base[key]) == base[key], key 75 | except ValueError: 76 | assert np.allclose(type_(base[key]), base[key]) 77 | 78 | def test_int(self): 79 | """Test incoming integer types""" 80 | keys = ["int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64"] 81 | types = [np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64] 82 | self.helper(self.data.num.int, keys, types) 83 | 84 | def test_floats(self): 85 | """Test incoming float types""" 86 | keys = ["float32", "float64", "complex", "complex_matrix"] 87 | types = [np.float64, np.float64, np.complex128, np.ndarray] 88 | self.helper(self.data.num, keys, types) 89 | assert self.data.num.complex_matrix.dtype == np.dtype("complex128") 90 | 91 | def test_misc_num(self): 92 | """Test incoming misc numeric types""" 93 | keys = ["inf", "matrix", "vector", "column_vector", "matrix3d", "matrix5d"] 94 | types = [np.float64, np.float64, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] 95 | self.helper(self.data.num, keys, types) 96 | 97 | def test_logical(self): 98 | """Test incoming logical type""" 99 | assert type(self.data.logical) == np.ndarray 100 | 101 | def test_string(self): 102 | """Test incoming string types""" 103 | keys = ["basic", "cell", "cell_array"] 104 | types = [str, Cell, Cell] 105 | self.helper(self.data.string, keys, types) 106 | assert self.data.string.char_array.shape == (3,) 107 | 108 | def test_struct_array(self): 109 | """Test incoming struct array types""" 110 | data = self.data.struct_array 111 | incoming, octave_type = self.oc.roundtrip(data, nout=2) 112 | assert incoming.tolist() == data.tolist() 113 | assert octave_type == "struct" 114 | 115 | def test_cells(self): 116 | """Test incoming cell types""" 117 | keys = ["vector", "matrix", "scalar", "string", "string_array", "empty", "array"] 118 | types = [Cell for i in range(len(keys))] 119 | self.helper(self.data.cell, keys, types) 120 | 121 | def test_cells_push_pull(self): 122 | cell_dims = [(1, 1), (1, 3), (3, 1), (1, 1, 1), (3, 1, 1, 1), (1, 3, 1, 1), (2, 2, 2)] 123 | for cell_dim in cell_dims: 124 | self.oc.eval(f"x = cell{cell_dim}; x(:)=1;") 125 | x_ = self.oc.pull("x") 126 | assert np.allclose(np.atleast_2d(np.shape(x_)), self.oc.eval("size(x)", verbose=False)) 127 | 128 | self.oc.push("x", x_) 129 | x_ = self.oc.pull("x") 130 | assert np.allclose(np.atleast_2d(np.shape(x_)), self.oc.eval("size(x)", verbose=False)) 131 | 132 | def test_python_conversions(self): 133 | """Test roundtrip python type conversions""" 134 | self.oc.addpath(os.path.dirname(__file__)) 135 | for out_type, oct_type, in_type in TYPE_CONVERSIONS: 136 | if out_type == dict: 137 | outgoing = dict(x=1) 138 | elif out_type is None: 139 | outgoing = None 140 | else: 141 | outgoing = out_type(1) 142 | incoming, octave_type = self.oc.roundtrip(outgoing, nout=2) 143 | if octave_type == "int32" and oct_type == "int64": 144 | pass 145 | elif octave_type == "char" and oct_type == "cell": 146 | pass 147 | elif octave_type == "single" and oct_type == "double": 148 | pass 149 | elif octave_type == "int64" and oct_type == "int32": 150 | pass 151 | else: 152 | assert octave_type == oct_type or ( 153 | octave_type == "double" and self.oc.convert_to_float 154 | ) 155 | if out_type is None: 156 | assert np.isnan(incoming) 157 | return 158 | 159 | if type(incoming) != in_type: 160 | if type(incoming) == np.int32 and in_type == np.int64: 161 | pass 162 | else: 163 | assert in_type(incoming) == incoming # type:ignore 164 | -------------------------------------------------------------------------------- /tests/test_datatypes.m: -------------------------------------------------------------------------------- 1 | 2 | function test = test_datatypes() 3 | % Test of returning a structure with multiple 4 | % nesting and multiple return types 5 | % Add a UTF char for test: 猫 6 | 7 | %%%%%%%%%%%%%%% 8 | % numeric types 9 | % integers 10 | test.num.int.int8 = int8(-2^7); 11 | test.num.int.int16 = int16(-2^15); 12 | test.num.int.int32 = int32(-2^31); 13 | test.num.int.int64 = int64(-2^63); 14 | test.num.int.uint8 = uint8(2^8-1); 15 | test.num.int.uint16 = uint16(2^16-1); 16 | test.num.int.uint32 = uint32(2^32-1); 17 | test.num.int.uint64 = uint64(2^64-1); 18 | 19 | %floats 20 | test.num.float32 = single(pi); 21 | test.num.float64 = double(pi); 22 | test.num.complex = 3 + 1j; 23 | test.num.complex_matrix = (1.2 + 1.1j) * magic(3); 24 | 25 | % misc 26 | test.num.inf = inf; 27 | test.num.NaN = NaN; 28 | test.num.matrix = [1 2; 3 4]; 29 | test.num.vector = [1 2 3 4]; 30 | test.num.column_vector = [1;2;3;4]; 31 | test.num.matrix3d = ones([2 3 4]) * pi; 32 | test.num.matrix5d = ones(1,2,3,4,5) * pi; 33 | 34 | %%%%%%%%%%%%%%% 35 | % logical type 36 | test.logical = [10 20 30 40 50] > 30; 37 | 38 | 39 | %%%%%%%%%%%%%%%% 40 | % sparse type 41 | test.sparse = speye(10); 42 | 43 | %%%%%%%%%%%%%%% 44 | % string types 45 | test.string.basic = 'spam'; 46 | test.string.cell = {'1'}; 47 | test.string.char_array = ['Thomas R. Lee'; ... 48 | 'Sr. Developer'; ... 49 | 'SFTware Corp.']; 50 | test.string.cell_array = {'spam', 'eggs'}; 51 | 52 | %%%%%%%%%%%%%%%% 53 | % User defined object 54 | test.object = polynomial([1,2,3]); 55 | 56 | %%%%%%%%%%%%%%% 57 | % struct array of shape 3x1 58 | test.struct_vector = [struct('key','a'); struct('key','b'); struct('key','c')]; 59 | 60 | %%%%%%%%%%%%%%% 61 | % struct array of shape 1x2 62 | test.struct_array(1).name = 'Sharon'; 63 | test.struct_array(1).age = 31; 64 | test.struct_array(2).name = 'Bill'; 65 | test.struct_array(2).age = 42; 66 | 67 | %%%%%%%%%%%%%%% 68 | % cell array types 69 | test.cell.vector = {'spam', 4.0, [1 2 3]}; 70 | test.cell.matrix = {'Bob', 40; 'Pam', 41}; 71 | test.cell.scalar = {1.8}; 72 | test.cell.string = {'1'}; 73 | test.cell.string_array = {'1', '2'}; 74 | test.cell.empty = cell(3,4,2); 75 | test.cell.array = {[0.4194 0.3629 -0.0000; 76 | 0.0376 0.3306 0.0000; 77 | 0 0 1.0000], 78 | [0.5645 -0.2903 0; 79 | 0.0699 0.1855 0.0000; 80 | 0.8500 0.8250 1.0000]}; 81 | 82 | %%%%%%%%%%%%%% 83 | % nest all of the above. 84 | test.nested = test; 85 | 86 | end 87 | -------------------------------------------------------------------------------- /tests/test_keep_matlab_shapes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | import numpy as np 5 | 6 | from oct2py import Oct2Py 7 | 8 | 9 | class TestNumpy: 10 | """Check value and type preservation of Numpy arrays""" 11 | 12 | oc: Oct2Py 13 | codes = np.typecodes["All"] 14 | 15 | @classmethod 16 | def setup_class(cls): 17 | cls.oc = Oct2Py(keep_matlab_shapes=True) 18 | cls.oc.addpath(os.path.dirname(__file__)) 19 | 20 | def teardown_class(cls): # noqa 21 | cls.oc.exit() 22 | 23 | def test_scalars(self): 24 | """Send scalar numpy types and make sure we get the same number back.""" 25 | for typecode in self.codes: 26 | if typecode == "V": 27 | continue 28 | outgoing = np.random.randint(-255, 255) + np.random.rand(1) 29 | if typecode in "US": 30 | outgoing = np.array("spam").astype(typecode) 31 | with warnings.catch_warnings(): 32 | warnings.simplefilter("ignore", RuntimeWarning) 33 | try: 34 | outgoing = outgoing.astype(typecode) 35 | except TypeError: 36 | continue 37 | incoming = self.oc.roundtrip(outgoing) 38 | try: 39 | assert np.allclose(incoming, outgoing) 40 | except (ValueError, TypeError, NotImplementedError, AssertionError): 41 | assert np.all(np.array(incoming).astype(typecode) == outgoing) 42 | 43 | def test_ndarrays(self): 44 | """Send ndarrays and make sure we get the same array back""" 45 | for typecode in self.codes: 46 | if typecode == "V": 47 | continue 48 | for ndims in [2, 3, 4]: 49 | size = [np.random.randint(1, 10) for i in range(ndims)] 50 | outgoing = np.random.randint(-255, 255, tuple(size)) 51 | try: 52 | outgoing += np.random.rand(*size).astype(outgoing.dtype, casting="unsafe") 53 | except TypeError: # pragma: no cover 54 | outgoing += np.random.rand(*size).astype(outgoing.dtype) 55 | if typecode in ["U", "S"]: 56 | outgoing = [ # type:ignore 57 | [["spam", "eggs", "hash"], ["spam", "eggs", "hash"]], 58 | [["spam", "eggs", "hash"], ["spam", "eggs", "hash"]], 59 | ] 60 | outgoing = np.array(outgoing).astype(typecode) 61 | else: 62 | try: 63 | outgoing = outgoing.astype(typecode) 64 | except TypeError: 65 | continue 66 | with warnings.catch_warnings(): 67 | warnings.simplefilter("ignore", FutureWarning) 68 | incoming = self.oc.roundtrip(outgoing) 69 | incoming = np.array(incoming) 70 | if outgoing.size == 1: 71 | outgoing = outgoing.squeeze() 72 | if len(outgoing.shape) > 2 and 1 in outgoing.shape: 73 | incoming = incoming.squeeze() 74 | outgoing = outgoing.squeeze() 75 | elif incoming.size == 1: 76 | incoming = incoming.squeeze() 77 | if typecode == "O": 78 | incoming = incoming.squeeze() 79 | outgoing = outgoing.squeeze() 80 | assert incoming.shape == outgoing.shape 81 | try: 82 | assert np.allclose(incoming, outgoing) 83 | except (AssertionError, ValueError, TypeError, NotImplementedError): 84 | if "c" in incoming.dtype.str: 85 | incoming = np.abs(incoming) 86 | outgoing = np.abs(outgoing) 87 | assert np.all(np.array(incoming).astype(typecode) == outgoing) 88 | 89 | def test_sparse(self): 90 | """Test roundtrip sparse matrices""" 91 | from scipy.sparse import csr_matrix, identity # type:ignore 92 | 93 | rand = np.random.rand(100, 100) 94 | rand = csr_matrix(rand) 95 | iden = identity(1000) 96 | for item in [rand, iden]: 97 | incoming, type_ = self.oc.roundtrip(item, nout=2) 98 | assert item.shape == incoming.shape 99 | assert item.nnz == incoming.nnz 100 | assert np.allclose(item.todense(), incoming.todense()) 101 | assert item.dtype == incoming.dtype 102 | assert type_ in ("double", "cell") 103 | 104 | def test_empty(self): 105 | """Test roundtrip empty matrices""" 106 | empty = np.empty((100, 100)) 107 | incoming, type_ = self.oc.roundtrip(empty, nout=2) 108 | assert empty.squeeze().shape == incoming.squeeze().shape 109 | assert np.allclose(empty[np.isfinite(empty)], incoming[np.isfinite(incoming)]) 110 | assert type_ == "double" 111 | 112 | def test_masked(self): 113 | """Test support for masked arrays""" 114 | test = np.random.rand(100) 115 | test = np.ma.array(test) 116 | incoming, type_ = self.oc.roundtrip(test, nout=2) 117 | assert np.allclose(test, incoming) 118 | assert test.dtype == incoming.dtype 119 | assert type_ == "double" 120 | 121 | def test_shaped_but_zero_sized(self): 122 | """Test support for shaped but zero-sized arrays""" 123 | test = np.zeros((0, 1, 2)) 124 | incoming, type_ = self.oc.roundtrip(test, nout=2) 125 | assert test.shape == incoming.shape 126 | assert test.dtype == incoming.dtype 127 | assert type_ == "double" 128 | 129 | def test_keep_matlab_shape(self): 130 | """Test support for keep_matlab_shape""" 131 | tests = [ 132 | ((1,), (1, 1)), 133 | ((2,), (1, 2)), 134 | ((1, 1), (1, 1)), 135 | ((1, 2), (1, 2)), 136 | ((2, 1), (2, 1)), 137 | ((2, 2), (2, 2)), 138 | ((1, 2, 3), (1, 2, 3)), 139 | ((2, 1, 1), (2, 1)), 140 | ] 141 | for test_out_shape, test_in_shape in tests: 142 | outgoing = np.zeros(test_out_shape) 143 | incoming, type_ = self.oc.roundtrip(outgoing, nout=2) 144 | assert incoming.shape == test_in_shape 145 | assert outgoing.dtype == incoming.dtype 146 | assert type_ == "double" 147 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import logging 3 | import os 4 | import shutil 5 | import tempfile 6 | from io import StringIO 7 | 8 | import numpy as np 9 | import pandas as pd 10 | import pytest 11 | 12 | import oct2py 13 | from oct2py import Oct2Py, Oct2PyError, StructArray 14 | 15 | 16 | class TestMisc: 17 | oc: Oct2Py 18 | 19 | @classmethod 20 | def setup_class(cls): 21 | cls.oc = Oct2Py() 22 | cls.oc.addpath(os.path.dirname(__file__)) 23 | 24 | @classmethod 25 | def teardown_class(cls): 26 | cls.oc.exit() 27 | 28 | def test_unicode_docstring(self): 29 | """Make sure unicode docstrings in Octave functions work""" 30 | help(self.oc.test_datatypes) 31 | 32 | def test_context_manager(self): 33 | """Make sure oct2py works within a context manager""" 34 | with Oct2Py() as oc1: 35 | ones = oc1.ones(1) 36 | assert ones == np.ones(1) 37 | with Oct2Py() as oc2: 38 | ones = oc2.ones(1) 39 | assert ones == np.ones(1) 40 | 41 | def test_singleton_sparses(self): 42 | """Make sure a singleton sparse matrix works""" 43 | import scipy.sparse # type:ignore 44 | 45 | data = scipy.sparse.csc_matrix(1) 46 | self.oc.push("x", data) 47 | assert np.allclose(data.toarray(), self.oc.pull("x").toarray()) 48 | self.oc.push("y", [data]) 49 | y = self.oc.pull("y") 50 | assert np.allclose(data.toarray(), y[0].toarray()) 51 | 52 | def test_logging(self): 53 | # create a stringio and a handler to log to it 54 | def get_handler(): 55 | sobj = StringIO() 56 | hdlr = logging.StreamHandler(sobj) 57 | hdlr.setLevel(logging.DEBUG) 58 | return hdlr 59 | 60 | hdlr = get_handler() 61 | self.oc.logger.addHandler(hdlr) 62 | 63 | # generate some messages (logged and not logged) 64 | self.oc.ones(1, verbose=True) 65 | 66 | self.oc.logger.setLevel(logging.DEBUG) 67 | self.oc.zeros(1) 68 | 69 | # check the output 70 | lines = hdlr.stream.getvalue().strip().split("\n") 71 | resp = "\n".join(lines) 72 | assert 'exist("zeros")' in resp 73 | assert 'exist("ones")' not in resp 74 | assert "_pyeval(" in resp 75 | 76 | # now make an object with a desired logger 77 | logger = oct2py.get_log("test") 78 | hdlr = get_handler() 79 | logger.addHandler(hdlr) 80 | logger.setLevel(logging.INFO) 81 | with Oct2Py(logger=logger) as oc2: 82 | # generate some messages (logged and not logged) 83 | oc2.ones(1, verbose=True) 84 | oc2.logger.setLevel(logging.DEBUG) 85 | oc2.zeros(1) 86 | 87 | # check the output 88 | lines = hdlr.stream.getvalue().strip().split("\n") 89 | resp = "\n".join(lines) 90 | assert 'exist("zeros")' in resp 91 | assert 'exist("ones")' not in resp 92 | assert "_pyeval(" in resp 93 | 94 | def test_demo(self): 95 | from oct2py import demo 96 | 97 | try: 98 | demo.demo(0.01, interactive=False) # type:ignore 99 | except AttributeError: 100 | demo(0.01, interactive=False) 101 | 102 | def test_threads(self): 103 | from oct2py import thread_check 104 | 105 | try: 106 | thread_check() 107 | except TypeError: 108 | thread_check.thread_check() # type:ignore 109 | 110 | def test_speed_check(self): 111 | from oct2py import speed_check 112 | 113 | try: 114 | speed_check() 115 | except TypeError: 116 | speed_check.speed_check() # type:ignore 117 | 118 | def test_plot(self): 119 | plot_dir = tempfile.mkdtemp().replace("\\", "/") 120 | self.oc.plot([1, 2, 3], plot_dir=plot_dir) 121 | assert glob.glob("%s/*" % plot_dir) 122 | assert self.oc.extract_figures(plot_dir) 123 | 124 | def test_narg_out(self): 125 | s = self.oc.svd(np.array([[1, 2], [1, 3]])) 126 | assert s.shape == (2, 1) 127 | U, S, V = self.oc.svd([[1, 2], [1, 3]], nout=3) 128 | assert U.shape == S.shape == V.shape == (2, 2) 129 | 130 | def test_help(self): 131 | help(self.oc) 132 | 133 | def test_trailing_underscore(self): 134 | x = self.oc.ones_() 135 | assert np.allclose(x, np.ones(1)) 136 | 137 | def test_pandas_series(self): 138 | data = [1, 2, 3, 4, 5, 6] 139 | series = pd.Series(data) 140 | self.oc.push("x", series) 141 | assert np.allclose(data, self.oc.pull("x")) 142 | 143 | def test_panda_dataframe(self): 144 | data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 145 | df = pd.DataFrame(data, columns=["a", "b", "c"]) 146 | self.oc.push("y", df) 147 | assert np.allclose(data, self.oc.pull("y")) 148 | 149 | def test_using_exited_session(self): 150 | with Oct2Py() as oc: 151 | oc.exit() 152 | with pytest.raises(Oct2PyError): 153 | oc.eval("ones") 154 | 155 | # def test_keyboard(self): 156 | # self.oc.eval('a=1') 157 | 158 | # stdin = sys.stdin 159 | # sys.stdin = StringIO('a\ndbquit\n') 160 | 161 | # try: 162 | # self.oc.keyboard(timeout=3) 163 | # except Oct2PyError as e: # pragma: no cover 164 | # if 'session timed out' in str(e).lower(): 165 | # # the keyboard command is not supported 166 | # # (likely using Octave 3.2) 167 | # return 168 | # else: 169 | # raise(e) 170 | # sys.stdin.flush() 171 | # sys.stdin = stdin 172 | 173 | # self.oc.pull('a') == 1 174 | 175 | def test_func_without_docstring(self): 176 | out = self.oc.test_nodocstring(5) 177 | assert out == 5 178 | assert "user-defined function" in self.oc.test_nodocstring.__doc__ 179 | assert os.path.dirname(__file__) in self.oc.test_nodocstring.__doc__ 180 | 181 | def test_func_noexist(self): 182 | with pytest.raises(Oct2PyError): 183 | self.oc.eval("oct2py_dummy") 184 | 185 | def test_timeout(self): 186 | with Oct2Py(timeout=2) as oc: 187 | oc.pause(2.1, timeout=5, nout=0) 188 | with pytest.raises(Oct2PyError): 189 | oc.pause(3, nout=0) 190 | 191 | def test_call_path(self): 192 | with Oct2Py() as oc: 193 | oc.addpath(os.path.dirname(__file__)) 194 | DATA = oc.test_datatypes() 195 | assert DATA.string.basic == "spam" 196 | 197 | def test_long_variable_name(self): 198 | name = "this_variable_name_is_over_32_char" 199 | self.oc.push(name, 1) 200 | x = self.oc.pull(name) 201 | assert x == 1 202 | 203 | def test_syntax_error_embedded(self): 204 | with pytest.raises(Oct2PyError): 205 | self.oc.eval("""eval("a='1")""") 206 | self.oc.push("b", 1) 207 | x = self.oc.pull("b") 208 | assert x == 1 209 | 210 | def test_oned_as(self): 211 | x = np.ones(10) 212 | self.oc.push("x", x) 213 | assert self.oc.pull("x").shape == x[:, np.newaxis].T.shape 214 | oc = Oct2Py(oned_as="column") 215 | oc.push("x", x) 216 | assert oc.pull("x").shape == x[:, np.newaxis].shape 217 | oc.exit() 218 | 219 | def test_temp_dir(self): 220 | temp_dir = tempfile.mkdtemp() 221 | oc = Oct2Py(temp_dir=temp_dir) 222 | oc.push("a", 1) 223 | assert len(os.listdir(temp_dir)) 224 | shutil.rmtree(temp_dir, ignore_errors=True) 225 | oc.exit() 226 | 227 | def test_clear(self): 228 | """Make sure clearing variables does not mess anything up.""" 229 | self.oc.eval("clear()") 230 | with pytest.raises(Oct2PyError): 231 | self.oc.__getattr__("clear") 232 | with pytest.raises(Oct2PyError): 233 | self.oc.feval("clear") 234 | 235 | def test_multiline_statement(self): 236 | sobj = StringIO() 237 | hdlr = logging.StreamHandler(sobj) 238 | hdlr.setLevel(logging.DEBUG) 239 | self.oc.logger.addHandler(hdlr) 240 | 241 | self.oc.logger.setLevel(logging.DEBUG) 242 | 243 | ans = self.oc.eval( 244 | """ 245 | a =1 246 | a + 1; 247 | b = 3 248 | b + 1""" 249 | ) 250 | text = hdlr.stream.getvalue().strip() 251 | self.oc.logger.removeHandler(hdlr) 252 | assert ans == 4 253 | lines = text.splitlines() 254 | lines = [line.replace(" ", " ") for line in lines] 255 | assert lines[-1] == "ans = 4" 256 | assert lines[-2] == "b = 3" 257 | assert lines[-3] == "a = 1" 258 | 259 | def test_empty_values(self): 260 | self.oc.push("a", "") 261 | assert not self.oc.pull("a") 262 | 263 | self.oc.push("a", []) 264 | assert self.oc.pull("a") == [] 265 | 266 | self.oc.push("a", None) 267 | assert np.isnan(self.oc.pull("a")) 268 | 269 | assert self.oc.struct() == [None] 270 | 271 | def test_deprecated_log(self): 272 | sobj = StringIO() 273 | hdlr = logging.StreamHandler(sobj) 274 | hdlr.setLevel(logging.DEBUG) 275 | self.oc.logger.addHandler(hdlr) 276 | 277 | self.oc.logger.setLevel(logging.DEBUG) 278 | self.oc.eval('disp("hi")', log=False) 279 | text = hdlr.stream.getvalue().strip() 280 | assert not text 281 | self.oc.logger.removeHandler(hdlr) 282 | 283 | def test_deprecated_return_both(self): 284 | text, value = self.oc.eval(['disp("hi")', "ones(3);"], return_both=True) 285 | assert text.strip() == "hi" 286 | assert np.allclose(value, np.ones((3, 3))) 287 | 288 | lines: list = [] 289 | text, value = self.oc.eval( 290 | ['disp("hi")', "ones(3);"], return_both=True, stream_handler=lines.append 291 | ) 292 | assert not text 293 | assert np.allclose(value, np.ones((3, 3))) 294 | assert lines[0].strip() == "hi" 295 | 296 | def test_logger(self): 297 | logger = self.oc.logger 298 | self.oc.logger = None 299 | assert self.oc.logger is not None 300 | assert self.oc.logger == logger 301 | 302 | def test_struct_array(self): 303 | self.oc.eval('x = struct("y", {1, 2}, "z", {3, 4});') 304 | x = self.oc.pull("x") 305 | assert set(x.fieldnames) == {"y", "z"} 306 | other = StructArray(x) 307 | assert other.shape == x.shape 308 | -------------------------------------------------------------------------------- /tests/test_nodocstring.m: -------------------------------------------------------------------------------- 1 | function [outp] = test_nodocstring(inp) 2 | outp = inp; 3 | end 4 | -------------------------------------------------------------------------------- /tests/test_numpy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | import numpy as np 5 | 6 | from oct2py import Oct2Py 7 | 8 | 9 | class TestNumpy: 10 | """Check value and type preservation of Numpy arrays""" 11 | 12 | oc: Oct2Py 13 | codes = np.typecodes["All"] 14 | 15 | @classmethod 16 | def setup_class(cls): 17 | cls.oc = Oct2Py() 18 | cls.oc.addpath(os.path.dirname(__file__)) 19 | 20 | def teardown_class(cls): # noqa 21 | cls.oc.exit() 22 | 23 | def test_scalars(self): 24 | """Send scalar numpy types and make sure we get the same number back.""" 25 | for typecode in self.codes: 26 | if typecode == "V": 27 | continue 28 | outgoing = np.random.randint(-255, 255) + np.random.rand(1) 29 | if typecode in "US": 30 | outgoing = np.array("spam").astype(typecode) 31 | with warnings.catch_warnings(): 32 | warnings.simplefilter("ignore", RuntimeWarning) 33 | try: 34 | outgoing = outgoing.astype(typecode) 35 | except TypeError: 36 | continue 37 | incoming = self.oc.roundtrip(outgoing) 38 | try: 39 | assert np.allclose(incoming, outgoing) 40 | except (ValueError, TypeError, NotImplementedError, AssertionError): 41 | assert np.all(np.array(incoming).astype(typecode) == outgoing) 42 | 43 | def test_ndarrays(self): 44 | """Send ndarrays and make sure we get the same array back""" 45 | for typecode in self.codes: 46 | if typecode == "V": 47 | continue 48 | for ndims in [2, 3, 4]: 49 | size = [np.random.randint(1, 10) for i in range(ndims)] 50 | outgoing = np.random.randint(-255, 255, tuple(size)) 51 | try: 52 | outgoing += np.random.rand(*size).astype(outgoing.dtype, casting="unsafe") 53 | except TypeError: # pragma: no cover 54 | outgoing += np.random.rand(*size).astype(outgoing.dtype) 55 | if typecode in ["U", "S"]: 56 | outgoing = [ # type:ignore 57 | [["spam", "eggs", "hash"], ["spam", "eggs", "hash"]], 58 | [["spam", "eggs", "hash"], ["spam", "eggs", "hash"]], 59 | ] 60 | outgoing = np.array(outgoing).astype(typecode) 61 | else: 62 | try: 63 | outgoing = outgoing.astype(typecode) 64 | except TypeError: 65 | continue 66 | with warnings.catch_warnings(): 67 | warnings.simplefilter("ignore", FutureWarning) 68 | incoming = self.oc.roundtrip(outgoing) 69 | incoming = np.array(incoming) 70 | if outgoing.size == 1: 71 | outgoing = outgoing.squeeze() 72 | if len(outgoing.shape) > 2 and 1 in outgoing.shape: 73 | incoming = incoming.squeeze() 74 | outgoing = outgoing.squeeze() 75 | elif incoming.size == 1: 76 | incoming = incoming.squeeze() 77 | if typecode == "O": 78 | incoming = incoming.squeeze() 79 | outgoing = outgoing.squeeze() 80 | assert incoming.shape == outgoing.shape 81 | try: 82 | assert np.allclose(incoming, outgoing) 83 | except (AssertionError, ValueError, TypeError, NotImplementedError): 84 | if "c" in incoming.dtype.str: 85 | incoming = np.abs(incoming) 86 | outgoing = np.abs(outgoing) 87 | assert np.all(np.array(incoming).astype(typecode) == outgoing) 88 | 89 | def test_sparse(self): 90 | """Test roundtrip sparse matrices""" 91 | from scipy.sparse import csr_matrix, identity # type:ignore 92 | 93 | rand = np.random.rand(100, 100) 94 | rand = csr_matrix(rand) 95 | iden = identity(1000) 96 | for item in [rand, iden]: 97 | incoming, type_ = self.oc.roundtrip(item, nout=2) 98 | assert item.shape == incoming.shape 99 | assert item.nnz == incoming.nnz 100 | assert np.allclose(item.todense(), incoming.todense()) 101 | assert item.dtype == incoming.dtype 102 | assert type_ in ("double", "cell") 103 | 104 | def test_empty(self): 105 | """Test roundtrip empty matrices""" 106 | empty = np.empty((100, 100)) 107 | incoming, type_ = self.oc.roundtrip(empty, nout=2) 108 | assert empty.squeeze().shape == incoming.squeeze().shape 109 | assert np.allclose(empty[np.isfinite(empty)], incoming[np.isfinite(incoming)]) 110 | assert type_ == "double" 111 | 112 | def test_masked(self): 113 | """Test support for masked arrays""" 114 | test = np.random.rand(100) 115 | test = np.ma.array(test) 116 | incoming, type_ = self.oc.roundtrip(test, nout=2) 117 | assert np.allclose(test, incoming) 118 | assert test.dtype == incoming.dtype 119 | assert type_ == "double" 120 | 121 | def test_shaped_but_zero_sized(self): 122 | """Test support for shaped but zero-sized arrays""" 123 | test = np.zeros((0, 1, 2)) 124 | incoming, type_ = self.oc.roundtrip(test, nout=2) 125 | assert test.shape == incoming.shape 126 | assert test.dtype == incoming.dtype 127 | assert type_ == "double" 128 | -------------------------------------------------------------------------------- /tests/test_roundtrip.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any 3 | 4 | import numpy as np 5 | 6 | from oct2py import Cell, Oct2Py, Oct2PyError, Struct 7 | 8 | TYPE_CONVERSIONS = [ 9 | (int, "double", np.float64), 10 | (int, "int64", np.int64), 11 | (float, "double", np.float64), 12 | (complex, "double", np.complex128), 13 | (str, "char", str), 14 | (bool, "logical", bool), 15 | (None, "double", np.nan), 16 | (dict, "struct", Struct), 17 | (np.int8, "int8", np.int8), 18 | (np.int16, "int16", np.int16), 19 | (np.int32, "int32", np.int32), 20 | (np.int64, "int64", np.int64), 21 | (np.uint8, "uint8", np.uint8), 22 | (np.uint16, "uint16", np.uint16), 23 | (np.uint32, "uint32", np.uint32), 24 | (np.uint64, "uint64", np.uint64), 25 | (np.float16, "double", np.float64), 26 | (np.float32, "double", np.float64), 27 | (np.float64, "double", np.float64), 28 | (np.double, "double", np.float64), 29 | (np.complex64, "double", np.complex128), 30 | (np.complex128, "double", np.complex128), 31 | ] 32 | 33 | 34 | class TestRoundTrip: 35 | """Test roundtrip value and type preservation between Python and Octave. 36 | 37 | Uses test_datatypes.m to read in a dictionary with all Octave types 38 | uses roundtrip.m to send each of the values out and back, 39 | making sure the value and the type are preserved. 40 | 41 | """ 42 | 43 | oc: Oct2Py 44 | data: Any 45 | 46 | @classmethod 47 | def setup_class(cls): 48 | cls.oc = Oct2Py() 49 | cls.oc.addpath(os.path.dirname(__file__)) 50 | cls.data = cls.oc.test_datatypes() 51 | 52 | @classmethod 53 | def teardown_class(cls): 54 | cls.oc.exit() 55 | 56 | def nested_equal(self, val1, val2): 57 | """Test for equality in a nested list or ndarray""" 58 | if isinstance(val1, list): 59 | for subval1, subval2 in zip(val1, val2): 60 | if isinstance(subval1, list): 61 | self.nested_equal(subval1, subval2) 62 | elif isinstance(subval1, np.ndarray): 63 | np.allclose(subval1, subval2) 64 | else: 65 | assert subval1 == subval2 66 | elif isinstance(val1, np.ndarray): 67 | np.allclose(val1, np.array(val2)) 68 | elif isinstance(val1, str): 69 | assert val1 == val2 70 | else: 71 | try: 72 | assert np.all(np.isnan(val1)) and np.all(np.isnan(val2)) 73 | except (AssertionError, NotImplementedError): 74 | assert np.allclose([val1], [val2]) 75 | 76 | def helper(self, outgoing, expected_type=None): 77 | """ 78 | Use roundtrip.m to make sure the data goes out and back intact. 79 | 80 | Parameters 81 | ========== 82 | outgoing : object 83 | Object to send to Octave. 84 | 85 | """ 86 | incoming = self.oc.roundtrip(outgoing) 87 | if expected_type is None: 88 | expected_type = type(outgoing) 89 | self.nested_equal(incoming, outgoing) 90 | try: 91 | assert type(incoming) == expected_type 92 | except AssertionError: 93 | if type(incoming) == np.float32 and expected_type == np.float64: 94 | pass 95 | 96 | def test_int(self): 97 | """Test roundtrip value and type preservation for integer types""" 98 | for key in ["int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64"]: 99 | self.helper(self.data.num.int[key]) 100 | 101 | def test_float(self): 102 | """Test roundtrip value and type preservation for float types""" 103 | for key in ["float64", "complex", "complex_matrix"]: 104 | self.helper(self.data.num[key]) 105 | self.helper(self.data.num["float32"], float) 106 | 107 | def test_misc_num(self): 108 | """Test roundtrip value and type preservation for misc numeric types""" 109 | for key in ["inf", "NaN", "matrix", "vector", "column_vector", "matrix3d", "matrix5d"]: 110 | self.helper(self.data.num[key]) 111 | 112 | def test_logical(self): 113 | """Test roundtrip value and type preservation for logical type""" 114 | self.helper(self.data.logical) 115 | 116 | def test_string(self): 117 | """Test roundtrip value and type preservation for string types""" 118 | self.helper(self.data.string["basic"], str) 119 | data = self.data.string["cell_array"] 120 | incoming = self.oc.roundtrip(data) 121 | assert isinstance(incoming, Cell) 122 | assert incoming.tolist() == data.tolist() 123 | 124 | def test_struct_array(self): 125 | """Test roundtrip value and type preservation for struct array types""" 126 | data = self.data.struct_array 127 | incoming = self.oc.roundtrip(data) 128 | assert incoming.name.tolist() == data.name.tolist() 129 | assert incoming.age.tolist() == data.age.tolist() 130 | 131 | def test_cell_array(self): 132 | """Test roundtrip value and type preservation for cell array types""" 133 | for key in ["vector", "matrix", "array"]: 134 | data = self.data.cell[key] 135 | incoming = self.oc.roundtrip(data) 136 | assert isinstance(incoming, Cell), type(incoming) 137 | assert incoming.squeeze().shape == data.squeeze().shape 138 | 139 | def test_octave_origin(self): 140 | """Test all of the types, originating in octave, and returning""" 141 | self.oc.eval("x = test_datatypes();") 142 | assert self.oc.pull("x") is not None 143 | self.oc.push("y", self.data) 144 | try: 145 | self.oc.isequaln # noqa 146 | func = "isequaln" 147 | except Oct2PyError: 148 | func = "isequalwithequalnans" 149 | 150 | # Handle simple objects. 151 | for key in self.data: 152 | if key not in ["nested", "sparse", "cell", "object", "struct_vector", "num"]: 153 | cmd = f"{func}(x.{key},y.{key});" 154 | assert self.oc.eval(cmd), key 155 | cmd = f"{func}(x.nested.{key},y.nested.{key});" 156 | assert self.oc.eval(cmd), key 157 | 158 | # Handle cell type. 159 | for key in self.data["cell"]: 160 | if key in ["empty", "array"]: 161 | continue 162 | cmd = f"{func}(x.cell.{key},y.cell.{key});" 163 | assert self.oc.eval(cmd), key 164 | cmd = f"{func}(x.nested.cell.{key},y.nested.cell.{key});" 165 | assert self.oc.eval(cmd), key 166 | for i in [1, 2]: 167 | cmd = "{0}(x.cell.{1}({2}),y.cell.{1}({2}))" 168 | cmd = cmd.format(func, "array", i) 169 | assert self.oc.eval(cmd, key) 170 | 171 | # Handle object type. 172 | cmd = '{0}(get(x.object, "poly"), get(y.object, "poly"))' 173 | cmd = cmd.format(func, key) 174 | assert self.oc.eval(cmd) 175 | 176 | cmd = '{0}(get(x.nested.object, "poly"), get(y.nested.object, "poly"))' 177 | cmd = cmd.format(func, key) 178 | assert self.oc.eval(cmd) 179 | 180 | # Handle sparse type. 181 | cmd = f"{func}(full(x.sparse), full(y.sparse))" 182 | assert self.oc.eval(cmd) 183 | cmd = f"{func}(full(x.nested.sparse), full(y.nested.sparse))" 184 | assert self.oc.eval(cmd) 185 | 186 | # Handle struct vector type. 187 | for i in range(self.data.struct_vector.size): 188 | cmd = "{0}(x.struct_vector({1}), y.struct_vector({1}))" 189 | assert self.oc.eval(cmd.format(func, i + 1)) 190 | cmd = "{0}(x.nested.struct_vector({1}), y.nested.struct_vector({1}))" 191 | assert self.oc.eval(cmd.format(func, i + 1)) 192 | 193 | # Handle the num type 194 | x = self.oc.pull("x") 195 | y = self.oc.pull("y") 196 | for key in self.data["num"]: 197 | if key == "int": 198 | continue 199 | if key == "NaN": 200 | assert np.isnan(x.num[key]) 201 | assert np.isnan(y.num[key]) 202 | continue 203 | assert np.allclose(x.num[key], y.num[key]) 204 | 205 | for key in self.data["num"]["int"]: 206 | assert np.allclose(x.num.int[key], y.num.int[key]) 207 | 208 | 209 | class TestBuiltins: 210 | """Test the exporting of standard Python data types, checking their type. 211 | 212 | Runs roundtrip.m and tests the types of all the values to make sure they 213 | were brought in properly. 214 | 215 | """ 216 | 217 | oc: Oct2Py 218 | 219 | @classmethod 220 | def setup_class(cls): 221 | cls.oc = Oct2Py() 222 | cls.oc.addpath(os.path.dirname(__file__)) 223 | 224 | @classmethod 225 | def teardown_class(cls): 226 | cls.oc.exit() 227 | 228 | def helper(self, outgoing, incoming=None, expected_type=None): 229 | """ 230 | Uses roundtrip.m to make sure the data goes out and back intact. 231 | 232 | Parameters 233 | ========== 234 | outgoing : object 235 | Object to send to Octave 236 | incoming : object, optional 237 | Object already retrieved from Octave 238 | 239 | """ 240 | if incoming is None: 241 | incoming = self.oc.roundtrip(outgoing) 242 | if not expected_type: 243 | for out_type, _, in_type in TYPE_CONVERSIONS: 244 | if out_type == type(outgoing): 245 | expected_type = in_type 246 | break 247 | if not expected_type: 248 | expected_type = np.ndarray 249 | try: 250 | assert incoming == outgoing 251 | except ValueError: 252 | assert np.allclose(np.array(incoming), np.array(outgoing)) 253 | if type(incoming) != expected_type: 254 | incoming = self.oc.roundtrip(outgoing) 255 | assert expected_type(incoming) == incoming 256 | 257 | def test_dict(self): 258 | """Test python dictionary""" 259 | test = dict(x="spam", y=[1, 2, 3]) 260 | incoming = self.oc.roundtrip(test) 261 | for key in incoming: 262 | self.helper(test[key], incoming[key]) 263 | 264 | def test_nested_dict(self): 265 | """Test nested python dictionary""" 266 | test = dict(x=dict(y=1e3, z=[1, 2]), y="spam") 267 | incoming = self.oc.roundtrip(test) 268 | incoming = dict(incoming) 269 | for key in test: 270 | if isinstance(test[key], dict): 271 | for subkey in test[key]: 272 | self.helper(test[key][subkey], incoming[key][subkey]) # type:ignore 273 | else: 274 | self.helper(test[key], incoming[key]) 275 | 276 | def test_set(self): 277 | """Test python set type""" 278 | test: Any = {1, 2, 3} 279 | incoming = self.oc.roundtrip(test) 280 | assert np.allclose(tuple(test), incoming) 281 | assert isinstance(incoming, np.ndarray) 282 | 283 | test = [{1, 2}] 284 | incoming = self.oc.roundtrip(test) 285 | assert isinstance(incoming, np.ndarray) 286 | assert np.allclose(incoming.tolist(), [1, 2]) 287 | 288 | def test_tuple(self): 289 | """Test python tuple type""" 290 | test = tuple((1, 2, 3)) 291 | incoming = self.oc.roundtrip(test) 292 | assert isinstance(incoming, Cell) 293 | assert incoming.squeeze().tolist() == list(test) 294 | 295 | def test_tuple_of_tuples(self): 296 | test = tuple(((1, 2), (3, 4))) 297 | incoming = self.oc.roundtrip(test) 298 | assert type(incoming) == Cell 299 | assert incoming.shape == (1, 2) 300 | incoming = incoming.squeeze() 301 | assert incoming[0].squeeze().tolist() == list(test[0]) 302 | assert incoming[1].squeeze().tolist() == list(test[1]) 303 | 304 | def test_list(self): 305 | """Test python list type""" 306 | incoming = self.oc.roundtrip([1, 2]) 307 | assert np.allclose(incoming, [1, 2]) 308 | incoming = self.oc.roundtrip(["a", "b"]) 309 | assert isinstance(incoming, Cell) 310 | assert incoming.squeeze().tolist() == ["a", "b"] 311 | 312 | def test_list_of_tuples(self): 313 | """Test python list of tuples""" 314 | test = [(1, 2), (1.5, 3.2)] 315 | incoming = self.oc.roundtrip(test) 316 | assert isinstance(incoming, Cell) 317 | incoming = incoming.squeeze() 318 | assert incoming[0].squeeze().tolist() == list(test[0]) 319 | assert incoming[1].squeeze().tolist() == list(test[1]) 320 | 321 | def test_numeric(self): 322 | """Test python numeric types""" 323 | test = np.random.randint(1000) 324 | self.helper(int(test)) 325 | self.helper(float(test)) 326 | self.helper(complex(1, 2)) 327 | 328 | def test_simple_string(self): 329 | """Test python str types""" 330 | tests = ["spam", "eggs"] 331 | for t in tests: 332 | self.helper(t) 333 | 334 | def test_nested_list(self): 335 | """Test python nested lists""" 336 | test: list = [["spam", "eggs", "baz"], ["foo ", "bar ", "baz "]] 337 | incoming = self.oc.roundtrip(test) 338 | assert isinstance(incoming, Cell) 339 | 340 | assert incoming[0, 0][0, 0] == "spam" 341 | assert incoming.shape == (1, 2) 342 | 343 | test = [[1, 2], [3, 4]] 344 | incoming = self.oc.roundtrip(test) 345 | assert isinstance(incoming, np.ndarray) 346 | assert np.allclose(incoming, test) 347 | 348 | test = [[1, 2], [3, 4, 5]] 349 | incoming = self.oc.roundtrip(test) 350 | assert isinstance(incoming, Cell) 351 | assert incoming.shape == (1, 2) 352 | 353 | def test_bool(self): 354 | """Test boolean values""" 355 | tests = (True, False) 356 | for t in tests: 357 | incoming = self.oc.roundtrip(t) 358 | assert incoming == t 359 | self.oc.convert_to_float = False 360 | incoming = self.oc.roundtrip(t) 361 | assert incoming == t 362 | self.oc.convert_to_float = True 363 | 364 | def test_none(self): 365 | """Test sending None type""" 366 | incoming = self.oc.roundtrip(None) 367 | assert np.isnan(incoming) 368 | -------------------------------------------------------------------------------- /tests/test_usage.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import pickle 4 | import tempfile 5 | from io import StringIO 6 | 7 | import numpy as np 8 | import pytest 9 | from IPython.display import SVG 10 | 11 | from oct2py import Oct2Py, Oct2PyError, Struct 12 | from oct2py.io import MatlabFunction 13 | 14 | 15 | class TestUsage: 16 | """Exercise the basic interface of the package""" 17 | 18 | oc: Oct2Py 19 | 20 | @classmethod 21 | def setup_class(cls): 22 | cls.oc = Oct2Py() 23 | cls.oc.addpath(os.path.realpath(os.path.dirname(__file__))) 24 | 25 | @classmethod 26 | def teardown_class(cls): 27 | cls.oc.exit() 28 | 29 | def test_run(self): 30 | """Test the run command""" 31 | out = self.oc.eval("ones(3,3)") 32 | desired = np.ones((3, 3)) 33 | assert np.allclose(out, desired) 34 | out = self.oc.eval("ans = mean([[1, 2], [3, 4]])", verbose=True) 35 | assert out == 2.5 36 | with pytest.raises(Oct2PyError): 37 | self.oc.eval("_spam") 38 | 39 | def test_dynamic_functions(self): 40 | """Test some dynamic functions""" 41 | out = self.oc.ones(1, 2) 42 | assert np.allclose(out, np.ones((1, 2))) 43 | 44 | U, S, V = self.oc.svd([[1, 2], [1, 3]], nout=3) 45 | assert np.allclose(U, ([[-0.57604844, -0.81741556], [-0.81741556, 0.57604844]])) 46 | assert np.allclose(S, ([[3.86432845, 0.0], [0.0, 0.25877718]])) 47 | assert np.allclose(V, ([[-0.36059668, -0.93272184], [-0.93272184, 0.36059668]])) 48 | out = self.oc.roundtrip(1) 49 | assert out == 1 50 | with pytest.raises(Oct2PyError): 51 | self.oc.eval("_spam") 52 | 53 | def test_push_pull(self): 54 | self.oc.push("spam", [1, 2]) 55 | out = self.oc.pull("spam") 56 | assert np.allclose(out, np.array([1, 2])) 57 | self.oc.push(["spam", "eggs"], ["foo", [1, 2, 3, 4]]) 58 | spam, eggs = self.oc.pull(["spam", "eggs"]) 59 | assert spam == "foo" 60 | assert np.allclose(eggs, np.array([[1, 2, 3, 4]])) 61 | 62 | def test_help(self): 63 | """Testing help command""" 64 | doc = self.oc.cos.__doc__ 65 | assert "Compute the cosine for each element of X in radians." in doc 66 | 67 | def test_dynamic(self): 68 | """Test the creation of a dynamic function""" 69 | tests = [self.oc.zeros, self.oc.ones, self.oc.plot] 70 | for item in tests: 71 | assert "class 'oct2py.dynamic" in repr(type(item)) 72 | with pytest.raises(Oct2PyError): 73 | self.oc.__getattr__("aaldkfasd") 74 | with pytest.raises(Oct2PyError): 75 | self.oc.__getattr__("_foo") 76 | with pytest.raises(Oct2PyError): 77 | self.oc.__getattr__("foo\\W") 78 | 79 | def test_open_close(self): 80 | """Test opening and closing the Octave session""" 81 | self.oc.exit() 82 | with pytest.raises(Oct2PyError): 83 | self.oc.push(name=["a"], var=[1.0]) 84 | self.oc.restart() 85 | self.oc.push("a", 5) 86 | a = self.oc.pull("a") 87 | assert a == 5 88 | 89 | def test_struct(self): 90 | """Test Struct construct""" 91 | test = Struct() 92 | test.spam = "eggs" # type:ignore 93 | test.eggs.spam = "eggs" 94 | assert test["spam"] == "eggs" 95 | assert test["eggs"]["spam"] == "eggs" 96 | test["foo"] = Struct() 97 | test["foo"]["bar"] = 10 98 | assert test.foo.bar == 10 99 | p = pickle.dumps(test) 100 | test2 = pickle.loads(p) # noqa 101 | assert test2["spam"] == "eggs" 102 | assert test2["eggs"]["spam"] == "eggs" 103 | assert test2.foo.bar == 10 104 | assert "spam" in test.__dict__ 105 | 106 | def test_syntax_error(self): 107 | """Make sure a syntax error in Octave throws an Oct2PyError""" 108 | with pytest.raises(Oct2PyError): 109 | self.oc.eval("a='1") 110 | 111 | if os.name == "nt": 112 | self.oc.restart() 113 | 114 | with pytest.raises(Oct2PyError): 115 | self.oc.eval("a=1++3") 116 | 117 | if os.name == "nt": 118 | self.oc.restart() 119 | 120 | self.oc.push("a", 1) 121 | a = self.oc.pull("a") 122 | assert a == 1 123 | 124 | def test_extract_figures(self): 125 | plot_dir = tempfile.mkdtemp().replace("\\", "/") 126 | code = """ 127 | figure 1 128 | plot([1,2,3]) 129 | figure 2 130 | temp=rand(100,100); 131 | imshow(temp) 132 | """ 133 | self.oc.eval(code, plot_dir=plot_dir, plot_format="svg") 134 | imgs = self.oc.extract_figures(plot_dir) 135 | assert len(imgs) == 2 136 | assert isinstance(imgs[0], SVG) or isinstance(imgs[1], SVG) 137 | 138 | def test_quit(self): 139 | with pytest.raises(Oct2PyError): 140 | self.oc.eval("quit") 141 | self.oc.eval("a=1") 142 | 143 | def test_octave_error(self): 144 | with pytest.raises(Oct2PyError): 145 | self.oc.eval("a = ones2(1)") 146 | 147 | def test_keyword_arguments(self): 148 | self.oc.set(0, DefaultFigureColor="b", nout=0) 149 | plot_dir = tempfile.mkdtemp().replace("\\", "/") 150 | self.oc.plot([1, 2, 3], linewidth=3, plot_dir=plot_dir) 151 | assert self.oc.extract_figures(plot_dir) 152 | 153 | def test_octave_function(self): 154 | func = MatlabFunction([1]) 155 | with pytest.raises(Oct2PyError): 156 | self.oc.push("x", func) 157 | 158 | def test_bad_getattr(self): 159 | self.oc.eval("foo = 1") 160 | with pytest.raises(Oct2PyError): 161 | self.oc.__getattr__("foo") 162 | 163 | def test_octave_class(self): 164 | self.oc.addpath(os.path.realpath(os.path.dirname(__file__))) 165 | polynomial = self.oc.polynomial 166 | p0 = polynomial([1, 2, 3]) 167 | assert np.allclose(p0.poly, [[1, 2, 3]]) 168 | 169 | p1 = polynomial([0, 1, 2]) 170 | sobj = StringIO() 171 | hdlr = logging.StreamHandler(sobj) 172 | hdlr.setLevel(logging.DEBUG) 173 | self.oc.logger.addHandler(hdlr) 174 | self.oc.logger.setLevel(logging.DEBUG) 175 | p1.display(verbose=True, nout=0) 176 | text = hdlr.stream.getvalue().strip() 177 | self.oc.logger.removeHandler(hdlr) 178 | assert "in poly display" in text 179 | 180 | self.oc.push("y", p0) 181 | p2 = self.oc.pull("y") 182 | assert np.allclose(p2.poly, [1, 2, 3]) 183 | 184 | p2.poly = [2, 3, 4] 185 | assert np.allclose(p2.poly, [2, 3, 4]) 186 | 187 | assert "Display a polynomial object" in p2.display.__doc__ 188 | 189 | self.oc.eval("p3 = polynomial([1,2,3])") 190 | p3 = self.oc.pull("p3") 191 | assert np.allclose(p3.poly, [1, 2, 3]) 192 | 193 | def test_get_pointer(self): 194 | self.oc.addpath(os.path.realpath(os.path.dirname(__file__))) 195 | self.oc.push("y", 1) 196 | yptr = self.oc.get_pointer("y") 197 | assert yptr.name == "y" 198 | assert yptr.value == 1 199 | assert yptr.address == "y" 200 | yptr.value = 2 201 | assert yptr.value == 2 202 | assert self.oc.pull("y") == 2 203 | assert "is a variable" in yptr.__doc__ 204 | ones = self.oc.ones(yptr) 205 | assert ones.shape == (2, 2) 206 | 207 | onesptr = self.oc.get_pointer("ones") 208 | assert onesptr.name == "ones" 209 | assert onesptr.address == "@ones" 210 | assert "ones" in onesptr.__doc__ 211 | 212 | sin = self.oc.get_pointer("sin") 213 | x = self.oc.quad(sin, 0, self.oc.pi()) 214 | assert x == 2 215 | 216 | self.oc.eval("p = polynomial([1,2,3])") 217 | ppter = self.oc.get_pointer("p") 218 | assert ppter.name == "p" 219 | assert ppter.address == "p" 220 | p = ppter.value 221 | assert np.allclose(p.poly, [1, 2, 3]) 222 | 223 | clsptr = self.oc.get_pointer("polynomial") 224 | value = clsptr([1, 2, 3]) 225 | assert np.allclose(value.poly, [1, 2, 3]) 226 | 227 | with pytest.raises(Oct2PyError): 228 | self.oc.get_pointer("foo123") 229 | 230 | def test_get_max_nout(self): 231 | self.oc.addpath(os.path.realpath(os.path.dirname(__file__))) 232 | here = os.path.dirname(__file__) 233 | max_nout = self.oc._get_max_nout(os.path.join(here, "roundtrip.m")) 234 | assert max_nout == 2 235 | 236 | def test_feval(self): 237 | self.oc.addpath(os.path.realpath(os.path.dirname(__file__))) 238 | a = self.oc.feval("ones", 3) 239 | assert np.allclose(a, np.ones((3, 3))) 240 | 241 | self.oc.feval("ones", 3, store_as="foo") 242 | b = self.oc.pull("foo") 243 | assert np.allclose(b, np.ones((3, 3))) 244 | 245 | self.oc.push("x", 3) 246 | ptr = self.oc.get_pointer("x") 247 | c = self.oc.feval("ones", ptr) 248 | assert np.allclose(c, np.ones((3, 3))) 249 | 250 | p = self.oc.polynomial(np.array([1, 2, 3])) 251 | poly = self.oc.feval("get", p, "poly") 252 | assert np.allclose(poly, [1, 2, 3]) 253 | 254 | val = self.oc.feval("disp", self.oc.zeros) 255 | assert val.strip() == "@zeros" 256 | 257 | lines: list = [] 258 | self.oc.feval( 259 | "evalin", "base", "disp(1);disp(2);disp(3)", nout=0, stream_handler=lines.append 260 | ) 261 | lines = [line.strip() for line in lines] 262 | assert lines == ["1", "2", "3"], lines 263 | 264 | val = self.oc.feval("svd", np.array([[1, 2], [1, 3]])) 265 | u, v, d = self.oc.feval("svd", np.array([[1, 2], [1, 3]]), nout=3) 266 | assert isinstance(val, np.ndarray) 267 | assert isinstance(u, np.ndarray) 268 | 269 | self.oc.feval("test_nodocstring.m", 1) 270 | with pytest.raises(TypeError): 271 | self.oc.feval("test_usage.py") 272 | 273 | def test_eval(self): 274 | a = self.oc.eval("ones(3);") 275 | assert np.allclose(a, np.ones((3, 3))) 276 | 277 | lines: list = [] 278 | self.oc.eval("disp(1);disp(2);disp(3)", nout=0, stream_handler=lines.append) 279 | lines = [line.strip() for line in lines] 280 | assert lines == ["1", "2", "3"], lines 281 | 282 | a = self.oc.eval(["zeros(3);", "ones(3);"]) 283 | assert np.allclose(a, np.ones((3, 3))) 284 | 285 | U, S, V = self.oc.eval("svd(hilb(3))", nout=3) 286 | assert isinstance(U, np.ndarray) 287 | 288 | def test_no_args_returned(self): 289 | # Test a function that only works when nargout=0 290 | here = os.path.dirname(__file__) 291 | self.oc.source(os.path.join(here, "roundtrip.m")) 292 | 293 | def test_script_error(self): 294 | here = os.path.dirname(__file__) 295 | with pytest.raises(Oct2PyError) as exec_info: 296 | self.oc.source(os.path.join(here, "script_error.m")) 297 | msg = str(exec_info.value) 298 | assert "Octave evaluation error:" in msg 299 | assert "error: called from:" in msg 300 | 301 | @pytest.mark.parametrize("fn", ["pyeval_like_error%s" % i for i in range(4)]) 302 | def test_script_error_like_my_pyeval(self, fn): 303 | exp = "element number 1 undefined in return list" 304 | here = os.path.dirname(__file__) 305 | with pytest.raises(Oct2PyError, match=exp): 306 | self.oc.source(os.path.join(here, "%s.m" % fn)) 307 | 308 | def test_script_error_like_my_pyeval0(self): 309 | exp = "element number 1 undefined in return list" 310 | with pytest.raises(Oct2PyError, match=exp): 311 | self.oc.pyeval_like_error0() 312 | 313 | def test_script_error_like_my_pyeval1(self): 314 | exp = "element number 1 undefined in return list" 315 | with pytest.raises(Oct2PyError, match=exp): 316 | self.oc.pyeval_like_error1() 317 | 318 | def test_script_error_like_my_pyeval2(self): 319 | exp = "element number 1 undefined in return list" 320 | with pytest.raises(Oct2PyError, match=exp): 321 | self.oc.pyeval_like_error2(1) 322 | 323 | def test_script_error_like_my_pyeval3(self): 324 | exp = "element number 1 undefined in return list" 325 | with pytest.raises(Oct2PyError, match=exp): 326 | self.oc.pyeval_like_error3(1) 327 | 328 | def test_pkg_load(self): 329 | self.oc.eval("pkg load signal") 330 | t = np.linspace(0, 1, num=100) 331 | x = np.cos(2 * np.pi * t * 3) 332 | # on Travis CI this is giving a dimension mismatch error 333 | try: 334 | y = self.oc.sgolayfilt(x, 3, 5) 335 | except Oct2PyError as e: 336 | if "dimensions mismatch" in str(e): 337 | return 338 | assert y.shape == (1, 100) 339 | 340 | def test_passing_integer_args(self): 341 | self.oc.eval( 342 | """ 343 | function [res, a, b] = foo(a, b) 344 | res = a * b; 345 | end 346 | """ 347 | ) 348 | res, a, b = self.oc.foo(np.nan, 2, nout=3) 349 | assert np.isnan(res) 350 | assert np.isnan(a) 351 | assert b == 2 352 | 353 | def test_carriage_return(self): 354 | self.oc.eval(r"disp('hi\rthere')") 355 | --------------------------------------------------------------------------------