Skip to content

Latest commit

 

History

History
331 lines (235 loc) · 25.5 KB

CONTRIBUTING.md

File metadata and controls

331 lines (235 loc) · 25.5 KB

Contributing to C3 Development

This guide assumes you have a working Python distribution (native or conda) and some basic understanding of Git and Github.

Check the instructions for installing and using a Conda or a Native Python distribution. If you are on Windows, we recommend a non Microsoft Store installation of a full native Python distribution. The Atlassian Git tutorials is a good resource for beginners along with the online Pro Git Book.

Where to Start

As a first-time contributor to the C3 project, the best place to explore for possible contributions is the Issues section. Please go through the existing open and closed issues before opening a new one. Issues that would allow a newcomer to contribute without facing too much friction are labelled good-first-issue. The documentation and examples are other places that could always use some extra help. Check the documentation label for suggestions on what needs work. We would also be very happy to review PRs that add tests for existing code, which happens to be a great way to understand and explore a new codebase.

If you are more adventurous and willing to dive deeper into the codebase, you will find several possible contribution opportunities annotated with a TODO comment and a short explanation of what needs to be done. Refer to the API documentation for the relevant section and open an issue if you would like to tackle any of these TODOs.

Opening a New Issue

Any new feature or bug-fix that is added to the codebase must typically be first discussed in an Issue. If you run into a bug while working with C3 or would like to suggest some new enhancement, please file an issue for the same. Use a helpful title for the Issue and make sure your Issue description conforms to the template that is automatically provided when you open a new Bug report or Feature request. One of the core developers will either address the bug or discuss possible implementations for the enhancement.

Forking and Cloning the Repository

In order to start contributing to C3, you must fork and make your own copy of the repository. Click the fork icon in the top right of the repository page. Your fork is a copy of the main repo, on which you can make edits, which you can then propose as changes by making a pull request. Once forked, you should find a repo like https://github.com/githubusername/c3. The next step involves making a local copy of this forked repository using the following command:

git clone https://github.com/githubusername/c3

Remotes and Branches

Once you have cloned your repository, you want to set up branches and remotes.

Remotes let you backup and sync your code with an online server. When you clone the repository, git automatically adds a remote called origin which is your fork of the C3 repo. You typically also want to add a remote of the parent C3 repository. The convention for naming the parent repository is upstream and you set this up with the commands below:

# navigate inside your local clone
cd c3/
git remote add upstream https://github.com/q-optimize/c3
git remote -v

This will show you the two remotes currently setup:

origin  https://github.com/githubusername/c3.git (fetch)
origin  https://github.com/githubusername/c3.git (push)
upstream        https://github.com/q-optimize/c3 (fetch)
upstream        https://github.com/q-optimize/c3 (push)

Branches let you work on a new feature or bug-fix without affecting the master version of your code. You should always create a branch when you start working on something new. Branches in C3 have the following stucture:

  • master - The current stable release, source code of the pip package release
  • dev - Development branch for ongoing work, might contain bugs or breaking changes
  • docs - Everything related to adding docs, which is also rendered on RTD
  • feature - Personal branches to work on new features or bug fixes

You should start your development by making a new feature branch off dev, as described below:

git checkout dev
git pull upstream dev
git checkout -b feature dev
git push --set-upstream origin feature

The golden rule is to always work on an atomic piece of code wihtin a feature branch. So you do not want to work on completely different sections of the codebase while building a new feature. However, it is possible that others might also be working on same sections of the code in parallel. When their code gets merged into dev, you typically want to pull in these changes into your feature branch with the following commands:

git checkout dev
git pull upstream dev
git checkout feature
git merge dev
git push origin feature

Make frequest commits, write useful and sensible commit messages and keep your local code synced with your fork and changes pulled in from the parent repository.

Setting up the development environment

We describe below the steps for setting up your development environment for locally building and testing C3, using either conda or virtualenv.

Using conda

conda create --name=c3-dev python=3.8
cd c3/ # navigate to the root of your local repository
conda activate c3-dev
pip install -e .

Using virtualenv

cd c3/ # navigate to the root of your local repository
python3 -m venv env
source env/bin/activate
pip install -e .

