diff --git a/.gitignore b/.gitignore index 347bf6f..45ceb4c 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,15 @@ ENV/ # Visual Studio Code .vscode/ + +# ------------------------------------------------------------------------------ +# docs & rhino +# ------------------------------------------------------------------------------ + +docs/reference/generated/ + +*.3dmbak +*.rhl + +temp/** +!temp/.gitkeep diff --git a/.travis.yml b/.travis.yml index b64ee78..90049ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,44 +14,17 @@ language: python python: - - "2.7" +# - "2.7" - "3.6" -# * use these when not using a conda env before_install: -- pip install Cython --install-option="--no-cython-compile" +# - pip install Cython --install-option="--no-cython-compile" - pip install cmake install: - pip install --no-cache-dir -r requirements-dev.txt -# install: -# - sudo apt-get update -# # We do this conditionally because it saves us some downloading if the -# # version is the same. -# - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then -# wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; -# else -# wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; -# fi -# - bash miniconda.sh -b -p $HOME/miniconda -# - source "$HOME/miniconda/etc/profile.d/conda.sh" -# - hash -r -# - conda config --set always_yes yes --set changeps1 no -# - conda update -q conda -# # Useful for debugging any issues with conda -# - conda info -a -# - conda env create -f test_env.yml python=$TRAVIS_PYTHON_VERSION -# - conda activate test_env -# - pip install Cython --install-option="--no-cython-compile" -# - pip install --no-cache-dir -r requirements-dev.txt -# # Replace dep1 dep2 ... with your dependencies -# - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION dep1 dep2 ... - script: - pip install ikfast_pybind - invoke test -# - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then -# invoke docs; -# fi after_success: - coveralls diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 82fe470..a770861 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_ and this project adheres to `Semantic Versioning `_. + +Unreleased +---------- + +**Changed** + +* Changed doc theme to sphinx-rtd-theme, doc hosted on readthedocs + 0.1.1 ---------- diff --git a/README.rst b/README.rst index 5bd4f21..75b82b7 100644 --- a/README.rst +++ b/README.rst @@ -4,9 +4,9 @@ pybullet_planning .. start-badges -.. image:: https://img.shields.io/badge/License-MIT-blue.svg - :target: https://github.com/yijiangh/pybullet_planning/blob/master/LICENSE - :alt: License MIT +.. image:: https://readthedocs.org/projects/pybullet-planning/badge/?version=latest + :target: https://pybullet-planning.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status .. image:: https://travis-ci.com/yijiangh/pybullet_planning.svg?branch=master @@ -18,78 +18,86 @@ pybullet_planning :target: https://coveralls.io/github/yijiangh/pybullet_planning?branch=master :alt: Coveralls + +.. image:: https://img.shields.io/badge/License-MIT-blue.svg + :target: https://github.com/yijiangh/pybullet_planning/blob/master/LICENSE + :alt: License MIT + .. end-badges .. Write project description -*pybullet_planning* is a suite of utility functions to facilitate robotic planning related research on the `pybullet `_ physics simulation engine. +**pybullet_planning** is a suite of utility functions to facilitate robotic planning related research on the `pybullet `_ physics simulation engine. Main features ------------- -* feature +* easy-to-use functions to connect with pybullet, tailored for task and motion planning research +* built-in implementations of standard motion planners, including PRM, RRT, biRRT, A* etc. -**pybullet_planning** runs on Python 2.5+ and 3.x. +Getting Started +--------------- -Documentation -------------- +**pybullet_planning** can be installed using ``pip``: -.. Explain how to access documentation: API, examples, etc. +:: -.. -.. optional sections: + pip install pybullet_planning -Requirements ------------- -.. Write requirements instructions here +.. note:: + On Windows, you may need to install + `Microsoft Visual C++ 14.0 `_. -Installation ------------- -.. Write installation instructions here +Once the installation is completed, you can verify your setup. +Start Python from the command prompt and run the following: +:: -Contributing ------------- + >>> import pybullet_planning -Make sure you setup your local development environment correctly: -* Clone the `pybullet_planning `_ repository. -* Install development dependencies: +First Steps +--------------- -:: +* `Documentation `_ + +Contributing +------------ - pip install -r requirements-dev.txt +We love contributions! -**You're ready to start working!** +Check the `Contributor's Guide <./CONTRIBUTING.rst>`_ +for more details. -During development, use tasks on the -command line to ease recurring operations: +Releasing this project +---------------------- -* ``invoke clean``: Clean all generated artifacts. -* ``invoke check``: Run various code and documentation style checks. -* ``invoke docs``: Generate documentation. -* ``invoke test``: Run all tests and checks in one swift command. -* ``invoke``: Show available tasks. +Ready to release a new version of **pybullet_planning**? Here's how to do it: -For more details, check the `Contributor's Guide `_. +* We use `semver `_, i.e. we bump versions as follows: + * ``patch``: bugfixes. + * ``minor``: backwards-compatible features added. + * ``major``: backwards-incompatible changes. -Releasing this project ----------------------- +* Update the ``CHANGELOG.rst`` with all novelty! +* Ready? Release everything in one command: + +:: -.. Write releasing instructions here + invoke release [patch|minor|major] + # with -b to bump version +* Celebrate! 💃 -.. end of optional sections -.. Credits ------------- This package was initiated by Caelan Garrett `@caelan `_, -with some maintainence from Yijiang Huang `@yijiangh `_. +and other `contributors <./AUTHORS.rst>`_. diff --git a/docs/conf.py b/docs/conf.py index a786fac..c02fffd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,6 +2,31 @@ from __future__ import unicode_literals import os +import sphinx_rtd_theme + +# -- General configuration ------------------------------------------------ + +project = 'pybullet_planning' +year = '2020' +author = 'Caelan Garrett & Yijiang Huang' +copyright = '{0}, {1}'.format(year, author) +version = release = '0.1.1' + +master_doc = 'index' +source_suffix = '.rst' +# templates_path = ['_templates', ] +exclude_patterns = ['_build', '**.ipynb_checkpoints', '_notebooks'] + +pygments_style = 'sphinx' +show_authors = True +add_module_names = True + +extlinks = { + 'issue': ('https://github.com/yijiangh/pybullet_planning/issues/%s', '#'), + 'pr': ('https://github.com/yijiangh/pybullet_planning/pull/%s', 'PR #'), +} + +# -- Extension configuration ------------------------------------------------ extensions = [ 'sphinx.ext.autodoc', @@ -13,46 +38,59 @@ 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', + "sphinx_rtd_theme", ] if os.getenv('SPELLCHECK'): extensions += 'sphinxcontrib.spelling', spelling_show_suggestions = True spelling_lang = 'en_US' -source_suffix = '.rst' -master_doc = 'index' -project = 'pybullet_planning' -year = '2019' -author = 'Caelan Garrett' -copyright = '{0}, {1}'.format(year, author) -version = release = '0.1.1' +# intersphinx options +# intersphinx_mapping = {'python': ('https://docs.python.org/', None), +# 'roslibpy': ('http://roslibpy.readthedocs.org/en/latest/', None)} -pygments_style = 'trac' # Perhaps change to sphinx -templates_path = ['.'] -extlinks = { - 'issue': ('https://github.com/yijiangh/pybullet_planning/issues/%s', '#'), - 'pr': ('https://github.com/yijiangh/pybullet_planning/pull/%s', 'PR #'), +# autodoc options +autodoc_default_options = { + 'member-order': 'bysource', + 'special-members': '__init__', + 'exclude-members': '__weakref__', + 'undoc-members': True, + 'private-members': True, + 'show-inheritance': True, } + +autodoc_member_order = 'alphabetical' + +# autosummary options +autosummary_generate = True + # on_rtd is whether we are on readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -html_theme = 'alabaster' -html_theme_options = { - # 'logo': 'logo.png', - 'description': 'pybullet_planning', - 'github_user': 'yijiangh', - 'github_repo': project, - 'fixed_sidebar': True, -} +# on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +html_theme = "sphinx_rtd_theme" +# options: https://sphinx-rtd-theme.readthedocs.io/en/latest/configuring.html + +# napoleon options +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_include_private_with_doc = True +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = False +napoleon_use_rtype = False + +# -- Options for HTML output ---------------------------------------------- -html_use_smartypants = True -html_last_updated_fmt = '%b %d, %Y' html_split_index = False -html_static_path = ['_static'] -html_sidebars = { - '**': ['about.html', 'navigation.html', 'searchbox.html'], -} html_short_title = '%s-%s' % (project, version) - -napoleon_use_ivar = True -napoleon_use_rtype = False -napoleon_use_param = False +html_context = {} +html_static_path = ['_static'] +html_last_updated_fmt = '%b %d, %Y' +html_copy_source = False +html_show_sourcelink = False +html_add_permalinks = '' +html_experimental_html5_writer = True +html_compact_lists = True diff --git a/docs/index.rst b/docs/index.rst index 3fbea44..ddd3784 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,14 +4,25 @@ pybullet_planning .. start-badges +.. image:: https://readthedocs.org/projects/pybullet-planning/badge/?version=latest + :target: https://pybullet-planning.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + + +.. image:: https://travis-ci.com/yijiangh/pybullet_planning.svg?branch=master + :target: https://travis-ci.com/yijiangh/pybullet_planning + :alt: Travis CI + + +.. image:: https://coveralls.io/repos/github/yijiangh/pybullet_planning/badge.svg?branch=master + :target: https://coveralls.io/github/yijiangh/pybullet_planning?branch=master + :alt: Coveralls + + .. image:: https://img.shields.io/badge/License-MIT-blue.svg :target: https://github.com/yijiangh/pybullet_planning/blob/master/LICENSE :alt: License MIT -.. .. image:: https://travis-ci.org/yijiangh/pybullet_planning.svg?branch=master -.. :target: https://travis-ci.org/yijiangh/pybullet_planning -.. :alt: Travis CI - .. end-badges .. Write project description @@ -25,17 +36,11 @@ Contents ======== .. toctree:: - :maxdepth: 2 + :maxdepth: 3 + :titlesonly: readme - reference/index + reference contributing authors changelog - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/reference.rst b/docs/reference.rst new file mode 100644 index 0000000..1c6639e --- /dev/null +++ b/docs/reference.rst @@ -0,0 +1,10 @@ +.. _reference: + +************* +API Reference +************* + +.. toctree:: + :maxdepth: 2 + + reference/pybullet_planning diff --git a/docs/reference/index.rst b/docs/reference/index.rst deleted file mode 100644 index 9e924f0..0000000 --- a/docs/reference/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -API Reference -============= - -.. testsetup:: - - from pybullet_planning import * - -.. automodule:: pybullet_planning diff --git a/docs/reference/pybullet_planning.interfaces.rst b/docs/reference/pybullet_planning.interfaces.rst new file mode 100644 index 0000000..1c62a04 --- /dev/null +++ b/docs/reference/pybullet_planning.interfaces.rst @@ -0,0 +1,2 @@ + +.. automodule:: pybullet_planning.interfaces diff --git a/docs/reference/pybullet_planning.motion_planners.rst b/docs/reference/pybullet_planning.motion_planners.rst new file mode 100644 index 0000000..e02cb13 --- /dev/null +++ b/docs/reference/pybullet_planning.motion_planners.rst @@ -0,0 +1,2 @@ + +.. automodule:: pybullet_planning.motion_planners diff --git a/docs/reference/pybullet_planning.rst b/docs/reference/pybullet_planning.rst new file mode 100644 index 0000000..a730e8b --- /dev/null +++ b/docs/reference/pybullet_planning.rst @@ -0,0 +1,2 @@ + +.. automodule:: pybullet_planning diff --git a/docs/reference/pybullet_planning.utils.rst b/docs/reference/pybullet_planning.utils.rst new file mode 100644 index 0000000..52c859b --- /dev/null +++ b/docs/reference/pybullet_planning.utils.rst @@ -0,0 +1,2 @@ + +.. automodule:: pybullet_planning.utils diff --git a/src/pybullet_planning/__init__.py b/src/pybullet_planning/__init__.py index 24fc783..8a57f36 100644 --- a/src/pybullet_planning/__init__.py +++ b/src/pybullet_planning/__init__.py @@ -1,28 +1,26 @@ """ +******************************************************************************** +pybullet_planning +******************************************************************************** -Intro to project ... +.. currentmodule:: pybullet_planning +This library is a suite of utility functions to facilitate robotic planning related research on the `pybullet `_ physics simulation engine. -Setup -===== - -In order to use this library, ... - - -Main concepts -============= - -Describe typical classes found in project - -.. autoclass:: SampleClassName - :members: +.. toctree:: + :maxdepth: 1 + pybullet_planning.interfaces + pybullet_planning.motion_planners + pybullet_planning.utils """ +from .__version__ import __author__, __author_email__, __copyright__, __description__, __license__, \ + __title__, __url__, __version__ + from .utils import * from .interfaces import * from .motion_planners import * -# TODO: export version author etc. -# __all__ = [name for name in dir() if not name.startswith('_')] +__all__ = ['__author__', '__author_email__', '__copyright__', '__description__', '__license__', '__title__', '__url__', '__version__'] diff --git a/src/pybullet_planning/__version__.py b/src/pybullet_planning/__version__.py index b7c5b2a..80a8d18 100644 --- a/src/pybullet_planning/__version__.py +++ b/src/pybullet_planning/__version__.py @@ -2,7 +2,7 @@ __description__ = 'a suite of utility functions to facilitate robotic planning related research on the pybullet physics simulation engine.' __url__ = 'https://github.com/yijiangh/pybullet_planning' __version__ = '0.1.1' -__author__ = 'Caelan Garrett and Yijiang Huang' +__author__ = 'Caelan Garrett' __author_email__ = 'yijiangh@mit.edu' # this is only for the purpose of pypi... # __author_email__ = 'caelan@csail.mit.edu; ' __license__ = 'MIT license' diff --git a/src/pybullet_planning/interfaces/__init__.py b/src/pybullet_planning/interfaces/__init__.py index e5ab181..146fa9a 100644 --- a/src/pybullet_planning/interfaces/__init__.py +++ b/src/pybullet_planning/interfaces/__init__.py @@ -1,3 +1,33 @@ +""" +******************************************************************************** +interfaces +******************************************************************************** + +.. currentmodule:: pybullet_planning.interfaces + + +control +======= + +todo + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + control_joint + control_joints + +debug_utils +=========== + +todo + +.. autosummary:: + :toctree: generated/ + :nosignatures: + +""" from __future__ import absolute_import diff --git a/src/pybullet_planning/interfaces/control/control.py b/src/pybullet_planning/interfaces/control/control.py index f2bd89f..2ebbe55 100644 --- a/src/pybullet_planning/interfaces/control/control.py +++ b/src/pybullet_planning/interfaces/control/control.py @@ -11,6 +11,22 @@ # Control def control_joint(body, joint, value): + """[summary] + + Parameters + ---------- + body : [type] + [description] + joint : [type] + [description] + value : [type] + [description] + + Returns + ------- + [type] + [description] + """ return p.setJointMotorControl2(bodyUniqueId=body, jointIndex=joint, controlMode=p.POSITION_CONTROL, @@ -21,6 +37,22 @@ def control_joint(body, joint, value): physicsClientId=CLIENT) def control_joints(body, joints, positions): + """[summary] + + Parameters + ---------- + body : [type] + [description] + joints : [type] + [description] + positions : [type] + [description] + + Returns + ------- + [type] + [description] + """ # TODO: the whole PR2 seems to jitter #kp = 1.0 #kv = 0.3 diff --git a/src/pybullet_planning/motion_planners/__init__.py b/src/pybullet_planning/motion_planners/__init__.py index ec10e10..1237189 100644 --- a/src/pybullet_planning/motion_planners/__init__.py +++ b/src/pybullet_planning/motion_planners/__init__.py @@ -1,38 +1,49 @@ """ +******************************************************************************** +motion_planners +******************************************************************************** + +.. currentmodule:: pybullet_planning.motion_planners Python implementations of several robotic motion planners Sampling-based: -* Probabilistic Roadmap (PRM) -* Rapidly-Exploring Random Tree (RRT) -* RRT-Connect (BiRRT) -* Linear Shortcutting -* MultiRRT -* RRT* -Grid search -* Breadth-First Search (BFS) -* A* +- Probabilistic Roadmap (PRM) +- Rapidly-Exploring Random Tree (RRT) +- RRT-Connect (BiRRT) +- Linear Shortcutting +- MultiRRT +- RRT* +Grid search: -Setup -===== +- Breadth-First Search (BFS) +- A* -In order to use this library, ... +Probabilistic Roadmap (PRM) +============================= +.. autosummary:: + :toctree: generated/ + :nosignatures: -Main concepts -============= + lazy_prm -Describe typical classes found in project +RRT-Connect (BiRRT) +===================== -.. autoclass:: SampleClassName - :members: +.. autosummary:: + :toctree: generated/ + :nosignatures: + rrt_connect + birrt + direct_path """ -from .rrt_connect import birrt, direct_path -from .lazy_prm import lazy_prm +from .rrt_connect import * +from .lazy_prm import * -# __all__ = [name for name in dir() if not name.startswith('_')] +__all__ = [name for name in dir() if not name.startswith('_')] diff --git a/src/pybullet_planning/motion_planners/lazy_prm.py b/src/pybullet_planning/motion_planners/lazy_prm.py index d2170d1..01d4bce 100644 --- a/src/pybullet_planning/motion_planners/lazy_prm.py +++ b/src/pybullet_planning/motion_planners/lazy_prm.py @@ -9,6 +9,10 @@ import time import numpy as np +__all__ = [ + 'lazy_prm' + ] + Node = namedtuple('Node', ['g', 'parent']) unit_cost_fn = lambda v1, v2: 1. zero_heuristic_fn = lambda v: 0 diff --git a/src/pybullet_planning/motion_planners/rrt_connect.py b/src/pybullet_planning/motion_planners/rrt_connect.py index f74bc95..97e6d60 100644 --- a/src/pybullet_planning/motion_planners/rrt_connect.py +++ b/src/pybullet_planning/motion_planners/rrt_connect.py @@ -2,8 +2,37 @@ from .rrt import TreeNode, configs from .utils import irange, argmin, RRT_ITERATIONS, RRT_RESTARTS, RRT_SMOOTHING +__all__ = [ + 'rrt_connect', + 'birrt', + 'direct_path', + ] def rrt_connect(q1, q2, distance, sample, extend, collision, iterations=RRT_ITERATIONS): + """rrt_connect summary + + Parameters + ---------- + q1 : [type] + [description] + q2 : [type] + [description] + distance : [type] + [description] + sample : [type] + [description] + extend : [type] + [description] + collision : [type] + [description] + iterations : [type], optional + [description], by default RRT_ITERATIONS + + Returns + ------- + [type] + [description] + """ if collision(q1) or collision(q2): return None root1, root2 = TreeNode(q1), TreeNode(q2) @@ -36,6 +65,24 @@ def rrt_connect(q1, q2, distance, sample, extend, collision, iterations=RRT_ITER # TODO: version which checks whether the segment is valid def direct_path(q1, q2, extend, collision): + """direct path [summary] + + Parameters + ---------- + q1 : [type] + [description] + q2 : [type] + [description] + extend : [type] + [description] + collision : [type] + [description] + + Returns + ------- + [type] + [description] + """ if collision(q1) or collision(q2): return None path = [q1] @@ -48,6 +95,40 @@ def direct_path(q1, q2, extend, collision): def birrt(q1, q2, distance, sample, extend, collision, restarts=RRT_RESTARTS, iterations=RRT_ITERATIONS, smooth=RRT_SMOOTHING): + """birrt [summary] + + TODO: add citation to the algorithm. + See `pybullet_planning.interfaces.planner_interface.joint_motion_planning.plan_joint_motion` for an example + of standard usage. + + Parameters + ---------- + q1 : [type] + [description] + q2 : [type] + [description] + distance : [type] + see `pybullet_planning.interfaces.planner_interface.joint_motion_planning.get_difference_fn` for an example + sample : function handle + configuration space sampler + see `pybullet_planning.interfaces.planner_interface.joint_motion_planning.get_sample_fn` for an example + extend : function handle + see `pybullet_planning.interfaces.planner_interface.joint_motion_planning.get_extend_fn` for an example + collision : function handle + collision checking function + see `pybullet_planning.interfaces.robots.collision.get_collision_fn` for an example + restarts : int, optional + [description], by default RRT_RESTARTS + iterations : int, optional + [description], by default RRT_ITERATIONS + smooth : int, optional + smoothing iterations, by default RRT_SMOOTHING + + Returns + ------- + [type] + [description] + """ if collision(q1) or collision(q2): return None path = direct_path(q1, q2, extend, collision) diff --git a/src/pybullet_planning/motion_planners/smoothing.py b/src/pybullet_planning/motion_planners/smoothing.py index 2dc9196..7a5c756 100644 --- a/src/pybullet_planning/motion_planners/smoothing.py +++ b/src/pybullet_planning/motion_planners/smoothing.py @@ -2,6 +2,24 @@ def smooth_path(path, extend, collision, iterations=50): + """smooth a trajectory path, randomly replace jigged subpath with shortcuts + + Parameters + ---------- + path : list + [description] + extend : function + [description] + collision : function + [description] + iterations : int, optional + number of iterations for the random smoothing procedure, by default 50 + + Returns + ------- + [type] + [description] + """ smoothed_path = path for _ in range(iterations): if len(smoothed_path) <= 2: @@ -17,4 +35,4 @@ def smooth_path(path, extend, collision, iterations=50): smoothed_path = smoothed_path[:i + 1] + shortcut + smoothed_path[j + 1:] return smoothed_path -# TODO: sparsify path to just waypoints \ No newline at end of file +# TODO: sparsify path to just waypoints diff --git a/src/pybullet_planning/utils/__init__.py b/src/pybullet_planning/utils/__init__.py index 646f457..2cd1b77 100644 --- a/src/pybullet_planning/utils/__init__.py +++ b/src/pybullet_planning/utils/__init__.py @@ -1,6 +1,40 @@ -"""utility variables and functions +""" +******************************************************************************** +utils +******************************************************************************** + +.. currentmodule:: pybullet_planning.utils + +Package containing a set of utility functions and variables + +File system functions +===================== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + read_pickle + write_pickle + +Sampling functions +=================== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + randomize + +Transformation functions +======================== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + rotation_from_matrix -These variables/functions should be pybullet-independent. """ from .shared_const import * diff --git a/src/pybullet_planning/utils/file_io.py b/src/pybullet_planning/utils/file_io.py index 411958c..34fae09 100644 --- a/src/pybullet_planning/utils/file_io.py +++ b/src/pybullet_planning/utils/file_io.py @@ -35,12 +35,26 @@ def write(filename, string): f.write(string) def read_pickle(filename): + """[summary] + + Parameters + ---------- + filename : [type] + [description] + + Returns + ------- + [type] + [description] + """ # Can sometimes read pickle3 from python2 by calling twice # Can possibly read pickle2 from python3 by using encoding='latin1' with open(filename, 'rb') as f: return pickle.load(f) def write_pickle(filename, data): # NOTE - cannot pickle lambda or nested functions + """[summary] + """ with open(filename, 'wb') as f: pickle.dump(data, f)