Contributing
We welcome all contributors. This page gives the guidelines to follow when contributing to this package.
Reporting bugs
We track bugs using GitHub issues. We encourage you to write complete, specific, reproducible bug reports. Mention the versions of Julia and ReactiveMP for which you observe unexpected behavior. Please provide a concise description of the problem and complement it with code snippets, test cases, screenshots, tracebacks or any other information that you consider relevant. This will help us to replicate the problem and narrow the search space for solutions.
Suggesting features
We welcome new feature proposals. However, before submitting a feature request, consider a few things:
- Does the feature require changes in the engine? A new factor node and its rules do not: they live in a rule package, in your script or notebook, or in a repository of your own (see The ecosystem).
- If you would like to add an implementation of a feature that changes a lot in the core ReactiveMP.jl code, please open an issue on GitHub and describe your proposal first. This will allow us to discuss your proposal with you before you invest your time in implementing something that may be difficult to merge later on.
Contributing code
Installing ReactiveMP
We suggest that you use the dev command of the Julia package manager to install ReactiveMP.jl for development purposes. To work on your fork of ReactiveMP.jl, use your fork's URL address in the dev command, for example:
] dev git@github.com:your_username/ReactiveMP.jl.gitThe dev command clones ReactiveMP.jl to ~/.julia/dev/ReactiveMP. All local changes to ReactiveMP code will be reflected in imported code.
Revise.jl lets you modify the code and use the changes without restarting Julia.
Committing code
We use the standard GitHub Flow workflow where all contributions are added through pull requests. In order to contribute, first fork the repository, then commit your contributions to your fork, and then create a pull request on the main branch of the ReactiveMP.jl repository.
Before opening a pull request, please make sure that all tests pass without failing, that make check-format reports no changes, and that CHANGELOG.md has an entry describing your change (CI enforces all three).
Style conventions
ReactiveMP.jl repository contains scripts to automatically format code according to our guidelines. Use make format command to fix code style. This command overwrites files.
We use default Julia style guide. We list here a few important points and our modifications to the Julia style guide:
- Use 4 spaces for indentation
- Type names use
UpperCamelCase, such asAbstractFactorNodeandRandomVariable. - Function names are
lowercase, as the Julia style guide has them: words run together where the name stays readable,getdata,randomvar,factornode, and are separated with underscores where it would not,compute_product_of_messages. - Variable names and function arguments use
snake_case - The name of a method that modifies its argument(s) must end in
!
Documentation
Every package has its own documentation site under its docs/, built with make docs-<package> (make docs-all builds every site in dependency order, and the ReactiveMP site last). Each site checks that every docstring of its package appears on a page, runs its doctests, and fails on a broken cross-reference.
A docstring is read at the REPL, on the site, and by agents working in the code, so it says enough to use the name without reading its source:
- The signature first, indented, with the real argument names, keywords and defaults, and
-> resultwhen that helps. Several methods get several lines. - One sentence saying what it is or does, right after: a noun phrase for a type, a value or an accessor ("The node's declared interfaces."), the imperative for an action ("Resolve the message rule towards
targetand run it."). - Then only the sections that carry something, in this order:
# Arguments,# Keywords(for each: what it accepts, its default, what it does),# Returns,# Throws,# Examples, and a finalSee alsoline. A simple accessor stays a line or two, as long as what it returns and when it fails are clear. - Examples are
jldoctests where they are cheap and deterministic,juliablocks otherwise. Compare floating-point results with≈, not by their printed digits. - Every name that has a docstring is a link: $[`name`](@ref)$ within the package's own site, $[`name`](@extref Package.name)$ into another package's site, such as $[`DefaultAlgorithm`](@extref MessagePassingRulesBase.DefaultAlgorithm)$. At the REPL both read as the plain name.
- Text several docstrings share is written once, as a
conststring fragment interpolated with$(FRAGMENT), so that no docstring says "as X, except …" and the copies cannot drift. - A rule package shows rules, not models. Its examples call the rules directly (
@call_message_update_rule), and never use another package's modelling syntax, such as GraphPPL's@modelor RxInfer'sinferand@algorithm, which a reader of the package does not have. Its overview page links to ReactiveMP, GraphPPL and RxInfer for how the rules are used in a model. - State limitations plainly: which factorisations a node supports, which message types a rule takes, "no average energy", "needs an initial message on
β". - Present tense, British spelling ("factorisation", "normalised"), no history ("now", "used to", "in v6"), no "for now" or asides. A reason is kept, in present terms.
- Internal helpers keep a docstring when a contributor needs it, and are listed on their site's Internals page, never among the public API.
- Names a user is told to call are public: exported, or declared with
Compat.@compat public name, which also parses on Julia 1.10.
Pages
A page is read by someone learning the topic, often a student, so it explains before it lists. The rules follow Google's developer documentation style guide, adapted to the ones above:
- Write to the reader: "you", the active voice, the present tense. "The engine calls the rule", not "the rule is called".
- One idea per sentence, one topic per paragraph. Split a chain of commas and semicolons into sentences. Keep the verb in each clause: "a rule takes messages from its own cluster and marginals from the others", not "…and marginals the others".
- Define a term where it first appears, and link it to the glossary. A page uses no term it has not defined or linked.
- Show, then explain. Put the code first, then one or two sentences on what it shows. Let the output speak: a rule's result, a node's declaration and a rule's inputs draw themselves.
- Every Julia block runs when the site is built:
@example,@setup,@replorjldoctest. Show an error withtry … catch err; showerror(stdout, err) endin an@example. The only blocks that do not run are shell commands and the v6 halves of a migration guide, which say# v6on their first line. - A site loads only its own package's dependencies, plus BayesBase, ExponentialFamily and Distributions. The engine's and the base package's pages declare the small nodes they need; they link a rule package's site, or RxInfer's, for a model with the standard nodes.
- Headings are nouns or tasks, in sentence case: "Log scales", "Define a node".
- Plain words. No "simply", "just", "easily", "powerful" or "seamless"; no analogy in place of the maths; no rhetorical questions; no summary repeating the page.
- Cross-reference every name and every concept another page explains, once per section.
Unit tests
We use the test-driven development (TDD) methodology for ReactiveMP.jl development. The test coverage should be as complete as possible. Please make sure that you write tests for each piece of code that you want to add.
The engine's tests are in the /test/ directory, which follows the structure of the /src/ directory; each rule package has its own test/ directory, in its repository. Test files are named *_tests.jl and hold @testitem blocks, each carrying a tag. A rule package's suite ends with a coverage check: every rule it defines must be selected by some test, a table case or a direct call_*. Some tests are also present in jldoctest docs annotations directly in the source code. See Julia's documentation about doctests.
The tests can be evaluated by running following command in the Julia REPL:
] test ReactiveMPIn addition tests can be evaluated by running following command in the ReactiveMP root directory:
make testFixes to external libraries
A bug in a dependency of ReactiveMP.jl is best reported in the dependency's own repository. src/fixes.jl holds hot-fixes until a release of the dependency fixes it.
Makefile
ReactiveMP.jl uses Makefile for most common operations. The repository is developed and tested on Julia 1.13.
make help: Shows help snippetmake test: Runs the engine's tests, except items tagged:slow;make test-allruns those too, as CI does. Both taketest_args:make test test_args="nodes"runs only the tests undertest/nodes/, andmake test test_args="engine:variational"onlytest/engine/variational_tests.jlmake test test_args="tag:engine"andmake test test_args="name:MessageMapping"select test items by tag and by name; entries of the same kind are alternatives, entries of different kinds all applyRUN_AQUA=false make testskips the slow Aqua checks, which are enabled by default
- Every rule package is tested in its own repository,
ReactiveBayes/<Package>.jl, withmake test. A suite skips items tagged:slowunlessTEST_ALL=trueis set, and the coverage check runs only on an unfiltered run make docsbuilds the ReactiveMP site, which links to the rule packages' published sitesmake check-format: Checks the formatting (Runic), without modifying filesmake format: Formats the code; this overwrites filesmake scripts_update: Bumps the pinned Runic version inscripts/Manifest.tomlmake test-reactant: Compiles an unrolled model with Reactant, fromtest/reactant's own environment
Releasing
A release of ReactiveMP, or of a package of the ecosystem, is the version in Project.toml on the default branch, with its CHANGELOG.md entry, registered in Julia's General registry:
- Merge the pull request that sets the version, with CI green.
- Comment on the merge commit,
@JuliaRegistrator register, followed byRelease notes:and the notes. Only collaborators and public members of the ReactiveBayes organisation can trigger Registrator; it answers on the commit with the General pull request it opened. - General's AutoMerge checks the pull request and merges it. A comment on that pull request pauses the merge, unless it contains
[noblock]. - TagBot then tags the release and makes a GitHub release, and the documentation job builds
stable/from the tag. TagBot cannot push a tag whose commit changes.github/workflows; check for the tag after the merge, and push it by hand on the registered commit if it is missing.