This will create an editable installation of c3-toolset in the c3-dev or env environment which is linked to your local c3/ directory making sure that any changes you make to the code are automatically included. For development purposes, you will probably want to additionally install jupyter, notebook, matplotlib, pytest, pytest-xdist and pytest-cov. If you wish to use Qiskit with C3, you must also install qiskit. The requirements.txt file lists out these dependencies with their versions.

Development on Apple Silicon

If you are planning to use or develop c3-toolset on Apple Silicon devices (eg, the M1 Macs), you need to follow a slightly more involved setup process. This is because several python packages do not distribute pre-built binaries for Apple Silicon using standard pip/conda repositories and must be obtained separately. This is true eg, for tensorflow which is a hard dependency or for qiskit which is required if you want to run circuits.

Developer Setup

  1. Install and setup Miniforge on your Apple Silicon Mac from here
  2. Create a new conda environment (conda create --name=c3-dev python=3.8)
  3. Install the latest tensorflow from conda-forge conda install tensorflow -c conda-forge
  4. Use pip to install the corresponding version of tensorflow-probability that matches the version of tensorflow from the previous step
  5. Install separately the different qiskit submodules (instead of the metapackage). Instructions here
  6. Use pip to install the rest of the requirements from the requirements.txt file after relaxing all the pinned versions
  7. Edit the setup.py file to remove all the package dependencies by removing the install_requires argument
  8. Install c3-toolset in development mode using pip install -e . from the root of the repository

User Setup

  1. Follow steps 1 to 4 from the Developer Setup above
  2. Use pip install c3-toolset to install the package
  3. If you need to run qiskit specific functionalities, follow step 5 above

Coding Standards

We use pre-commit hooks to enforce coding standards. C3 uses black for code formatting, mypy for type checking and flake8 as a linter for static checking. Pre-commit hooks provide a seamless way to integrate these three tools in your development workflow. Follow the steps below to set it up:

conda activate c3-dev # or source env/bin/activate
pip install pre-commit
pre-commit install

This will install the pre-commit hooks for your local git repository. When you make a commit the next time, pre-commit will spend a few minutes to download and install black, flake8 and mypy environments for testing your commits meet the requirements enforced using these tools. This initial setup is only done once and subsequent commits won't run this installation process. black will reformat your code and the other tools will throw errors where there is a possible type violation or syntax error. You must remember to stage any changes made at this point before trying to commit your code again. So it typically goes in the following order:

# you write some cool code
git add c3/cool-c3-feature.py
git commit -m "made cool new hack"
.
.
.
# black says it reformatted the code
# flake8 shows unused imports
# mypy shows type violations
# you fix issues highlighted above
git add c3/cool-c3-feature.py
git commit -m "made cool new hack"
# all pre-commit hooks pass
# commit successful!

Tests and Test Coverage

Untested code is bad code. There is a constant effort to increase the test coverage for the C3 codebase. In this light, any new code being pushed must come with accompanying tests. If you are writing a new feature, make sure you add detailed tests that check that your code does what is expected of it by checking the output for known typical and edge cases. If you are fixing a bug, write regression tests to ensure the bug doesn't pop up again in a later release due to changes elsewhere in the code. Additionally, we actively welcome contributions that aren't a bug-fix/feature but include tests to increase the coverage of the current codebase.

Our test-suite is built using pytest and is stored inside test/ in the root of the repository. Using pytest, writing tests is a straightforward task as outlined below:

  • Import the C3 modules that you wish to test inside your new file
  • Write functions of the form test_some_feature()
  • Create a new file test/test_some_c3_module.py
  • These functions can either check some individual feature (unit test) or the interaction between a variety of features (integration test)
  • Testing is done using assert statements which check some output against a known value

For more inspiration, check some of the existing tests. We encourage you to actively use pytest fixtures, parameters, and markers, to make testing efficient, readable and robust. Details on these features is beyond the scope of this document, so we ask you to refer here.

Running Tests Locally

As discussed later, we have automated testing on remote servers set up for our codebase on Github. Here we outline how to run the tests and check test coverage locally.

At any moment, you can run the whole suit of tests from the root of the C3 repository using the command below:

pytest -v --cov=c3 test/

The -v flag enables verbosity providing useful insights on why some tests failed. The --cov=c3 flag will check the coverage of tests for our c3/ codebase and generate a detailed report showing the coverage for individual files. By default, outputs to console from the code are disabled when running tests. You can use the -s flag to enable these outputs.

