├── examples ├── ipyparallel ├── apps │ ├── __init__.py │ ├── ipengineapp.py │ ├── launcher.py │ ├── ipcontrollerapp.py │ ├── ipclusterapp.py │ ├── iploggerapp.py │ └── logwatcher.py ├── client │ ├── __init__.py │ ├── _joblib.py │ ├── futures.py │ └── map.py ├── engine │ ├── __init__.py │ ├── __main__.py │ ├── log.py │ └── datapub.py ├── controller │ ├── __init__.py │ └── __main__.py ├── cluster │ ├── __init__.py │ └── __main__.py ├── datapub.py ├── logger.py ├── tests │ ├── _test_startup_crash.py │ ├── test_mpi.py │ ├── test_util.py │ ├── test_async.py │ ├── test_slurm.py │ ├── test_remotefunction.py │ ├── test_mongodb.py │ ├── test_joblib.py │ ├── test_executor.py │ ├── test_ssh.py │ ├── test_canning.py │ ├── test_view_broadcast.py │ └── test_dependency.py ├── nbextension │ ├── static │ │ ├── clusterlist.css │ │ └── main.js │ ├── __init__.py │ ├── install.py │ └── base.py ├── serialize │ ├── __init__.py │ └── codeutil.py ├── _version.py ├── joblib.py ├── traitlets.py ├── shellcmd.py ├── __init__.py └── _async.py ├── benchmarks ├── benchmarks │ ├── __init__.py │ ├── testing.py │ ├── constants.py │ └── utils.py ├── profiling │ ├── __init__.py │ ├── view_profiling_results.py │ ├── profiling_code.py │ └── profiling_code_runner.py ├── .gitignore ├── asv_normal.sh ├── utils.py ├── asv_quick.sh ├── explore │ ├── scheduler.py │ └── control_flow.py ├── machine_configs │ ├── asv-testing-16.json │ ├── asv-testing-32.json │ ├── asv-testing-64.json │ └── asv-testing-96.json ├── asv.conf.json ├── engines_test.py ├── logger.py ├── instance_setup.py ├── cluster_start.py ├── results │ └── asv_testing-16-2020-05-04-17-43 │ │ └── results.json └── asv_runner.py ├── ci ├── slurm │ ├── etc_slurm │ │ ├── cgroup.conf │ │ ├── slurmdbd.conf │ │ └── slurm.conf │ ├── README.md │ ├── docker-compose.yaml │ ├── Dockerfile │ └── entrypoint.sh └── ssh │ ├── docker-compose.yaml │ ├── win_docker-compose.yaml │ ├── ipcluster_config.py │ ├── win_base_Dockerfile │ ├── Dockerfile │ └── win_Dockerfile_template ├── .eslintignore ├── .yarnrc.yml ├── .binder ├── jupyter_config.json ├── requirements.txt └── postBuild ├── docs ├── source │ ├── _static │ │ ├── basic.mp4 │ │ └── IPyParallel-MPI-Example.png │ ├── reference │ │ ├── figs │ │ │ ├── dagdeps.pdf │ │ │ ├── dagdeps.png │ │ │ ├── hbfade.png │ │ │ ├── iopubfade.png │ │ │ ├── nbconvert.png │ │ │ ├── notiffade.png │ │ │ ├── queryfade.png │ │ │ ├── queuefade.png │ │ │ ├── simpledag.pdf │ │ │ ├── simpledag.png │ │ │ ├── other_kernels.png │ │ │ ├── allconnections.png │ │ │ ├── frontend-kernel.png │ │ │ ├── notebook_components.png │ │ │ └── ipy_kernel_and_terminal.png │ │ └── index.md │ ├── tutorial │ │ ├── figs │ │ │ ├── asian_put.pdf │ │ │ ├── asian_put.png │ │ │ ├── wideView.png │ │ │ ├── asian_call.pdf │ │ │ ├── asian_call.png │ │ │ ├── mec_simple.pdf │ │ │ ├── mec_simple.png │ │ │ ├── parallel_pi.pdf │ │ │ ├── parallel_pi.png │ │ │ ├── single_digits.pdf │ │ │ ├── single_digits.png │ │ │ ├── two_digit_counts.pdf │ │ │ └── two_digit_counts.png │ │ └── index.md │ ├── examples │ │ ├── broadcast │ │ │ ├── Dockerfile │ │ │ └── docker-compose.yaml │ │ ├── task_mod.py │ │ ├── rmt │ │ │ ├── rmtkernel.py │ │ │ └── rmt.ipy │ │ ├── phistogram.py │ │ ├── index.md │ │ ├── interengine │ │ │ ├── interengine.py │ │ │ ├── communicator.py │ │ │ └── bintree_script.py │ │ ├── throughput.py │ │ ├── customresults.py │ │ ├── wave2D │ │ │ └── communicator.py │ │ ├── pi │ │ │ └── parallelpi.py │ │ ├── daVinci Word Count │ │ │ ├── wordfreq.py │ │ │ └── pwordfreq.py │ │ ├── task_profiler.py │ │ ├── itermapresult.py │ │ ├── iopubwatcher.py │ │ ├── Parallel Decorator and map.ipynb │ │ ├── fetchparse.py │ │ └── dependencies.py │ ├── redirects.txt │ ├── api │ │ └── ipyparallel.rst │ └── index.md ├── requirements.txt ├── make.bat └── Makefile ├── jupyter-config ├── nbconfig │ └── tree.d │ │ └── ipyparallel.json ├── jupyter_notebook_config.d │ └── ipyparallel.json └── jupyter_server_config.d │ └── ipyparallel.json ├── lab ├── src │ ├── svg.d.ts │ ├── commands.ts │ └── sidebar.ts ├── style │ ├── code-dark.svg │ ├── code-light.svg │ └── logo.svg ├── schema │ └── plugin.json └── webpack.config.js ├── tsconfig.eslint.json ├── .prettierignore ├── install.json ├── asv.conf.json ├── .coveragerc ├── readthedocs.yml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── windows-ssh-image.yaml │ ├── release.yml │ └── test-docs.yml ├── tsconfig.json ├── README.md ├── hatch_build.py ├── CONTRIBUTING.md ├── .pre-commit-config.yaml ├── .eslintrc.js ├── COPYING.md └── package.json /examples: -------------------------------------------------------------------------------- 1 | docs/source/examples -------------------------------------------------------------------------------- /ipyparallel/apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchmarks/benchmarks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchmarks/benchmarks/testing.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchmarks/profiling/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ipyparallel/client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ipyparallel/engine/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ipyparallel/controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ci/slurm/etc_slurm/cgroup.conf: -------------------------------------------------------------------------------- 1 | CgroupPlugin=cgroup/v1 2 | -------------------------------------------------------------------------------- /ipyparallel/cluster/__init__.py: -------------------------------------------------------------------------------- 1 | from .cluster import * # noqa 2 | -------------------------------------------------------------------------------- /benchmarks/benchmarks/constants.py: -------------------------------------------------------------------------------- 1 | DEFAULT_NUMBER_OF_ENGINES = 16 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests 6 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableImmutableInstalls: false 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /ipyparallel/datapub.py: -------------------------------------------------------------------------------- 1 | from .engine.datapub import publish_data # noqa: F401 2 | -------------------------------------------------------------------------------- /.binder/jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LabApp": { 3 | "collaborative": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/_static/basic.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/_static/basic.mp4 -------------------------------------------------------------------------------- /ipyparallel/cluster/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | from .app import main 3 | 4 | main() 5 | -------------------------------------------------------------------------------- /ipyparallel/engine/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | from .app import main 3 | 4 | main() 5 | -------------------------------------------------------------------------------- /ipyparallel/controller/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | from .app import main 3 | 4 | main() 5 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | ipyparallel/ 4 | env/ 5 | html/ 6 | __pycache__ 7 | 8 | .ipynb_checkpoints 9 | -------------------------------------------------------------------------------- /jupyter-config/nbconfig/tree.d/ipyparallel.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "ipyparallel/main": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /lab/src/svg.d.ts: -------------------------------------------------------------------------------- 1 | // svg.d.ts 2 | 3 | declare module "*.svg" { 4 | const value: string; 5 | export default value; 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "include": [".eslintrc.js", "*", "lab/src/*", "lab/*.js"] 4 | } 5 | -------------------------------------------------------------------------------- /docs/source/reference/figs/dagdeps.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/dagdeps.pdf -------------------------------------------------------------------------------- /docs/source/reference/figs/dagdeps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/dagdeps.png -------------------------------------------------------------------------------- /docs/source/reference/figs/hbfade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/hbfade.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/asian_put.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/asian_put.pdf -------------------------------------------------------------------------------- /docs/source/tutorial/figs/asian_put.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/asian_put.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/wideView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/wideView.png -------------------------------------------------------------------------------- /docs/source/reference/figs/iopubfade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/iopubfade.png -------------------------------------------------------------------------------- /docs/source/reference/figs/nbconvert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/nbconvert.png -------------------------------------------------------------------------------- /docs/source/reference/figs/notiffade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/notiffade.png -------------------------------------------------------------------------------- /docs/source/reference/figs/queryfade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/queryfade.png -------------------------------------------------------------------------------- /docs/source/reference/figs/queuefade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/queuefade.png -------------------------------------------------------------------------------- /docs/source/reference/figs/simpledag.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/simpledag.pdf -------------------------------------------------------------------------------- /docs/source/reference/figs/simpledag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/simpledag.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/asian_call.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/asian_call.pdf -------------------------------------------------------------------------------- /docs/source/tutorial/figs/asian_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/asian_call.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/mec_simple.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/mec_simple.pdf -------------------------------------------------------------------------------- /docs/source/tutorial/figs/mec_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/mec_simple.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/parallel_pi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/parallel_pi.pdf -------------------------------------------------------------------------------- /docs/source/tutorial/figs/parallel_pi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/parallel_pi.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs/build 3 | htmlcov 4 | ipyparallel/labextension 5 | **/node_modules 6 | **/lib 7 | **/package.json 8 | -------------------------------------------------------------------------------- /benchmarks/asv_normal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ipcluster start -n 200 --daemon --profile=asv 3 | asv run 4 | ipcluster stop --profile=asv 5 | -------------------------------------------------------------------------------- /docs/source/reference/figs/other_kernels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/other_kernels.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/single_digits.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/single_digits.pdf -------------------------------------------------------------------------------- /docs/source/tutorial/figs/single_digits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/single_digits.png -------------------------------------------------------------------------------- /docs/source/reference/figs/allconnections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/allconnections.png -------------------------------------------------------------------------------- /docs/source/reference/figs/frontend-kernel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/frontend-kernel.png -------------------------------------------------------------------------------- /docs/source/tutorial/figs/two_digit_counts.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/two_digit_counts.pdf -------------------------------------------------------------------------------- /docs/source/tutorial/figs/two_digit_counts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/tutorial/figs/two_digit_counts.png -------------------------------------------------------------------------------- /ipyparallel/logger.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | from ipyparallel.apps import iploggerapp as app 3 | 4 | app.launch_new_instance() 5 | -------------------------------------------------------------------------------- /benchmarks/utils.py: -------------------------------------------------------------------------------- 1 | def seconds_to_ms(seconds): 2 | return round(seconds * 1000, 2) 3 | 4 | 5 | def ms_to_seconds(ms): 6 | return ms / 1000 7 | -------------------------------------------------------------------------------- /docs/source/_static/IPyParallel-MPI-Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/_static/IPyParallel-MPI-Example.png -------------------------------------------------------------------------------- /ipyparallel/tests/_test_startup_crash.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | time.sleep(int(os.environ.get("CRASH_DELAY") or "1")) 5 | os._exit(1) 6 | -------------------------------------------------------------------------------- /docs/source/reference/figs/notebook_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/notebook_components.png -------------------------------------------------------------------------------- /ipyparallel/nbextension/static/clusterlist.css: -------------------------------------------------------------------------------- 1 | .action_col { 2 | text-align: right; 3 | } 4 | 5 | input.engine_num_input { 6 | width: 60px; 7 | } 8 | -------------------------------------------------------------------------------- /docs/source/reference/figs/ipy_kernel_and_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipython/ipyparallel/HEAD/docs/source/reference/figs/ipy_kernel_and_terminal.png -------------------------------------------------------------------------------- /ci/slurm/README.md: -------------------------------------------------------------------------------- 1 | # Slurm cluster example for testing 2 | 3 | adapted and simplified from https://github.com/giovtorres/slurm-docker-cluster/ 4 | 5 | License: MIT 6 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_notebook_config.d/ipyparallel.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "ipyparallel": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_server_config.d/ipyparallel.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "ipyparallel": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.binder/requirements.txt: -------------------------------------------------------------------------------- 1 | ipywidgets 2 | jupyterlab >=3.2.4 3 | jupyterlab-link-share 4 | matplotlib 5 | networkx 6 | pidigits 7 | requests 8 | retrolab >=0.3.13 9 | wordfreq 10 | -------------------------------------------------------------------------------- /ci/ssh/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | sshd: 3 | image: ipyparallel-sshd 4 | build: 5 | context: ../.. 6 | dockerfile: ci/ssh/Dockerfile 7 | ports: 8 | - "2222:22" 9 | -------------------------------------------------------------------------------- /docs/source/examples/broadcast/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/scipy-notebook:4a6f5b7e5db1 2 | RUN mamba install -yq openmpi mpi4py 3 | RUN pip install --upgrade https://github.com/ipython/ipyparallel/archive/HEAD.tar.gz 4 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "ipyparallel", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package ipyparallel" 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/examples/task_mod.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from numpy.linalg import norm 3 | 4 | 5 | def task(n): 6 | """Generates a 1xN array and computes its 2-norm""" 7 | A = numpy.ones(n) 8 | return norm(A, 2) 9 | -------------------------------------------------------------------------------- /docs/source/reference/index.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | ```{toctree} 4 | --- 5 | maxdepth: 2 6 | --- 7 | mpi 8 | db 9 | security 10 | dag_dependencies 11 | details 12 | messages 13 | connections 14 | launchers 15 | ``` 16 | -------------------------------------------------------------------------------- /ci/ssh/win_docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | sshd: 3 | image: ipyparallel-sshd 4 | tty: true 5 | build: 6 | context: ../.. 7 | dockerfile: ci/ssh/win_Dockerfile 8 | ports: 9 | - "2222:22" 10 | -------------------------------------------------------------------------------- /benchmarks/asv_quick.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "starting engines" 3 | #ipcluster start -n 200 --daemon --profile=asv 4 | echo "starting benchmarks" 5 | asv run --quick --show-stderr 6 | echo "Benchmarks finished" 7 | #ipcluster stop --profile=asv 8 | -------------------------------------------------------------------------------- /benchmarks/explore/scheduler.py: -------------------------------------------------------------------------------- 1 | from ipyparallel.apps import ipclusterapp as app 2 | 3 | 4 | def main(): 5 | app.launch_new_instance(['start', '-n', '16', '--debug', '--profile=asv']) 6 | 7 | 8 | if __name__ == '__main__': 9 | main() 10 | -------------------------------------------------------------------------------- /lab/style/code-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lab/style/code-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/source/tutorial/index.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | ```{toctree} 4 | --- 5 | maxdepth: 2 6 | --- 7 | intro 8 | process 9 | direct 10 | task 11 | asyncresult 12 | demos 13 | ``` 14 | 15 | :::{seealso} 16 | [Tutorial example notebooks](example-tutorials) 17 | 18 | ::: 19 | -------------------------------------------------------------------------------- /asv.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "project": "ipyparallel", 4 | "project_url": "https://github.com/ipython/ipyparallel/", 5 | "repo": "https://github.com/ipython/ipyparallel.git", 6 | "environment_type": "conda", 7 | "matrix": { 8 | "numpy": [] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # pin jinja2 due to broken deprecation used in nbconvert 2 | autodoc-traits 3 | intersphinx-registry 4 | jinja2==3.0.* 5 | matplotlib 6 | myst-nb 7 | myst-parser 8 | numpy 9 | pydata-sphinx-theme 10 | sphinx 11 | sphinx-copybutton 12 | sphinxext-rediraffe 13 | -------------------------------------------------------------------------------- /lab/schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.setting-icon-class": "ipp-Logo", 3 | "jupyter.lab.setting-icon-label": "IPython Parallel", 4 | "title": "IPython Parallel", 5 | "description": "Settings for the IPython Parallel plugin.", 6 | "properties": {}, 7 | "type": "object" 8 | } 9 | -------------------------------------------------------------------------------- /benchmarks/machine_configs/asv-testing-16.json: -------------------------------------------------------------------------------- 1 | { 2 | "asv-testing-16": { 3 | "arch": "x86_64", 4 | "cpu": "Intel(R) Xeon(R) CPU @ 2.30GHz", 5 | "machine": "asv-testing-16", 6 | "os": "Linux 4.15.0-1029-gcp", 7 | "ram": "16423396" 8 | }, 9 | "version": 1 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/machine_configs/asv-testing-32.json: -------------------------------------------------------------------------------- 1 | { 2 | "asv-testing-16": { 3 | "arch": "x86_64", 4 | "cpu": "Intel(R) Xeon(R) CPU @ 2.30GHz", 5 | "machine": "asv-testing-32", 6 | "os": "Linux 4.15.0-1029-gcp", 7 | "ram": "32423396" 8 | }, 9 | "version": 1 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/machine_configs/asv-testing-64.json: -------------------------------------------------------------------------------- 1 | { 2 | "asv-testing-64": { 3 | "arch": "x86_64", 4 | "cpu": "Intel(R) Xeon(R) CPU @ 2.30GHz", 5 | "machine": "asv-testing-64", 6 | "os": "Linux 4.15.0-1029-gcp", 7 | "ram": "64423396" 8 | }, 9 | "version": 1 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/asv.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "project": "ipyparallel", 4 | "project_url": "https://github.com/ipython/ipyparallel/", 5 | "repo": "https://github.com/ipython/ipyparallel.git", 6 | "environment_type": "conda", 7 | "matrix": { 8 | "numpy": [] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/machine_configs/asv-testing-96.json: -------------------------------------------------------------------------------- 1 | { 2 | "asv-testing-16-16": { 3 | "arch": "x86_64", 4 | "cpu": "Intel(R) Xeon(R) CPU @ 2.30GHz", 5 | "machine": "asv-testing-96-96", 6 | "os": "Linux 4.15.0-1029-gcp", 7 | "ram": "96423396" 8 | }, 9 | "version": 1 10 | } 11 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel = True 3 | omit = 4 | ipyparallel/tests/* 5 | 6 | [report] 7 | exclude_lines = 8 | if self.debug: 9 | pragma: no cover 10 | raise NotImplementedError 11 | if __name__ == .__main__.: 12 | ignore_errors = True 13 | omit = 14 | ipyparallel/tests/* 15 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.10" 7 | nodejs: "18" 8 | 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | python: 13 | install: 14 | # install ipp itself 15 | - path: . 16 | - requirements: docs/requirements.txt 17 | -------------------------------------------------------------------------------- /ipyparallel/apps/ipengineapp.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn(f"{__name__} is deprecated in ipyparallel 7. Use ipyparallel.engine") 4 | 5 | from ipyparallel.engine.app import IPEngine, main 6 | 7 | IPEngineApp = IPEngine 8 | launch_new_instance = main 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /ipyparallel/apps/launcher.py: -------------------------------------------------------------------------------- 1 | """Deprecated import for ipyparallel.cluster.launcher""" 2 | 3 | import warnings 4 | 5 | from ipyparallel.cluster.launcher import * # noqa 6 | 7 | warnings.warn( 8 | f"{__name__} is deprecated in ipyparallel 7. Use ipyparallel.cluster.launcher.", 9 | DeprecationWarning, 10 | ) 11 | -------------------------------------------------------------------------------- /lab/webpack.config.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | 3 | // Workaround for loaders using "md4" by default, which is not supported in FIPS-compliant OpenSSL 4 | const cryptoOrigCreateHash = crypto.createHash; 5 | crypto.createHash = (algorithm) => 6 | cryptoOrigCreateHash(algorithm == "md4" ? "sha256" : algorithm); 7 | -------------------------------------------------------------------------------- /ipyparallel/apps/ipcontrollerapp.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn(f"{__name__} is deprecated in ipyparallel 7. Use ipyparallel.controller") 4 | 5 | from ipyparallel.controller.app import IPController, main 6 | 7 | IPControllerApp = IPController 8 | launch_new_instance = main 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /ipyparallel/apps/ipclusterapp.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn(f"{__name__} is deprecated in ipyparallel 7. Use ipyparallel.cluster") 4 | 5 | from ipyparallel.cluster.app import IPCluster, IPClusterStart, main # noqa 6 | 7 | IPClusterApp = IPCluster 8 | launch_new_instance = main 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /ipyparallel/nbextension/__init__.py: -------------------------------------------------------------------------------- 1 | """Jupyter server extension(s)""" 2 | 3 | import warnings 4 | 5 | 6 | def load_jupyter_server_extension(app): 7 | warnings.warn( 8 | "Using ipyparallel.nbextension as server extension is deprecated in IPython Parallel 7.0. Use top-level ipyparallel.", 9 | DeprecationWarning, 10 | ) 11 | from ipyparallel import _load_jupyter_server_extension 12 | 13 | return _load_jupyter_server_extension(app) 14 | -------------------------------------------------------------------------------- /benchmarks/engines_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | from subprocess import check_call 3 | 4 | import ipyparallel as ipp 5 | 6 | n = 20 7 | 8 | 9 | check_call(f'ipcluster start -n {n} --daemon --profile=asv --debug', shell=True) 10 | c = ipp.Client(profile='asv') 11 | seen = -1 12 | 13 | running_engines = len(c) 14 | 15 | while running_engines < n: 16 | if seen != running_engines: 17 | print(running_engines) 18 | seen = running_engines 19 | running_engines = len(c) 20 | time.sleep(0.1) 21 | 22 | check_call('ipcluster stop --profile=asv', shell=True) 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | build 3 | dist 4 | _build 5 | docs/man/*.gz 6 | docs/source/api/generated 7 | docs/source/config/options 8 | docs/source/interactive/magics-generated.txt 9 | examples/daVinci Word Count/*.txt 10 | examples/testdill.py 11 | *.py[co] 12 | __pycache__ 13 | *.egg-info 14 | *~ 15 | *.bak 16 | .ipynb_checkpoints 17 | .tox 18 | .DS_Store 19 | \#*# 20 | .#* 21 | .coverage 22 | *coverage.xml 23 | .coverage.* 24 | .idea 25 | htmlcov 26 | id_*sa 27 | .vscode 28 | 29 | node_modules 30 | lib 31 | 32 | ipyparallel/labextension 33 | tsconfig.tsbuildinfo 34 | dask-worker-space 35 | .yarn 36 | package-lock.json 37 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | jlpm --prefer-offline --ignore-optional --ignore-scripts 3 | 4 | jlpm build 5 | 6 | IPP_DISABLE_JS=1 python -m pip install -e .[test,benchmark] 7 | 8 | jupyter serverextension enable --sys-prefix --py ipyparallel 9 | jupyter serverextension list 10 | 11 | jupyter server extension enable --sys-prefix --py ipyparallel 12 | jupyter server extension list 13 | 14 | jupyter nbextension install --symlink --sys-prefix --py ipyparallel 15 | jupyter nbextension enable --sys-prefix --py ipyparallel 16 | jupyter nbextension list 17 | 18 | jlpm install:extension 19 | jupyter labextension list 20 | -------------------------------------------------------------------------------- /ipyparallel/engine/log.py: -------------------------------------------------------------------------------- 1 | from zmq.log.handlers import PUBHandler 2 | 3 | 4 | class EnginePUBHandler(PUBHandler): 5 | """A simple PUBHandler subclass that sets root_topic""" 6 | 7 | engine = None 8 | 9 | def __init__(self, engine, *args, **kwargs): 10 | PUBHandler.__init__(self, *args, **kwargs) 11 | self.engine = engine 12 | 13 | @property 14 | def root_topic(self): 15 | """this is a property, in case the handler is created 16 | before the engine gets registered with an id""" 17 | if isinstance(getattr(self.engine, 'id', None), int): 18 | return f"engine.{self.engine.id}" 19 | else: 20 | return "engine" 21 | -------------------------------------------------------------------------------- /ipyparallel/serialize/__init__.py: -------------------------------------------------------------------------------- 1 | from .canning import ( 2 | Reference, 3 | can, 4 | can_map, 5 | uncan, 6 | uncan_map, 7 | use_cloudpickle, 8 | use_dill, 9 | use_pickle, 10 | ) 11 | from .serialize import ( 12 | PrePickled, 13 | deserialize_object, 14 | pack_apply_message, 15 | serialize_object, 16 | unpack_apply_message, 17 | ) 18 | 19 | __all__ = ( 20 | 'Reference', 21 | 'PrePickled', 22 | 'can_map', 23 | 'uncan_map', 24 | 'can', 25 | 'uncan', 26 | 'use_dill', 27 | 'use_cloudpickle', 28 | 'use_pickle', 29 | 'serialize_object', 30 | 'deserialize_object', 31 | 'pack_apply_message', 32 | 'unpack_apply_message', 33 | ) 34 | -------------------------------------------------------------------------------- /benchmarks/benchmarks/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | from collections.abc import Callable 4 | 5 | 6 | def wait_for(condition: Callable): 7 | for _ in range(750): 8 | if condition(): 9 | break 10 | else: 11 | time.sleep(0.1) 12 | if not condition(): 13 | raise TimeoutError('wait_for took to long to finish') 14 | 15 | 16 | def echo(delay=0): 17 | def inner_echo(x, **kwargs): 18 | import time 19 | 20 | if delay: 21 | time.sleep(delay) 22 | return x 23 | 24 | return inner_echo 25 | 26 | 27 | def get_time_stamp() -> str: 28 | return ( 29 | str(datetime.datetime.now()).split(".")[0].replace(" ", "-").replace(":", "-") 30 | ) 31 | -------------------------------------------------------------------------------- /ipyparallel/tests/test_mpi.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | import pytest 4 | 5 | from .test_cluster import ( 6 | test_get_output, # noqa: F401 7 | test_restart_engines, # noqa: F401 8 | test_signal_engines, # noqa: F401 9 | test_start_stop_cluster, # noqa: F401 10 | test_to_from_dict, # noqa: F401 11 | ) 12 | 13 | # import tests that use engine_launcher_class fixture 14 | 15 | 16 | # override engine_launcher_class 17 | @pytest.fixture 18 | def engine_launcher_class(): 19 | if shutil.which("mpiexec") is None: 20 | pytest.skip("Requires mpiexec") 21 | return 'mpi' 22 | 23 | 24 | @pytest.fixture 25 | def controller_launcher_class(): 26 | if shutil.which("mpiexec") is None: 27 | pytest.skip("Requires mpiexec") 28 | return 'mpi' 29 | -------------------------------------------------------------------------------- /ci/ssh/ipcluster_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | c = get_config() # noqa 4 | 5 | c.Cluster.controller_ip = '0.0.0.0' 6 | c.Cluster.engine_launcher_class = 'SSH' 7 | 8 | ssh_key = os.path.join(os.path.dirname(__file__), "id_rsa") 9 | c.Cluster.controller_ip = '0.0.0.0' 10 | c.Cluster.engine_launcher_class = 'SSH' 11 | c.SSHEngineSetLauncher.scp_args = c.SSHLauncher.ssh_args = [ 12 | "-o", 13 | "UserKnownHostsFile=/dev/null", 14 | "-o", 15 | "StrictHostKeyChecking=no", 16 | "-i", 17 | ssh_key, 18 | ] 19 | c.SSHEngineSetLauncher.engines = {"ciuser@127.0.0.1:2222": 4} 20 | c.SSHEngineSetLauncher.remote_python = "/opt/conda/bin/python3" 21 | c.SSHEngineSetLauncher.remote_profile_dir = "/home/ciuser/.ipython/profile_default" 22 | c.SSHEngineSetLauncher.engine_args = ['--debug'] 23 | -------------------------------------------------------------------------------- /ci/ssh/win_base_Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1.2.1 2 | FROM python:3.12-windowsservercore-ltsc2022 3 | SHELL ["powershell"] 4 | 5 | 6 | RUN Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'; \ 7 | Write-Host 'Install OpenSSH Server...'; \ 8 | Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0; \ 9 | Write-Host 'Initializing OpenSSH Server...'; \ 10 | Start-Service sshd; \ 11 | Stop-Service sshd 12 | 13 | # This is apparently the only way to keep the sshd service running. 14 | # Running sshd in the foreground in the context of a user (as it is done for linux), doesn't work under Windows. 15 | # Even if it is started as admin user, errors occur during logon (lack of some system rights) 16 | CMD powershell -NoExit -Command "Start-Service sshd" 17 | -------------------------------------------------------------------------------- /ipyparallel/_version.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | __version__ = "9.1.0.dev" 4 | 5 | # matches tbump regex in pyproject.toml 6 | _version_regex = re.compile( 7 | r''' 8 | (?P\d+) 9 | \. 10 | (?P\d+) 11 | \. 12 | (?P\d+) 13 | (?P
((a|b|rc)\d+))?
14 |   (\.
15 |     (?Pdev\d*)
16 |   )?
17 |   ''',
18 |     re.VERBOSE,
19 | )
20 | 
21 | _version_fields = _version_regex.match(__version__).groupdict()
22 | version_info = tuple(
23 |     field
24 |     for field in (
25 |         int(_version_fields["major"]),
26 |         int(_version_fields["minor"]),
27 |         int(_version_fields["patch"]),
28 |         _version_fields["pre"],
29 |         _version_fields["dev"],
30 |     )
31 |     if field is not None
32 | )
33 | 
34 | __all__ = ["__version__", "version_info"]
35 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_util.py:
--------------------------------------------------------------------------------
 1 | import socket
 2 | 
 3 | import pytest
 4 | from jupyter_client.localinterfaces import localhost, public_ips
 5 | 
 6 | from ipyparallel import util
 7 | 
 8 | 
 9 | def test_disambiguate_ip():
10 |     # garbage in, garbage out
11 |     assert util.disambiguate_ip_address('garbage') == 'garbage'
12 |     assert util.disambiguate_ip_address('0.0.0.0', socket.gethostname()) == localhost()
13 |     wontresolve = 'this.wontresolve.dns'
14 |     with pytest.warns(
15 |         RuntimeWarning, match=f"IPython could not determine IPs for {wontresolve}"
16 |     ):
17 |         assert util.disambiguate_ip_address('0.0.0.0', wontresolve) == wontresolve
18 |     if public_ips():
19 |         public_ip = public_ips()[0]
20 |         assert util.disambiguate_ip_address('0.0.0.0', public_ip) == localhost()
21 | 


--------------------------------------------------------------------------------
/docs/source/redirects.txt:
--------------------------------------------------------------------------------
 1 | "asyncresult.rst" "tutorial/asyncresult.md"
 2 | "dag_dependencies.rst" "reference/dag_dependencies.md"
 3 | "db.rst" "reference/db.md"
 4 | "demos.rst" "tutorial/demos.md"
 5 | "details.rst" "reference/details.md"
 6 | "development/connections.rst" "reference/connections.md"
 7 | "development/messages.rst" "reference/messages.md"
 8 | "intro.rst" "tutorial/intro.md"
 9 | "magics.rst" "examples/Parallel Magics.ipynb"
10 | "tutorial/magics.md" "examples/Parallel Magics.ipynb"
11 | "mpi.rst" "reference/mpi.md"
12 | "multiengine.rst" "tutorial/direct.md"
13 | "process.rst" "tutorial/process.md"
14 | "security.rst" "reference/security.md"
15 | "task.rst" "tutorial/task.md"
16 | "transition.rst" "tutorial/index.md"
17 | "winhpc.rst" "reference/launchers.md"
18 | 
19 | "examples/Index.ipynb" "examples/index.md"
20 | 


--------------------------------------------------------------------------------
/lab/src/commands.ts:
--------------------------------------------------------------------------------
 1 | export namespace CommandIDs {
 2 |   /**
 3 |    * Inject client code into the active editor.
 4 |    */
 5 |   export const injectClientCode = "ipyparallel:inject-client-code";
 6 | 
 7 |   /**
 8 |    * Launch a new cluster.
 9 |    */
10 |   export const newCluster = "ipyparallel:new-cluster";
11 | 
12 |   /**
13 |    * Launch a new cluster.
14 |    */
15 |   export const startCluster = "ipyparallel:start-cluster";
16 | 
17 |   /**
18 |    * Shutdown a cluster.
19 |    */
20 |   export const stopCluster = "ipyparallel:stop-cluster";
21 | 
22 |   /**
23 |    * Scale a cluster.
24 |    */
25 |   export const scaleCluster = "ipyparallel:scale-cluster";
26 | 
27 |   /**
28 |    * Toggle the auto-starting of clients.
29 |    */
30 |   export const toggleAutoStartClient = "ipyparallel:toggle-auto-start-client";
31 | }
32 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
 1 | # dependabot.yaml reference: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
 2 | version: 2
 3 | updates:
 4 |   # Maintain dependencies in our GitHub Workflows
 5 |   - package-ecosystem: github-actions
 6 |     directory: "/"
 7 |     schedule:
 8 |       interval: monthly
 9 | 
10 |   # jupyterlab extension
11 |   - package-ecosystem: npm
12 |     directory: "/"
13 |     schedule:
14 |       interval: monthly
15 |     groups:
16 |       # one big pull request
17 |       lab-minor:
18 |         patterns:
19 |           - "*"
20 |         update-types:
21 |           - minor
22 |           - patch
23 |       # group major bumps
24 |       lab-major:
25 |         patterns:
26 |           - "*"
27 |         update-types:
28 |           - major
29 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "allowSyntheticDefaultImports": true,
 4 |     "composite": true,
 5 |     "declaration": true,
 6 |     "esModuleInterop": true,
 7 |     "incremental": true,
 8 |     "lib": [
 9 |       "DOM",
10 |       "DOM.Iterable",
11 |       "ES2018",
12 |       "ES2020.BigInt",
13 |       "ES2020.Intl",
14 |       "ES2020.String"
15 |     ],
16 |     "jsx": "react",
17 |     "module": "esnext",
18 |     "moduleResolution": "node",
19 |     "noEmitOnError": true,
20 |     "noImplicitAny": true,
21 |     "noUnusedLocals": true,
22 |     "preserveWatchOutput": true,
23 |     "resolveJsonModule": true,
24 |     "outDir": "lab/lib",
25 |     "rootDir": "lab/src",
26 |     "strict": true,
27 |     "strictNullChecks": false,
28 |     "target": "es2018",
29 |     "types": []
30 |   },
31 |   "include": ["lab/src/*"]
32 | }
33 | 


--------------------------------------------------------------------------------
/benchmarks/logger.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | from datetime import date
 3 | 
 4 | from ipyparallel_master_project.benchmarks.utils import get_time_stamp
 5 | 
 6 | LOGS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs')
 7 | GCLOUD_DIR = os.path.join(LOGS_DIR, 'gcloud_output')
 8 | PROFILING_DIR = os.path.join(LOGS_DIR, 'profiling')
 9 | TODAY = str(date.today())
10 | 
11 | 
12 | def get_dir(main_dir):
13 |     target_dir = f"{main_dir}/{TODAY}"
14 |     if not os.path.exists(target_dir):
15 |         os.makedirs(target_dir)
16 |     return target_dir
17 | 
18 | 
19 | def get_profiling_log_file_name():
20 |     return os.path.join(get_dir(PROFILING_DIR), f'profiling_{get_time_stamp()}')
21 | 
22 | 
23 | def get_gcloud_log_file_name(instance_name):
24 |     return os.path.join(get_dir(GCLOUD_DIR), instance_name)
25 | 
26 | 
27 | if __name__ == "__main__":
28 |     print(LOGS_DIR)
29 | 


--------------------------------------------------------------------------------
/ci/slurm/etc_slurm/slurmdbd.conf:
--------------------------------------------------------------------------------
 1 | #
 2 | # Example slurmdbd.conf file.
 3 | #
 4 | # See the slurmdbd.conf man page for more information.
 5 | #
 6 | # Archive info
 7 | #ArchiveJobs=yes
 8 | #ArchiveDir="/tmp"
 9 | #ArchiveSteps=yes
10 | #ArchiveScript=
11 | #JobPurge=12
12 | #StepPurge=1
13 | #
14 | # Authentication info
15 | AuthType=auth/munge
16 | #AuthInfo=/var/run/munge/munge.socket.2
17 | #
18 | # slurmDBD info
19 | DbdAddr=slurmdbd
20 | DbdHost=slurmdbd
21 | #DbdPort=6819
22 | SlurmUser=slurm
23 | #MessageTimeout=300
24 | DebugLevel=4
25 | #DefaultQOS=normal,standby
26 | LogFile=/var/log/slurm/slurmdbd.log
27 | PidFile=/var/run/slurmdbd/slurmdbd.pid
28 | #PluginDir=/usr/lib/slurm
29 | #PrivateData=accounts,users,usage,jobs
30 | #TrackWCKey=yes
31 | #
32 | # Database info
33 | StorageType=accounting_storage/mysql
34 | StorageHost=mysql
35 | StorageUser=slurm
36 | StoragePass=password
37 | #StorageLoc=slurm_acct_db
38 | 


--------------------------------------------------------------------------------
/lab/style/logo.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 8 |    
 9 |     
11 |       
15 |       
19 |   
20 | 
21 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Interactive Parallel Computing with IPython
 2 | 
 3 | IPython Parallel (`ipyparallel`) is a Python package and collection of CLI scripts for controlling clusters of IPython processes, built on the Jupyter protocol.
 4 | 
 5 | IPython Parallel provides the following commands:
 6 | 
 7 | - ipcluster - start/stop/list clusters
 8 | - ipcontroller - start a controller
 9 | - ipengine - start an engine
10 | 
11 | ## Install
12 | 
13 | Install IPython Parallel:
14 | 
15 |     pip install ipyparallel
16 | 
17 | This will install and enable the IPython Parallel extensions
18 | for Jupyter Notebook and (as of 7.0) Jupyter Lab 3.0.
19 | 
20 | ## Run
21 | 
22 | Start a cluster:
23 | 
24 |     ipcluster start
25 | 
26 | Use it from Python:
27 | 
28 | ```python
29 | import os
30 | import ipyparallel as ipp
31 | 
32 | cluster = ipp.Cluster(n=4)
33 | with cluster as rc:
34 |     ar = rc[:].apply_async(os.getpid)
35 |     pid_map = ar.get_dict()
36 | ```
37 | 
38 | See [the docs](https://ipyparallel.readthedocs.io) for more info.
39 | 


--------------------------------------------------------------------------------
/benchmarks/profiling/view_profiling_results.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | master_project_path = os.path.abspath(
 4 |     os.path.join(os.path.dirname(__file__), os.pardir)
 5 | )
 6 | ALL_RESULTS_DIRECTORY = os.path.join(master_project_path, 'results', 'profiling')
 7 | 
 8 | 
 9 | def get_latest_results_dir():
10 |     return os.path.join(
11 |         'results',
12 |         'profiling',
13 |         max(
14 |             dirname
15 |             for dirname in os.listdir(ALL_RESULTS_DIRECTORY)
16 |             if 'initial_results' not in dirname
17 |         ),
18 |     )
19 | 
20 | 
21 | def get_initial_results_dir():
22 |     return os.path.join(
23 |         'results',
24 |         'profiling',
25 |         next(
26 |             (
27 |                 dirname
28 |                 for dirname in os.listdir(ALL_RESULTS_DIRECTORY)
29 |                 if 'initial_results' in dirname
30 |             ),
31 |             max(dirname for dirname in os.listdir(ALL_RESULTS_DIRECTORY)),
32 |         ),
33 |     )
34 | 
35 | 
36 | if __name__ == '__main__':
37 |     print(get_latest_results_dir())
38 | 


--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
 1 | @ECHO OFF
 2 | 
 3 | pushd %~dp0
 4 | 
 5 | REM Command file for Sphinx documentation
 6 | 
 7 | if "%SPHINXBUILD%" == "" (
 8 | 	set SPHINXBUILD=--color -W --keep-going
 9 | )
10 | if "%SPHINXBUILD%" == "" (
11 | 	set SPHINXBUILD=sphinx-build
12 | )
13 | set SOURCEDIR=source
14 | set BUILDDIR=_build
15 | 
16 | if "%1" == "" goto help
17 | if "%1" == "devenv" goto devenv
18 | goto default
19 | 
20 | 
21 | :default
22 | %SPHINXBUILD% >NUL 2>NUL
23 | if errorlevel 9009 (
24 | 	echo.
25 | 	echo.The 'sphinx-build' command was not found. Open and read README.md!
26 | 	exit /b 1
27 | )
28 | %SPHINXBUILD% -M %1 "%SOURCEDIR%" "%BUILDDIR%" %SPHINXOPTS%
29 | goto end
30 | 
31 | 
32 | :help
33 | %SPHINXBUILD% -M help "%SOURCEDIR%" "%BUILDDIR%" %SPHINXOPTS%
34 | goto end
35 | 
36 | 
37 | :devenv
38 | sphinx-autobuild >NUL 2>NUL
39 | if errorlevel 9009 (
40 | 	echo.
41 | 	echo.The 'sphinx-autobuild' command was not found. Open and read README.md!
42 | 	exit /b 1
43 | )
44 | sphinx-autobuild -b html --open-browser "%SOURCEDIR%" "%BUILDDIR%/html"
45 | goto end
46 | 
47 | 
48 | :end
49 | popd
50 | 


--------------------------------------------------------------------------------
/ipyparallel/nbextension/install.py:
--------------------------------------------------------------------------------
 1 | """Install the IPython clusters tab in the Jupyter notebook dashboard
 2 | 
 3 | Only applicable for notebook < 7
 4 | """
 5 | 
 6 | # Copyright (c) IPython Development Team.
 7 | # Distributed under the terms of the Modified BSD License.
 8 | 
 9 | 
10 | def install_extensions(enable=True, user=False):
11 |     """Register ipyparallel clusters tab as notebook extensions
12 | 
13 |     Toggle with enable=True/False.
14 |     """
15 |     import notebook  # noqa
16 | 
17 |     from notebook.nbextensions import (
18 |         disable_nbextension,
19 |         enable_nbextension,
20 |         install_nbextension_python,
21 |     )
22 |     from notebook.serverextensions import toggle_serverextension_python
23 | 
24 |     toggle_serverextension_python('ipyparallel', user=user)
25 |     install_nbextension_python('ipyparallel', user=user)
26 |     if enable:
27 |         enable_nbextension('tree', 'ipyparallel/main', user=user)
28 |     else:
29 |         disable_nbextension('tree', 'ipyparallel/main')
30 | 
31 | 
32 | install_server_extension = install_extensions
33 | 


--------------------------------------------------------------------------------
/ipyparallel/joblib.py:
--------------------------------------------------------------------------------
 1 | """IPython parallel backend for joblib
 2 | 
 3 | To enable the default view as a backend for joblib::
 4 | 
 5 |     import ipyparallel as ipp
 6 |     ipp.register_joblib_backend()
 7 | 
 8 | Or to enable a particular View you have already set up::
 9 | 
10 |     view.register_joblib_backend()
11 | 
12 | At this point, you can use it with::
13 | 
14 |     with parallel_backend('ipyparallel'):
15 |         Parallel(n_jobs=2)(delayed(some_function)(i) for i in range(10))
16 | 
17 | .. versionadded:: 5.1
18 | """
19 | 
20 | # Copyright (c) IPython Development Team.
21 | # Distributed under the terms of the Modified BSD License.
22 | from joblib.parallel import register_parallel_backend
23 | 
24 | from .client._joblib import IPythonParallelBackend
25 | 
26 | 
27 | def register(name='ipyparallel', make_default=False):
28 |     """Register the default ipyparallel Client as a joblib backend
29 | 
30 |     See joblib.parallel.register_parallel_backend for details.
31 |     """
32 |     return register_parallel_backend(
33 |         name, IPythonParallelBackend, make_default=make_default
34 |     )
35 | 


--------------------------------------------------------------------------------
/benchmarks/explore/control_flow.py:
--------------------------------------------------------------------------------
 1 | import numpy as np
 2 | 
 3 | import ipyparallel as ipp
 4 | 
 5 | 
 6 | def main():
 7 |     client = ipp.Client(profile='asv')
 8 |     # client.debug = True
 9 |     direct_view = client[:]
10 |     direct_view.targets = list(range(5))
11 |     direct_view_result = direct_view.apply(lambda x: x * 2, 13)
12 |     print(direct_view_result.get())
13 |     # load_balanced_view = client.load_balanced_view()
14 |     # result = load_balanced_view.apply(lambda x: x * 2, 13)
15 |     # print(result.get())
16 |     broadcast_view = client.broadcast_view(is_coalescing=True)
17 |     broadcast_result = broadcast_view.apply_sync(
18 |         lambda x: x, np.array([0] * 8, dtype=np.int8)
19 |     )
20 | 
21 |     print(broadcast_result)
22 |     print(len(broadcast_result))
23 |     # broadcast_view2 = client.broadcast_view(is_coalescing=False)
24 |     # broadcast_result = broadcast_view2.apply_sync(
25 |     #     lambda x: x, np.array([0] * 8, dtype=np.int8)
26 |     # )
27 |     #
28 |     # print(broadcast_result)
29 |     # print(len(broadcast_result))
30 | 
31 | 
32 | if __name__ == '__main__':
33 |     main()
34 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_async.py:
--------------------------------------------------------------------------------
 1 | """tests for async utilities"""
 2 | 
 3 | import pytest
 4 | 
 5 | from ipyparallel._async import AsyncFirst
 6 | 
 7 | 
 8 | class A(AsyncFirst):
 9 |     a = 5
10 | 
11 |     def sync_method(self):
12 |         return 'sync'
13 | 
14 |     async def async_method(self):
15 |         return 'async'
16 | 
17 | 
18 | def test_async_first_dir():
19 |     a = A()
20 |     attrs = sorted(dir(a))
21 |     real_attrs = [a for a in attrs if not a.startswith("_")]
22 |     assert real_attrs == ['a', 'async_method', 'async_method_sync', 'sync_method']
23 | 
24 | 
25 | def test_getattr():
26 |     a = A()
27 |     assert a.a == 5
28 |     with pytest.raises(AttributeError):
29 |         a.a_sync
30 |     with pytest.raises(AttributeError):
31 |         a.some_other_sync
32 |     with pytest.raises(AttributeError):
33 |         a.sync_method_sync
34 | 
35 | 
36 | def test_sync_no_asyncio():
37 |     a = A()
38 |     assert a.async_method_sync() == 'async'
39 |     assert a._async_thread is None
40 | 
41 | 
42 | async def test_sync_asyncio():
43 |     a = A()
44 |     assert a.async_method_sync() == 'async'
45 |     assert a._async_thread is not None
46 | 


--------------------------------------------------------------------------------
/docs/source/api/ipyparallel.rst:
--------------------------------------------------------------------------------
 1 | API Reference
 2 | =============
 3 | 
 4 | .. module:: ipyparallel
 5 | 
 6 | .. autodata:: version_info
 7 | 
 8 |     The IPython parallel version as a tuple of integers.
 9 |     There will always be 3 integers. Development releases will have 'dev' as a fourth element.
10 | 
11 | Classes
12 | -------
13 | 
14 | .. autoconfigurable:: Cluster
15 |     :inherited-members:
16 | 
17 | .. autoclass:: Client
18 |     :inherited-members:
19 | 
20 | .. autoclass:: DirectView
21 |     :inherited-members:
22 | 
23 | .. autoclass:: LoadBalancedView
24 |     :inherited-members:
25 | 
26 | .. autoclass:: BroadcastView
27 |     :inherited-members:
28 | 
29 | .. autoclass:: AsyncResult
30 |     :inherited-members:
31 | 
32 | .. autoclass:: ViewExecutor
33 |     :inherited-members:
34 | 
35 | Decorators
36 | ----------
37 | 
38 | IPython parallel provides some decorators to assist in using your functions as tasks.
39 | 
40 | .. autodecorator:: interactive
41 | .. autodecorator:: require
42 | .. autodecorator:: depend
43 | .. autodecorator:: remote
44 | .. autodecorator:: parallel
45 | 
46 | 
47 | Exceptions
48 | ----------
49 | 
50 | .. autoexception:: RemoteError
51 |     :no-members:
52 | .. autoexception:: CompositeError
53 |     :no-members:
54 | .. autoexception:: NoEnginesRegistered
55 | .. autoexception:: ImpossibleDependency
56 | .. autoexception:: InvalidDependency
57 | 


--------------------------------------------------------------------------------
/docs/source/examples/rmt/rmtkernel.py:
--------------------------------------------------------------------------------
 1 | # -------------------------------------------------------------------------------
 2 | # Core routines for computing properties of symmetric random matrices.
 3 | # -------------------------------------------------------------------------------
 4 | import numpy as np
 5 | 
 6 | ra = np.random
 7 | la = np.linalg
 8 | 
 9 | 
10 | def GOE(N):
11 |     """Creates an NxN element of the Gaussian Orthogonal Ensemble"""
12 |     m = ra.standard_normal((N, N))
13 |     m += m.T
14 |     return m / 2
15 | 
16 | 
17 | def center_eigenvalue_diff(mat):
18 |     """Compute the eigvals of mat and then find the center eigval difference."""
19 |     N = len(mat)
20 |     evals = np.sort(la.eigvals(mat))
21 |     diff = np.abs(evals[N / 2] - evals[N / 2 - 1])
22 |     return diff
23 | 
24 | 
25 | def ensemble_diffs(num, N):
26 |     """Return num eigenvalue diffs for the NxN GOE ensemble."""
27 |     diffs = np.empty(num)
28 |     for i in range(num):
29 |         mat = GOE(N)
30 |         diffs[i] = center_eigenvalue_diff(mat)
31 |     return diffs
32 | 
33 | 
34 | def normalize_diffs(diffs):
35 |     """Normalize an array of eigenvalue diffs."""
36 |     return diffs / diffs.mean()
37 | 
38 | 
39 | def normalized_ensemble_diffs(num, N):
40 |     """Return num *normalized* eigenvalue diffs for the NxN GOE ensemble."""
41 |     diffs = ensemble_diffs(num, N)
42 |     return normalize_diffs(diffs)
43 | 


--------------------------------------------------------------------------------
/docs/source/examples/phistogram.py:
--------------------------------------------------------------------------------
 1 | """Parallel histogram function"""
 2 | 
 3 | from ipyparallel import Reference
 4 | 
 5 | 
 6 | def phistogram(view, a, bins=10, rng=None, normed=False):
 7 |     """Compute the histogram of a remote array a.
 8 | 
 9 |     Parameters
10 |     ----------
11 |         view
12 |             IPython DirectView instance
13 |         a : str
14 |             String name of the remote array
15 |         bins : int
16 |             Number of histogram bins
17 |         rng : (float, float)
18 |             Tuple of min, max of the range to histogram
19 |         normed : boolean
20 |             Should the histogram counts be normalized to 1
21 |     """
22 |     nengines = len(view.targets)
23 | 
24 |     # view.push(dict(bins=bins, rng=rng))
25 |     with view.sync_imports():
26 |         import numpy
27 |     rets = view.apply_sync(
28 |         lambda a, b, rng: numpy.histogram(a, b, rng), Reference(a), bins, rng
29 |     )
30 |     hists = [r[0] for r in rets]
31 |     lower_edges = [r[1] for r in rets]
32 |     # view.execute('hist, lower_edges = numpy.histogram(%s, bins, rng)' % a)
33 |     lower_edges = view.pull('lower_edges', targets=0)
34 |     hist_array = numpy.array(hists).reshape(nengines, -1)
35 |     # hist_array.shape = (nengines,-1)
36 |     total_hist = numpy.sum(hist_array, 0)
37 |     if normed:
38 |         total_hist = total_hist / numpy.sum(total_hist, dtype=float)
39 |     return total_hist, lower_edges
40 | 


--------------------------------------------------------------------------------
/ci/slurm/docker-compose.yaml:
--------------------------------------------------------------------------------
 1 | services:
 2 |   mysql:
 3 |     image: mariadb:10.11
 4 |     hostname: mysql
 5 |     container_name: mysql
 6 |     environment:
 7 |       MYSQL_RANDOM_ROOT_PASSWORD: "yes"
 8 |       MYSQL_DATABASE: slurm_acct_db
 9 |       MYSQL_USER: slurm
10 |       MYSQL_PASSWORD: password
11 |     volumes:
12 |       - var_lib_mysql:/var_lib/mysql
13 | 
14 |   slurmdbd:
15 |     image: ipp-cluster:slurm
16 |     build: .
17 |     command:
18 |       - slurmdbd
19 |     container_name: slurmdbd
20 |     hostname: slurmdbd
21 |     volumes:
22 |       - etc_munge:/etc/munge
23 |       - var_log_slurm:/var/log/slurm
24 |     depends_on:
25 |       - mysql
26 | 
27 |   slurmctld:
28 |     image: ipp-cluster:slurm
29 |     build: .
30 |     command:
31 |       - slurmctld
32 |     container_name: slurmctld
33 |     hostname: slurmctld
34 |     volumes:
35 |       - etc_munge:/etc/munge
36 |       - slurm_jobdir:/data
37 |       - var_log_slurm:/var/log/slurm
38 |       - ../..:/io
39 |     depends_on:
40 |       - slurmdbd
41 | 
42 |   compute:
43 |     image: ipp-cluster:slurm
44 |     build: .
45 |     command:
46 |       - slurmd
47 |     deploy:
48 |       replicas: 2
49 | 
50 |     volumes:
51 |       - etc_munge:/etc/munge
52 |       - slurm_jobdir:/data
53 |       - var_log_slurm:/var/log/slurm
54 |       - ../..:/io
55 |     depends_on:
56 |       - "slurmctld"
57 | 
58 | volumes:
59 |   etc_munge:
60 |   # etc_slurm:
61 |   slurm_jobdir:
62 |   var_lib_mysql:
63 |   var_log_slurm:
64 | 


--------------------------------------------------------------------------------
/hatch_build.py:
--------------------------------------------------------------------------------
 1 | """Custom build script for hatch backend"""
 2 | 
 3 | import glob
 4 | import os
 5 | import subprocess
 6 | 
 7 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface
 8 | 
 9 | 
10 | class CustomHook(BuildHookInterface):
11 |     def initialize(self, version, build_data):
12 |         if self.target_name not in ["wheel", "sdist"]:
13 |             return
14 |         cmd = "build:prod" if version == "standard" else "build"
15 |         osp = os.path
16 |         here = osp.abspath(osp.dirname(__file__))
17 |         lab_path = osp.join(here, 'ipyparallel', 'labextension')
18 | 
19 |         if os.environ.get("IPP_DISABLE_JS") == "1":
20 |             print("Skipping js installation")
21 |             return
22 | 
23 |         # this tells us if labextension is built at all, not if it's up-to-date
24 |         labextension_built = glob.glob(osp.join(lab_path, "*"))
25 |         needs_js = True
26 |         if not osp.isdir(osp.join(here, ".git")):
27 |             print("Installing from a dist, not a repo")
28 |             # not in a repo, probably installing from sdist
29 |             # could be git-archive, though!
30 |             # skip rebuilding js if it's already present
31 |             if labextension_built:
32 |                 print(f"Not regenerating labextension in {lab_path}")
33 |                 needs_js = False
34 | 
35 |         if needs_js:
36 |             subprocess.check_call(['jlpm'], cwd=here)
37 |             subprocess.check_call(['jlpm', 'run', cmd], cwd=here)
38 | 


--------------------------------------------------------------------------------
/docs/source/examples/broadcast/docker-compose.yaml:
--------------------------------------------------------------------------------
 1 | services:
 2 |   # need /ipython to be writable by the container user (1000)
 3 |   # can't seem to start volumes with the right permissions
 4 |   init-permissions:
 5 |     image: alpine
 6 |     volumes:
 7 |       - ipython:/ipython
 8 |     command: chown -Rv 1000:1000 /ipython
 9 | 
10 |   controller:
11 |     depends_on:
12 |       - init-permissions
13 |       # condition: service_completed_successfully
14 | 
15 |     build: .
16 |     volumes:
17 |       - ipython:/ipython
18 |     environment:
19 |       IPYTHONDIR: /ipython
20 |     command: |
21 |       ipcontroller --location controller --ip 0.0.0.0 --nodb --HubFactory.broadcast_scheduler_depth=2
22 |     deploy:
23 |       restart_policy:
24 |         condition: on-failure
25 |   engines:
26 |     build: .
27 |     depends_on:
28 |       - controller
29 |     volumes_from:
30 |       - controller
31 |     environment:
32 |       IPYTHONDIR: /ipython
33 |     links:
34 |       - controller
35 |     command: |
36 |       ipcluster engines -n 16
37 |     deploy:
38 |       mode: replicated
39 |       replicas: 4
40 |       restart_policy:
41 |         condition: on-failure
42 | 
43 |   client:
44 |     build: .
45 |     ports:
46 |       - "9999:8888"
47 |     links:
48 |       - controller
49 |     volumes_from:
50 |       - controller
51 |     volumes:
52 |       - .:/home/jovyan/example
53 |     environment:
54 |       IPYTHONDIR: /ipython
55 |       JUPYTER_CONFIG_DIR: /ipython/jupyter
56 | 
57 | volumes:
58 |   ipython: {}
59 | 


--------------------------------------------------------------------------------
/ci/ssh/Dockerfile:
--------------------------------------------------------------------------------
 1 | FROM ubuntu:24.04
 2 | RUN --mount=type=cache,target=/var/cache/apt \
 3 |     rm -f /etc/apt/apt.conf.d/docker-clean \
 4 |  && apt-get update \
 5 |  && apt-get -y install \
 6 |         iputils-ping \
 7 |         bind9-utils \
 8 |         wget \
 9 |         openssh-server
10 | 
11 | ENV MAMBA_ROOT_PREFIX=/opt/conda
12 | ENV PATH=$MAMBA_ROOT_PREFIX/bin:$PATH
13 | ENV IPP_DISABLE_JS=1
14 | # x86_64 -> 64, aarch64 unmodified
15 | RUN ARCH=$(uname -m | sed s@x86_@@) \
16 |  && wget -qO- https://github.com/mamba-org/micromamba-releases/releases/latest/download/micromamba-linux-${ARCH} > /usr/local/bin/micromamba \
17 |  && chmod +x /usr/local/bin/micromamba
18 | 
19 | RUN --mount=type=cache,target=${MAMBA_ROOT_PREFIX}/pkgs \
20 |     micromamba install -y -p $MAMBA_ROOT_PREFIX -c conda-forge \
21 |         python=3.10 \
22 |         pip \
23 |         ipyparallel
24 | 
25 | # generate a user with home directory and trusted ssh keypair
26 | RUN useradd -m -s /bin/bash -N ciuser
27 | USER ciuser
28 | RUN mkdir ~/.ssh \
29 |  && chmod 0700 ~/.ssh \
30 |  && ssh-keygen -q -t rsa -N '' -f /home/ciuser/.ssh/id_rsa \
31 |  && cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys \
32 |  && chmod 0600 ~/.ssh/*
33 | USER root
34 | 
35 | 
36 | ENV PIP_CACHE_DIR=/tmp/pip-cache
37 | COPY . /src/ipyparallel
38 | RUN --mount=type=cache,target=${PIP_CACHE_DIR} python3 -m pip install -e 'file:///src/ipyparallel#egg=ipyparallel[test]'
39 | 
40 | # needed for sshd to start
41 | RUN mkdir /run/sshd
42 | # run sshd in the foreground
43 | CMD /usr/sbin/sshd -D -e
44 | 


--------------------------------------------------------------------------------
/docs/source/examples/index.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | file_format: mystnb
 3 | kernelspec:
 4 |   name: python3
 5 | mystnb:
 6 |   execution_mode: "force"
 7 |   remove_code_source: true
 8 | ---
 9 | 
10 | # examples
11 | 
12 | Here you will find example notebooks and scripts for working with IPython Parallel.
13 | 
14 | (example-tutorials)=
15 | 
16 | ## Tutorials
17 | 
18 | ```{toctree}
19 | Cluster API
20 | Parallel Magics
21 | progress
22 | Data Publication API
23 | visualizing-tasks
24 | broadcast/Broadcast view
25 | broadcast/memmap Broadcast
26 | broadcast/MPI Broadcast
27 | ```
28 | 
29 | ## Examples
30 | 
31 | ```{toctree}
32 | Monitoring an MPI Simulation - 1
33 | Monitoring an MPI Simulation - 2
34 | Parallel Decorator and map
35 | Using MPI with IPython Parallel
36 | Monte Carlo Options
37 | rmt/rmt
38 | ```
39 | 
40 | ## Integrating IPython Parallel with other tools
41 | 
42 | There are lots of cool tools for working with asynchronous and parallel execution. IPython Parallel aims to be fairly compatible with these, both by implementing explicit support via methods such as `Client.become_dask`, and by using standards such as the `concurrent.futures.Future` API.
43 | 
44 | ```{toctree}
45 | Futures
46 | joblib
47 | dask
48 | Using Dill
49 | ```
50 | 
51 | ## Non-notebook examples
52 | 
53 | This directory also contains some examples that are scripts instead of notebooks.
54 | 
55 | ```{code-cell}
56 | import glob
57 | import os
58 | 
59 | from IPython.display import FileLink, FileLinks, display
60 | 
61 | FileLinks(".", included_suffixes=[".py"], recursive=True)
62 | ```
63 | 


--------------------------------------------------------------------------------
/docs/source/examples/interengine/interengine.py:
--------------------------------------------------------------------------------
 1 | import ipyparallel as ipp
 2 | 
 3 | rc = ipp.Client()
 4 | rc.block = True
 5 | view = rc[:]
 6 | view.run('communicator.py')
 7 | view.execute('com = EngineCommunicator()')
 8 | 
 9 | # gather the connection information into a dict
10 | ar = view.apply_async(lambda: com.info)  # noqa: F821
11 | peers = ar.get_dict()
12 | # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
13 | 
14 | # connect the engines to each other:
15 | view.apply_sync(lambda pdict: com.connect(pdict), peers)  # noqa: F821
16 | 
17 | # now all the engines are connected, and we can communicate between them:
18 | 
19 | 
20 | def broadcast(client, sender, msg_name, dest_name=None, block=None):
21 |     """broadcast a message from one engine to all others."""
22 |     dest_name = msg_name if dest_name is None else dest_name
23 |     client[sender].execute(f'com.publish({msg_name})', block=None)
24 |     targets = client.ids
25 |     targets.remove(sender)
26 |     return client[targets].execute(f'{dest_name}=com.consume()', block=None)
27 | 
28 | 
29 | def send(client, sender, targets, msg_name, dest_name=None, block=None):
30 |     """send a message from one to one-or-more engines."""
31 |     dest_name = msg_name if dest_name is None else dest_name
32 | 
33 |     def _send(targets, m_name):
34 |         msg = globals()[m_name]
35 |         return com.send(targets, msg)  # noqa: F821
36 | 
37 |     client[sender].apply_async(_send, targets, msg_name)
38 | 
39 |     return client[targets].execute(f'{dest_name}=com.recv()', block=None)
40 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
 1 | # Contributing
 2 | 
 3 | We follow the [Jupyter Contributing Guide](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html).
 4 | 
 5 | Make sure to follow the [Jupyter Code of Conduct](https://jupyter.org/conduct/).
 6 | 
 7 | ## Development install
 8 | 
 9 | A basic development install of IPython parallel
10 | is the same as almost all Python packages:
11 | 
12 | ```bash
13 | pip install -e .
14 | ```
15 | 
16 | To enable the server extension from a development install:
17 | 
18 | ```bash
19 | # for jupyterlab
20 | jupyter server extension enable --sys-prefix ipyparallel
21 | # for classic notebook
22 | jupyter serverextension enable --sys-prefix ipyparallel
23 | ```
24 | 
25 | As described in the [JupyterLab documentation](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#developing-a-prebuilt-extension)
26 | for a development install of the jupyterlab extension you can run the following in this directory:
27 | 
28 | ```bash
29 | jlpm  # Install npm package dependencies
30 | jlpm build  # Compile the TypeScript sources to Javascript
31 | jupyter labextension develop . --overwrite  # Install the current directory as an extension
32 | ```
33 | 
34 | If you are working on the lab extension,
35 | you can run jupyterlab in dev mode to always rebuild and reload your extensions.
36 | In two terminals, run:
37 | 
38 | ```bash
39 | [term 1] $ jlpm watch
40 | [term 2] $ jupyter lab --extensions-in-dev-mode
41 | ```
42 | 
43 | You should then be able to refresh the JupyterLab page
44 | and it will pick up the changes to the extension as you work.
45 | 


--------------------------------------------------------------------------------
/ipyparallel/nbextension/base.py:
--------------------------------------------------------------------------------
 1 | """Place to put the base Handler"""
 2 | 
 3 | import warnings
 4 | from functools import lru_cache
 5 | 
 6 | _APIHandler = None
 7 | 
 8 | 
 9 | @lru_cache
10 | def _guess_api_handler():
11 |     """Fallback to guess API handler by availability"""
12 |     try:
13 |         from notebook.base.handlers import APIHandler
14 |     except ImportError:
15 |         from jupyter_server.base.handlers import APIHandler
16 |     global _APIHandler
17 |     _APIHandler = APIHandler
18 |     return _APIHandler
19 | 
20 | 
21 | def get_api_handler(app=None):
22 |     """Get the base APIHandler class to use
23 | 
24 |     Inferred from app class (either jupyter_server or notebook app)
25 |     """
26 |     global _APIHandler
27 |     if _APIHandler is not None:
28 |         return _APIHandler
29 |     if app is None:
30 |         warnings.warn(
31 |             "Guessing base APIHandler class. Specify an app to ensure the right APIHandler is used.",
32 |             stacklevel=2,
33 |         )
34 |         return _guess_api_handler()
35 | 
36 |     top_modules = {cls.__module__.split(".", 1)[0] for cls in app.__class__.mro()}
37 |     if "jupyter_server" in top_modules:
38 |         from jupyter_server.base.handlers import APIHandler
39 | 
40 |         _APIHandler = APIHandler
41 |         return APIHandler
42 |     if "notebook" in top_modules:
43 |         from notebook.base.handlers import APIHandler
44 | 
45 |         _APIHandler = APIHandler
46 |         return APIHandler
47 | 
48 |     warnings.warn(f"Failed to detect base APIHandler class for {app}.", stacklevel=2)
49 |     return _guess_api_handler()
50 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_slurm.py:
--------------------------------------------------------------------------------
 1 | import shutil
 2 | from unittest import mock
 3 | 
 4 | import pytest
 5 | from traitlets.config import Config
 6 | 
 7 | from . import test_cluster
 8 | from .conftest import temporary_ipython_dir
 9 | from .test_cluster import (
10 |     test_get_output,  # noqa: F401
11 |     test_restart_engines,  # noqa: F401
12 |     test_signal_engines,  # noqa: F401
13 |     test_start_stop_cluster,  # noqa: F401
14 |     test_to_from_dict,  # noqa: F401
15 | )
16 | 
17 | 
18 | @pytest.fixture(autouse=True, scope="module")
19 | def longer_timeout():
20 |     # slurm tests started failing with timeouts
21 |     # when adding timeout to test_restart_engines
22 |     # maybe it's just slow...
23 |     with mock.patch.object(test_cluster, "_timeout", 120):
24 |         yield
25 | 
26 | 
27 | # put ipython dir on shared filesystem
28 | @pytest.fixture(autouse=True, scope="module")
29 | def ipython_dir(request):
30 |     if shutil.which("sbatch") is None:
31 |         pytest.skip("Requires slurm")
32 |     with temporary_ipython_dir(prefix="/data/") as ipython_dir:
33 |         yield ipython_dir
34 | 
35 | 
36 | @pytest.fixture
37 | def cluster_config():
38 |     c = Config()
39 |     c.Cluster.controller_ip = '0.0.0.0'
40 |     return c
41 | 
42 | 
43 | # override launcher classes
44 | @pytest.fixture
45 | def engine_launcher_class():
46 |     if shutil.which("sbatch") is None:
47 |         pytest.skip("Requires slurm")
48 |     return 'slurm'
49 | 
50 | 
51 | @pytest.fixture
52 | def controller_launcher_class():
53 |     if shutil.which("sbatch") is None:
54 |         pytest.skip("Requires slurm")
55 |     return 'slurm'
56 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_remotefunction.py:
--------------------------------------------------------------------------------
 1 | """Tests for remote functions"""
 2 | 
 3 | # Copyright (c) IPython Development Team.
 4 | # Distributed under the terms of the Modified BSD License.
 5 | import ipyparallel as ipp
 6 | 
 7 | from .clienttest import ClusterTestCase
 8 | 
 9 | 
10 | class TestRemoteFunctions(ClusterTestCase):
11 |     def test_remote(self):
12 |         v = self.client[-1]
13 | 
14 |         @ipp.remote(v, block=True)
15 |         def foo(x, y=5):
16 |             """multiply x * y"""
17 |             return x * y
18 | 
19 |         assert foo.__name__ == 'foo'
20 |         assert 'RemoteFunction' in foo.__doc__
21 |         assert 'multiply x' in foo.__doc__
22 | 
23 |         z = foo(5)
24 |         assert z == 25
25 |         z = foo(2, 3)
26 |         assert z == 6
27 |         z = foo(x=5, y=2)
28 |         assert z == 10
29 | 
30 |     def test_parallel(self):
31 |         n = 2
32 |         v = self.client[:n]
33 | 
34 |         @ipp.parallel(v, block=True)
35 |         def foo(x):
36 |             """multiply x * y"""
37 |             return x * 2
38 | 
39 |         assert foo.__name__ == 'foo'
40 |         assert 'ParallelFunction' in foo.__doc__
41 |         assert 'multiply x' in foo.__doc__
42 | 
43 |         z = foo([1, 2, 3, 4])
44 |         assert z, [1, 2, 1, 2, 3, 4, 3 == 4]
45 | 
46 |     def test_parallel_map(self):
47 |         v = self.client.load_balanced_view()
48 | 
49 |         @ipp.parallel(v, block=True)
50 |         def foo(x, y=5):
51 |             """multiply x * y"""
52 |             return x * y
53 | 
54 |         z = foo.map([1, 2, 3])
55 |         assert z, [5, 10 == 15]
56 |         z = foo.map([1, 2, 3], [1, 2, 3])
57 |         assert z, [1, 4 == 9]
58 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_mongodb.py:
--------------------------------------------------------------------------------
 1 | """Tests for mongodb backend"""
 2 | 
 3 | # Copyright (c) IPython Development Team.
 4 | # Distributed under the terms of the Modified BSD License.
 5 | import os
 6 | from unittest import TestCase
 7 | 
 8 | import pytest
 9 | 
10 | from . import test_db
11 | 
12 | c = None
13 | 
14 | 
15 | @pytest.fixture(scope='module')
16 | def mongo_conn(request):
17 |     global c
18 |     try:
19 |         from pymongo import MongoClient
20 |     except ImportError:
21 |         pytest.skip("Requires mongodb")
22 | 
23 |     conn_kwargs = {}
24 |     if 'DB_IP' in os.environ:
25 |         conn_kwargs['host'] = os.environ['DB_IP']
26 |     if 'DBA_MONGODB_ADMIN_URI' in os.environ:
27 |         # On ShiningPanda, we need a username and password to connect. They are
28 |         # passed in a mongodb:// URI.
29 |         conn_kwargs['host'] = os.environ['DBA_MONGODB_ADMIN_URI']
30 |     if 'DB_PORT' in os.environ:
31 |         conn_kwargs['port'] = int(os.environ['DB_PORT'])
32 | 
33 |     try:
34 |         c = MongoClient(**conn_kwargs, serverSelectionTimeoutMS=2000)
35 |         servinfo = c.server_info()
36 |     except Exception:
37 |         c = None
38 |     if c is not None:
39 |         request.addfinalizer(lambda: c.drop_database('iptestdb'))
40 |     return c
41 | 
42 | 
43 | @pytest.mark.usefixtures('mongo_conn')
44 | class TestMongoBackend(test_db.TaskDBTest, TestCase):
45 |     """MongoDB backend tests"""
46 | 
47 |     def create_db(self):
48 |         try:
49 |             from ipyparallel.controller.mongodb import MongoDB
50 | 
51 |             return MongoDB(database='iptestdb', _connection=c)
52 |         except Exception:
53 |             pytest.skip("Couldn't connect to mongodb")
54 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_joblib.py:
--------------------------------------------------------------------------------
 1 | import warnings
 2 | from unittest import mock
 3 | 
 4 | import pytest
 5 | 
 6 | import ipyparallel as ipp
 7 | 
 8 | from .clienttest import ClusterTestCase, add_engines
 9 | 
10 | try:
11 |     with warnings.catch_warnings():
12 |         warnings.simplefilter("ignore")
13 |         from joblib import Parallel, delayed
14 | 
15 |         from ipyparallel.client._joblib import IPythonParallelBackend  # noqa
16 | except (ImportError, TypeError):
17 |     have_joblib = False
18 | else:
19 |     have_joblib = True
20 | 
21 | 
22 | def neg(x):
23 |     return -1 * x
24 | 
25 | 
26 | class TestJobLib(ClusterTestCase):
27 |     def setup_method(self):
28 |         if not have_joblib:
29 |             pytest.skip("Requires joblib >= 0.10")
30 |         super().setup_method()
31 |         add_engines(1, total=True)
32 | 
33 |     def test_default_backend(self):
34 |         """ipyparallel.register_joblib_backend() registers default backend"""
35 |         ipp.register_joblib_backend()
36 |         with mock.patch.object(ipp.Client, "__new__", lambda *a, **kw: self.client):
37 |             p = Parallel(backend='ipyparallel')
38 |             assert p._backend._view.client is self.client
39 | 
40 |     def test_register_backend(self):
41 |         view = self.client.load_balanced_view()
42 |         view.register_joblib_backend('view')
43 |         p = Parallel(backend='view')
44 |         assert p._backend._view is view
45 | 
46 |     def test_joblib_backend(self):
47 |         view = self.client.load_balanced_view()
48 |         view.register_joblib_backend('view')
49 |         p = Parallel(backend='view')
50 |         result = p(delayed(neg)(i) for i in range(10))
51 |         assert result == [neg(i) for i in range(10)]
52 | 


--------------------------------------------------------------------------------
/.github/workflows/windows-ssh-image.yaml:
--------------------------------------------------------------------------------
 1 | # because it's too unbelievably slow to install ssh server on Windows
 2 | name: Build image for Windows SSH CI
 3 | 
 4 | on:
 5 |   workflow_dispatch:
 6 | 
 7 | env:
 8 |   REGISTRY: ghcr.io
 9 |   IMAGE_NAME: ipyparallel-windows-ssh
10 |   TAG: 3.12-2022
11 | 
12 | jobs:
13 |   build-and-push-image:
14 |     runs-on: windows-2022
15 | 
16 |     permissions:
17 |       contents: read
18 |       packages: write
19 | 
20 |     steps:
21 |       - name: Checkout repository
22 |         uses: actions/checkout@v5
23 | 
24 |       - name: Log in to the Container registry
25 |         uses: docker/login-action@v3
26 |         with:
27 |           registry: ${{ env.REGISTRY }}
28 |           username: ${{ github.actor }}
29 |           password: ${{ secrets.GITHUB_TOKEN }}
30 | 
31 |       #       - name: Extract metadata (tags, labels) for Docker
32 |       #         id: meta
33 |       #         uses: docker/metadata-action@v5
34 |       #         with:
35 |       #           labels: |
36 |       #             org.opencontainers.image.title=${{ env.IMAGE_NAME }}
37 |       #
38 |       #           images: ${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
39 |       #           tags: |
40 |       #             type=raw,value=${{ env.TAG }}
41 | 
42 |       - name: Build image
43 |         # can't use build-push-action on Windows
44 |         # https://github.com/docker/build-push-action/issues/18
45 |         run: |
46 |           docker build -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} -f ci/ssh/win_base_Dockerfile ci/ssh
47 |       - name: Push image
48 |         run: |
49 |           docker push ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}
50 | 


--------------------------------------------------------------------------------
/benchmarks/instance_setup.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | DEFAULT_MINICONDA_PATH = os.path.join(os.getcwd(), "miniconda3/bin/:")
 4 | env = os.environ.copy()
 5 | env["PATH"] = DEFAULT_MINICONDA_PATH + env["PATH"]
 6 | 
 7 | from subprocess import check_call
 8 | 
 9 | GITHUB_TOKEN = ""  # Token for machine user
10 | ASV_TESTS_REPO = "github.com/tomoboy/ipyparallel_master_project.git"
11 | IPYPARALLEL_REPO = "github.com/tomoboy/ipyparallel.git"
12 | 
13 | 
14 | def cmd_run(*args, log_filename=None, error_filename=None):
15 |     if len(args) == 1:
16 |         args = args[0].split(" ")
17 |     print(f'$ {" ".join(args)}')
18 |     if not log_filename and not error_filename:
19 |         check_call(args, env=env)
20 |     else:
21 |         check_call(
22 |             args,
23 |             env=env,
24 |             stdout=open(log_filename, 'w'),
25 |             stderr=open(error_filename, 'w'),
26 |         )
27 | 
28 | 
29 | if __name__ == "__main__":
30 |     cmd_run(
31 |         f"git clone -q https://{GITHUB_TOKEN}@{ASV_TESTS_REPO}"
32 |     )  # Get benchmarks from repo
33 |     print("Finished cloning benchmark repo")
34 |     # Installing ipyparallel from the dev branch
35 |     cmd_run(f"pip install -q git+https://{GITHUB_TOKEN}@{IPYPARALLEL_REPO}")
36 |     print("Installed ipyparallel")
37 |     # Create profile for ipyparallel, (should maybe be copied if we want some cusom values here)
38 |     cmd_run("ipython profile create --parallel --profile=asv")
39 |     cmd_run('echo 120000 > /proc/sys/kernel/threads-max')
40 |     cmd_run('echo 600000 > /proc/sys/vm/max_map_count')
41 |     cmd_run('echo 200000 > /proc/sys/kernel/piad_max')
42 |     cmd_run('echo "*    hard    nproc    100000" > /etc/security/limits.d')
43 |     cmd_run('echo "*    soft    nproc    100000" > /etc/security/limits.d')
44 | 


--------------------------------------------------------------------------------
/lab/src/sidebar.ts:
--------------------------------------------------------------------------------
 1 | import { Widget, PanelLayout } from "@lumino/widgets";
 2 | import { CommandRegistry } from "@lumino/commands";
 3 | 
 4 | import { ClusterManager, IClusterModel } from "./clusters";
 5 | 
 6 | /**
 7 |  * A widget for hosting IPP cluster widgets
 8 |  */
 9 | export class Sidebar extends Widget {
10 |   /**
11 |    * Create a new IPP sidebar.
12 |    */
13 |   constructor(options: Sidebar.IOptions) {
14 |     super();
15 |     this.addClass("ipp-Sidebar");
16 |     let layout = (this.layout = new PanelLayout());
17 | 
18 |     const injectClientCodeForCluster = options.clientCodeInjector;
19 |     const getClientCodeForCluster = options.clientCodeGetter;
20 |     // Add the cluster manager component.
21 |     this._clusters = new ClusterManager({
22 |       registry: options.registry,
23 |       injectClientCodeForCluster,
24 |       getClientCodeForCluster,
25 |     });
26 |     layout.addWidget(this._clusters);
27 |   }
28 | 
29 |   /**
30 |    * Get the cluster manager associated with the sidebar.
31 |    */
32 |   get clusterManager(): ClusterManager {
33 |     return this._clusters;
34 |   }
35 | 
36 |   private _clusters: ClusterManager;
37 | }
38 | 
39 | /**
40 |  * A namespace for Sidebar statics.
41 |  */
42 | export namespace Sidebar {
43 |   /**
44 |    * Options for the constructor.
45 |    */
46 |   export interface IOptions {
47 |     /**
48 |      * Registry of all commands
49 |      */
50 |     registry: CommandRegistry;
51 | 
52 |     /**
53 |      * A function that injects client-connection code for a given cluster.
54 |      */
55 |     clientCodeInjector: (model: IClusterModel) => void;
56 | 
57 |     /**
58 |      * A function that gets client-connection code for a given cluster.
59 |      */
60 |     clientCodeGetter: (model: IClusterModel) => string;
61 |   }
62 | }
63 | 


--------------------------------------------------------------------------------
/ci/slurm/Dockerfile:
--------------------------------------------------------------------------------
 1 | FROM ubuntu:24.04
 2 | 
 3 | ENV DEBIAN_FRONTEND=noninteractive
 4 | RUN --mount=type=cache,target=/var/cache/apt \
 5 |  rm -f /etc/apt/apt.conf.d/docker-clean \
 6 |  && apt-get update && apt-get -y install \
 7 |         gosu \
 8 |         mysql-client \
 9 |         python3-venv \
10 |         python3-pip \
11 |         slurm-wlm \
12 |         slurmdbd \
13 |         slurm
14 | 
15 | ENV PIP_CACHE_DIR=/tmp/pip-cache \
16 |     VIRTUAL_ENV=/srv/env \
17 |     PATH=/srv/env/bin:${PATH} \
18 |     IPP_DISABLE_JS=1
19 | 
20 | RUN --mount=type=cache,target=${PIP_CACHE_DIR} \
21 |     python3 -m venv $VIRTUAL_ENV \
22 |  && $VIRTUAL_ENV/bin/python3 -m pip install ipyparallel pytest-asyncio pytest-cov
23 | 
24 | # initialize some filesystem
25 | RUN mkdir -p /etc/sysconfig/slurm \
26 |         /var/spool/slurmd \
27 |         /var/run/slurmd \
28 |         /var/run/slurmdbd \
29 |         /var/lib/slurmd \
30 |         /data \
31 |     && touch /var/lib/slurmd/node_state \
32 |         /var/lib/slurmd/front_end_state \
33 |         /var/lib/slurmd/job_state \
34 |         /var/lib/slurmd/resv_state \
35 |         /var/lib/slurmd/trigger_state \
36 |         /var/lib/slurmd/assoc_mgr_state \
37 |         /var/lib/slurmd/assoc_usage \
38 |         /var/lib/slurmd/qos_usage \
39 |         /var/lib/slurmd/fed_mgr_state \
40 |     && chown -R slurm:slurm /var/*/slurm* \
41 |     && mkdir /run/munge \
42 |     && chown munge:munge /run/munge \
43 |     && chmod a+rwxt /run/munge
44 |     # && mungekey -c
45 | 
46 | COPY --chown=slurm:slurm --chmod=0600 etc_slurm/ /etc/slurm/
47 | 
48 | 
49 | COPY entrypoint.sh /entrypoint
50 | ENTRYPOINT ["/entrypoint"]
51 | 
52 | # the mounted directory
53 | RUN mkdir /io
54 | ENV PYTHONPATH=/io
55 | WORKDIR "/io"
56 | 
57 | CMD [ "tail", "-f", "/var/log/slurm/slurmd.log" ]
58 | 


--------------------------------------------------------------------------------
/docs/source/examples/throughput.py:
--------------------------------------------------------------------------------
 1 | import time
 2 | 
 3 | import numpy as np
 4 | 
 5 | import ipyparallel as parallel
 6 | 
 7 | nlist = map(int, np.logspace(2, 9, 16, base=2))
 8 | nlist2 = map(int, np.logspace(2, 8, 15, base=2))
 9 | tlist = map(int, np.logspace(7, 22, 16, base=2))
10 | nt = 16
11 | 
12 | 
13 | def wait(t=0):
14 |     import time
15 | 
16 |     time.sleep(t)
17 | 
18 | 
19 | def echo(s=''):
20 |     return s
21 | 
22 | 
23 | def time_throughput(nmessages, t=0, f=wait):
24 |     client = parallel.Client()
25 |     view = client.load_balanced_view()
26 |     # do one ping before starting timing
27 |     if f is echo:
28 |         t = np.random.random(t / 8)
29 |     view.apply_sync(echo, '')
30 |     client.spin()
31 |     tic = time.time()
32 |     for i in range(nmessages):
33 |         view.apply(f, t)
34 |     lap = time.time()
35 |     client.wait()
36 |     toc = time.time()
37 |     return lap - tic, toc - tic
38 | 
39 | 
40 | def do_runs(nlist, t=0, f=wait, trials=2, runner=time_throughput):
41 |     A = np.zeros((len(nlist), 2))
42 |     for i, n in enumerate(nlist):
43 |         t1 = t2 = 0
44 |         for _ in range(trials):
45 |             time.sleep(0.25)
46 |             ts = runner(n, t, f)
47 |             t1 += ts[0]
48 |             t2 += ts[1]
49 |         t1 /= trials
50 |         t2 /= trials
51 |         A[i] = (t1, t2)
52 |         A[i] = n / A[i]
53 |         print(n, A[i])
54 |     return A
55 | 
56 | 
57 | def do_echo(n, tlist=[0], f=echo, trials=2, runner=time_throughput):
58 |     A = np.zeros((len(tlist), 2))
59 |     for i, t in enumerate(tlist):
60 |         t1 = t2 = 0
61 |         for _ in range(trials):
62 |             time.sleep(0.25)
63 |             ts = runner(n, t, f)
64 |             t1 += ts[0]
65 |             t2 += ts[1]
66 |         t1 /= trials
67 |         t2 /= trials
68 |         A[i] = (t1, t2)
69 |         A[i] = n / A[i]
70 |         print(t, A[i])
71 |     return A
72 | 


--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
 1 | ci:
 2 |   autoupdate_schedule: monthly
 3 | 
 4 | repos:
 5 |   - repo: https://github.com/astral-sh/ruff-pre-commit
 6 |     rev: v0.14.3
 7 |     hooks:
 8 |       - id: ruff
 9 |         args:
10 |           - "--fix"
11 |         exclude_types:
12 |           - jupyter
13 |       - id: ruff
14 |         name: ruff (notebooks)
15 |         args:
16 |           - "--fix"
17 |         types:
18 |           - jupyter
19 |       - id: ruff-format
20 |         # run via nbqa to handle cell magics
21 |         exclude_types:
22 |           - jupyter
23 |   # run ruff via nbqa, which has better and configurable handling of cell magics
24 |   # than ruff itself
25 |   # but has trouble with isort rules
26 |   - repo: https://github.com/nbQA-dev/nbQA
27 |     rev: 1.9.1
28 |     hooks:
29 |       - id: nbqa-ruff-format
30 |       - id: nbqa-ruff-check
31 |         args:
32 |           - --fix
33 |           # isort rules get confused in the temporary files
34 |           - --ignore=I
35 |   - repo: https://github.com/rbubley/mirrors-prettier
36 |     rev: v3.6.2
37 |     hooks:
38 |       - id: prettier
39 |   - repo: https://github.com/pre-commit/pre-commit-hooks
40 |     rev: v6.0.0
41 |     hooks:
42 |       - id: end-of-file-fixer
43 |       - id: check-case-conflict
44 |       - id: check-executables-have-shebangs
45 |       - id: requirements-txt-fixer
46 |   - repo: https://github.com/pre-commit/mirrors-eslint
47 |     rev: v9.39.0
48 |     hooks:
49 |       - id: eslint
50 |         files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
51 |         exclude: ipyparallel/nbextension/.*
52 |         types: [file]
53 |         additional_dependencies:
54 |           - "@typescript-eslint/eslint-plugin@2.27.0"
55 |           - "@typescript-eslint/parser@2.27.0"
56 |           - eslint@^6.0.0
57 |           - eslint-config-prettier@6.10.1
58 |           - eslint-plugin-prettier@3.1.4
59 |           - eslint-plugin-react@7.21.5
60 |           - typescript@4.1.3
61 | 


--------------------------------------------------------------------------------
/ipyparallel/engine/datapub.py:
--------------------------------------------------------------------------------
 1 | """Publishing native (typically pickled) objects."""
 2 | 
 3 | # Copyright (c) IPython Development Team.
 4 | # Distributed under the terms of the Modified BSD License.
 5 | from ipykernel.jsonutil import json_clean
 6 | from jupyter_client.session import Session, extract_header
 7 | from traitlets import Any, CBytes, Dict, Instance
 8 | from traitlets.config import Configurable
 9 | 
10 | from ipyparallel.serialize import serialize_object
11 | 
12 | 
13 | class ZMQDataPublisher(Configurable):
14 |     topic = topic = CBytes(b'datapub')
15 |     session = Instance(Session, allow_none=True)
16 |     pub_socket = Any(allow_none=True)
17 |     parent_header = Dict({})
18 | 
19 |     def set_parent(self, parent):
20 |         """Set the parent for outbound messages."""
21 |         self.parent_header = extract_header(parent)
22 | 
23 |     def publish_data(self, data):
24 |         """publish a data_message on the IOPub channel
25 | 
26 |         Parameters
27 |         ----------
28 |         data : dict
29 |             The data to be published. Think of it as a namespace.
30 |         """
31 |         session = self.session
32 |         buffers = serialize_object(
33 |             data,
34 |             buffer_threshold=session.buffer_threshold,
35 |             item_threshold=session.item_threshold,
36 |         )
37 |         content = json_clean(dict(keys=list(data.keys())))
38 |         session.send(
39 |             self.pub_socket,
40 |             'data_message',
41 |             content=content,
42 |             parent=self.parent_header,
43 |             buffers=buffers,
44 |             ident=self.topic,
45 |         )
46 | 
47 | 
48 | def publish_data(data):
49 |     """publish a data_message on the IOPub channel
50 | 
51 |     Parameters
52 |     ----------
53 |     data : dict
54 |         The data to be published. Think of it as a namespace.
55 |     """
56 |     from ipykernel.zmqshell import ZMQInteractiveShell
57 | 
58 |     ZMQInteractiveShell.instance().data_pub.publish_data(data)
59 | 


--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
 1 | # Makefile for Sphinx documentation generated by sphinx-quickstart
 2 | # ----------------------------------------------------------------------------
 3 | 
 4 | # You can set these variables from the command line, and also
 5 | # from the environment for the first two.
 6 | SPHINXOPTS    ?= --color -W --keep-going
 7 | SPHINXBUILD   ?= sphinx-build
 8 | SOURCEDIR     = source
 9 | BUILDDIR      = _build
10 | 
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | 	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
14 | 
15 | .PHONY: help Makefile
16 | 
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option.
19 | #
20 | # Several sphinx-build commands can be used through this, for example:
21 | #
22 | # - make clean
23 | # - make linkcheck
24 | # - make spelling
25 | #
26 | %: Makefile
27 | 	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
28 | 
29 | 
30 | # Manually added targets - related to code generation
31 | # ----------------------------------------------------------------------------
32 | 
33 | # For local development:
34 | # - builds the html
35 | # - NOTE: If the pre-requisites for the html target is updated, also update the
36 | #         Read The Docs section in docs/source/conf.py.
37 | #
38 | html:
39 | 	$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS)
40 | 	@echo
41 | 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
42 | 
43 | 
44 | # Manually added targets - related to development
45 | # ----------------------------------------------------------------------------
46 | 
47 | # For local development:
48 | # - requires sphinx-autobuild, see
49 | #   https://sphinxcontrib-spelling.readthedocs.io/en/latest/
50 | # - builds and rebuilds html on changes to source, but does not re-generate
51 | #   metrics/scopes files
52 | # - starts a livereload enabled webserver and opens up a browser
53 | devenv: html
54 | 	sphinx-autobuild -b html --open-browser "$(SOURCEDIR)" "$(BUILDDIR)/html"
55 | 


--------------------------------------------------------------------------------
/ci/slurm/entrypoint.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | set -ex
 3 | 
 4 | if [ "$1" = "slurmdbd" ]
 5 | then
 6 |     echo "---> Starting the MUNGE Authentication service (munged) ..."
 7 |     gosu munge /usr/sbin/munged
 8 | 
 9 |     echo "---> Starting the Slurm Database Daemon (slurmdbd) ..."
10 | 
11 |     {
12 |         . /etc/slurm/slurmdbd.conf
13 |         until echo "SELECT 1" | mysql -h $StorageHost -u$StorageUser -p$StoragePass 2>&1 > /dev/null
14 |         do
15 |             echo "-- Waiting for database to become active ..."
16 |             sleep 2
17 |         done
18 |     }
19 |     echo "-- Database is now active ..."
20 | 
21 |     exec gosu slurm /usr/sbin/slurmdbd -Dvvv
22 | fi
23 | 
24 | if [ "$1" = "slurmctld" ]
25 | then
26 |     echo "---> Starting the MUNGE Authentication service (munged) ..."
27 |     gosu munge /usr/sbin/munged
28 | 
29 |     echo "---> Waiting for slurmdbd to become active before starting slurmctld ..."
30 | 
31 |     until 2>/dev/null >/dev/tcp/slurmdbd/6819
32 |     do
33 |         echo "-- slurmdbd is not available.  Sleeping ..."
34 |         sleep 2
35 |     done
36 |     echo "-- slurmdbd is now active ..."
37 | 
38 |     echo "---> Starting the Slurm Controller Daemon (slurmctld) ..."
39 |     if /usr/sbin/slurmctld -V | grep -q '17.02' ; then
40 |         exec gosu slurm /usr/sbin/slurmctld -Dvvv
41 |     else
42 |         exec gosu slurm /usr/sbin/slurmctld -i -Dvvv
43 |     fi
44 | fi
45 | 
46 | if [ "$1" = "slurmd" ]
47 | then
48 |     echo "---> Starting the MUNGE Authentication service (munged) ..."
49 |     gosu munge /usr/sbin/munged
50 | 
51 |     echo "---> Waiting for slurmctld to become active before starting slurmd..."
52 | 
53 |     until 2>/dev/null >/dev/tcp/slurmctld/6817
54 |     do
55 |         echo "-- slurmctld is not available.  Sleeping ..."
56 |         sleep 2
57 |     done
58 |     echo "-- slurmctld is now active ..."
59 | 
60 |     echo "---> Starting the Slurm Node Daemon (slurmd) ..."
61 |     exec /usr/sbin/slurmd -Z --conf "CPUs=2 RealMemory=1000 Feature=compute" -Dvvv 
62 | fi
63 | 
64 | exec "$@"
65 | 


--------------------------------------------------------------------------------
/docs/source/examples/customresults.py:
--------------------------------------------------------------------------------
 1 | """An example for handling results in a way that AsyncMapResult doesn't provide
 2 | 
 3 | Specifically, out-of-order results with some special handing of metadata.
 4 | 
 5 | This just submits a bunch of jobs, waits on the results, and prints the stdout
 6 | and results of each as they finish.
 7 | 
 8 | Authors
 9 | -------
10 | * MinRK
11 | """
12 | 
13 | import random
14 | 
15 | import ipyparallel as ipp
16 | 
17 | # create client & views
18 | rc = ipp.Client()
19 | dv = rc[:]
20 | v = rc.load_balanced_view()
21 | 
22 | 
23 | # scatter 'id', so id=0,1,2 on engines 0,1,2
24 | dv.scatter('id', rc.ids, flatten=True)
25 | print(dv['id'])
26 | 
27 | 
28 | def sleep_here(count, t):
29 |     """simple function that takes args, prints a short message, sleeps for a time, and returns the same args"""
30 |     import sys
31 |     import time
32 | 
33 |     print(f"hi from engine {id}")
34 |     sys.stdout.flush()
35 |     time.sleep(t)
36 |     return count, t
37 | 
38 | 
39 | amr = v.map(sleep_here, range(100), [random.random() for i in range(100)], chunksize=2)
40 | 
41 | pending = set(amr.msg_ids)
42 | while pending:
43 |     try:
44 |         rc.wait(pending, 1e-3)
45 |     except TimeoutError:
46 |         # ignore timeouterrors, since they only mean that at least one isn't done
47 |         pass
48 |     # finished is the set of msg_ids that are complete
49 |     finished = pending.difference(rc.outstanding)
50 |     # update pending to exclude those that just finished
51 |     pending = pending.difference(finished)
52 |     for msg_id in finished:
53 |         # we know these are done, so don't worry about blocking
54 |         ar = rc.get_result(msg_id)
55 |         print(f"job id {msg_id} finished on engine {ar.engine_id}")
56 |         print("with stdout:")
57 |         print('    ' + ar.stdout.replace('\n', '\n    ').rstrip())
58 |         print("and results:")
59 | 
60 |         # note that each job in a map always returns a list of length chunksize
61 |         # even if chunksize == 1
62 |         for count, t in ar.get():
63 |             print(f"  item {count}: slept for {t:.2f}s")
64 | 


--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
 1 | # Build releases and (on tags) publish to PyPI
 2 | name: Release
 3 | 
 4 | # always build releases (to make sure wheel-building works)
 5 | # but only publish to PyPI on tags
 6 | on:
 7 |   push:
 8 |     branches-ignore:
 9 |       - "pre-commit-ci*"
10 |     tags:
11 |       - "*"
12 |   pull_request:
13 | 
14 | concurrency:
15 |   group: >-
16 |     ${{ github.workflow }}-
17 |     ${{ github.ref_type }}-
18 |     ${{ github.event.pull_request.number || github.sha }}
19 |   cancel-in-progress: true
20 | 
21 | jobs:
22 |   build-release:
23 |     runs-on: ubuntu-22.04
24 |     steps:
25 |       - uses: actions/checkout@v5
26 |       - uses: actions/setup-python@v6
27 |         with:
28 |           python-version: 3.11
29 |           cache: pip
30 | 
31 |       - name: install build package
32 |         run: |
33 |           pip install --upgrade pip
34 |           pip install build
35 |           pip freeze
36 | 
37 |       - name: build release
38 |         run: |
39 |           python -m build --sdist --wheel .
40 |           ls -l dist
41 | 
42 |       - name: verify wheel
43 |         run: |
44 |           cd dist
45 |           pip install ./*.whl jupyterlab==4.*
46 |           ipcluster --help-all
47 |           ipcontroller --help-all
48 |           ipengine --help-all
49 |           jupyter labextension list 2>&1 | grep ipyparallel
50 |           jupyter server extension list 2>&1 | grep ipyparallel
51 | 
52 |       # ref: https://github.com/actions/upload-artifact#readme
53 |       - uses: actions/upload-artifact@v5
54 |         with:
55 |           name: ipyparallel-${{ github.sha }}
56 |           path: "dist/*"
57 |           if-no-files-found: error
58 | 
59 |   upload-pypi:
60 |     permissions:
61 |       id-token: write
62 |     environment: release
63 |     runs-on: ubuntu-22.04
64 |     if: startsWith(github.ref, 'refs/tags/')
65 |     needs:
66 |       - build-release
67 |     steps:
68 |       - uses: actions/download-artifact@v6
69 |         with:
70 |           path: dist
71 |           merge-multiple: true
72 |       - name: Publish wheels to PyPI
73 |         uses: pypa/gh-action-pypi-publish@release/v1
74 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_executor.py:
--------------------------------------------------------------------------------
 1 | """Tests for Executor API"""
 2 | 
 3 | # Copyright (c) IPython Development Team.
 4 | # Distributed under the terms of the Modified BSD License.
 5 | import time
 6 | 
 7 | from ipyparallel.client.view import LazyMapIterator, LoadBalancedView
 8 | 
 9 | from .clienttest import ClusterTestCase
10 | 
11 | 
12 | def wait(n):
13 |     import time
14 | 
15 |     time.sleep(n)
16 |     return n
17 | 
18 | 
19 | def echo(x):
20 |     return x
21 | 
22 | 
23 | class TestExecutor(ClusterTestCase):
24 |     def test_client_executor(self):
25 |         executor = self.client.executor()
26 |         assert isinstance(executor.view, LoadBalancedView)
27 |         f = executor.submit(lambda x: 2 * x, 5)
28 |         r = f.result()
29 |         assert r == 10
30 | 
31 |     def test_view_executor(self):
32 |         view = self.client.load_balanced_view()
33 |         executor = view.executor
34 |         assert executor.view is view
35 | 
36 |     def test_executor_submit(self):
37 |         view = self.client.load_balanced_view()
38 |         executor = view.executor
39 |         f = executor.submit(lambda x, y: x * y, 2, 3)
40 |         r = f.result()
41 |         assert r == 6
42 | 
43 |     def test_executor_map(self):
44 |         view = self.client.load_balanced_view()
45 |         executor = view.executor
46 |         gen = executor.map(lambda x: x, range(5))
47 |         assert isinstance(gen, LazyMapIterator)
48 |         for i, r in enumerate(gen):
49 |             assert i == r
50 | 
51 |     def test_executor_context(self):
52 |         view = self.client.load_balanced_view()
53 |         executor = view.executor
54 |         with executor:
55 |             f = executor.submit(time.sleep, 0.5)
56 |             assert not f.done()
57 |             m = executor.map(lambda x: x, range(10))
58 |         assert len(view.history) == 11
59 |         # Executor context calls shutdown
60 |         # shutdown doesn't shutdown engines,
61 |         # but it should at least wait for results to finish
62 |         assert f.done()
63 |         tic = time.perf_counter()
64 |         list(m)
65 |         toc = time.perf_counter()
66 |         assert toc - tic < 0.5
67 | 


--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   env: {
 3 |     browser: true,
 4 |     es6: true,
 5 |     commonjs: true,
 6 |     node: true,
 7 |   },
 8 |   root: true,
 9 |   extends: [
10 |     "eslint:recommended",
11 |     "plugin:@typescript-eslint/eslint-recommended",
12 |     "plugin:@typescript-eslint/recommended",
13 |     "prettier",
14 |     "plugin:react/recommended",
15 |   ],
16 |   parser: "@typescript-eslint/parser",
17 |   parserOptions: {
18 |     project: "tsconfig.eslint.json",
19 |   },
20 |   plugins: ["@typescript-eslint"],
21 |   rules: {
22 |     "@typescript-eslint/no-floating-promises": ["error", { ignoreVoid: true }],
23 |     "@typescript-eslint/naming-convention": [
24 |       "error",
25 |       {
26 |         selector: "interface",
27 |         format: ["PascalCase"],
28 |         custom: {
29 |           regex: "^I[A-Z]",
30 |           match: true,
31 |         },
32 |       },
33 |     ],
34 |     "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
35 |     "@typescript-eslint/no-use-before-define": "off",
36 |     "@typescript-eslint/camelcase": "off",
37 |     "@typescript-eslint/no-explicit-any": "off",
38 |     "@typescript-eslint/no-non-null-assertion": "off",
39 |     "@typescript-eslint/no-namespace": "off",
40 |     "@typescript-eslint/interface-name-prefix": "off",
41 |     "@typescript-eslint/explicit-function-return-type": "off",
42 |     "@typescript-eslint/ban-ts-comment": ["warn", { "ts-ignore": true }],
43 |     "@typescript-eslint/ban-types": "warn",
44 |     "@typescript-eslint/no-non-null-asserted-optional-chain": "warn",
45 |     "@typescript-eslint/no-var-requires": "off",
46 |     "@typescript-eslint/no-empty-interface": "off",
47 |     "@typescript-eslint/triple-slash-reference": "warn",
48 |     "@typescript-eslint/no-inferrable-types": "off",
49 |     "no-inner-declarations": "off",
50 |     "no-prototype-builtins": "off",
51 |     "no-control-regex": "warn",
52 |     "no-undef": "warn",
53 |     "no-case-declarations": "warn",
54 |     "no-useless-escape": "off",
55 |     "prefer-const": "off",
56 |     "react/prop-types": "warn",
57 |   },
58 |   settings: {
59 |     react: {
60 |       version: "detect",
61 |     },
62 |   },
63 | };
64 | 


--------------------------------------------------------------------------------
/docs/source/examples/wave2D/communicator.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | """A simple Communicator class that has N,E,S,W neighbors connected via 0MQ PEER sockets"""
 3 | 
 4 | import socket
 5 | 
 6 | import zmq
 7 | 
 8 | from ipyparallel.util import disambiguate_url
 9 | 
10 | 
11 | class EngineCommunicator:
12 |     """An object that connects Engines to each other.
13 |     north and east sockets listen, while south and west sockets connect.
14 | 
15 |     This class is useful in cases where there is a set of nodes that
16 |     must communicate only with their nearest neighbors.
17 |     """
18 | 
19 |     def __init__(self, interface='tcp://*', identity=None):
20 |         self._ctx = zmq.Context()
21 |         self.north = self._ctx.socket(zmq.PAIR)
22 |         self.west = self._ctx.socket(zmq.PAIR)
23 |         self.south = self._ctx.socket(zmq.PAIR)
24 |         self.east = self._ctx.socket(zmq.PAIR)
25 | 
26 |         # bind to ports
27 |         northport = self.north.bind_to_random_port(interface)
28 |         eastport = self.east.bind_to_random_port(interface)
29 | 
30 |         self.north_url = f"{interface}:{northport}"
31 |         self.east_url = f"{interface}:{eastport}"
32 | 
33 |         # guess first public IP from socket
34 |         self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
35 | 
36 |     def __del__(self):
37 |         self.north.close()
38 |         self.south.close()
39 |         self.east.close()
40 |         self.west.close()
41 |         self._ctx.term()
42 | 
43 |     @property
44 |     def info(self):
45 |         """return the connection info for this object's sockets."""
46 |         return (self.location, self.north_url, self.east_url)
47 | 
48 |     def connect(self, south_peer=None, west_peer=None):
49 |         """connect to peers.  `peers` will be a 3-tuples, of the form:
50 |         (location, north_addr, east_addr)
51 |         as produced by
52 |         """
53 |         if south_peer is not None:
54 |             location, url, _ = south_peer
55 |             self.south.connect(disambiguate_url(url, location))
56 |         if west_peer is not None:
57 |             location, _, url = west_peer
58 |             self.west.connect(disambiguate_url(url, location))
59 | 


--------------------------------------------------------------------------------
/docs/source/examples/pi/parallelpi.py:
--------------------------------------------------------------------------------
 1 | """Calculate statistics on the digits of pi in parallel.
 2 | 
 3 | This program uses the functions in :file:`pidigits.py` to calculate
 4 | the frequencies of 2 digit sequences in the digits of pi. The
 5 | results are plotted using matplotlib.
 6 | 
 7 | To run, text files from https://www.super-computing.org/
 8 | must be installed in the working directory of the IPython engines.
 9 | The actual filenames to be used can be set with the ``filestring``
10 | variable below.
11 | 
12 | The dataset we have been using for this is the 200 million digit one here:
13 | ftp://pi.super-computing.org/.2/pi200m/
14 | 
15 | and the files used will be downloaded if they are not in the working directory
16 | of the IPython engines.
17 | """
18 | 
19 | from timeit import default_timer as clock
20 | 
21 | from matplotlib import pyplot as plt
22 | from pidigits import (
23 |     compute_two_digit_freqs,
24 |     fetch_pi_file,
25 |     plot_two_digit_freqs,
26 |     reduce_freqs,
27 | )
28 | 
29 | import ipyparallel as ipp
30 | 
31 | # Files with digits of pi (10m digits each)
32 | filestring = 'pi200m.ascii.%(i)02dof20'
33 | files = [filestring % {'i': i} for i in range(1, 21)]
34 | 
35 | # Connect to the IPython cluster
36 | c = ipp.Client()
37 | c[:].run('pidigits.py')
38 | 
39 | # the number of engines
40 | n = len(c)
41 | id0 = c.ids[0]
42 | v = c[:]
43 | v.block = True
44 | # fetch the pi-files
45 | print(f"downloading {n} files of pi")
46 | v.map(fetch_pi_file, files[:n])  # noqa: F821
47 | print("done")
48 | 
49 | # Run 10m digits on 1 engine
50 | t1 = clock()
51 | freqs10m = c[id0].apply_sync(compute_two_digit_freqs, files[0])
52 | t2 = clock()
53 | digits_per_second1 = 10.0e6 / (t2 - t1)
54 | print("Digits per second (1 core, 10m digits):   ", digits_per_second1)
55 | 
56 | 
57 | # Run n*10m digits on all engines
58 | t1 = clock()
59 | freqs_all = v.map(compute_two_digit_freqs, files[:n])
60 | freqs150m = reduce_freqs(freqs_all)
61 | t2 = clock()
62 | digits_per_second8 = n * 10.0e6 / (t2 - t1)
63 | print(f"Digits per second ({n} engines, {n}0m digits): ", digits_per_second8)
64 | 
65 | print("Speedup: ", digits_per_second8 / digits_per_second1)
66 | 
67 | plot_two_digit_freqs(freqs150m)
68 | plt.title(f"2 digit sequences in {n}0m digits of pi")
69 | plt.show()
70 | 


--------------------------------------------------------------------------------
/docs/source/examples/daVinci Word Count/wordfreq.py:
--------------------------------------------------------------------------------
 1 | """Count the frequencies of words in a string"""
 2 | 
 3 | 
 4 | def wordfreq(text, is_filename=False):
 5 |     """Return a dictionary of words and word counts in a string."""
 6 |     if is_filename:
 7 |         with open(text) as f:
 8 |             text = f.read()
 9 |     freqs = {}
10 |     for word in text.split():
11 |         lword = word.lower()
12 |         freqs[lword] = freqs.get(lword, 0) + 1
13 |     return freqs
14 | 
15 | 
16 | def print_wordfreq(freqs, n=10):
17 |     """Print the n most common words and counts in the freqs dict."""
18 | 
19 |     words, counts = freqs.keys(), freqs.values()
20 |     items = zip(counts, words)
21 |     items = sorted(items, reverse=True)
22 |     for count, word in items[:n]:
23 |         print(word, count)
24 | 
25 | 
26 | def wordfreq_to_weightsize(
27 |     worddict, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0
28 | ):
29 |     mincount = min(worddict.itervalues())
30 |     maxcount = max(worddict.itervalues())
31 |     weights = {}
32 |     for k, v in worddict.iteritems():
33 |         w = (v - mincount) / (maxcount - mincount)
34 |         alpha = minalpha + (maxalpha - minalpha) * w
35 |         size = minsize + (maxsize - minsize) * w
36 |         weights[k] = (alpha, size)
37 |     return weights
38 | 
39 | 
40 | def tagcloud(worddict, n=10, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0):
41 |     import random
42 | 
43 |     from matplotlib import pyplot as plt
44 | 
45 |     worddict = wordfreq_to_weightsize(worddict, minsize, maxsize, minalpha, maxalpha)
46 | 
47 |     fig = plt.figure()
48 |     ax = fig.add_subplot(111)
49 |     ax.set_position([0.0, 0.0, 1.0, 1.0])
50 |     plt.xticks([])
51 |     plt.yticks([])
52 | 
53 |     words = worddict.keys()
54 |     alphas = [v[0] for v in worddict.values()]
55 |     sizes = [v[1] for v in worddict.values()]
56 |     items = zip(alphas, sizes, words)
57 |     items.sort(reverse=True)
58 |     for alpha, size, word in items[:n]:
59 |         # xpos = random.normalvariate(0.5, 0.3)
60 |         # ypos = random.normalvariate(0.5, 0.3)
61 |         xpos = random.uniform(0.0, 1.0)
62 |         ypos = random.uniform(0.0, 1.0)
63 |         ax.text(xpos, ypos, word.lower(), alpha=alpha, fontsize=size)
64 |     ax.autoscale_view()
65 |     return ax
66 | 


--------------------------------------------------------------------------------
/ipyparallel/serialize/codeutil.py:
--------------------------------------------------------------------------------
 1 | """Utilities to enable code objects to be pickled.
 2 | 
 3 | Any process that import this module will be able to pickle code objects.  This
 4 | includes the func_code attribute of any function.  Once unpickled, new
 5 | functions can be built using new.function(code, globals()).  Eventually
 6 | we need to automate all of this so that functions themselves can be pickled.
 7 | 
 8 | Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305
 9 | """
10 | 
11 | # Copyright (c) IPython Development Team.
12 | # Distributed under the terms of the Modified BSD License.
13 | import copyreg
14 | import inspect
15 | import sys
16 | import types
17 | 
18 | 
19 | def code_ctor(*args):
20 |     return types.CodeType(*args)
21 | 
22 | 
23 | # map CodeType constructor args to code co_ attribute names
24 | # (they _almost_ all match, and new ones probably will)
25 | _code_attr_map = {
26 |     "codestring": "code",
27 |     "constants": "consts",
28 | }
29 | # pass every supported arg to the code constructor
30 | # this should be more forward-compatible
31 | # (broken on pypy: https://github.com/ipython/ipyparallel/issues/845)
32 | if sys.version_info >= (3, 10) and getattr(sys, "pypy_version_info", (7, 3, 19)) >= (
33 |     7,
34 |     3,
35 |     19,
36 | ):
37 |     _code_attr_names = tuple(
38 |         _code_attr_map.get(name, name)
39 |         for name, param in inspect.signature(types.CodeType).parameters.items()
40 |         if (param.POSITIONAL_ONLY or param.POSITIONAL_OR_KEYWORD)
41 |         and not (hasattr(sys, "pypy_version_info") and name == "magic")
42 |     )
43 | else:
44 |     # can't inspect types.CodeType on Python < 3.10
45 |     _code_attr_names = [
46 |         "argcount",
47 |         "kwonlyargcount",
48 |         "nlocals",
49 |         "stacksize",
50 |         "flags",
51 |         "code",
52 |         "consts",
53 |         "names",
54 |         "varnames",
55 |         "filename",
56 |         "name",
57 |         "firstlineno",
58 |         "lnotab",
59 |         "freevars",
60 |         "cellvars",
61 |     ]
62 |     if hasattr(types.CodeType, "co_posonlyargcount"):
63 |         _code_attr_names.insert(1, "posonlyargcount")
64 | 
65 |     _code_attr_names = tuple(_code_attr_names)
66 | 
67 | 
68 | def reduce_code(obj):
69 |     """codeobject reducer"""
70 |     return code_ctor, tuple(getattr(obj, f'co_{name}') for name in _code_attr_names)
71 | 
72 | 
73 | copyreg.pickle(types.CodeType, reduce_code)
74 | 


--------------------------------------------------------------------------------
/.github/workflows/test-docs.yml:
--------------------------------------------------------------------------------
 1 | name: Test docs
 2 | 
 3 | on:
 4 |   pull_request:
 5 |   push:
 6 |     branches-ignore:
 7 |       - "dependabot/**"
 8 |       - "pre-commit-ci-update-config"
 9 |     tags:
10 |       - "**"
11 |   workflow_dispatch:
12 | 
13 | env:
14 |   # UTF-8 content may be interpreted as ascii and causes errors without this.
15 |   LANG: C.UTF-8
16 |   PYTEST_ADDOPTS: "--verbose --color=yes"
17 | 
18 | jobs:
19 |   test-docs:
20 |     runs-on: ubuntu-24.04
21 |     steps:
22 |       - uses: actions/checkout@v5
23 |         with:
24 |           # make rediraffecheckdiff requires git history to compare current
25 |           # commit with the main branch and previous releases.
26 |           fetch-depth: 0
27 | 
28 |       - uses: actions/setup-python@v6
29 |         with:
30 |           python-version: "3.13"
31 | 
32 |       - name: Install requirements
33 |         run: |
34 |           pip install -r docs/requirements.txt .
35 | 
36 |       # readthedocs doesn't halt on warnings,
37 |       # so raise any warnings here
38 |       - name: build docs
39 |         run: |
40 |           cd docs
41 |           make html
42 | 
43 |       - name: check links
44 |         run: |
45 |           cd docs
46 |           make linkcheck
47 | 
48 |       # make rediraffecheckdiff compares files for different changesets
49 |       # these diff targets aren't always available
50 |       # - compare with base ref (usually 'main', always on 'origin') for pull requests
51 |       # - only compare with tags when running against main repo
52 |       #   to avoid errors on forks, which often lack tags
53 |       - name: check redirects for this PR
54 |         if: github.event_name == 'pull_request'
55 |         run: |
56 |           cd docs
57 |           export REDIRAFFE_BRANCH=origin/${{ github.base_ref }}
58 |           make rediraffecheckdiff
59 | 
60 |       # this should check currently published 'stable' links for redirects
61 |       - name: check redirects since last release
62 |         if: github.repository == 'ipython/ipyparallel'
63 |         run: |
64 |           cd docs
65 |           export REDIRAFFE_BRANCH=$(git describe --tags --abbrev=0)
66 |           make rediraffecheckdiff
67 | 
68 |       # longer-term redirect check (fixed version) for older links
69 |       - name: check redirects since 8.6.0
70 |         if: github.repository == 'ipython/ipyparallel'
71 |         run: |
72 |           cd docs
73 |           export REDIRAFFE_BRANCH=8.6.0
74 |           make rediraffecheckdiff
75 | 


--------------------------------------------------------------------------------
/benchmarks/profiling/profiling_code.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | 
 3 | import numpy as np
 4 | 
 5 | import ipyparallel as ipp
 6 | 
 7 | 
 8 | def echo(delay=0):
 9 |     def inner_echo(x, **kwargs):
10 |         import time
11 | 
12 |         if delay:
13 |             time.sleep(delay)
14 |         return x
15 | 
16 |     return inner_echo
17 | 
18 | 
19 | def profile_many_empty_tasks(lview, n, block=True):
20 |     lview.map(echo(0), [None] * n, block=block)
21 | 
22 | 
23 | # def profile_echo_many_arguments(lview, number_of_arguments):
24 | #     lview.map(
25 | #         lambda x: echo_many_arguments(*x),
26 | #         [
27 | #                         tuple(np.empty(1, dtype=np.int8) for n in range(number_of_arguments))
28 | #             for x in range(16)
29 | #         ],
30 | #         block=False,
31 | #     )
32 | 
33 | 
34 | def profile_tasks_with_large_data(lview, num_bytes):
35 |     for _ in range(10):
36 |         for i in range(10):
37 |             lview.apply_sync(echo(0), np.array([0] * num_bytes, dtype=np.int8))
38 | 
39 | 
40 | def run_profiling(selected_profiling_task, selected_view):
41 |     client = ipp.Client(profile='asv')
42 |     # add it to path on the engines
43 |     # client[:].apply_sync(add_to_path, master_project_parent)
44 |     print('profiling task: ', selected_profiling_task)
45 |     print('profiling view: ', selected_view)
46 |     if selected_view == 'direct':
47 |         view = client[:]
48 |     elif selected_view == 'spanning_tree':
49 |         view = client.spanning_tree_view()
50 |     else:
51 |         view = client.load_balanced_view()
52 | 
53 |     # view = client[:] if selected_view == 'direct' else client.load_balanced_view()
54 | 
55 |     if selected_profiling_task == 'many_empty_tasks':
56 |         for x in range(1, 5):
57 |             profile_many_empty_tasks(view, 10**x)
58 |     elif selected_profiling_task == 'many_empty_tasks_non_blocking':
59 |         for x in range(1, 5):
60 |             profile_many_empty_tasks(view, 10**x, block=False)
61 |     elif selected_profiling_task == 'tasks_with_large_data':
62 |         for x in range(1, 8):
63 |             profile_tasks_with_large_data(view, 10**x)
64 |     # elif selected_profiling_task == 'echo_many_arguments':
65 |     #     for i in range(100):
66 |     #         for number_of_arguments in ((2 ** x) - 1 for x in range(1, 9)):
67 |     #             profile_echo_many_arguments(view, number_of_arguments)
68 | 
69 | 
70 | if __name__ == "__main__":
71 |     run_profiling(sys.argv[1], sys.argv[2])
72 | 


--------------------------------------------------------------------------------
/ipyparallel/tests/test_ssh.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | from functools import partial
 3 | 
 4 | import pytest
 5 | from traitlets.config import Config
 6 | 
 7 | from .conftest import Cluster as BaseCluster  # noqa: F401
 8 | from .test_cluster import (
 9 |     test_get_output,  # noqa: F401
10 |     test_restart_engines,  # noqa: F401
11 |     test_signal_engines,  # noqa: F401
12 |     test_start_stop_cluster,  # noqa: F401
13 |     test_to_from_dict,  # noqa: F401
14 | )
15 | 
16 | # import tests that use engine_launcher_class fixture
17 | 
18 | 
19 | @pytest.fixture(params=["SSH", "SSHProxy"])
20 | def ssh_config(ssh_key, request):
21 |     windows = os.name == "nt"
22 | 
23 |     if (
24 |         windows and request.param == "SSHProxy"
25 |     ):  # SSHProxy currently not working under Windows
26 |         pytest.skip("Proxy tests currently not working under Windows")
27 | 
28 |     c = Config()
29 |     c.Cluster.controller_ip = '0.0.0.0'
30 |     c.Cluster.engine_launcher_class = request.param
31 |     engine_set_cfg = c[f"{request.param}EngineSetLauncher"]
32 |     engine_set_cfg.ssh_args = []
33 |     if not windows:
34 |         engine_set_cfg.ssh_args.extend(
35 |             [
36 |                 "-o",
37 |                 "UserKnownHostsFile=/dev/null",
38 |                 "-o",
39 |                 "StrictHostKeyChecking=no",
40 |                 "-i",
41 |                 ssh_key,
42 |             ]
43 |         )
44 |     engine_set_cfg.scp_args = list(engine_set_cfg.ssh_args)  # copy
45 |     if windows:
46 |         engine_set_cfg.remote_python = "python"
47 |         engine_set_cfg.remote_profile_dir = "C:/Users/ciuser/.ipython/profile_default"
48 |     else:
49 |         engine_set_cfg.remote_python = "/opt/conda/bin/python3"
50 |         engine_set_cfg.remote_profile_dir = "/home/ciuser/.ipython/profile_default"
51 |     engine_set_cfg.engine_args = ['--debug']
52 |     c.SSHProxyEngineSetLauncher.hostname = "127.0.0.1"
53 |     c.SSHProxyEngineSetLauncher.ssh_args.append("-p2222")
54 |     c.SSHProxyEngineSetLauncher.scp_args.append("-P2222")
55 |     c.SSHProxyEngineSetLauncher.user = "ciuser"
56 |     c.SSHEngineSetLauncher.engines = {"ciuser@127.0.0.1:2222": 4}
57 |     return c
58 | 
59 | 
60 | @pytest.fixture
61 | def Cluster(ssh_config, BaseCluster):  # noqa: F811
62 |     """Override Cluster to add ssh config"""
63 |     return partial(BaseCluster, config=ssh_config)
64 | 
65 | 
66 | # override engine_launcher_class
67 | @pytest.fixture
68 | def engine_launcher_class(ssh_config):
69 |     return ssh_config.Cluster.engine_launcher_class
70 | 


--------------------------------------------------------------------------------
/benchmarks/cluster_start.py:
--------------------------------------------------------------------------------
 1 | import atexit
 2 | import sys
 3 | import time
 4 | from subprocess import Popen
 5 | 
 6 | from benchmarks.throughput import wait_for
 7 | 
 8 | import ipyparallel as ipp
 9 | 
10 | 
11 | def start_cluster(depth, number_of_engines, path='', log_output_to_file=False):
12 |     ipcontroller_cmd = (
13 |         f'{path}ipcontroller --profile=asv --nodb '
14 |         f'--cluster-id=depth_{depth} '
15 |         f'--HubFactory.broadcast_scheduler_depth={depth} '
16 |         f'--HubFactory.db_class=NoDB'
17 |     )
18 |     print(ipcontroller_cmd)
19 |     ipengine_cmd = f'{path}ipengine --profile=asv --cluster-id=depth_{depth} '
20 |     ps = [
21 |         Popen(
22 |             ipcontroller_cmd.split(),
23 |             stdout=(
24 |                 open('ipcontroller_output.log', 'a+')
25 |                 if log_output_to_file
26 |                 else sys.stdout
27 |             ),
28 |             stderr=(
29 |                 open('ipcontroller_error_output.log', 'a+')
30 |                 if log_output_to_file
31 |                 else sys.stdout
32 |             ),
33 |             stdin=sys.stdin,
34 |         )
35 |     ]
36 |     time.sleep(2)
37 |     client = ipp.Client(profile='asv', cluster_id=f'depth_{depth}')
38 |     print(ipengine_cmd)
39 |     for i in range(number_of_engines):
40 |         ps.append(
41 |             Popen(
42 |                 ipengine_cmd.split(),
43 |                 stdout=(
44 |                     open('ipengine_output.log', 'a+')
45 |                     if log_output_to_file
46 |                     else sys.stdout
47 |                 ),
48 |                 stderr=(
49 |                     open('ipengine_error_output.log', 'a+')
50 |                     if log_output_to_file
51 |                     else sys.stdout
52 |                 ),
53 |                 stdin=sys.stdin,
54 |             )
55 |         )
56 |         if i % 10 == 0:
57 |             wait_for(lambda: len(client) >= i - 10)
58 |         if i % 20 == 0:
59 |             time.sleep(2)
60 |             print(f'{len(client)} engines started')
61 | 
62 |     return ps
63 | 
64 | 
65 | if __name__ == '__main__':
66 |     if len(sys.argv) > 3:
67 |         depth = sys.argv[1]
68 |         number_of_engines = int(sys.argv[3])
69 |     else:
70 |         depth = 3
71 |         number_of_engines = 30
72 | 
73 |     ps = start_cluster(depth, number_of_engines)
74 | 
75 |     for p in ps:
76 |         p.wait()
77 | 
78 |     def clean_up():
79 |         for p in ps:
80 |             p.kill()
81 | 
82 |     atexit.register(clean_up)
83 | 


--------------------------------------------------------------------------------
/ipyparallel/client/_joblib.py:
--------------------------------------------------------------------------------
 1 | """joblib parallel backend for IPython Parallel"""
 2 | 
 3 | # Copyright (c) IPython Development Team.
 4 | # Distributed under the terms of the Modified BSD License.
 5 | from joblib.parallel import AutoBatchingMixin, ParallelBackendBase
 6 | 
 7 | import ipyparallel as ipp
 8 | 
 9 | 
10 | class IPythonParallelBackend(AutoBatchingMixin, ParallelBackendBase):
11 |     def __init__(self, view=None, **kwargs):
12 |         super().__init__(**kwargs)
13 |         self._cluster_owner = False
14 |         self._client_owner = False
15 |         if view is None:
16 |             self._client_owner = True
17 |             try:
18 |                 # load the default cluster
19 |                 cluster = ipp.Cluster.from_file()
20 |             except FileNotFoundError:
21 |                 # other load errors?
22 |                 cluster = self._cluster = ipp.Cluster()
23 |                 self._cluster_owner = True
24 |                 cluster.start_cluster_sync()
25 |             else:
26 |                 # cluster running, ensure some engines are, too
27 |                 if not cluster.engines:
28 |                     cluster.start_engines_sync()
29 |             rc = cluster.connect_client_sync()
30 |             rc.wait_for_engines(cluster.n or 1)
31 |             view = rc.load_balanced_view()
32 | 
33 |             # use cloudpickle or dill for closures, if available.
34 |             # joblib tends to create closures default pickle can't handle.
35 |             try:
36 |                 import cloudpickle  # noqa
37 |             except ImportError:
38 |                 try:
39 |                     import dill  # noqa
40 |                 except ImportError:
41 |                     pass
42 |                 else:
43 |                     view.client[:].use_dill()
44 |             else:
45 |                 view.client[:].use_cloudpickle()
46 |         self._view = view
47 | 
48 |     def effective_n_jobs(self, n_jobs):
49 |         """A View can run len(view) jobs at a time"""
50 |         return len(self._view)
51 | 
52 |     def terminate(self):
53 |         """Close the client if we created it"""
54 |         if self._client_owner:
55 |             self._view.client.close()
56 |         if self._cluster_owner:
57 |             self._cluster.stop_cluster_sync()
58 | 
59 |     def apply_async(self, func, callback=None):
60 |         """Schedule a func to be run"""
61 |         future = self._view.apply_async(func)
62 |         if callback:
63 |             future.add_done_callback(lambda f: callback(f.result()))
64 |         return future
65 | 


--------------------------------------------------------------------------------
/ipyparallel/apps/iploggerapp.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | """
 3 | A simple IPython logger application
 4 | """
 5 | 
 6 | from IPython.core.profiledir import ProfileDir
 7 | from traitlets import Dict
 8 | 
 9 | from ipyparallel.apps.baseapp import (
10 |     BaseParallelApplication,
11 |     base_aliases,
12 |     catch_config_error,
13 | )
14 | from ipyparallel.apps.logwatcher import LogWatcher
15 | 
16 | # -----------------------------------------------------------------------------
17 | # Module level variables
18 | # -----------------------------------------------------------------------------
19 | 
20 | #: The default config file name for this application
21 | _description = """Start an IPython logger for parallel computing.
22 | 
23 | IPython controllers and engines (and your own processes) can broadcast log messages
24 | by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
25 | logger can be configured using command line options or using a cluster
26 | directory. Cluster directories contain config, log and security files and are
27 | usually located in your ipython directory and named as "profile_name".
28 | See the `profile` and `profile-dir` options for details.
29 | """
30 | 
31 | 
32 | # -----------------------------------------------------------------------------
33 | # Main application
34 | # -----------------------------------------------------------------------------
35 | aliases = {}
36 | aliases.update(base_aliases)
37 | aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
38 | 
39 | 
40 | class IPLoggerApp(BaseParallelApplication):
41 |     name = 'iplogger'
42 |     description = _description
43 |     classes = [LogWatcher, ProfileDir]
44 |     aliases = Dict(aliases)
45 | 
46 |     @catch_config_error
47 |     def initialize(self, argv=None):
48 |         super().initialize(argv)
49 |         self.init_watcher()
50 | 
51 |     def init_watcher(self):
52 |         try:
53 |             self.watcher = LogWatcher(parent=self, log=self.log)
54 |         except BaseException:
55 |             self.log.error("Couldn't start the LogWatcher", exc_info=True)
56 |             self.exit(1)
57 |         self.log.info(f"Listening for log messages on {self.watcher.url!r}")
58 | 
59 |     def start(self):
60 |         self.watcher.start()
61 |         try:
62 |             self.watcher.loop.start()
63 |         except KeyboardInterrupt:
64 |             self.log.critical("Logging Interrupted, shutting down...\n")
65 | 
66 | 
67 | launch_new_instance = IPLoggerApp.launch_instance
68 | 
69 | 
70 | if __name__ == '__main__':
71 |     launch_new_instance()
72 | 


--------------------------------------------------------------------------------
/docs/source/examples/task_profiler.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | """Test the performance of the task farming system.
 3 | 
 4 | This script submits a set of tasks via a LoadBalancedView.  The tasks
 5 | are basically just a time.sleep(t), where t is a random number between
 6 | two limits that can be configured at the command line.  To run
 7 | the script there must first be an IPython controller and engines running::
 8 | 
 9 |     ipcluster start -n 16
10 | 
11 | A good test to run with 16 engines is::
12 | 
13 |     python task_profiler.py -n 128 -t 0.01 -T 1.0
14 | 
15 | This should show a speedup of 13-14x.  The limitation here is that the
16 | overhead of a single task is about 0.001-0.01 seconds.
17 | """
18 | 
19 | import random
20 | import time
21 | from optparse import OptionParser
22 | 
23 | import ipyparallel as ipp
24 | 
25 | 
26 | def main():
27 |     parser = OptionParser()
28 |     parser.set_defaults(n=100)
29 |     parser.set_defaults(tmin=1e-3)
30 |     parser.set_defaults(tmax=1)
31 |     parser.set_defaults(profile='default')
32 | 
33 |     parser.add_option("-n", type='int', dest='n', help='the number of tasks to run')
34 |     parser.add_option(
35 |         "-t", type='float', dest='tmin', help='the minimum task length in seconds'
36 |     )
37 |     parser.add_option(
38 |         "-T", type='float', dest='tmax', help='the maximum task length in seconds'
39 |     )
40 |     parser.add_option(
41 |         "-p",
42 |         '--profile',
43 |         type='str',
44 |         dest='profile',
45 |         help="the cluster profile [default: 'default']",
46 |     )
47 | 
48 |     (opts, args) = parser.parse_args()
49 |     assert opts.tmax >= opts.tmin, "tmax must not be smaller than tmin"
50 | 
51 |     rc = ipp.Client()
52 |     view = rc.load_balanced_view()
53 |     print(view)
54 |     rc.block = True
55 |     nengines = len(rc.ids)
56 | 
57 |     # the jobs should take a random time within a range
58 |     times = [
59 |         random.random() * (opts.tmax - opts.tmin) + opts.tmin for i in range(opts.n)
60 |     ]
61 |     stime = sum(times)
62 | 
63 |     print(f"executing {opts.n} tasks, totalling {stime:.1f} secs on {nengines} engines")
64 |     time.sleep(1)
65 |     start = time.perf_counter()
66 |     amr = view.map(time.sleep, times)
67 |     amr.get()
68 |     stop = time.perf_counter()
69 | 
70 |     ptime = stop - start
71 |     scale = stime / ptime
72 | 
73 |     print(f"executed {stime:.1f} secs in {ptime:.1f} secs")
74 |     print(f"{scale:3f}x parallel performance on {nengines} engines")
75 |     print(f"{scale / nengines:.0%} of theoretical max")
76 | 
77 | 
78 | if __name__ == '__main__':
79 |     main()
80 | 


--------------------------------------------------------------------------------
/docs/source/examples/itermapresult.py:
--------------------------------------------------------------------------------
 1 | """Example of iteration through AsyncMapResults, without waiting for all results
 2 | 
 3 | When you call view.map(func, sequence), you will receive a special AsyncMapResult
 4 | object.  These objects are used to reconstruct the results of the split call.
 5 | One feature AsyncResults provide is that they are iterable *immediately*, so
 6 | you can iterate through the actual results as they complete.
 7 | 
 8 | This is useful if you submit a large number of tasks that may take some time,
 9 | but want to perform logic on elements in the result, or even abort subsequent
10 | tasks in cases where you are searching for the first affirmative result.
11 | 
12 | By default, the results will match the ordering of the submitted sequence, but
13 | if you call `map(...ordered=False)`, then results will be provided to the iterator
14 | on a first come first serve basis.
15 | 
16 | Authors
17 | -------
18 | * MinRK
19 | """
20 | 
21 | import time
22 | 
23 | import ipyparallel as ipp
24 | 
25 | # create client & view
26 | rc = ipp.Client()
27 | dv = rc[:]
28 | v = rc.load_balanced_view()
29 | 
30 | # scatter 'id', so id=0,1,2 on engines 0,1,2
31 | dv.scatter('id', rc.ids, flatten=True)
32 | print("Engine IDs: ", dv['id'])
33 | 
34 | # create a Reference to `id`. This will be a different value on each engine
35 | ref = ipp.Reference('id')
36 | print("sleeping for `id` seconds on each engine")
37 | tic = time.perf_counter()
38 | ar = dv.apply(time.sleep, ref)
39 | for i, r in enumerate(ar):
40 |     print(f"{i}: {time.perf_counter() - tic:.3f}")
41 | 
42 | 
43 | def sleep_here(t):
44 |     import time
45 | 
46 |     time.sleep(t)
47 |     return id, t
48 | 
49 | 
50 | # one call per task
51 | print("running with one call per task")
52 | amr = v.map(sleep_here, [0.01 * t for t in range(100)])
53 | tic = time.perf_counter()
54 | for i, r in enumerate(amr):
55 |     print(f"task {i} on engine {r[0]}: {time.perf_counter() - tic:.3f}")
56 | 
57 | print("running with four calls per task")
58 | # with chunksize, we can have four calls per task
59 | amr = v.map(sleep_here, [0.01 * t for t in range(100)], chunksize=4)
60 | tic = time.perf_counter()
61 | for i, r in enumerate(amr):
62 |     print(f"task {i} on engine {r[0]}: {time.perf_counter() - tic:.3f}")
63 | 
64 | print("running with two calls per task, with unordered results")
65 | # We can even iterate through faster results first, with ordered=False
66 | amr = v.map(
67 |     sleep_here, [0.01 * t for t in range(100, 0, -1)], ordered=False, chunksize=2
68 | )
69 | tic = time.perf_counter()
70 | for i, r in enumerate(amr):
71 |     print(f"slept {r[1]:.2f}s on engine {r[0]}: {time.perf_counter() - tic:.3f}")
72 | 


--------------------------------------------------------------------------------
/benchmarks/results/asv_testing-16-2020-05-04-17-43/results.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "results": {
 3 |     "throughput.CoalescingBroadcast.time_broadcast": {
 4 |       "result": null,
 5 |       "params": [
 6 |         ["0", "0.1"],
 7 |         ["1", "10", "50", "100", "200"],
 8 |         ["10", "100", "1000", "10000", "100000", "1000000"]
 9 |       ]
10 |     },
11 |     "throughput.DirectViewBroadCast.time_broadcast": {
12 |       "result": null,
13 |       "params": [
14 |         ["0", "0.1"],
15 |         ["1", "10", "50", "100", "200"],
16 |         ["10", "100", "1000", "10000", "100000", "1000000"]
17 |       ]
18 |     },
19 |     "throughput.NonCoalescingBroadcast.time_broadcast": {
20 |       "result": null,
21 |       "params": [
22 |         ["0", "0.1"],
23 |         ["1", "10", "50", "100", "200"],
24 |         ["10", "100", "1000", "10000", "100000", "1000000"]
25 |       ]
26 |     },
27 |     "throughput.SpanningTreeBroadCast.time_broadcast": {
28 |       "result": null,
29 |       "params": [
30 |         ["0", "0.1"],
31 |         ["1", "10", "50", "100", "200"],
32 |         ["10", "100", "1000", "10000", "100000", "1000000"]
33 |       ]
34 |     }
35 |   },
36 |   "params": {
37 |     "arch": "x86_64",
38 |     "cpu": "Intel(R) Xeon(R) CPU @ 2.30GHz",
39 |     "machine": "asv-testing-16",
40 |     "os": "Linux 4.15.0-1029-gcp",
41 |     "ram": "16423396",
42 |     "python": "3.7",
43 |     "numpy": ""
44 |   },
45 |   "requirements": {
46 |     "numpy": ""
47 |   },
48 |   "commit_hash": "766bdabf611694c8bf1807677ed3a501bf0b87f2",
49 |   "date": 1588108958000,
50 |   "env_name": "conda-py3.7-numpy",
51 |   "python": "3.7",
52 |   "profiles": {},
53 |   "started_at": {
54 |     "throughput.CoalescingBroadcast.time_broadcast": 1588607341576,
55 |     "throughput.DirectViewBroadCast.time_broadcast": 1588607371588,
56 |     "throughput.NonCoalescingBroadcast.time_broadcast": 1588607401077,
57 |     "throughput.SpanningTreeBroadCast.time_broadcast": 1588607436141
58 |   },
59 |   "ended_at": {
60 |     "throughput.CoalescingBroadcast.time_broadcast": 1588607371588,
61 |     "throughput.DirectViewBroadCast.time_broadcast": 1588607401077,
62 |     "throughput.NonCoalescingBroadcast.time_broadcast": 1588607436140,
63 |     "throughput.SpanningTreeBroadCast.time_broadcast": 1588607459984
64 |   },
65 |   "benchmark_version": {
66 |     "throughput.CoalescingBroadcast.time_broadcast": "fa34b3be29f11e8e92ff3f69ee3db3cef435c17dc271602df2b926f774e93a8e",
67 |     "throughput.DirectViewBroadCast.time_broadcast": "fa34b3be29f11e8e92ff3f69ee3db3cef435c17dc271602df2b926f774e93a8e",
68 |     "throughput.NonCoalescingBroadcast.time_broadcast": "fa34b3be29f11e8e92ff3f69ee3db3cef435c17dc271602df2b926f774e93a8e",
69 |     "throughput.SpanningTreeBroadCast.time_broadcast": "fa34b3be29f11e8e92ff3f69ee3db3cef435c17dc271602df2b926f774e93a8e"
70 |   },
71 |   "version": 1
72 | }
73 | 


--------------------------------------------------------------------------------
/docs/source/examples/daVinci Word Count/pwordfreq.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | """Parallel word frequency counter.
 3 | 
 4 | This only works for a local cluster, because the filenames are local paths.
 5 | """
 6 | 
 7 | import os
 8 | import time
 9 | from itertools import repeat
10 | 
11 | import requests
12 | from wordfreq import print_wordfreq, wordfreq
13 | 
14 | import ipyparallel as ipp
15 | 
16 | davinci_url = "https://www.gutenberg.org/files/5000/5000-8.txt"
17 | 
18 | 
19 | def pwordfreq(view, fnames):
20 |     """Parallel word frequency counter.
21 | 
22 |     view - An IPython DirectView
23 |     fnames - The filenames containing the split data.
24 |     """
25 |     assert len(fnames) == len(view.targets)
26 |     view.scatter('fname', fnames, flatten=True)
27 |     ar = view.apply(wordfreq, ipp.Reference('fname'))
28 |     freqs_list = ar.get()
29 |     word_set = set()
30 |     for f in freqs_list:
31 |         word_set.update(f.keys())
32 |     freqs = dict(zip(word_set, repeat(0)))
33 |     for f in freqs_list:
34 |         for word, count in f.items():
35 |             freqs[word] += count
36 |     return freqs
37 | 
38 | 
39 | if __name__ == '__main__':
40 |     # Create a Client and View
41 |     rc = ipp.Client()
42 | 
43 |     view = rc[:]
44 |     view.apply_sync(os.chdir, os.getcwd())
45 | 
46 |     if not os.path.exists('davinci.txt'):
47 |         # download from project gutenberg
48 |         print("Downloading Da Vinci's notebooks from Project Gutenberg")
49 |         r = requests.get(davinci_url)
50 |         with open('davinci.txt', 'w', encoding='utf8') as f:
51 |             f.write(r.text)
52 | 
53 |     # Run the serial version
54 |     print("Serial word frequency count:")
55 |     text = open('davinci.txt', encoding='latin1').read()
56 |     tic = time.time()
57 |     freqs = wordfreq(text)
58 |     toc = time.time()
59 |     print_wordfreq(freqs, 10)
60 |     print(f"Took {toc - tic:.3f}s to calculate")
61 | 
62 |     # The parallel version
63 |     print("\nParallel word frequency count:")
64 |     # split the davinci.txt into one file per engine:
65 |     lines = text.splitlines()
66 |     nlines = len(lines)
67 |     n = len(rc)
68 |     block = nlines // n
69 |     for i in range(n):
70 |         chunk = lines[i * block : i * (block + 1)]
71 |         with open(f'davinci{i}.txt', 'w', encoding='utf8') as f:
72 |             f.write('\n'.join(chunk))
73 | 
74 |     try:  # python2
75 |         cwd = os.path.abspath(os.getcwdu())
76 |     except AttributeError:  # python3
77 |         cwd = os.path.abspath(os.getcwd())
78 |     fnames = [os.path.join(cwd, f'davinci{i}.txt') for i in range(n)]
79 |     tic = time.time()
80 |     pfreqs = pwordfreq(view, fnames)
81 |     toc = time.time()
82 |     print_wordfreq(freqs)
83 |     print(f"Took {toc - tic:.3f} s to calculate on {len(view.targets)} engines")
84 |     # cleanup split files
85 |     map(os.remove, fnames)
86 | 


--------------------------------------------------------------------------------
/ci/ssh/win_Dockerfile_template:
--------------------------------------------------------------------------------
 1 | # syntax = docker/dockerfile:1.2.1
 2 | FROM python:3.12-windowsservercore-ltsc2022
 3 | SHELL ["powershell"]
 4 | 
 5 | # using docker env doesn't seem to work fully correctly. The environment variable is set in the powershell
 6 | # but somehow it is not present during pip install process, which will cause the ipyparallel installation
 7 | # to fail. Therefore, we use the SetEnvironmentVariable command (for the machine) to make this 'permanent'.
 8 | # See run command below.
 9 | ENV IPP_DISABLE_JS=1
10 | 
11 | # the following env variable values will be 'statically' replaced by the corresponding github workflow script
12 | # if the values aren't replaced the container hosts file isn't changed which is typically no problem in a local
13 | # environment. but it's a necessity for github runners, since the host name resolution inside the container doesn't
14 | # work correctly while trying to register with the ipyparallel controller (for linux runners this isn't an issue)
15 | ENV docker_host_ip   ${docker_host_ip}
16 | ENV docker_host_name ${docker_host_name}
17 | 
18 | # set IPP_DISABLE_JS=1 and install latest node js version
19 | RUN [System.Environment]::SetEnvironmentVariable('IPP_DISABLE_JS','1', [EnvironmentVariableTarget]::Machine); \
20 | #add the docker host name and ip to the container hosts file (needed for the github runners since the docker host name resolution doesn't work there)
21 | 	$hostsfile='C:\Windows\System32\drivers\etc\hosts'; \
22 | 	$line=\"$env:docker_host_ip $env:docker_host_name\"; \
23 | 	if ($line.Trim().Length -eq 0) { \
24 | 		Write-Host 'Environment variables docker_host_[name|ip] not set. Hosts file unchanged!'; \
25 | 	} else { \
26 | 		Write-Host 'Adapting hosts file '; \
27 | 		$h=(Get-Content $hostsfile)+$line; \
28 | 		echo $h | out-file -encoding ASCII $hostsfile; \
29 | 		type $hostsfile; \
30 | 	}
31 | 
32 | RUN Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'; \
33 | 	Write-Host 'Install OpenSSH Server...'; \
34 | 	Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0; \
35 | 	Write-Host 'Initializing OpenSSH Server...'; \
36 | 	Start-Service sshd; \
37 | 	Stop-Service sshd
38 | 
39 | 
40 | # create ciuser including key pair
41 | RUN Write-Host 'Create user ciuser...';\
42 | 	NET USER ciuser /add
43 | USER ciuser
44 | RUN Write-Host 'Create key pair and copy public key...';\
45 | 	ssh-keygen -t rsa -N '\"\"' -f $env:USERPROFILE/.ssh/id_rsa; \
46 | 	cp $env:USERPROFILE/.ssh/id_rsa.pub $env:USERPROFILE/.ssh/authorized_keys
47 | 
48 | # switch back to the admin user
49 | USER containeradministrator
50 | 
51 | # This is apparently the only way to keep the sshd service running.
52 | # Running sshd in the foreground in the context of a user (as it is done for linux), doesn't work under Windows.
53 | # Even if it is started as admin user, errors occur during logon (lack of some system rights)
54 | CMD powershell -NoExit -Command "Start-Service sshd"
55 | 


--------------------------------------------------------------------------------
/docs/source/examples/interengine/communicator.py:
--------------------------------------------------------------------------------
 1 | import socket
 2 | import uuid
 3 | 
 4 | import zmq
 5 | 
 6 | from ipyparallel.util import disambiguate_url
 7 | 
 8 | 
 9 | class EngineCommunicator:
10 |     def __init__(self, interface='tcp://*', identity=None):
11 |         self._ctx = zmq.Context()
12 |         self.socket = self._ctx.socket(zmq.XREP)
13 |         self.pub = self._ctx.socket(zmq.PUB)
14 |         self.sub = self._ctx.socket(zmq.SUB)
15 | 
16 |         # configure sockets
17 |         self.identity = identity or bytes(uuid.uuid4())
18 |         self.socket.IDENTITY = self.identity
19 |         self.sub.SUBSCRIBE = b''
20 | 
21 |         # bind to ports
22 |         port = self.socket.bind_to_random_port(interface)
23 |         pub_port = self.pub.bind_to_random_port(interface)
24 |         self.url = f"{interface}:{port}"
25 |         self.pub_url = f"{interface}:{pub_port}"
26 |         # guess first public IP from socket
27 |         self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
28 |         self.peers = {}
29 | 
30 |     def __del__(self):
31 |         self.socket.close()
32 |         self.pub.close()
33 |         self.sub.close()
34 |         self._ctx.term()
35 | 
36 |     @property
37 |     def info(self):
38 |         """return the connection info for this object's sockets."""
39 |         return (self.identity, self.url, self.pub_url, self.location)
40 | 
41 |     def connect(self, peers):
42 |         """connect to peers.  `peers` will be a dict of 4-tuples, keyed by name.
43 |         {peer : (ident, addr, pub_addr, location)}
44 |         where peer is the name, ident is the XREP identity, addr,pub_addr are the
45 |         """
46 |         for peer, (ident, url, pub_url, location) in peers.items():
47 |             self.peers[peer] = ident
48 |             if ident != self.identity:
49 |                 self.sub.connect(disambiguate_url(pub_url, location))
50 |             if ident > self.identity:
51 |                 # prevent duplicate xrep, by only connecting
52 |                 # engines to engines with higher IDENTITY
53 |                 # a doubly-connected pair will crash
54 |                 self.socket.connect(disambiguate_url(url, location))
55 | 
56 |     def send(self, peers, msg, flags=0, copy=True):
57 |         if not isinstance(peers, list):
58 |             peers = [peers]
59 |         if not isinstance(msg, list):
60 |             msg = [msg]
61 |         for p in peers:
62 |             ident = self.peers[p]
63 |             self.socket.send_multipart([ident] + msg, flags=flags, copy=copy)
64 | 
65 |     def recv(self, flags=0, copy=True):
66 |         return self.socket.recv_multipart(flags=flags, copy=copy)[1:]
67 | 
68 |     def publish(self, msg, flags=0, copy=True):
69 |         if not isinstance(msg, list):
70 |             msg = [msg]
71 |         self.pub.send_multipart(msg, copy=copy)
72 | 
73 |     def consume(self, flags=0, copy=True):
74 |         return self.sub.recv_multipart(flags=flags, copy=copy)
75 | 


--------------------------------------------------------------------------------
/ipyparallel/nbextension/static/main.js:
--------------------------------------------------------------------------------
 1 | // Copyright (c) IPython Development Team.
 2 | // Distributed under the terms of the Modified BSD License.
 3 | 
 4 | define(function (require) {
 5 |   var $ = require("jquery");
 6 |   var IPython = require("base/js/namespace");
 7 |   var clusterlist = require("./clusterlist");
 8 | 
 9 |   var cluster_html = $(
10 |     [
11 |       '
', 12 | '
', 13 | '
', 14 | ' IPython parallel computing clusters', 15 | "
", 16 | '
', 17 | ' ', 18 | ' ', 19 | " ", 20 | "
", 21 | "
", 22 | '
', 23 | '
', 24 | '
profile
', 25 | '
cluster id
', 26 | '
status
', 27 | '
# of engines
', 28 | '
action
', 29 | "
", 30 | "
", 31 | "
", 32 | ].join("\n"), 33 | ); 34 | 35 | function load() { 36 | if (!IPython.notebook_list) return; 37 | var base_url = IPython.notebook_list.base_url; 38 | // hide the deprecated clusters tab 39 | $("#tabs").find('[href="#clusters"]').hide(); 40 | $("head").append( 41 | $("") 42 | .attr("rel", "stylesheet") 43 | .attr("type", "text/css") 44 | .attr("href", base_url + "nbextensions/ipyparallel/clusterlist.css"), 45 | ); 46 | $(".tab-content").append(cluster_html); 47 | $("#tabs").append( 48 | $("
  • ").append( 49 | $("") 50 | .attr("href", "#ipyclusters") 51 | .attr("data-toggle", "tab") 52 | .text("IPython Clusters") 53 | .click(function (e) { 54 | window.history.pushState(null, null, "#ipyclusters"); 55 | }), 56 | ), 57 | ); 58 | var cluster_list = new clusterlist.ClusterList("#cluster_list", { 59 | base_url: IPython.notebook_list.base_url, 60 | notebook_path: IPython.notebook_list.notebook_path, 61 | }); 62 | cluster_list.load_list(); 63 | if (document.location.hash === "#ipyclusters") { 64 | // focus clusters tab once it exists 65 | // since it won't have been created when the browser first handles the document hash 66 | $("[href='#ipyclusters']").tab("show"); 67 | } 68 | } 69 | return { 70 | load_ipython_extension: load, 71 | }; 72 | }); 73 | -------------------------------------------------------------------------------- /ci/slurm/etc_slurm/slurm.conf: -------------------------------------------------------------------------------- 1 | # slurm.conf 2 | # 3 | # See the slurm.conf man page for more information. 4 | # 5 | ClusterName=linux 6 | ControlMachine=slurmctld 7 | ControlAddr=slurmctld 8 | #BackupController= 9 | #BackupAddr= 10 | # 11 | SlurmUser=slurm 12 | #SlurmdUser=root 13 | SlurmctldPort=6817 14 | SlurmdPort=6818 15 | AuthType=auth/munge 16 | #JobCredentialPrivateKey= 17 | #JobCredentialPublicCertificate= 18 | StateSaveLocation=/var/lib/slurmd 19 | SlurmdSpoolDir=/var/spool/slurmd 20 | SwitchType=switch/none 21 | MpiDefault=none 22 | SlurmctldPidFile=/var/run/slurmd/slurmctld.pid 23 | SlurmdPidFile=/var/run/slurmd/slurmd.pid 24 | ProctrackType=proctrack/linuxproc 25 | #PluginDir= 26 | #CacheGroups=0 27 | #FirstJobId= 28 | ReturnToService=0 29 | #MaxJobCount= 30 | #PlugStackConfig= 31 | #PropagatePrioProcess= 32 | #PropagateResourceLimits= 33 | #PropagateResourceLimitsExcept= 34 | #Prolog= 35 | #Epilog= 36 | #SrunProlog= 37 | #SrunEpilog= 38 | #TaskProlog= 39 | #TaskEpilog= 40 | #TaskPlugin= 41 | #TrackWCKey=no 42 | #TreeWidth=50 43 | #TmpFS= 44 | #UsePAM= 45 | # 46 | # TIMERS 47 | SlurmctldTimeout=300 48 | SlurmdTimeout=300 49 | InactiveLimit=0 50 | MinJobAge=300 51 | KillWait=30 52 | Waittime=0 53 | # 54 | # SCHEDULING 55 | SchedulerType=sched/backfill 56 | #SchedulerAuth= 57 | #SchedulerPort= 58 | #SchedulerRootFilter= 59 | SelectType=select/cons_tres # <-- MODIFICAÇÃO 1: Atualizado para compatibilidade com TRES 60 | SelectTypeParameters=CR_Core_Memory 61 | #FastSchedule=1 # <-- MODIFICAÇÃO 2: Comentado por ser obsoleto 62 | #PriorityType=priority/multifactor 63 | #PriorityDecayHalfLife=14-0 64 | #PriorityUsageResetPeriod=14-0 65 | #PriorityWeightFairshare=100000 66 | #PriorityWeightAge=1000 67 | #PriorityWeightPartition=10000 68 | #PriorityWeightJobSize=1000 69 | #PriorityMaxAge=1-0 70 | # 71 | # LOGGING 72 | SlurmctldDebug=3 73 | SlurmctldLogFile=/var/log/slurm/slurmctld.log 74 | SlurmdDebug=3 75 | SlurmdLogFile=/var/log/slurm/slurmd.log 76 | JobCompType=jobcomp/filetxt 77 | JobCompLoc=/var/log/slurm/jobcomp.log 78 | # 79 | # ACCOUNTING 80 | JobAcctGatherType=jobacct_gather/linux 81 | JobAcctGatherFrequency=30 82 | # 83 | AccountingStorageType=accounting_storage/slurmdbd 84 | AccountingStorageHost=slurmdbd 85 | AccountingStoragePort=6819 86 | #AccountingStorageLoc=slurm_acct_db 87 | #AccountingStoragePass= 88 | #AccountingStorageUser= 89 | # 90 | # COMPUTE NODES 91 | #NodeName=c1 NodeAddr=slurm-compute-1 CPUs=2 RealMemory=1000 State=UNKNOWN 92 | #NodeName=c2 NodeAddr=slurm-compute-2 CPUs=2 RealMemory=1000 State=UNKNOWN 93 | # 94 | # PARTITIONS 95 | #PartitionName=normal Default=yes Nodes=c1,c2 Priority=50 DefMemPerCPU=500 Shared=NO MaxNodes=2 MaxTime=5-00:00:00 DefaultTime=5-00:00:00 State=UP 96 | # PartitionName=normal Nodes=c1,c2 Default=YES MaxTime=INFINITE State=UP 97 | 98 | # allow dynamic registration of compute nodes (slurmd -Z) 99 | Nodeset=ns1 Feature=compute 100 | 101 | PartitionName=all Nodes=ALL Default=yes MaxNodes=100 102 | MaxNodeCount=100 103 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | # Using IPython for parallel computing 2 | 3 | > ```{eval-rst} 4 | > 5 | > :Release: |release| 6 | > :Date: |today| 7 | > ``` 8 | 9 | (install)= 10 | 11 | ## Installing IPython Parallel 12 | 13 | As of 4.0, IPython parallel is now a standalone package called {mod}`ipyparallel`. 14 | You can install it with: 15 | 16 | ``` 17 | pip install ipyparallel 18 | ``` 19 | 20 | or: 21 | 22 | ``` 23 | conda install ipyparallel 24 | ``` 25 | 26 | As of IPython Parallel 7, 27 | this will include installing/enabling an extension for both the classic Jupyter Notebook 28 | and JupyterLab ≥ 3.0. 29 | 30 | ## Quickstart 31 | 32 | IPython Parallel 33 | 34 | A quick example to: 35 | 36 | 1. allocate a cluster (collection of IPython engines for use in parallel) 37 | 2. run a collection of tasks on the cluster 38 | 3. wait interactively for results 39 | 4. cleanup resources after the task is done 40 | 41 | ```python 42 | import time 43 | import ipyparallel as ipp 44 | 45 | task_durations = [1] * 25 46 | # request a cluster 47 | with ipp.Cluster() as rc: 48 | # get a view on the cluster 49 | view = rc.load_balanced_view() 50 | # submit the tasks 51 | asyncresult = view.map_async(time.sleep, task_durations) 52 | # wait interactively for results 53 | asyncresult.wait_interactive() 54 | # retrieve actual results 55 | result = asyncresult.get() 56 | # at this point, the cluster processes have been shutdown 57 | ``` 58 | 59 | 62 | 63 | You can similarly run MPI code using IPyParallel (requires [mpi4py](https://mpi4py.readthedocs.io/en/stable/install.html)): 64 | 65 | ```python 66 | import ipyparallel as ipp 67 | 68 | def mpi_example(): 69 | from mpi4py import MPI 70 | comm = MPI.COMM_WORLD 71 | return f"Hello World from rank {comm.Get_rank()}. total ranks={comm.Get_size()}" 72 | 73 | # request an MPI cluster with 4 engines 74 | with ipp.Cluster(engines='mpi', n=4) as rc: 75 | # get a broadcast_view on the cluster which is best 76 | # suited for MPI style computation 77 | view = rc.broadcast_view() 78 | # run the mpi_example function on all engines in parallel 79 | r = view.apply_sync(mpi_example) 80 | # Retrieve and print the result from the engines 81 | print("\n".join(r)) 82 | # at this point, the cluster processes have been shutdown 83 | ``` 84 | 85 | ![IPyParallel-MPI-Example](./_static/IPyParallel-MPI-Example.png) 86 | 87 | Follow the [tutorial][] to learn more. 88 | 89 | [tutorial]: ./tutorial/index 90 | 91 | ## Contents 92 | 93 | ```{toctree} 94 | :maxdepth: 2 95 | 96 | tutorial/index 97 | reference/index 98 | ``` 99 | 100 | ```{toctree} 101 | :maxdepth: 2 102 | 103 | examples/index 104 | ``` 105 | 106 | ```{toctree} 107 | :maxdepth: 1 108 | 109 | changelog 110 | ``` 111 | 112 | # IPython Parallel API 113 | 114 | ```{toctree} 115 | :maxdepth: 2 116 | 117 | api/ipyparallel.rst 118 | ``` 119 | 120 | # Indices and tables 121 | 122 | - {ref}`genindex` 123 | - {ref}`modindex` 124 | - {ref}`search` 125 | -------------------------------------------------------------------------------- /COPYING.md: -------------------------------------------------------------------------------- 1 | # Licensing terms 2 | 3 | Traitlets is adapted from enthought.traits, Copyright (c) Enthought, Inc., 4 | under the terms of the Modified BSD License. 5 | 6 | This project is licensed under the terms of the Modified BSD License 7 | (also known as New or Revised or 3-Clause BSD), as follows: 8 | 9 | - Copyright (c) 2001-, IPython Development Team 10 | 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | Redistributions of source code must retain the above copyright notice, this 17 | list of conditions and the following disclaimer. 18 | 19 | Redistributions in binary form must reproduce the above copyright notice, this 20 | list of conditions and the following disclaimer in the documentation and/or 21 | other materials provided with the distribution. 22 | 23 | Neither the name of the IPython Development Team nor the names of its 24 | contributors may be used to endorse or promote products derived from this 25 | software without specific prior written permission. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 28 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 29 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | 38 | ## About the IPython Development Team 39 | 40 | The IPython Development Team is the set of all contributors to the IPython project. 41 | This includes all of the IPython subprojects. 42 | 43 | The core team that coordinates development on GitHub can be found here: 44 | https://github.com/jupyter/. 45 | 46 | ## Our Copyright Policy 47 | 48 | IPython uses a shared copyright model. Each contributor maintains copyright 49 | over their contributions to IPython. But, it is important to note that these 50 | contributions are typically only changes to the repositories. Thus, the IPython 51 | source code, in its entirety is not the copyright of any single person or 52 | institution. Instead, it is the collective copyright of the entire IPython 53 | Development Team. If individual contributors want to maintain a record of what 54 | changes/contributions they have specific copyright on, they should indicate 55 | their copyright in the commit message of the change, when they commit the 56 | change to one of the IPython repositories. 57 | 58 | With this in mind, the following banner should be used in any source code file 59 | to indicate the copyright and license terms: 60 | 61 | # Copyright (c) IPython Development Team. 62 | # Distributed under the terms of the Modified BSD License. 63 | -------------------------------------------------------------------------------- /docs/source/examples/interengine/bintree_script.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Script for setting up and using [all]reduce with a binary-tree engine interconnect. 4 | 5 | usage: `python bintree_script.py` 6 | 7 | This spanning tree strategy ensures that a single node node mailbox will never 8 | receive more that 2 messages at once. This is very important to scale to large 9 | clusters (e.g. 1000 nodes) since if you have many incoming messages of a couple 10 | of megabytes you might saturate the network interface of a single node and 11 | potentially its memory buffers if the messages are not consumed in a streamed 12 | manner. 13 | 14 | Note that the AllReduce scheme implemented with the spanning tree strategy 15 | impose the aggregation function to be commutative and distributive. It might 16 | not be the case if you implement the naive gather / reduce / broadcast strategy 17 | where you can reorder the partial data before performing the reduce. 18 | """ 19 | 20 | import ipyparallel as ipp 21 | 22 | # connect client and create views 23 | rc = ipp.Client() 24 | rc.block = True 25 | ids = rc.ids 26 | 27 | root_id = ids[0] 28 | root = rc[root_id] 29 | 30 | view = rc[:] 31 | 32 | from bintree import bintree, print_bintree 33 | 34 | # generate binary tree of parents 35 | btree = bintree(ids) 36 | 37 | print("setting up binary tree interconnect:") 38 | print_bintree(btree) 39 | 40 | view.run('bintree.py') 41 | view.scatter('id', ids, flatten=True) 42 | view['root_id'] = root_id 43 | 44 | # create the Communicator objects on the engines 45 | view.execute('com = BinaryTreeCommunicator(id, root = id==root_id )') 46 | pub_url = root.apply_sync(lambda: com.pub_url) # noqa: F821 47 | 48 | # gather the connection information into a dict 49 | ar = view.apply_async(lambda: com.info) # noqa: F821 50 | peers = ar.get_dict() 51 | # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators 52 | 53 | 54 | # connect the engines to each other: 55 | def connect(com, peers, tree, pub_url, root_id): 56 | """this function will be called on the engines""" 57 | com.connect(peers, tree, pub_url, root_id) 58 | 59 | 60 | view.apply_sync(connect, ipp.Reference('com'), peers, btree, pub_url, root_id) 61 | 62 | 63 | # functions that can be used for reductions 64 | # max and min builtins can be used as well 65 | def add(a, b): 66 | """cumulative sum reduction""" 67 | return a + b 68 | 69 | 70 | def mul(a, b): 71 | """cumulative product reduction""" 72 | return a * b 73 | 74 | 75 | view['add'] = add 76 | view['mul'] = mul 77 | 78 | # scatter some data 79 | data = list(range(1000)) 80 | view.scatter('data', data) 81 | 82 | # perform cumulative sum via allreduce 83 | print("reducing") 84 | ar = view.execute("data_sum = com.allreduce(add, data, flat=False)", block=False) 85 | ar.wait_interactive() 86 | print("allreduce sum of data on all engines:", view['data_sum']) 87 | 88 | # perform cumulative sum *without* final broadcast 89 | # when not broadcasting with allreduce, the final result resides on the root node: 90 | view.execute("ids_sum = com.reduce(add, id, flat=True)") 91 | print("reduce sum of engine ids (not broadcast):", root['ids_sum']) 92 | print("partial result on each engine:", view['ids_sum']) 93 | -------------------------------------------------------------------------------- /docs/source/examples/iopubwatcher.py: -------------------------------------------------------------------------------- 1 | """A script for watching all traffic on the IOPub channel (stdout/stderr/pyerr) of engines. 2 | 3 | This connects to the default cluster, or you can pass the path to your ipcontroller-client.json 4 | 5 | Try running this script, and then running a few jobs that print (and call sys.stdout.flush), 6 | and you will see the print statements as they arrive, notably not waiting for the results 7 | to finish. 8 | 9 | You can use the zeromq SUBSCRIBE mechanism to only receive information from specific engines, 10 | and easily filter by message type. 11 | 12 | Authors 13 | ------- 14 | * MinRK 15 | """ 16 | 17 | import json 18 | import sys 19 | 20 | import zmq 21 | from ipykernel.connect import find_connection_file 22 | from jupyter_client.session import Session 23 | 24 | 25 | def main(connection_file): 26 | """watch iopub channel, and print messages""" 27 | 28 | ctx = zmq.Context.instance() 29 | 30 | with open(connection_file) as f: 31 | cfg = json.loads(f.read()) 32 | 33 | reg_url = cfg['interface'] 34 | iopub_port = cfg['iopub'] 35 | iopub_url = f"{reg_url}:{iopub_port}" 36 | 37 | session = Session(key=cfg['key'].encode('ascii')) 38 | sub = ctx.socket(zmq.SUB) 39 | 40 | # This will subscribe to all messages: 41 | sub.SUBSCRIBE = b'' 42 | # replace with b'' with b'engine.1.stdout' to subscribe only to engine 1's stdout 43 | # 0MQ subscriptions are simple 'foo*' matches, so 'engine.1.' subscribes 44 | # to everything from engine 1, but there is no way to subscribe to 45 | # just stdout from everyone. 46 | # multiple calls to subscribe will add subscriptions, e.g. to subscribe to 47 | # engine 1's stderr and engine 2's stdout: 48 | # sub.SUBSCRIBE = b'engine.1.stderr' 49 | # sub.SUBSCRIBE = b'engine.2.stdout' 50 | sub.connect(iopub_url) 51 | while True: 52 | try: 53 | idents, msg = session.recv(sub, mode=0) 54 | except KeyboardInterrupt: 55 | return 56 | # ident always length 1 here 57 | topic = idents[0].decode('utf8', 'replace') 58 | if msg['msg_type'] == 'stream': 59 | # stdout/stderr 60 | # stream names are in msg['content']['name'], if you want to handle 61 | # them differently 62 | print("{}: {}".format(topic, msg['content']['text'])) 63 | elif msg['msg_type'] == 'error': 64 | # Python traceback 65 | c = msg['content'] 66 | print(topic + ':') 67 | for line in c['traceback']: 68 | # indent lines 69 | print(' ' + line) 70 | elif msg['msg_type'] == 'error': 71 | # Python traceback 72 | c = msg['content'] 73 | print(topic + ':') 74 | for line in c['traceback']: 75 | # indent lines 76 | print(' ' + line) 77 | 78 | 79 | if __name__ == '__main__': 80 | if len(sys.argv) > 1: 81 | pattern = sys.argv[1] 82 | else: 83 | # This gets the security file for the default profile: 84 | pattern = 'ipcontroller-client.json' 85 | cf = find_connection_file(pattern) 86 | print(f"Using connection file {cf}") 87 | main(cf) 88 | -------------------------------------------------------------------------------- /docs/source/examples/Parallel Decorator and map.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Load balanced map and parallel function decorator" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "collapsed": true 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "import ipyparallel as ipp" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "rc = ipp.Client()\n", 28 | "v = rc.load_balanced_view()" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 3, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "name": "stdout", 38 | "output_type": "stream", 39 | "text": [ 40 | "Simple, default map: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n" 41 | ] 42 | } 43 | ], 44 | "source": [ 45 | "result = v.map(lambda x: 2 * x, range(10))\n", 46 | "print(\"Simple, default map: \", list(result))" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 4, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Submitted tasks, got ids: ['b21595ef-61f4-4ec3-ac7a-7888c025e492', '5e29335b-526d-4516-b22f-0a4b358fa242', '18cd0bd2-7aad-4340-8c81-9b2e384d73d9', '1ef7ccbc-6790-4479-aa90-c4acb5fc8cc4', '8d2c6d43-6e59-4dcf-9511-70707871aeb1', '58042f85-a7c1-492e-a698-d2655c095424', 'd629bf13-4d8b-4a54-996e-d531306293ea', '79039685-1b02-4aa5-a259-4eb9b8d8a65a', '16ffe6f3-fe82-4610-9ec9-a0a3138313a9', 'a3d9050b-faf2-4fa4-873a-65c81cab4c56']\n", 59 | "Using a mapper: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n" 60 | ] 61 | } 62 | ], 63 | "source": [ 64 | "ar = v.map_async(lambda x: 2 * x, range(10))\n", 65 | "print(\"Submitted tasks, got ids: \", ar.msg_ids)\n", 66 | "result = ar.get()\n", 67 | "print(\"Using a mapper: \", result)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 5, 73 | "metadata": {}, 74 | "outputs": [ 75 | { 76 | "name": "stdout", 77 | "output_type": "stream", 78 | "text": [ 79 | "Using a parallel function: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n" 80 | ] 81 | } 82 | ], 83 | "source": [ 84 | "@v.parallel(block=True)\n", 85 | "def f(x):\n", 86 | " return 2 * x\n", 87 | "\n", 88 | "\n", 89 | "result = f.map(range(10))\n", 90 | "print(\"Using a parallel function: \", result)" 91 | ] 92 | } 93 | ], 94 | "metadata": { 95 | "kernelspec": { 96 | "display_name": "Python 3", 97 | "language": "python", 98 | "name": "python3" 99 | }, 100 | "language_info": { 101 | "codemirror_mode": { 102 | "name": "ipython", 103 | "version": 3 104 | }, 105 | "file_extension": ".py", 106 | "mimetype": "text/x-python", 107 | "name": "python", 108 | "nbconvert_exporter": "python", 109 | "pygments_lexer": "ipython3", 110 | "version": "3.5.1" 111 | } 112 | }, 113 | "nbformat": 4, 114 | "nbformat_minor": 0 115 | } 116 | -------------------------------------------------------------------------------- /ipyparallel/tests/test_canning.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | from binascii import b2a_hex 4 | from functools import partial 5 | 6 | from ipyparallel.serialize import canning 7 | from ipyparallel.serialize.canning import can, uncan 8 | 9 | 10 | def interactive(f): 11 | f.__module__ = '__main__' 12 | return f 13 | 14 | 15 | def dumps(obj): 16 | return pickle.dumps(can(obj)) 17 | 18 | 19 | def loads(obj): 20 | return uncan(pickle.loads(obj)) 21 | 22 | 23 | def roundtrip(obj): 24 | return loads(dumps(obj)) 25 | 26 | 27 | def test_no_closure(): 28 | @interactive 29 | def foo(): 30 | a = 5 31 | return a 32 | 33 | pfoo = dumps(foo) 34 | bar = loads(pfoo) 35 | assert foo() == bar() 36 | 37 | 38 | def test_generator_closure(): 39 | # this only creates a closure on Python 3 40 | @interactive 41 | def foo(): 42 | i = 'i' 43 | r = [i for j in (1, 2)] 44 | return r 45 | 46 | pfoo = dumps(foo) 47 | bar = loads(pfoo) 48 | assert foo() == bar() 49 | 50 | 51 | def test_nested_closure(): 52 | @interactive 53 | def foo(): 54 | i = 'i' 55 | 56 | def g(): 57 | return i 58 | 59 | return g() 60 | 61 | pfoo = dumps(foo) 62 | bar = loads(pfoo) 63 | assert foo() == bar() 64 | 65 | 66 | def test_closure(): 67 | i = 'i' 68 | 69 | @interactive 70 | def foo(): 71 | return i 72 | 73 | pfoo = dumps(foo) 74 | bar = loads(pfoo) 75 | assert foo() == bar() 76 | 77 | 78 | def test_uncan_bytes_buffer(): 79 | data = b'data' 80 | canned = can(data) 81 | canned.buffers = [memoryview(buf) for buf in canned.buffers] 82 | out = uncan(canned) 83 | assert out == data 84 | 85 | 86 | def test_can_partial(): 87 | def foo(x, y, z): 88 | return x * y * z 89 | 90 | partial_foo = partial(foo, 2, y=5) 91 | canned = can(partial_foo) 92 | assert isinstance(canned, canning.CannedPartial) 93 | dumped = pickle.dumps(canned) 94 | loaded = pickle.loads(dumped) 95 | pfoo2 = uncan(loaded) 96 | assert pfoo2(z=3) == partial_foo(z=3) 97 | 98 | 99 | def test_can_partial_buffers(): 100 | def foo(arg1, arg2, kwarg1, kwarg2): 101 | return f'{arg1}{arg2[:32]}{b2a_hex(kwarg1.tobytes()[:32])}{kwarg2}' 102 | 103 | buf1 = os.urandom(1024 * 1024) 104 | buf2 = memoryview(os.urandom(1024 * 1024)) 105 | partial_foo = partial(foo, 5, buf1, kwarg1=buf2, kwarg2=10) 106 | canned = can(partial_foo) 107 | assert len(canned.buffers) == 2 108 | assert isinstance(canned, canning.CannedPartial) 109 | buffers, canned.buffers = canned.buffers, [] 110 | dumped = pickle.dumps(canned) 111 | loaded = pickle.loads(dumped) 112 | loaded.buffers = buffers 113 | pfoo2 = uncan(loaded) 114 | assert pfoo2() == partial_foo() 115 | 116 | 117 | def test_keyword_only_arguments(): 118 | def compute(a, *, b=3): 119 | return a * b 120 | 121 | assert compute(2) == 6 122 | compute_2 = roundtrip(compute) 123 | assert compute_2(2) == 6 124 | 125 | 126 | def test_annotations(): 127 | def f(a: int): 128 | return a 129 | 130 | f2 = roundtrip(f) 131 | assert f2.__annotations__ == f.__annotations__ 132 | -------------------------------------------------------------------------------- /docs/source/examples/fetchparse.py: -------------------------------------------------------------------------------- 1 | """ 2 | An exceptionally lousy site spider 3 | Ken Kinder 4 | 5 | Updated for newparallel by Min Ragan-Kelley 6 | 7 | This module gives an example of how the task interface to the 8 | IPython controller works. Before running this script start the IPython controller 9 | and some engines using something like:: 10 | 11 | ipcluster start -n 4 12 | """ 13 | 14 | import sys 15 | import time 16 | 17 | import bs4 # noqa this isn't necessary, but it helps throw the dependency error earlier 18 | 19 | import ipyparallel as ipp 20 | 21 | 22 | def fetchAndParse(url, data=None): 23 | from urllib.parse import urljoin 24 | 25 | import bs4 # noqa 26 | import requests 27 | 28 | links = [] 29 | r = requests.get(url, data=data) 30 | r.raise_for_status() 31 | if 'text/html' in r.headers.get('content-type'): 32 | doc = bs4.BeautifulSoup(r.text, "html.parser") 33 | for node in doc.findAll('a'): 34 | href = node.get('href', None) 35 | if href: 36 | links.append(urljoin(url, href)) 37 | return links 38 | 39 | 40 | class DistributedSpider: 41 | # Time to wait between polling for task results. 42 | pollingDelay = 0.5 43 | 44 | def __init__(self, site): 45 | self.client = ipp.Client() 46 | self.view = self.client.load_balanced_view() 47 | self.mux = self.client[:] 48 | 49 | self.allLinks = [] 50 | self.linksWorking = {} 51 | self.linksDone = {} 52 | 53 | self.site = site 54 | 55 | def visitLink(self, url): 56 | if url not in self.allLinks: 57 | self.allLinks.append(url) 58 | if url.startswith(self.site): 59 | print(' ', url) 60 | self.linksWorking[url] = self.view.apply(fetchAndParse, url) 61 | 62 | def onVisitDone(self, links, url): 63 | print(url + ':') 64 | self.linksDone[url] = None 65 | del self.linksWorking[url] 66 | for link in links: 67 | self.visitLink(link) 68 | 69 | def run(self): 70 | self.visitLink(self.site) 71 | while self.linksWorking: 72 | print(len(self.linksWorking), 'pending...') 73 | self.synchronize() 74 | time.sleep(self.pollingDelay) 75 | 76 | def synchronize(self): 77 | for url, ar in list(self.linksWorking.items()): 78 | # Calling get_task_result with block=False will return None if the 79 | # task is not done yet. This provides a simple way of polling. 80 | try: 81 | links = ar.get(0) 82 | except ipp.error.TimeoutError: 83 | continue 84 | except Exception as e: 85 | self.linksDone[url] = None 86 | del self.linksWorking[url] 87 | print(f'{url}: {e}') 88 | else: 89 | self.onVisitDone(links, url) 90 | 91 | 92 | def main(): 93 | if len(sys.argv) > 1: 94 | site = sys.argv[1] 95 | else: 96 | site = input('Enter site to crawl: ') 97 | distributedSpider = DistributedSpider(site) 98 | distributedSpider.run() 99 | 100 | 101 | if __name__ == '__main__': 102 | main() 103 | -------------------------------------------------------------------------------- /ipyparallel/traitlets.py: -------------------------------------------------------------------------------- 1 | """Custom ipyparallel trait types""" 2 | 3 | from importlib.metadata import entry_points 4 | 5 | from traitlets import List, TraitError, Type 6 | 7 | 8 | class Launcher(Type): 9 | """Entry point-extended Type 10 | 11 | classes can be registered via entry points 12 | in addition to standard 'mypackage.MyClass' strings 13 | """ 14 | 15 | def __init__(self, *args, entry_point_group, **kwargs): 16 | self.entry_point_group = entry_point_group 17 | kwargs.setdefault('klass', 'ipyparallel.cluster.launcher.BaseLauncher') 18 | super().__init__(*args, **kwargs) 19 | 20 | _original_help = '' 21 | 22 | @property 23 | def help(self): 24 | """Extend help by listing currently installed choices""" 25 | chunks = [self._original_help] 26 | chunks.append("Currently installed: ") 27 | for key, entry_point in self.load_entry_points().items(): 28 | chunks.append(f" - {key}: {entry_point.value}") 29 | return '\n'.join(chunks) 30 | 31 | @help.setter 32 | def help(self, value): 33 | self._original_help = value 34 | 35 | def load_entry_points(self): 36 | """Load my entry point group""" 37 | return { 38 | entry_point.name.lower(): entry_point 39 | for entry_point in entry_points(group=self.entry_point_group) 40 | } 41 | 42 | def validate(self, obj, value): 43 | if isinstance(value, str): 44 | # first, look up in entry point registry 45 | registry = self.load_entry_points() 46 | key = value.lower() 47 | if key in registry: 48 | value = registry[key].load() 49 | return super().validate(obj, value) 50 | 51 | 52 | class PortList(List): 53 | """List of ports 54 | 55 | For use configuring a list of ports to consume 56 | 57 | Ports will be a list of valid ports 58 | 59 | Can be specified as a port-range string for convenience 60 | (mainly for use on the command-line) 61 | e.g. '10101-10105,10108' 62 | """ 63 | 64 | @staticmethod 65 | def parse_port_range(s): 66 | """Parse a port range string in the form '1,3-5,6' into [1,3,4,5,6]""" 67 | ports = [] 68 | ranges = s.split(",") 69 | for r in ranges: 70 | start, _, end = r.partition("-") 71 | start = int(start) 72 | if end: 73 | end = int(end) 74 | ports.extend(range(start, end + 1)) 75 | else: 76 | ports.append(start) 77 | return ports 78 | 79 | def from_string_list(self, s_list): 80 | ports = [] 81 | for s in s_list: 82 | ports.extend(self.parse_port_range(s)) 83 | return ports 84 | 85 | def validate(self, obj, value): 86 | if isinstance(value, str): 87 | value = self.parse_port_range(value) 88 | value = super().validate(obj, value) 89 | for item in value: 90 | if not isinstance(item, int): 91 | raise TraitError( 92 | f"Ports must be integers in range 1-65536, not {item!r}" 93 | ) 94 | if not 1 <= item <= 65536: 95 | raise TraitError( 96 | f"Ports must be integers in range 1-65536, not {item!r}" 97 | ) 98 | return value 99 | -------------------------------------------------------------------------------- /benchmarks/asv_runner.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import os 3 | import resource 4 | import socket 5 | import sys 6 | from subprocess import check_call 7 | 8 | import googleapiclient.discovery as gcd 9 | from cluster_start import start_cluster 10 | from google.cloud import storage 11 | 12 | os.environ['OPENBLAS_NUM_THREADS'] = '1' 13 | 14 | DEFAULT_MINICONDA_PATH = os.path.abspath(os.path.join('..', "miniconda3/bin/:")) 15 | env = os.environ.copy() 16 | env["PATH"] = DEFAULT_MINICONDA_PATH + env["PATH"] 17 | 18 | ZONE = "europe-west1-b" 19 | PROJECT_NAME = "jupyter-simula" 20 | BUCKET_NAME = 'ipyparallel_dev' 21 | 22 | instance_name = socket.gethostname() 23 | compute = gcd.build("compute", "v1") 24 | 25 | 26 | def cmd_run(*args, log_filename=None, error_filename=None): 27 | if len(args) == 1: 28 | args = args[0].split(" ") 29 | print(f'$ {" ".join(args)}') 30 | if not log_filename and not error_filename: 31 | check_call(args, env=env) 32 | else: 33 | check_call( 34 | args, 35 | env=env, 36 | stdout=open(log_filename, 'w'), 37 | stderr=open(error_filename, 'w'), 38 | ) 39 | 40 | 41 | def delete_self(): 42 | print(f'deleting: {instance_name}') 43 | compute.instances().delete( 44 | project=PROJECT_NAME, zone=ZONE, instance=instance_name 45 | ).execute() 46 | 47 | 48 | def upload_file(filename): 49 | blob_name = sys.argv[1] 50 | storage_client = storage.Client() 51 | bucket = storage_client.bucket(BUCKET_NAME) 52 | blob = bucket.blob(f'{instance_name}/{blob_name}') 53 | print(f'Uploading {filename}') 54 | blob.upload_from_filename(filename) 55 | 56 | 57 | if __name__ == '__main__': 58 | # atexit.register(delete_self) 59 | benchmark_name = sys.argv[1] 60 | template_name = sys.argv[2] 61 | 62 | soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) 63 | resource.setrlimit(resource.RLIMIT_NOFILE, (hard_limit, hard_limit)) 64 | 65 | ps = [] 66 | for i in [3, 4]: 67 | ps += start_cluster(i, 1030, '../miniconda3/bin/', log_output_to_file=True) 68 | # time.sleep(10) 69 | # ps += start_cluster( 70 | # 0, 'depth_0', 300, '../miniconda3/bin/', 71 | # log_output_to_file=True) 72 | # time.sleep(10) 73 | # ps += start_cluster( 74 | # 0, 'depth_1', 150, '../miniconda3/bin/', 75 | # log_output_to_file=True) 76 | 77 | log_filename = f'{instance_name}.log' 78 | error_log_filename = f'{instance_name}.error.log' 79 | 80 | def clean_up(): 81 | for p in ps: 82 | p.kill() 83 | 84 | atexit.register(clean_up) 85 | # cmd_run("ipcluster start -n 200 --daemon --profile=asv") # Starting 200 engines 86 | cmd_run( 87 | 'asv run -b DepthTestingSuite --verbose --show-stderr', 88 | # log_filename=log_filename, 89 | # error_filename=error_log_filename, 90 | ) 91 | clean_up() 92 | # cmd_run("ipcluster stop --profile=asv") 93 | print('uploading files') 94 | # upload_file(log_filename) 95 | # upload_file(error_log_filename) 96 | results_dir = f'results/{template_name}' 97 | for file_name in os.listdir(results_dir): 98 | if 'conda' in file_name: 99 | upload_file(f'{results_dir}/{file_name}') 100 | print('script finished') 101 | -------------------------------------------------------------------------------- /ipyparallel/shellcmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Commandline interface to OS independent shell commands 4 | 5 | Currently the following command are supported: 6 | * start: starts a process into background and returns its pid 7 | * running: check if a process with a given pid is running 8 | * kill: kill a process with a given pid 9 | * mkdir: creates directory recursively (no error if directory already exists) 10 | * rmdir: remove directory recursively 11 | * exists: checks if a file/directory exists 12 | * remove: removes a file 13 | """ 14 | 15 | import os 16 | import sys 17 | from argparse import ArgumentParser 18 | 19 | from .cluster.shellcmd import ShellCommandReceive 20 | 21 | 22 | def main(): 23 | parser = ArgumentParser( 24 | description='Perform some standard shell command in a platform independent way' 25 | ) 26 | parser.add_argument( 27 | '--debug', 28 | action='store_true', 29 | help='append command line parameters to \'sys_arg.txt\'', 30 | ) 31 | subparsers = parser.add_subparsers(dest='cmd', help='sub-command help') 32 | 33 | # create the parser for the "a" command 34 | parser_start = subparsers.add_parser( 35 | 'start', help='start a process into background' 36 | ) 37 | parser_start.add_argument('--env', help='optional environment dictionary') 38 | parser_start.add_argument( 39 | '--output_file', help='optional output redirection (for stdout and stderr)' 40 | ) 41 | parser_start.add_argument('start_cmd', nargs='+', help='command that help') 42 | 43 | parser_running = subparsers.add_parser( 44 | 'running', help='check if a process is running' 45 | ) 46 | parser_running.add_argument( 47 | 'pid', type=int, help='pid of process that should be checked' 48 | ) 49 | 50 | parser_kill = subparsers.add_parser('kill', help='kill a process') 51 | parser_kill.add_argument( 52 | 'pid', type=int, help='pid of process that should be killed' 53 | ) 54 | parser_kill.add_argument('--sig', type=int, help='signals to send') 55 | 56 | parser_mkdir = subparsers.add_parser('mkdir', help='create directory recursively') 57 | parser_mkdir.add_argument('path', help='directory path to be created') 58 | 59 | parser_rmdir = subparsers.add_parser('rmdir', help='remove directory recursively') 60 | parser_rmdir.add_argument('path', help='directory path to be removed') 61 | 62 | parser_exists = subparsers.add_parser( 63 | 'exists', help='checks if a file/directory exists' 64 | ) 65 | parser_exists.add_argument('path', help='path to check') 66 | 67 | parser_remove = subparsers.add_parser('remove', help='removes a file') 68 | parser_remove.add_argument('path', help='path to remove') 69 | 70 | if len(sys.argv) == 1: 71 | parser.print_help() 72 | sys.exit(0) 73 | 74 | args = parser.parse_args() 75 | cmd = args.__dict__.pop('cmd') 76 | 77 | if args.debug: 78 | with open("sys_arg.txt", "a") as f: 79 | f.write(f"'{__file__}' started in '{os.getcwd()}':\n") 80 | for idx, arg in enumerate(sys.argv[1:]): 81 | f.write(f"\t{idx}:{arg}$\n") 82 | del args.debug 83 | 84 | receiver = ShellCommandReceive() 85 | with receiver as r: 86 | recevier_method = getattr(r, f"cmd_{cmd}") 87 | recevier_method(**vars(args)) 88 | 89 | 90 | if __name__ == '__main__': 91 | main() 92 | -------------------------------------------------------------------------------- /ipyparallel/apps/logwatcher.py: -------------------------------------------------------------------------------- 1 | """ 2 | A logger object that consolidates messages incoming from ipcluster processes. 3 | """ 4 | 5 | import logging 6 | 7 | import zmq 8 | from jupyter_client.localinterfaces import localhost 9 | from tornado import ioloop 10 | from traitlets import Instance, List, Unicode 11 | from traitlets.config.configurable import LoggingConfigurable 12 | from zmq.eventloop import zmqstream 13 | 14 | 15 | class LogWatcher(LoggingConfigurable): 16 | """A simple class that receives messages on a SUB socket, as published 17 | by subclasses of `zmq.log.handlers.PUBHandler`, and logs them itself. 18 | 19 | This can subscribe to multiple topics, but defaults to all topics. 20 | """ 21 | 22 | # configurables 23 | topics = List( 24 | [''], 25 | config=True, 26 | help="The ZMQ topics to subscribe to. Default is to subscribe to all messages", 27 | ) 28 | url = Unicode(config=True, help="ZMQ url on which to listen for log messages") 29 | 30 | def _url_default(self): 31 | return f'tcp://{localhost()}:20202' 32 | 33 | # internals 34 | stream = Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True) 35 | 36 | context = Instance(zmq.Context) 37 | 38 | def _context_default(self): 39 | return zmq.Context.instance() 40 | 41 | loop = Instance('tornado.ioloop.IOLoop') 42 | 43 | def _loop_default(self): 44 | return ioloop.IOLoop.current() 45 | 46 | def __init__(self, **kwargs): 47 | super().__init__(**kwargs) 48 | s = self.context.socket(zmq.SUB) 49 | s.bind(self.url) 50 | self.stream = zmqstream.ZMQStream(s, self.loop) 51 | self.subscribe() 52 | self.on_trait_change(self.subscribe, 'topics') 53 | 54 | def start(self): 55 | self.stream.on_recv(self.log_message) 56 | 57 | def stop(self): 58 | self.stream.stop_on_recv() 59 | 60 | def subscribe(self): 61 | """Update our SUB socket's subscriptions.""" 62 | self.stream.setsockopt(zmq.UNSUBSCRIBE, '') 63 | if '' in self.topics: 64 | self.log.debug("Subscribing to: everything") 65 | self.stream.setsockopt(zmq.SUBSCRIBE, '') 66 | else: 67 | for topic in self.topics: 68 | self.log.debug(f"Subscribing to: {topic!r}") 69 | self.stream.setsockopt(zmq.SUBSCRIBE, topic) 70 | 71 | def _extract_level(self, topic_str): 72 | """Turn 'engine.0.INFO.extra' into (logging.INFO, 'engine.0.extra')""" 73 | topics = topic_str.split('.') 74 | for idx, t in enumerate(topics): 75 | level = getattr(logging, t, None) 76 | if level is not None: 77 | break 78 | 79 | if level is None: 80 | level = logging.INFO 81 | else: 82 | topics.pop(idx) 83 | 84 | return level, '.'.join(topics) 85 | 86 | def log_message(self, raw): 87 | """receive and parse a message, then log it.""" 88 | if len(raw) != 2 or '.' not in raw[0]: 89 | self.log.error(f"Invalid log message: {raw}") 90 | return 91 | else: 92 | topic, msg = raw 93 | # don't newline, since log messages always newline: 94 | topic, level_name = topic.rsplit('.', 1) 95 | level, topic = self._extract_level(topic) 96 | if msg[-1] == '\n': 97 | msg = msg[:-1] 98 | self.log.log(level, f"[{topic}] {msg}") 99 | -------------------------------------------------------------------------------- /ipyparallel/__init__.py: -------------------------------------------------------------------------------- 1 | """The IPython ZMQ-based parallel computing interface.""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | # export return_when constants 6 | import os 7 | from concurrent.futures import ALL_COMPLETED # noqa 8 | from concurrent.futures import FIRST_COMPLETED # noqa 9 | from concurrent.futures import FIRST_EXCEPTION # noqa 10 | 11 | from traitlets.config.configurable import MultipleInstanceError 12 | 13 | from ._version import __version__ # noqa 14 | from ._version import version_info # noqa 15 | from .client.asyncresult import * # noqa 16 | from .client.client import Client # noqa 17 | from .client.remotefunction import * # noqa 18 | from .client.view import * # noqa 19 | from .cluster import Cluster # noqa 20 | from .cluster import ClusterManager # noqa 21 | from .controller.dependency import * # noqa 22 | from .error import * # noqa 23 | from .serialize import * # noqa 24 | from .util import interactive # noqa 25 | 26 | # ----------------------------------------------------------------------------- 27 | # Functions 28 | # ----------------------------------------------------------------------------- 29 | 30 | 31 | def bind_kernel(**kwargs): 32 | """Bind an Engine's Kernel to be used as a full IPython kernel. 33 | 34 | This allows a running Engine to be used simultaneously as a full IPython kernel 35 | with the QtConsole or other frontends. 36 | 37 | This function returns immediately. 38 | """ 39 | from ipykernel.kernelapp import IPKernelApp 40 | 41 | from ipyparallel.engine.app import IPEngine 42 | 43 | if IPEngine.initialized(): 44 | try: 45 | app = IPEngine.instance() 46 | except MultipleInstanceError: 47 | pass 48 | else: 49 | return app.bind_kernel(**kwargs) 50 | 51 | raise RuntimeError("bind_kernel must be called from an IPEngine instance") 52 | 53 | 54 | def register_joblib_backend(name='ipyparallel', make_default=False): 55 | """Register the default ipyparallel backend for joblib.""" 56 | from .joblib import register 57 | 58 | return register(name=name, make_default=make_default) 59 | 60 | 61 | # nbextension installation (requires notebook ≥ 4.2) 62 | def _jupyter_server_extension_paths(): 63 | return [{'module': 'ipyparallel'}] 64 | 65 | 66 | def _jupyter_nbextension_paths(): 67 | return [ 68 | { 69 | 'section': 'tree', 70 | 'src': 'nbextension/static', 71 | 'dest': 'ipyparallel', 72 | 'require': 'ipyparallel/main', 73 | } 74 | ] 75 | 76 | 77 | def _jupyter_labextension_paths(): 78 | return [ 79 | { 80 | "src": "labextension", 81 | "dest": "ipyparallel-labextension", 82 | } 83 | ] 84 | 85 | 86 | def _load_jupyter_server_extension(app): 87 | """Load the server extension""" 88 | # localte the appropriate APIHandler base class before importing our handler classes 89 | from .nbextension.base import get_api_handler 90 | 91 | get_api_handler(app) 92 | 93 | from .nbextension.handlers import load_jupyter_server_extension 94 | 95 | return load_jupyter_server_extension(app) 96 | 97 | 98 | # backward-compat 99 | load_jupyter_server_extension = _load_jupyter_server_extension 100 | 101 | _NONINTERACTIVE = os.getenv("IPP_NONINTERACTIVE", "") not in {"", "0"} 102 | -------------------------------------------------------------------------------- /benchmarks/profiling/profiling_code_runner.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import sys 4 | import time 5 | from subprocess import Popen, check_call, check_output 6 | 7 | import ipyparallel as ipp 8 | 9 | 10 | def wait_for(condition): 11 | for _ in range(750): 12 | if condition(): 13 | break 14 | else: 15 | time.sleep(0.1) 16 | if not condition(): 17 | raise TimeoutError('wait_for took to long to finish') 18 | 19 | 20 | def get_time_stamp() -> str: 21 | return ( 22 | str(datetime.datetime.now()).split(".")[0].replace(" ", "-").replace(":", "-") 23 | ) 24 | 25 | 26 | def start_cmd(cmd, blocking=True): 27 | print(cmd) 28 | return ( 29 | check_call( 30 | cmd, 31 | stdout=sys.__stdout__, 32 | stderr=open('spanning_tree_error.out', 'a+'), 33 | shell=True, 34 | ) 35 | if blocking 36 | else Popen( 37 | cmd, 38 | stdout=sys.__stdout__, 39 | stderr=open('spanning_tree_error.out', 'a+'), 40 | shell=True, 41 | ) 42 | ) 43 | 44 | 45 | def stop_cluster(): 46 | if '-s' not in sys.argv: 47 | start_cmd('ipcluster stop --profile=asv') 48 | 49 | 50 | # atexit.register(stop_cluster) 51 | 52 | PROFILING_TASKS = [ 53 | 'many_empty_tasks', 54 | 'many_empty_tasks_non_blocking', 55 | 'tasks_with_large_data', 56 | 'echo_many_arguments', 57 | ] 58 | 59 | VIEW_TYPES = ['direct', 'load_balanced'] 60 | 61 | 62 | def get_tasks_to_execute(program_arguments): 63 | return ( 64 | [f'{program_arguments[2]} {view}' for view in VIEW_TYPES] 65 | if len(program_arguments) >= 2 66 | else [f'{task} {view}' for task in PROFILING_TASKS for view in VIEW_TYPES] 67 | ) 68 | 69 | 70 | if __name__ == "__main__": 71 | print('profiling_code_runner_started') 72 | if '-s' not in sys.argv: 73 | n = int(sys.argv[1]) if len(sys.argv) > 1 else 16 74 | client = ipp.Client(profile='asv') 75 | print(f'Waiting for {n} engines to get available') 76 | try: 77 | wait_for(lambda: len(client) >= n) 78 | except TimeoutError as e: 79 | print(e) 80 | exit(1) 81 | print('Starting the profiling') 82 | 83 | controller_pid = check_output('pgrep -f ipyparallel.controller', shell=True) 84 | number_of_schedulers = 15 85 | scheduler_pids = sorted(int(x) for x in controller_pid.decode('utf-8').split())[ 86 | -number_of_schedulers: 87 | ] 88 | 89 | client_output_path = os.path.join(os.getcwd(), 'spanning_tree_client.svg') 90 | 91 | files_to_upload = [client_output_path] 92 | ps = [] 93 | for i, scheduler_pid in enumerate(scheduler_pids): 94 | scheduler_output_path = os.path.join( 95 | os.getcwd(), f'spanning_tree_{i}_scheduler.svg' 96 | ) 97 | files_to_upload.append(scheduler_output_path) 98 | ps.append( 99 | start_cmd( 100 | f'sudo py-spy --function -d 60 --flame {scheduler_output_path} --pid {scheduler_pid}', 101 | blocking=False, 102 | ) 103 | ) 104 | 105 | start_cmd( 106 | f'sudo py-spy --function -d 60 --flame {client_output_path} -- python profiling_code.py tasks_with_large_data spanning_tree' 107 | ) 108 | print('client ended') 109 | for p in ps: 110 | p.wait() 111 | -------------------------------------------------------------------------------- /ipyparallel/_async.py: -------------------------------------------------------------------------------- 1 | """Async utilities""" 2 | 3 | import asyncio 4 | import concurrent.futures 5 | import inspect 6 | import threading 7 | from functools import partial 8 | 9 | from tornado.ioloop import IOLoop 10 | 11 | from ipyparallel.util import _OutputProducingThread as Thread 12 | 13 | 14 | def _asyncio_run(coro): 15 | """Like asyncio.run, but works when there's no event loop""" 16 | # for now: using tornado for broader compatibility with FDs, 17 | # e.g. when using the only partially functional default 18 | # Proactor on windows 19 | loop = IOLoop(make_current=False) 20 | try: 21 | return loop.run_sync(lambda: asyncio.ensure_future(coro)) 22 | finally: 23 | loop.close() 24 | 25 | 26 | class AsyncFirst: 27 | """Wrapper class that defines synchronous `_sync` method wrappers 28 | 29 | around async-native methods. 30 | 31 | Every coroutine method automatically gets an `_sync` alias 32 | that runs it synchronously. 33 | """ 34 | 35 | _async_thread = None 36 | 37 | def _thread_main(self): 38 | loop = self._thread_loop = IOLoop(make_current=False) 39 | loop.add_callback(self._loop_started.set) 40 | loop.start() 41 | 42 | def _in_thread(self, async_f, *args, **kwargs): 43 | """Run an async function in a background thread""" 44 | if self._async_thread is None: 45 | self._loop_started = threading.Event() 46 | self._async_thread = Thread(target=self._thread_main, daemon=True) 47 | self._async_thread.start() 48 | self._loop_started.wait(timeout=5) 49 | 50 | future = concurrent.futures.Future() 51 | 52 | async def thread_callback(): 53 | try: 54 | future.set_result(await async_f(*args, **kwargs)) 55 | except Exception as e: 56 | future.set_exception(e) 57 | 58 | self._thread_loop.add_callback(thread_callback) 59 | return future.result() 60 | 61 | def _synchronize(self, async_f, *args, **kwargs): 62 | """Run a method synchronously 63 | 64 | Uses asyncio.run if asyncio is not running, 65 | otherwise puts it in a background thread 66 | """ 67 | try: 68 | loop = asyncio.get_running_loop() 69 | except RuntimeError: 70 | # not in a running loop 71 | loop = None 72 | if loop: 73 | return self._in_thread(async_f, *args, **kwargs) 74 | else: 75 | return _asyncio_run(async_f(*args, **kwargs)) 76 | 77 | def __getattr__(self, name): 78 | if name.endswith("_sync"): 79 | # lazily define `_sync` method wrappers for coroutine methods 80 | async_name = name[:-5] 81 | async_method = super().__getattribute__(async_name) 82 | if not inspect.iscoroutinefunction(async_method): 83 | raise AttributeError(async_name) 84 | return partial(self._synchronize, async_method) 85 | return super().__getattribute__(name) 86 | 87 | def __dir__(self): 88 | attrs = super().__dir__() 89 | seen = set() 90 | for cls in self.__class__.mro(): 91 | for name, value in cls.__dict__.items(): 92 | if name in seen: 93 | continue 94 | seen.add(name) 95 | if inspect.iscoroutinefunction(value): 96 | async_name = name + "_sync" 97 | attrs.append(async_name) 98 | return attrs 99 | -------------------------------------------------------------------------------- /ipyparallel/tests/test_view_broadcast.py: -------------------------------------------------------------------------------- 1 | """test BroadcastView objects""" 2 | 3 | import pytest 4 | 5 | from . import test_view 6 | 7 | needs_map = pytest.mark.xfail(reason="map not yet implemented") 8 | 9 | 10 | @pytest.mark.usefixtures('ipython') 11 | class TestBroadcastView(test_view.TestView): 12 | is_coalescing = False 13 | 14 | def setup_method(self): 15 | super().setup_method() 16 | self._broadcast_view_used = False 17 | # use broadcast view for direct API 18 | real_direct_view = self.client.real_direct_view = self.client.direct_view 19 | 20 | def broadcast_or_direct(targets): 21 | if isinstance(targets, int): 22 | return real_direct_view(targets) 23 | else: 24 | self._broadcast_view_used = True 25 | return self.client.broadcast_view( 26 | targets, is_coalescing=self.is_coalescing 27 | ) 28 | 29 | self.client.direct_view = broadcast_or_direct 30 | 31 | def teardown_method(self): 32 | super().teardown_method() 33 | # note that a test didn't use a broadcast view 34 | if not self._broadcast_view_used: 35 | pytest.skip("No broadcast view used") 36 | 37 | @pytest.mark.xfail(reason="Tracking gets disconnected from original message") 38 | def test_scatter_tracked(self): 39 | pass 40 | 41 | 42 | class TestBroadcastViewCoalescing(TestBroadcastView): 43 | is_coalescing = True 44 | 45 | @pytest.mark.xfail(reason="coalescing view doesn't preserve target order") 46 | def test_target_ordering(self): 47 | self.minimum_engines(4) 48 | ids_in_order = self.client.ids 49 | dv = self.client.real_direct_view(ids_in_order) 50 | 51 | dv.scatter('rank', ids_in_order, flatten=True, block=True) 52 | assert dv['rank'] == ids_in_order 53 | 54 | view = self.client.broadcast_view(ids_in_order, is_coalescing=True) 55 | assert view['rank'] == ids_in_order 56 | 57 | view = self.client.broadcast_view(ids_in_order[::-1], is_coalescing=True) 58 | assert view['rank'] == ids_in_order[::-1] 59 | 60 | view = self.client.broadcast_view(ids_in_order[::2], is_coalescing=True) 61 | assert view['rank'] == ids_in_order[::2] 62 | 63 | view = self.client.broadcast_view(ids_in_order[::-2], is_coalescing=True) 64 | assert view['rank'] == ids_in_order[::-2] 65 | 66 | def test_engine_metadata(self): 67 | self.minimum_engines(4) 68 | ids_in_order = sorted(self.client.ids) 69 | dv = self.client.real_direct_view(ids_in_order) 70 | dv.scatter('rank', ids_in_order, flatten=True, block=True) 71 | view = self.client.broadcast_view(ids_in_order, is_coalescing=True) 72 | ar = view.pull('rank', block=False) 73 | result = ar.get(timeout=10) 74 | assert isinstance(ar.engine_id, list) 75 | assert isinstance(ar.engine_uuid, list) 76 | assert result == ar.engine_id 77 | assert sorted(ar.engine_id) == ids_in_order 78 | 79 | even_ids = ids_in_order[::-2] 80 | view = self.client.broadcast_view(even_ids, is_coalescing=True) 81 | ar = view.pull('rank', block=False) 82 | result = ar.get(timeout=10) 83 | assert isinstance(ar.engine_id, list) 84 | assert isinstance(ar.engine_uuid, list) 85 | assert result == ar.engine_id 86 | assert sorted(ar.engine_id) == sorted(even_ids) 87 | 88 | @pytest.mark.xfail(reason="displaypub ordering not preserved") 89 | def test_apply_displaypub(self): 90 | pass 91 | 92 | 93 | # FIXME 94 | del TestBroadcastView 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipyparallel-labextension", 3 | "version": "9.1.0", 4 | "private": true, 5 | "description": "A JupyterLab extension for IPython Parallel.", 6 | "keywords": [ 7 | "ipython", 8 | "jupyter", 9 | "jupyterlab", 10 | "jupyterlab-extension" 11 | ], 12 | "homepage": "https://github.com/ipython/ipyparallel", 13 | "bugs": { 14 | "url": "https://github.com/ipython/ipyparallel/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ipython/ipyparallel" 19 | }, 20 | "license": "BSD-3-Clause", 21 | "author": "Min Ragan-Kelley", 22 | "files": [ 23 | "lab/lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 24 | "lab/schema/*.json", 25 | "lab/style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" 26 | ], 27 | "main": "lab/lib/index.js", 28 | "types": "lab/lib/index.d.ts", 29 | "scripts": { 30 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 31 | "build:labextension": "jupyter labextension build .", 32 | "build:labextension:dev": "jupyter labextension build --development True .", 33 | "build:lib": "tsc", 34 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 35 | "clean": "jlpm run clean:lib", 36 | "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", 37 | "clean:labextension": "rimraf ipyparallel/labextension", 38 | "clean:lib": "rimraf lab/lib tsconfig.tsbuildinfo", 39 | "eslint": "eslint . --ext .ts,.tsx --fix", 40 | "eslint:check": "eslint . --ext .ts,.tsx", 41 | "install:extension": "jupyter labextension develop --overwrite .", 42 | "lint": "prettier --check '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}' && jlpm eslint:check", 43 | "prettier": "prettier --write '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}'", 44 | "prettier:check": "prettier --list-different '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}'", 45 | "test": "mocha", 46 | "watch": "run-p watch:src watch:labextension", 47 | "watch:labextension": "jupyter labextension watch .", 48 | "watch:src": "tsc -w" 49 | }, 50 | "dependencies": { 51 | "@jupyterlab/application": "^4.4.9", 52 | "@jupyterlab/apputils": "^4.5.9", 53 | "@jupyterlab/codeeditor": "^4.4.9", 54 | "@jupyterlab/console": "^4.4.9", 55 | "@jupyterlab/coreutils": "^6.4.9", 56 | "@jupyterlab/nbformat": "^4.4.9", 57 | "@jupyterlab/notebook": "^4.4.9", 58 | "@jupyterlab/services": "^7.4.9", 59 | "@jupyterlab/settingregistry": "^4.4.9", 60 | "@jupyterlab/statedb": "^4.4.9", 61 | "@jupyterlab/ui-components": "^4.4.9", 62 | "@lumino/algorithm": "^2.0.3", 63 | "@lumino/commands": "^2.3.2", 64 | "@lumino/coreutils": "^2.2.1", 65 | "@lumino/domutils": "^2.0.3", 66 | "@lumino/dragdrop": "^2.1.6", 67 | "@lumino/messaging": "^2.0.3", 68 | "@lumino/polling": "^2.1.4", 69 | "@lumino/signaling": "^2.1.4", 70 | "@lumino/widgets": "^2.7.1", 71 | "react": "^18.2.0", 72 | "react-dom": "^18.2.0" 73 | }, 74 | "devDependencies": { 75 | "@jupyterlab/builder": "^4.4.9", 76 | "@types/react": "^18.0.26", 77 | "@types/react-dom": "~18.3.1", 78 | "@typescript-eslint/eslint-plugin": "^8.46.0", 79 | "@typescript-eslint/parser": "^8.46.0", 80 | "eslint": "^9.37.0", 81 | "eslint-config-prettier": "^10.1.8", 82 | "eslint-plugin-prettier": "^5.5.4", 83 | "eslint-plugin-react": "^7.37.5", 84 | "npm-run-all2": "^8.0.4", 85 | "prettier": "^3.6.2", 86 | "rimraf": "^6.0.1", 87 | "typescript": "~5.9.3" 88 | }, 89 | "resolutions": { 90 | "@types/react": "^18.0.26" 91 | }, 92 | "jupyterlab": { 93 | "extension": true, 94 | "schemaDir": "lab/schema", 95 | "webpackConfig": "lab/webpack.config.js", 96 | "outputDir": "ipyparallel/labextension" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/source/examples/dependencies.py: -------------------------------------------------------------------------------- 1 | import ipyparallel as ipp 2 | from ipyparallel import Dependency 3 | 4 | client = ipp.Client() 5 | 6 | 7 | # this will only run on machines that can import numpy: 8 | @ipp.require('numpy') 9 | def norm(A): 10 | from numpy.linalg import norm 11 | 12 | return norm(A, 2) 13 | 14 | 15 | def checkpid(pid): 16 | """return the pid of the engine""" 17 | import os 18 | 19 | return os.getpid() == pid 20 | 21 | 22 | def checkhostname(host): 23 | import socket 24 | 25 | return socket.gethostname() == host 26 | 27 | 28 | def getpid(): 29 | import os 30 | 31 | return os.getpid() 32 | 33 | 34 | pid0 = client[0].apply_sync(getpid) 35 | 36 | 37 | # this will depend on the pid being that of target 0: 38 | @ipp.depend(checkpid, pid0) 39 | def getpid2(): 40 | import os 41 | 42 | return os.getpid() 43 | 44 | 45 | view = client.load_balanced_view() 46 | view.block = True 47 | 48 | # will run on anything: 49 | pids1 = [view.apply(getpid) for i in range(len(client.ids))] 50 | print(pids1) 51 | # will only run on e0: 52 | pids2 = [view.apply(getpid2) for i in range(len(client.ids))] 53 | print(pids2) 54 | 55 | print("now test some dependency behaviors") 56 | 57 | 58 | def wait(t): 59 | import time 60 | 61 | time.sleep(t) 62 | return t 63 | 64 | 65 | # fail after some time: 66 | def wait_and_fail(t): 67 | import time 68 | 69 | time.sleep(t) 70 | return 1 / 0 71 | 72 | 73 | successes = [view.apply_async(wait, 1).msg_ids[0] for i in range(len(client.ids))] 74 | failures = [ 75 | view.apply_async(wait_and_fail, 1).msg_ids[0] for i in range(len(client.ids)) 76 | ] 77 | 78 | mixed = [failures[0], successes[0]] 79 | d1a = Dependency(mixed, all=False, failure=True) # yes 80 | d1b = Dependency(mixed, all=False) # yes 81 | d2a = Dependency(mixed, all=True, failure=True) # yes after / no follow 82 | d2b = Dependency(mixed, all=True) # no 83 | d3 = Dependency(failures, all=False) # no 84 | d4 = Dependency(failures, all=False, failure=True) # yes 85 | d5 = Dependency(failures, all=True, failure=True) # yes after / no follow 86 | d6 = Dependency(successes, all=True, failure=True) # yes after / no follow 87 | 88 | view.block = False 89 | flags = view.temp_flags 90 | with flags(after=d1a): 91 | r1a = view.apply(getpid) 92 | with flags(follow=d1b): 93 | r1b = view.apply(getpid) 94 | with flags(after=d2b, follow=d2a): 95 | r2a = view.apply(getpid) 96 | with flags(after=d2a, follow=d2b): 97 | r2b = view.apply(getpid) 98 | with flags(after=d3): 99 | r3 = view.apply(getpid) 100 | with flags(after=d4): 101 | r4a = view.apply(getpid) 102 | with flags(follow=d4): 103 | r4b = view.apply(getpid) 104 | with flags(after=d3, follow=d4): 105 | r4c = view.apply(getpid) 106 | with flags(after=d5): 107 | r5 = view.apply(getpid) 108 | with flags(follow=d5, after=d3): 109 | r5b = view.apply(getpid) 110 | with flags(follow=d6): 111 | r6 = view.apply(getpid) 112 | with flags(after=d6, follow=d2b): 113 | r6b = view.apply(getpid) 114 | 115 | 116 | def should_fail(f): 117 | try: 118 | f() 119 | except ipp.error.KernelError: 120 | pass 121 | else: 122 | print('should have raised') 123 | # raise Exception("should have raised") 124 | 125 | 126 | # print(r1a.msg_ids) 127 | r1a.get() 128 | # print(r1b.msg_ids) 129 | r1b.get() 130 | # print(r2a.msg_ids) 131 | should_fail(r2a.get) 132 | # print(r2b.msg_ids) 133 | should_fail(r2b.get) 134 | # print(r3.msg_ids) 135 | should_fail(r3.get) 136 | # print(r4a.msg_ids) 137 | r4a.get() 138 | # print(r4b.msg_ids) 139 | r4b.get() 140 | # print(r4c.msg_ids) 141 | should_fail(r4c.get) 142 | # print(r5.msg_ids) 143 | r5.get() 144 | # print(r5b.msg_ids) 145 | should_fail(r5b.get) 146 | # print(r6.msg_ids) 147 | should_fail(r6.get) # assuming > 1 engine 148 | # print(r6b.msg_ids) 149 | should_fail(r6b.get) 150 | print('done') 151 | -------------------------------------------------------------------------------- /ipyparallel/client/futures.py: -------------------------------------------------------------------------------- 1 | """Future-related utils""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | import sys 6 | from concurrent.futures import Future 7 | from threading import Event 8 | 9 | from tornado.log import app_log 10 | 11 | 12 | class MessageFuture(Future): 13 | """Future class to wrap async messages""" 14 | 15 | def __init__(self, msg_id, header=None, *, track=False): 16 | super().__init__() 17 | self.msg_id = msg_id 18 | self.header = header or {"msg_type": "unknown_request"} 19 | self._evt = Event() 20 | self.track = track 21 | self._tracker = None 22 | self.tracker = Future() 23 | self.iopub_callbacks = [] 24 | if not track: 25 | self.tracker.set_result(None) 26 | self.add_done_callback(lambda f: self._evt.set()) 27 | 28 | def wait(self, timeout=None): 29 | if not self.done(): 30 | return self._evt.wait(timeout) 31 | return True 32 | 33 | 34 | # The following are from tornado 5.0b1 35 | # avoids hang using gen.multi_future on asyncio, 36 | # because Futures cannot be created in another thread 37 | 38 | 39 | def future_set_result_unless_cancelled(future, value): 40 | """Set the given ``value`` as the `Future`'s result, if not cancelled. 41 | 42 | Avoids asyncio.InvalidStateError when calling set_result() on 43 | a cancelled `asyncio.Future`. 44 | 45 | .. versionadded:: 5.0 46 | """ 47 | if not future.cancelled(): 48 | future.set_result(value) 49 | 50 | 51 | def future_set_exc_info(future, exc_info): 52 | """Set the given ``exc_info`` as the `Future`'s exception. 53 | 54 | Understands both `asyncio.Future` and Tornado's extensions to 55 | enable better tracebacks on Python 2. 56 | 57 | .. versionadded:: 5.0 58 | """ 59 | if hasattr(future, 'set_exc_info'): 60 | # Tornado's Future 61 | future.set_exc_info(exc_info) 62 | else: 63 | # asyncio.Future 64 | future.set_exception(exc_info[1]) 65 | 66 | 67 | def future_add_done_callback(future, callback): 68 | """Arrange to call ``callback`` when ``future`` is complete. 69 | 70 | ``callback`` is invoked with one argument, the ``future``. 71 | 72 | If ``future`` is already done, ``callback`` is invoked immediately. 73 | This may differ from the behavior of ``Future.add_done_callback``, 74 | which makes no such guarantee. 75 | 76 | .. versionadded:: 5.0 77 | """ 78 | if future.done(): 79 | callback(future) 80 | else: 81 | future.add_done_callback(callback) 82 | 83 | 84 | def multi_future(children): 85 | """Wait for multiple asynchronous futures in parallel. 86 | 87 | This function is similar to `multi`, but does not support 88 | `YieldPoints `. 89 | 90 | .. versionadded:: 4.0 91 | """ 92 | unfinished_children = set(children) 93 | 94 | future = Future() 95 | if not children: 96 | future_set_result_unless_cancelled(future, []) 97 | 98 | def callback(f): 99 | unfinished_children.remove(f) 100 | if not unfinished_children: 101 | result_list = [] 102 | for f in children: 103 | try: 104 | result_list.append(f.result()) 105 | except Exception as e: 106 | if future.done(): 107 | app_log.error( 108 | "Multiple exceptions in yield list", exc_info=True 109 | ) 110 | else: 111 | future_set_exc_info(future, sys.exc_info()) 112 | if not future.done(): 113 | future_set_result_unless_cancelled(future, result_list) 114 | 115 | listening = set() 116 | for f in children: 117 | if f not in listening: 118 | listening.add(f) 119 | future_add_done_callback(f, callback) 120 | return future 121 | -------------------------------------------------------------------------------- /ipyparallel/tests/test_dependency.py: -------------------------------------------------------------------------------- 1 | """Tests for dependency.py""" 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | import ipyparallel as ipp 6 | from ipyparallel.serialize import can, uncan 7 | from ipyparallel.util import interactive 8 | 9 | from .clienttest import ClusterTestCase, raises_remote 10 | 11 | 12 | @ipp.require('time') 13 | def wait(n): 14 | time.sleep(n) # noqa: F821 15 | return n 16 | 17 | 18 | @ipp.interactive 19 | def func(x): 20 | return x * x 21 | 22 | 23 | mixed = list(map(str, range(10))) 24 | completed = list(map(str, range(0, 10, 2))) 25 | failed = list(map(str, range(1, 10, 2))) 26 | 27 | 28 | class TestDependency(ClusterTestCase): 29 | def setup_method(self): 30 | super().setup_method() 31 | self.user_ns = {'__builtins__': __builtins__} 32 | self.view = self.client.load_balanced_view() 33 | self.dview = self.client[-1] 34 | self.succeeded = set(map(str, range(0, 25, 2))) 35 | self.failed = set(map(str, range(1, 25, 2))) 36 | 37 | def assertMet(self, dep): 38 | assert dep.check(self.succeeded, self.failed), "Dependency should be met" 39 | 40 | def assertUnmet(self, dep): 41 | assert not dep.check(self.succeeded, self.failed), ( 42 | "Dependency should not be met" 43 | ) 44 | 45 | def assertUnreachable(self, dep): 46 | assert dep.unreachable(self.succeeded, self.failed), ( 47 | "Dependency should be unreachable" 48 | ) 49 | 50 | def assertReachable(self, dep): 51 | assert not dep.unreachable(self.succeeded, self.failed), ( 52 | "Dependency should be reachable" 53 | ) 54 | 55 | def cancan(self, f): 56 | """decorator to pass through canning into self.user_ns""" 57 | return uncan(can(f), self.user_ns) 58 | 59 | def test_require_imports(self): 60 | """test that @require imports names""" 61 | 62 | @self.cancan 63 | @ipp.require('base64') 64 | @interactive 65 | def encode(arg): 66 | return base64.b64encode(arg) # noqa: F821 67 | 68 | # must pass through canning to properly connect namespaces 69 | assert encode(b'foo') == b'Zm9v' 70 | 71 | def test_success_only(self): 72 | dep = ipp.Dependency(mixed, success=True, failure=False) 73 | self.assertUnmet(dep) 74 | self.assertUnreachable(dep) 75 | dep.all = False 76 | self.assertMet(dep) 77 | self.assertReachable(dep) 78 | dep = ipp.Dependency(completed, success=True, failure=False) 79 | self.assertMet(dep) 80 | self.assertReachable(dep) 81 | dep.all = False 82 | self.assertMet(dep) 83 | self.assertReachable(dep) 84 | 85 | def test_failure_only(self): 86 | dep = ipp.Dependency(mixed, success=False, failure=True) 87 | self.assertUnmet(dep) 88 | self.assertUnreachable(dep) 89 | dep.all = False 90 | self.assertMet(dep) 91 | self.assertReachable(dep) 92 | dep = ipp.Dependency(completed, success=False, failure=True) 93 | self.assertUnmet(dep) 94 | self.assertUnreachable(dep) 95 | dep.all = False 96 | self.assertUnmet(dep) 97 | self.assertUnreachable(dep) 98 | 99 | def test_require_function(self): 100 | @ipp.interactive 101 | def bar(a): 102 | return func(a) 103 | 104 | @ipp.require(func) 105 | @ipp.interactive 106 | def bar2(a): 107 | return func(a) 108 | 109 | self.client[:].clear() 110 | with raises_remote(NameError): 111 | self.view.apply_sync(bar, 5) 112 | ar = self.view.apply_async(bar2, 5) 113 | assert ar.get(5) == func(5) 114 | 115 | def test_require_object(self): 116 | @ipp.require(foo=func) 117 | @ipp.interactive 118 | def bar(a): 119 | return foo(a) # noqa: F821 120 | 121 | ar = self.view.apply_async(bar, 5) 122 | assert ar.get(5) == func(5) 123 | -------------------------------------------------------------------------------- /ipyparallel/client/map.py: -------------------------------------------------------------------------------- 1 | """Classes used in scattering and gathering sequences. 2 | 3 | Scattering consists of partitioning a sequence and sending the various 4 | pieces to individual nodes in a cluster. 5 | """ 6 | 7 | # Copyright (c) IPython Development Team. 8 | # Distributed under the terms of the Modified BSD License. 9 | import sys 10 | from itertools import chain, islice 11 | 12 | numpy = None 13 | 14 | 15 | def is_array(obj): 16 | """Is an object a numpy array? 17 | 18 | Avoids importing numpy until it is requested 19 | """ 20 | global numpy 21 | if 'numpy' not in sys.modules: 22 | return False 23 | 24 | if numpy is None: 25 | import numpy 26 | return isinstance(obj, numpy.ndarray) 27 | 28 | 29 | class Map: 30 | """A class for partitioning a sequence using a map.""" 31 | 32 | def getPartition(self, seq, p, q, n=None): 33 | """Returns the pth partition of q partitions of seq. 34 | 35 | The length can be specified as `n`, 36 | otherwise it is the value of `len(seq)` 37 | """ 38 | n = len(seq) if n is None else n 39 | # Test for error conditions here 40 | if p < 0 or p >= q: 41 | raise ValueError(f"must have 0 <= p <= q, but have p={p},q={q}") 42 | 43 | remainder = n % q 44 | basesize = n // q 45 | 46 | if p < remainder: 47 | low = p * (basesize + 1) 48 | high = low + basesize + 1 49 | else: 50 | low = p * basesize + remainder 51 | high = low + basesize 52 | 53 | try: 54 | result = seq[low:high] 55 | except TypeError: 56 | # some objects (iterators) can't be sliced, 57 | # use islice: 58 | result = list(islice(seq, low, high)) 59 | 60 | return result 61 | 62 | def joinPartitions(self, listOfPartitions): 63 | return self.concatenate(listOfPartitions) 64 | 65 | def concatenate(self, listOfPartitions): 66 | testObject = listOfPartitions[0] 67 | # First see if we have a known array type 68 | if is_array(testObject): 69 | return numpy.concatenate(listOfPartitions) 70 | # Next try for Python sequence types 71 | if isinstance(testObject, (list, tuple)): 72 | return list(chain.from_iterable(listOfPartitions)) 73 | # If we have scalars, just return listOfPartitions 74 | return listOfPartitions 75 | 76 | 77 | class RoundRobinMap(Map): 78 | """Partitions a sequence in a round robin fashion. 79 | 80 | This currently does not work! 81 | """ 82 | 83 | def getPartition(self, seq, p, q, n=None): 84 | n = len(seq) if n is None else n 85 | return seq[p:n:q] 86 | 87 | def joinPartitions(self, listOfPartitions): 88 | testObject = listOfPartitions[0] 89 | # First see if we have a known array type 90 | if is_array(testObject): 91 | return self.flatten_array(listOfPartitions) 92 | if isinstance(testObject, (list, tuple)): 93 | return self.flatten_list(listOfPartitions) 94 | return listOfPartitions 95 | 96 | def flatten_array(self, listOfPartitions): 97 | test = listOfPartitions[0] 98 | shape = list(test.shape) 99 | shape[0] = sum(p.shape[0] for p in listOfPartitions) 100 | A = numpy.ndarray(shape) 101 | N = shape[0] 102 | q = len(listOfPartitions) 103 | for p, part in enumerate(listOfPartitions): 104 | A[p:N:q] = part 105 | return A 106 | 107 | def flatten_list(self, listOfPartitions): 108 | flat = [] 109 | for i in range(len(listOfPartitions[0])): 110 | flat.extend([part[i] for part in listOfPartitions if len(part) > i]) 111 | return flat 112 | 113 | 114 | def mappable(obj): 115 | """return whether an object is mappable or not.""" 116 | if isinstance(obj, (tuple, list)): 117 | return True 118 | if is_array(obj): 119 | return True 120 | return False 121 | 122 | 123 | dists = {'b': Map, 'r': RoundRobinMap} 124 | -------------------------------------------------------------------------------- /docs/source/examples/rmt/rmt.ipy: -------------------------------------------------------------------------------- 1 | # 2 2 | 3 | # 4 | 5 | # # Eigenvalue distribution of Gaussian orthogonal random matrices 6 | 7 | # 8 | 9 | # The eigenvalues of random matrices obey certain statistical laws. Here we construct random matrices 10 | # from the Gaussian Orthogonal Ensemble (GOE), find their eigenvalues and then investigate the nearest 11 | # neighbor eigenvalue distribution $\rho(s)$. 12 | 13 | # 14 | 15 | from rmtkernel import ensemble_diffs, normalize_diffs, GOE 16 | import numpy as np 17 | import ipyparallel as ipp 18 | 19 | # 20 | 21 | # ## Wigner's nearest neighbor eigenvalue distribution 22 | 23 | # 24 | 25 | # The Wigner distribution gives the theoretical result for the nearest neighbor eigenvalue distribution 26 | # for the GOE: 27 | # 28 | # $$\rho(s) = \frac{\pi s}{2} \exp(-\pi s^2/4)$$ 29 | 30 | # 31 | 32 | def wigner_dist(s): 33 | """Returns (s, rho(s)) for the Wigner GOE distribution.""" 34 | return (np.pi*s/2.0) * np.exp(-np.pi*s**2/4.) 35 | 36 | # 37 | 38 | def generate_wigner_data(): 39 | s = np.linspace(0.0,4.0,400) 40 | rhos = wigner_dist(s) 41 | return s, rhos 42 | 43 | # 44 | 45 | s, rhos = generate_wigner_data() 46 | 47 | # 48 | 49 | plot(s, rhos) 50 | xlabel('Normalized level spacing s') 51 | ylabel('Probability $\rho(s)$') 52 | 53 | # 54 | 55 | # ## Serial calculation of nearest neighbor eigenvalue distribution 56 | 57 | # 58 | 59 | # In this section we numerically construct and diagonalize a large number of GOE random matrices 60 | # and compute the nerest neighbor eigenvalue distribution. This comptation is done on a single core. 61 | 62 | # 63 | 64 | def serial_diffs(num, N): 65 | """Compute the nearest neighbor distribution for num NxX matrices.""" 66 | diffs = ensemble_diffs(num, N) 67 | normalized_diffs = normalize_diffs(diffs) 68 | return normalized_diffs 69 | 70 | # 71 | 72 | serial_nmats = 1000 73 | serial_matsize = 50 74 | 75 | # 76 | 77 | %timeit -r1 -n1 serial_diffs(serial_nmats, serial_matsize) 78 | 79 | # 80 | 81 | serial_diffs = serial_diffs(serial_nmats, serial_matsize) 82 | 83 | # 84 | 85 | # The numerical computation agrees with the predictions of Wigner, but it would be nice to get more 86 | # statistics. For that we will do a parallel computation. 87 | 88 | # 89 | 90 | hist_data = hist(serial_diffs, bins=30, normed=True) 91 | plot(s, rhos) 92 | xlabel('Normalized level spacing s') 93 | ylabel('Probability $P(s)$') 94 | 95 | # 96 | 97 | # ## Parallel calculation of nearest neighbor eigenvalue distribution 98 | 99 | # 100 | 101 | # Here we perform a parallel computation, where each process constructs and diagonalizes a subset of 102 | # the overall set of random matrices. 103 | 104 | # 105 | 106 | def parallel-diffs(rc, num, N): 107 | nengines = len(rc.targets) 108 | num_per_engine = num/nengines 109 | print "Running with", num_per_engine, "per engine." 110 | ar = rc.apply_async(ensemble_diffs, num_per_engine, N) 111 | diffs = np.array(ar.get()).flatten() 112 | normalized_diffs = normalize_diffs(diffs) 113 | return normalized_diffs 114 | 115 | # 116 | 117 | client = ipp.Client() 118 | view = client[:] 119 | view.run('rmtkernel.py') 120 | view.block = False 121 | 122 | # 123 | 124 | parallel-nmats = 40*serial_nmats 125 | parallel-matsize = 50 126 | 127 | # 128 | 129 | %timeit -r1 -n1 parallel-diffs(view, parallel-nmats, parallel-matsize) 130 | 131 | # 132 | 133 | pdiffs = parallel-diffs(view, parallel-nmats, parallel-matsize) 134 | 135 | # 136 | 137 | # Again, the agreement with the Wigner distribution is excellent, but now we have better 138 | # statistics. 139 | 140 | # 141 | 142 | hist_data = hist(pdiffs, bins=30, normed=True) 143 | plot(s, rhos) 144 | xlabel('Normalized level spacing s') 145 | ylabel('Probability $P(s)$') 146 | --------------------------------------------------------------------------------