Contribute

How to contribute to ReLax

This library uses nbdev for development. We love great flexibility offered by jupyter notebook, and nbdev in addressing limitations of using Notebook in developing large-scale projects (e.g., sync between notebooks and python modules, documentations).

Here, we only cover basis of our development procedure. For an in-depth use of nbdev, please refer to the nbdev tutorial. Following links are particularly useful:

Set up the working environment

Refer to installation guidance for installing ReLax. For running ReLax in CPU, you should

pip install jax-relax

Next, install Quarto for the documentation system. See nbdev docs for more details.

nbdev_install_quarto

Next, install hooks for cleaning Jupyter Notebooks.

nbdev_install_hooks

Write Code in Jupyter Notebook

Note that nbdev provides a best practice guidline to writing code in Jupyter Notebooks. Here, we present some of the most important steps.

Export Cell to Python Module

#| export marks code cells (in Notebook; .ipynb) to be exported to Python Module (.py). By default, this cell will be exported to the file defined in #| default_exp file_name (usually presented upfront).

For example, the below function will be exported to the Python module.

#| export
def func(args):
    ...

We can also specify files to be exported.

#| export file_name.py
def func(args):
    ...

For private functions/objects, we can use #| exporti. In this way, the code will still be exported to the file, but not included in __all__.

More about directives.

Two-way Sync between Notebooks (.ipynb) and Python Code (.py)

To update code written in Jupyter Notebook to Python Module (i.e., .ipynb -> .py)

nbdev_export

To sync code updated in Python Module back to Jupyter Notebook (i.e., .py -> .ipynb)

nbdev_update
Warning

If you write a new function/object in .py, nbdev_update will not include this function in __all__. The best practice is to write functions/objects in Jupyter Notebook, and debug in Python Module (via IDE).

Code Style

ReLax follows the black code style. See black’s code style document.

Write Test Cases in Jupyter Notebook

It is desirable to write some unit tests for each function and object. nbdev recommends to write test cases after implementing a feature. A normal cell is considered for testing.

For example, let’s consider a function which adds up all the inputs:

def add_numbers(*args):
    return sum(args)

To test this function, we write unit tests via assert.

# check correctness
assert add_numbers(1, 2, 3) == 6
# check types
assert type(add_numbers(1, 2, 3)) == int
assert type(add_numbers(1., 2, 3)) == float
Note

Note that all the test cases should be quickly run. If a cell takes a long time to run (e.g., model training), mark the cell as #| eval: false to skip this cell.

Write Documentations in Jupyter Notebook

Doc string

To write documentations in nbdev, it is recommended to

  1. use simple type annotations
  2. describe each arguments with short comments
  3. provide code examples and explanations in separate cells
Tip

Union typing is introduced after Python 3.10. For Python 3.7 - 3.9 users, you should

from __future__ import annotations
def validate_configs(
    configs: dict|BaseParser, # A configuration of the model/data.
    config_cls: BaseParser # The desired configuration class.
) -> BaseParser:
    """return a valid configuration object."""
    ...

nbdev will automatically render the documentation:


source

validate_configs

 validate_configs (configs:Union[dict,pydantic.main.BaseModel],
                   config_cls:pydantic.main.BaseModel)

return a valid configuration object.

Type Details
configs dict | BaseParser A configuration of the model/data.
config_cls BaseParser The desired configuration class.
Returns BaseParser

Next, we elaborate the use of this function with more descriptions and code examples.

We define a configuration object (which inherent BaseParser) to manage training/model/data configurations. validate_configs ensures to return the designated configuration object.

For example, we define a configuration object:

class LearningConfigs(BaseParser):
    lr: float

A configuration can be LearningConfigs, or the raw data in dictionary.

configs = dict(lr=0.01)

validate_configs will return a designated configuration object.

validate_configs(configs, LearningConfigs)
LearningConfigs(lr=0.01)

Callout

We can also use callout for clear documentations.

:::{.callout-note}
Note that there are five types of callouts, including:
`note`, `warning`, `important`, `tip`, and `caution`.
:::

which renders:

Note

Note that there are five types of callouts, including: note, warning, important, tip, and caution.

Preparing a Code Commit

Preview the documentation system

nbdev_preview

If everything is in your satisfaction, prepare code before commit to GitHub

nbdev_prepare

Summary

  • Install all required packages based on installation guidance
  • Install the git hook nbdev_install_hooks
  • Write code in Jupyter Notebooks; add approprate directives, e.g., #| export
  • Write tests after the code in the Notebooks; test the code via nbdev_test
  • Write documents directly in the Notebooks; preview the docs nbdev_preview
  • Prepare changes with nbdev_prepare
  • Create pull requests and push changes to GitHub