Typically when developing, you do not want to run all the tests everytime (because it can be quite slow at times). This is where filtering tests is useful.

  • Name-based filtering: You can limit pytest to running only those tests whose fully qualified names match a particular expression. You can do this with the -k parameter.
  • Directory scoping: By default, pytest will run only those tests that are in or under the current directory or any directory explicitly provided by you
  • Test categorization: pytest can include or exclude tests from particular categories that you define. You can do this with the -m parameter. Markers are listed in pytest.ini
  • Explicit filenames: You can run only a single test file by explicitly mentioning the file name instead of a directory

Type Annotations

Python is a dynamically typed language which is great when you are solving a homework problem and your codebase is a single .py monolith but it breaks pretty soon when you start working in large distributed teams. Hence we use type annotations to make everyone's life easier when they are either contributing to the C3 codebase or building code on top of it. Type annotations are also very easily integrated with IDEs (discussed later) and guide type checkers (such as mypy) to preemptively detect possible errors.

Example type annotation:

def func(x: tf.constant) -> tf.constant:
  """
  Do cool TensorFlow Stuff
  """
  # fancy tensorflow GPU autodiff tensor operations
  return tf.constant(result)

Check this resource for more details on Type Checking and Annotation.

Doc Strings & Documentation

If untested code is bad code, undocumented code is unusable code. Besides striving for generous inline comments, mnenomic variable names and readable implementations, we extensively document C3 through docstrings. These docstrings follow the numpydoc docstring format and are automatically picked up by our documentation building tool (Sphinx) to generate the API documentation. A sample docstring is shown below:

def func(arg1: float) -> int:
  """Short Note

  Parameters
  -----------
  arg1: float
      1st argument to the function
  
  Returns
  -----------
  int
      Processed result
  
  Raises
  -----------
  ZeroDivisionError
      Throws error when dividing by zero
  """

Additionally, you can include a detailed note after Raises with an example code snippet showing the usage of specific module. Besides these module docstrings, we also include example documentation which is found inside the docs/ directory. These are reStructured Text .rst files which are also parsed and rendered by Sphinx. You can find more details in the relevant documentation. The Sphinx cheatsheet is handy when writing example documentations. Please contact one of the core developers or open an issue if you are planning to add example documentation and would need some help.

Building and Viewing Documentation Locally

In order to build and view the docs locally, you need to perform the following steps:

pip install sphinx sphinx-rtd-theme autoapi autodoc
sudo apt install graphviz

cd docs/
make clean
make html

This will render the documentation into html files (it can take a few minutes) which can then be viewed by launching a simple python server:

cd docs/_build/html
python -m http.server 4242

You can now open localhost:4242 in your browser and view the docs as they would be rendered online. For checking a live version of the online docs as rendered from the master, dev or docs branch, head over to c3-toolset.rtfd.io.

Pull Requests

Once you feel your work is ready to be merged into the main codebase, open a Pull Request to merge your feature branch to the dev branch on q-optimize/c3. Please write a useful title and a detailed description for your Pull Request.

Follow the What, Why, How, Remarks structure when writing the description of your Pull Request. In the remarks section, add comments to help the reviewer or explain possible quirks/issues with your implementation or aspects that might require extra attention. For new features, please add code snippets that demonstrate how this feature is to be used. Wherever possible, add screenshots and GIFs so that it's convenient to quickly review your PR.

Make sure you allow maintainers to edit your branch when you are creating a Pull Request from a fork. Then, anyone with Write access to the upstream repository will be able to add commits to your branch. This can make the review process easier for maintainers; sometimes it’s quicker for a maintainer to make a small change themselves instead of asking you to make the change.

You can keep your Pull Request as a Draft as long as you feel you still need to make changes. It is often advisable to open a PR when you are maybe 85% done with the work, which gives maintainers enough time to check what's going on and guide you if something needs correction. When you are ready, request for review on your PR. Your PR must be approved by at least 2 core developers before it can be merged. The Code review might involve suggested changes, which you can incorporate by committing and pushing more code in the same feature. You DO NOT need to open a new PR or create a new branch if someone requests for changes on your Pull Request. If your PR is not reviewed by any of the core developers within 3 days, please request politely in the comments with a follow-up.

Changelog

We use the CHANGELOG.md as a living document that not only records past changes in various releases but also tracks development of bug fixes, new features, API changes etc for the upcoming release. Every Pull Request should update the Changelog with a short note explaining the addition and noting the PR number. When making a new release, this gets updated with the version number and date; and is also included in the description for Github Releases. The next time a PR is opened for merging into dev, a new Upcoming Release section should be added to the top of the Changelog.

Continuous Integration Checks

As previously discussed, tests are an inherent part of good code. All Pull Requests made to the repository are automatically tested on Windows, MacOS and Ubuntu with Python 3.9, which come up in the PR as Checks, notifying you whenever a particular check is failing on any platform. Except the Code Complexity, all checks should be passing for your PR to be ready for review/merge. The Codecov bot will also comment on the PR with a report on the change in the test coverage for that particular PR. If your PR reduces the overall test coverage of the codebase, it is not yet ready and you need to add more tests.

Code Reviews

Code Reviews are an integral aspect of our development workflow with the explicit goal of ensuring that every new Pull Request either maintains or preferably improves the overall code quality of this codebase. We try to automate large chunks of the traditional code review process through the PR checks (formatters, linters, tests, code-coverage, code-climate etc) while expecting human reviewers to focus more on things lower in the Code Review Pyramid. This generally means that code reviewers shouldn't be nitpicking on style or tests but address possible issues in the design and implementation of the bugfix/feature. Everyone is welcome and encouraged to participate in our code-review process. Particularly, new contributors have a lot to gain by reviewing Pull Requests since it gives them a crash course in what it means to add a new feature to this codebase and what are the general pitfalls one needs to look out for. A good introduction to code reviews is the Google Engineering Practices Documentation. This blog on StackOverflow has further insights on improving the process.

Contributor License Agreement

Before you can submit any code, all contributors must sign a contributor license agreement (CLA). By signing a CLA, you’re attesting that you are the author of the contribution, and that you’re freely contributing it under the terms of the Apache-2.0 license. Additionally if you are contributing as part of your employment, you will need to state that you have obtained necessary permissions from your employer to contribute freely to the C3 project.

When you contribute to the C3 project with a new pull request, a bot will evaluate whether you have signed the CLA. If required (typically for first time contributors), the bot will comment on the pull request, including a link to electronically accept and sign the agreement. The individual CLA document will be available for review as a PDF in the comment.

Git Flow Development Style

We follow the git-flow style development in C3. Along with semver, this translates to the following branching and release structure for the repository:

  • We maintain a semantic versioning based release numbering.
  • Stable/Production ready code goes into master.
  • We build features by making branches off the dev, working on a feature and merging it back into dev.
  • When we are ready to make a release (maybe once every month), i.e, release code into master and pip, we create a new branch release/x.y.z off dev, where we usually have a list of To-Do for pre-release tasks (called a release sprint/final run). We make all changes into this and then release by merging this release/x.y.z into master (with no fast-forward, so making a merge commit) and then merging master back into dev (fast-forward with no merge commit). This cycle continues for every release.
  • When we find a bug in the stable release and need to make a hotfix, we branch off master, as hotfix/x.y.z+1; make changes and merge (--no-ff) it into master and then merge (--ff) master into dev.

This would ensure we have a clean release cycle, hotfixes are quick and well maintained and the master is always stable.

Semantic versioning in short would mean the following - For version x.y.z, we don't change x unless the API is going to break in a backwards incompatible way. We change y for major releases with lots of new features and change z for bug hotfixes and minor releases.

Developer Tools and Tips

The general philosophy of development for C3 is captured in the following points:

  • DRY (Do not Repeat Yourself) - Inherit and extend, Import and reuse.
  • TDD (Test Driven Development) - Add tests for everything.
  • Do one thing well - Write atomic code that only solves 1 problem at a time.
  • Open-Closed Principle - Open to extension, but Closed to modification.
  • Useful Docstrings - Include code snippets and technical summary.
  • Correct Physics - Ensure technical consistency and add ref paper & notes.

It is useful to configure and use an IDE for your development purposes, since that greatly augments your experience and will often seamlessly integrate the various tools mentioned previously. We list the relevant tools for some of the common IDEs below: