From 7211153f40f339817f4b3a826fc23459d4f3de7d Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Tue, 24 Sep 2024 17:46:20 -0400 Subject: [PATCH 01/34] added doctest testing abd fixed glm.py and solvers.py --- docs/how_to_guide/plot_04_population_glm.py | 5 +- docs/how_to_guide/plot_05_batch_glm.py | 5 +- .../plot_06_sklearn_pipeline_cv_demo.py | 9 ++-- src/nemos/glm.py | 47 +++++++++++-------- src/nemos/solvers.py | 19 ++++---- tox.ini | 1 + 6 files changed, 49 insertions(+), 37 deletions(-) diff --git a/docs/how_to_guide/plot_04_population_glm.py b/docs/how_to_guide/plot_04_population_glm.py index 84282477..70dac9cd 100644 --- a/docs/how_to_guide/plot_04_population_glm.py +++ b/docs/how_to_guide/plot_04_population_glm.py @@ -23,9 +23,10 @@ """ import jax.numpy as jnp -import nemos as nmo -import numpy as np import matplotlib.pyplot as plt +import numpy as np + +import nemos as nmo np.random.seed(123) diff --git a/docs/how_to_guide/plot_05_batch_glm.py b/docs/how_to_guide/plot_05_batch_glm.py index f9e758fc..84f64d98 100644 --- a/docs/how_to_guide/plot_05_batch_glm.py +++ b/docs/how_to_guide/plot_05_batch_glm.py @@ -6,10 +6,11 @@ """ +import matplotlib.pyplot as plt +import numpy as np import pynapple as nap + import nemos as nmo -import numpy as np -import matplotlib.pyplot as plt nap.nap_config.suppress_conversion_warnings = True diff --git a/docs/how_to_guide/plot_06_sklearn_pipeline_cv_demo.py b/docs/how_to_guide/plot_06_sklearn_pipeline_cv_demo.py index 4556e235..85d104c7 100644 --- a/docs/how_to_guide/plot_06_sklearn_pipeline_cv_demo.py +++ b/docs/how_to_guide/plot_06_sklearn_pipeline_cv_demo.py @@ -71,20 +71,19 @@ # ## Combining basis transformations and GLM in a pipeline # Let's start by creating some toy data. -import nemos as nmo +import matplotlib.pyplot as plt import numpy as np import pandas as pd import scipy.stats -import matplotlib.pyplot as plt import seaborn as sns - -from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV +from sklearn.pipeline import Pipeline + +import nemos as nmo # some helper plotting functions from nemos import _documentation_utils as doc_plots - # predictors, shape (n_samples, n_features) X = np.random.uniform(low=0, high=1, size=(1000, 1)) # observed counts, shape (n_samples,) diff --git a/src/nemos/glm.py b/src/nemos/glm.py index bec4304f..03684859 100644 --- a/src/nemos/glm.py +++ b/src/nemos/glm.py @@ -523,11 +523,11 @@ def _initialize_parameters( >>> import numpy as np >>> X = np.zeros((100, 5)) # Example input >>> y = np.exp(np.random.normal(size=(100, ))) # Simulated firing rates - >>> coeff, intercept = nmo.glm.GLM._initialize_parameters(X, y) + >>> coeff, intercept = nmo.glm.GLM()._initialize_parameters(X, y) >>> coeff.shape - (5, ) + (5,) >>> intercept.shape - (1, ) + (1,) """ if isinstance(X, FeaturePytree): data = X.data @@ -823,9 +823,12 @@ def initialize_params( Examples -------- - >>> X, y = load_data() # Hypothetical function to load data + >>> import numpy as np + >>> import nemos as nmo + >>> X, y = np.random.normal(size=(10, 2)), np.random.uniform(size=10) + >>> model = nmo.glm.GLM() >>> params = model.initialize_params(X, y) - >>> opt_state = model.initialize_state(X, y) + >>> opt_state = model.initialize_state(X, y, params) >>> # Now ready to run optimization or update steps """ if init_params is None: @@ -950,7 +953,10 @@ def update( Examples -------- - >>> # Assume glm_instance is an instance of GLM that has been previously fitted. + >>> import nemos as nmo + >>> import numpy as np + >>> X, y = np.random.normal(size=(10, 2)), np.random.uniform(size=10) + >>> glm_instance = nmo.glm.GLM().fit(X, y) >>> params = glm_instance.coef_, glm_instance.intercept_ >>> opt_state = glm_instance.solver_state_ >>> new_params, new_opt_state = glm_instance.update(params, opt_state, X, y) @@ -1057,15 +1063,15 @@ class PopulationGLM(GLM): >>> y = np.random.poisson(np.exp(X.dot(weights))) >>> # Define a feature mask, shape (num_features, num_neurons) >>> feature_mask = jnp.array([[1, 0], [1, 1], [0, 1]]) - >>> print("Feature mask:") - >>> print(feature_mask) + >>> feature_mask + Array([[1, 0], + [1, 1], + [0, 1]], dtype=int32) >>> # Create and fit the model - >>> model = PopulationGLM(feature_mask=feature_mask) - >>> model.fit(X, y) - >>> # Check the fitted coefficients and intercepts - >>> print("Model coefficients:") - >>> print(model.coef_) - + >>> model = PopulationGLM(feature_mask=feature_mask).fit(X, y) + >>> # Check the fitted coefficients + >>> print(model.coef_.shape) + (3, 2) >>> # Example with a FeaturePytree mask >>> from nemos.pytrees import FeaturePytree >>> # Define two features @@ -1078,14 +1084,17 @@ class PopulationGLM(GLM): >>> rate = np.exp(X["feature_1"].dot(weights["feature_1"]) + X["feature_2"].dot(weights["feature_2"])) >>> y = np.random.poisson(rate) >>> # Define a feature mask with arrays of shape (num_neurons, ) + >>> feature_mask = FeaturePytree(feature_1=jnp.array([0, 1]), feature_2=jnp.array([1, 0])) - >>> print("Feature mask:") >>> print(feature_mask) + feature_1: shape (2,), dtype int32 + feature_2: shape (2,), dtype int32 + >>> # Fit a PopulationGLM - >>> model = PopulationGLM(feature_mask=feature_mask) - >>> model.fit(X, y) - >>> print("Model coefficients:") - >>> print(model.coef_) + >>> model = PopulationGLM(feature_mask=feature_mask).fit(X, y) + >>> # Coefficients are stored in a dictionary with keys the feature labels + >>> print(model.coef_.keys()) + dict_keys(['feature_1', 'feature_2']) """ def __init__( diff --git a/src/nemos/solvers.py b/src/nemos/solvers.py index d1b2deeb..4c060609 100644 --- a/src/nemos/solvers.py +++ b/src/nemos/solvers.py @@ -80,11 +80,13 @@ class ProxSVRG: Examples -------- - >>> def loss_fn(params, X, y): - >>> ... - >>> - >>> svrg = ProxSVRG(loss_fn, prox_fun) - >>> params, state = svrg.run(init_params, hyperparams_prox, X, y) + >>> import numpy as np + >>> from jaxopt.prox import prox_lasso + >>> loss_fn = lambda params, X, y: ((X.dot(params) - y)**2).sum() + >>> svrg = ProxSVRG(loss_fn, prox_lasso) + >>> hyperparams_prox = 0.1 + >>> params, state = svrg.run(np.zeros(2), hyperparams_prox, np.ones((10, 2)), np.zeros(10)) + References ---------- @@ -615,11 +617,10 @@ class SVRG(ProxSVRG): Examples -------- - >>> def loss_fn(params, X, y): - >>> ... - >>> + >>> import numpy as np + >>> loss_fn = lambda params, X, y: ((X.dot(params) - y)**2).sum() >>> svrg = SVRG(loss_fn) - >>> params, state = svrg.run(init_params, X, y) + >>> params, state = svrg.run(np.zeros(2), np.ones((10, 2)), np.zeros(10)) References ---------- diff --git a/tox.ini b/tox.ini index e7160b5f..d4c5df19 100644 --- a/tox.ini +++ b/tox.ini @@ -19,6 +19,7 @@ commands = isort docs/background --profile=black isort docs/tutorials --profile=black flake8 --config={toxinidir}/tox.ini src + pytest --doctest-modules src/nemos/ pytest --cov=nemos --cov-report=xml [gh-actions] From 66ad520f18fdc6b5a5414d39231708199fe838a9 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Wed, 25 Sep 2024 09:33:08 -0400 Subject: [PATCH 02/34] doctest as part of pytest run --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d6c9d731..8384958c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,11 @@ profile = "black" # Configure pytest [tool.pytest.ini_options] -testpaths = ["tests"] # Specify the directory where test files are located +testpaths = ["tests", "src/nemos"] # Specify the directory where test files are located +addopts = [ + "--doctest-modules", +] + [tool.coverage.run] omit = [ From 52ae736d94bf301b20b42f47d02160573b180883 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 15:43:37 -0400 Subject: [PATCH 03/34] fixed all doctests --- src/nemos/basis.py | 48 ++++++++++++++++++++------------- src/nemos/exceptions.py | 3 +-- src/nemos/fetch/fetch_data.py | 15 +++++++++-- src/nemos/observation_models.py | 6 ++--- src/nemos/regularizer.py | 5 ++-- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/nemos/basis.py b/src/nemos/basis.py index 2cc48f95..2a52e75a 100644 --- a/src/nemos/basis.py +++ b/src/nemos/basis.py @@ -152,15 +152,17 @@ class TransformerBasis: >>> # transformer can be used in pipelines >>> transformer = TransformerBasis(basis) >>> pipeline = Pipeline([ ("compute_features", transformer), ("glm", GLM()),]) - >>> pipeline.fit(x[:, None], y) # x need to be 2D for sklearn transformer API - >>> print(pipeline.predict(np.random.normal(size=(10, 1)))) # predict rate from new data - + >>> pipeline = pipeline.fit(x[:, None], y) # x need to be 2D for sklearn transformer API + >>> print(pipeline.predict(np.arange(10)[:, None])) # predict rate from new data + [0.40688667 0.7551465 2.2994423 4.849418 2.1493833 1.3244786 + 0.7732168 0.46250358 0.41326943 0.990009 ] >>> # TransformerBasis parameter can be cross-validated. >>> # 5-fold cross-validate the number of basis >>> param_grid = dict(compute_features__n_basis_funcs=[4, 10]) >>> grid_cv = GridSearchCV(pipeline, param_grid, cv=5) - >>> grid_cv.fit(x[:, None], y) + >>> grid_cv = grid_cv.fit(x[:, None], y) >>> print("Cross-validated number of basis:", grid_cv.best_params_) + Cross-validated number of basis: {'compute_features__n_basis_funcs': 10} """ def __init__(self, basis: Basis): @@ -289,7 +291,7 @@ def __getattr__(self, name: str): return getattr(self._basis, name) def __setattr__(self, name: str, value) -> None: - """ + r""" Allow setting _basis or the attributes of _basis with a convenient dot assignment syntax. Setting any other attribute is not allowed. @@ -312,10 +314,11 @@ def __setattr__(self, name: str, value) -> None: >>> # allowed >>> trans_bas.n_basis_funcs = 20 >>> # not allowed - >>> tran_bas.random_attribute_name = "some value" - Traceback (most recent call last): - ... - ValueError: Only setting _basis or existing attributes of _basis is allowed. + >>> try: + ... trans_bas.random_attribute_name = "some value" + ... except ValueError as e: + ... print(e) + Only setting _basis or existing attributes of _basis is allowed. """ # allow self._basis = basis if name == "_basis": @@ -343,7 +346,7 @@ def __sklearn_clone__(self) -> TransformerBasis: return cloned_obj def set_params(self, **parameters) -> TransformerBasis: - """ + r""" Set TransformerBasis parameters. When used with `sklearn.model_selection`, users can set either the `_basis` attribute directly @@ -357,12 +360,16 @@ def set_params(self, **parameters) -> TransformerBasis: >>> # setting parameters of _basis is allowed >>> print(transformer_basis.set_params(n_basis_funcs=8).n_basis_funcs) - + 8 >>> # setting _basis directly is allowed - >>> print(transformer_basis.set_params(_basis=BSplineBasis(10))._basis) - + >>> print(type(transformer_basis.set_params(_basis=BSplineBasis(10))._basis)) + >>> # mixing is not allowed, this will raise an exception - >>> transformer_basis.set_params(_basis=BSplineBasis(10), n_basis_funcs=2) + >>> try: + ... transformer_basis.set_params(_basis=BSplineBasis(10), n_basis_funcs=2) + ... except ValueError as e: + ... print(e) + Set either new _basis object or parameters for existing _basis, not both. """ new_basis = parameters.pop("_basis", None) if new_basis is not None: @@ -996,8 +1003,8 @@ def to_transformer(self) -> TransformerBasis: >>> from sklearn.pipeline import Pipeline >>> from sklearn.model_selection import GridSearchCV >>> # load some data - >>> X, y = ... # X: features, y: neural activity - >>> basis = nmo.basis.RaisedCosineBasisLinear(10) + >>> X, y = np.random.normal(size=(30, 1)), np.random.poisson(size=30) + >>> basis = nmo.basis.RaisedCosineBasisLinear(10).to_transformer() >>> glm = nmo.glm.GLM(regularizer="Ridge") >>> pipeline = Pipeline([("basis", basis), ("glm", glm)]) >>> param_grid = dict( @@ -1009,7 +1016,7 @@ def to_transformer(self) -> TransformerBasis: ... param_grid=param_grid, ... cv=5, ... ) - >>> gridsearch.fit(X, y) + >>> gridsearch = gridsearch.fit(X, y) """ return TransformerBasis(copy.deepcopy(self)) @@ -1502,11 +1509,14 @@ def evaluate_on_grid(self, n_samples: int) -> Tuple[NDArray, NDArray]: >>> mspline_basis = MSplineBasis(n_basis_funcs=4, order=3) >>> sample_points, basis_values = mspline_basis.evaluate_on_grid(100) >>> for i in range(4): - ... plt.plot(sample_points, basis_values[:, i], label=f'Function {i+1}') + ... p = plt.plot(sample_points, basis_values[:, i], label=f'Function {i+1}') >>> plt.title('M-Spline Basis Functions') + Text(0.5, 1.0, 'M-Spline Basis Functions') >>> plt.xlabel('Domain') + Text(0.5, 0, 'Domain') >>> plt.ylabel('Basis Function Value') - >>> plt.legend() + Text(0, 0.5, 'Basis Function Value') + >>> l = plt.legend() >>> plt.show() """ return super().evaluate_on_grid(n_samples) diff --git a/src/nemos/exceptions.py b/src/nemos/exceptions.py index 4537aafb..8e3caa29 100644 --- a/src/nemos/exceptions.py +++ b/src/nemos/exceptions.py @@ -15,6 +15,5 @@ class NotFittedError(ValueError, AttributeError): ... GLM().predict([[[1, 2], [2, 3], [3, 4]]]) ... except NotFittedError as e: ... print(repr(e)) - ... # NotFittedError("This GLM instance is not fitted yet. Call 'fit' with - ... # appropriate arguments.") + NotFittedError("This GLM instance is not fitted yet. Call 'fit' with appropriate arguments.") """ diff --git a/src/nemos/fetch/fetch_data.py b/src/nemos/fetch/fetch_data.py index a5b76b5c..80d5f263 100644 --- a/src/nemos/fetch/fetch_data.py +++ b/src/nemos/fetch/fetch_data.py @@ -126,7 +126,7 @@ def fetch_data( def download_dandi_data(dandiset_id: str, filepath: str) -> NWBHDF5IO: - """Download a dataset from the DANDI Archive (https://dandiarchive.org/) + r"""Download a dataset from the DANDI Archive (https://dandiarchive.org/) Parameters ---------- @@ -143,10 +143,21 @@ def download_dandi_data(dandiset_id: str, filepath: str) -> NWBHDF5IO: Examples -------- >>> import nemos as nmo + >>> import pynapple as nap >>> io = nmo.fetch.download_dandi_data("000582", - "sub-11265/sub-11265_ses-07020602_behavior+ecephys.nwb") + ... "sub-11265/sub-11265_ses-07020602_behavior+ecephys.nwb") >>> nwb = nap.NWBFile(io.read(), lazy_loading=False) >>> print(nwb) + 07020602 + ┍━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━┑ + │ Keys │ Type │ + ┝━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━┥ + │ units │ TsGroup │ + │ ElectricalSeriesLFP │ Tsd │ + │ SpatialSeriesLED2 │ TsdFrame │ + │ SpatialSeriesLED1 │ TsdFrame │ + │ ElectricalSeries │ Tsd │ + ┕━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┙ """ if dandi is None: diff --git a/src/nemos/observation_models.py b/src/nemos/observation_models.py index ecb7e76b..9d683ae1 100644 --- a/src/nemos/observation_models.py +++ b/src/nemos/observation_models.py @@ -846,7 +846,7 @@ def estimate_scale( def check_observation_model(observation_model): - """ + r""" Check the attributes of an observation model for compliance. This function ensures that the observation model has the required attributes and that each @@ -877,10 +877,10 @@ def check_observation_model(observation_model): ... def _negative_log_likelihood(self, params, y_true, aggregate_sample_scores=jnp.mean): ... return -aggregate_sample_scores(y_true * jax.scipy.special.logit(params) + \ ... (1 - y_true) * jax.scipy.special.logit(1 - params)) - ... def pseudo_r2(self, params, y_true, aggregate_sample_scores): + ... def pseudo_r2(self, params, y_true, aggregate_sample_scores=jnp.mean): ... return 1 - (self._negative_log_likelihood(y_true, params, aggregate_sample_scores) / ... jnp.sum((y_true - y_true.mean()) ** 2)) - ... def sample_generator(self, key, params): + ... def sample_generator(self, key, params, scale=1.): ... return jax.random.bernoulli(key, params) >>> model = MyObservationModel() >>> check_observation_model(model) # Should pass without error if the model is correctly implemented. diff --git a/src/nemos/regularizer.py b/src/nemos/regularizer.py index 91e59f51..bfe72a32 100644 --- a/src/nemos/regularizer.py +++ b/src/nemos/regularizer.py @@ -352,10 +352,11 @@ class GroupLasso(Regularizer): >>> mask[2] = [0, 0, 1, 0, 1] # Group 2 includes features 2 and 4 >>> # Create the GroupLasso regularizer instance - >>> group_lasso = GroupLasso(regularizer_strength=0.1, mask=mask) + >>> group_lasso = GroupLasso(mask=mask) >>> # fit a group-lasso glm >>> model = GLM(regularizer=group_lasso).fit(X, y) - >>> print(f"coeff: {model.coef_}") + >>> print(f"coeff shape: {model.coef_.shape}") + coeff shape: (5,) """ _allowed_solvers = ( From 7847551b39e30334e79162e0552fbdbef2e81b70 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 16:04:52 -0400 Subject: [PATCH 04/34] added mpl --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b2ce9c36..44e0de0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dev = [ "pytest-cov", # Test coverage plugin for pytest "statsmodels", # Used to compare model pseudo-r2 in testing "scikit-learn", # Testing compatibility with CV & pipelines + "matplotlib>=3.7", # Needed by doctest to run docstrings examples ] docs = [ "mkdocs", # Documentation generator From b006a5f2602d1367010331d6536a16f425348ccf Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 16:16:32 -0400 Subject: [PATCH 05/34] fixed test logic --- src/nemos/basis.py | 1 - tox.ini | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nemos/basis.py b/src/nemos/basis.py index 2a52e75a..cd878b91 100644 --- a/src/nemos/basis.py +++ b/src/nemos/basis.py @@ -1517,7 +1517,6 @@ def evaluate_on_grid(self, n_samples: int) -> Tuple[NDArray, NDArray]: >>> plt.ylabel('Basis Function Value') Text(0, 0.5, 'Basis Function Value') >>> l = plt.legend() - >>> plt.show() """ return super().evaluate_on_grid(n_samples) diff --git a/tox.ini b/tox.ini index d4c5df19..e59984cd 100644 --- a/tox.ini +++ b/tox.ini @@ -19,8 +19,8 @@ commands = isort docs/background --profile=black isort docs/tutorials --profile=black flake8 --config={toxinidir}/tox.ini src - pytest --doctest-modules src/nemos/ - pytest --cov=nemos --cov-report=xml + pytest --doctest-modules src/nemos/ --ignore=src/nemos/_documentation_utils --ignore=src/nemos/fetch + pytest --cov=nemos --cov-report=xml tests/ [gh-actions] python = From 95f244d924b393def4c35d8cf1116a942765890a Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 16:22:18 -0400 Subject: [PATCH 06/34] added basis test --- src/nemos/basis.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nemos/basis.py b/src/nemos/basis.py index cd878b91..ea242752 100644 --- a/src/nemos/basis.py +++ b/src/nemos/basis.py @@ -153,9 +153,7 @@ class TransformerBasis: >>> transformer = TransformerBasis(basis) >>> pipeline = Pipeline([ ("compute_features", transformer), ("glm", GLM()),]) >>> pipeline = pipeline.fit(x[:, None], y) # x need to be 2D for sklearn transformer API - >>> print(pipeline.predict(np.arange(10)[:, None])) # predict rate from new data - [0.40688667 0.7551465 2.2994423 4.849418 2.1493833 1.3244786 - 0.7732168 0.46250358 0.41326943 0.990009 ] + >>> out = pipeline.predict(np.arange(10)[:, None]) # predict rate from new datas >>> # TransformerBasis parameter can be cross-validated. >>> # 5-fold cross-validate the number of basis >>> param_grid = dict(compute_features__n_basis_funcs=[4, 10]) From 10ed673ab7928f9404a46f32ebe07a4f41c632b8 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 16:26:24 -0400 Subject: [PATCH 07/34] removed test path to src --- pyproject.toml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 44e0de0b..2edf5dee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,7 @@ profile = "black" # Configure pytest [tool.pytest.ini_options] -testpaths = ["tests", "src/nemos"] # Specify the directory where test files are located +testpaths = ["tests"] # Specify the directory where test files are located addopts = [ "--doctest-modules", ] diff --git a/tox.ini b/tox.ini index e59984cd..1c7794b6 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ commands = isort docs/tutorials --profile=black flake8 --config={toxinidir}/tox.ini src pytest --doctest-modules src/nemos/ --ignore=src/nemos/_documentation_utils --ignore=src/nemos/fetch - pytest --cov=nemos --cov-report=xml tests/ + pytest --cov=nemos --cov-report=xml [gh-actions] python = From bd929006ae266a88c0d2ab7297b8368a50dcb293 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 16:32:14 -0400 Subject: [PATCH 08/34] use repr for exceptions --- src/nemos/basis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nemos/basis.py b/src/nemos/basis.py index ea242752..8be3018f 100644 --- a/src/nemos/basis.py +++ b/src/nemos/basis.py @@ -315,8 +315,8 @@ def __setattr__(self, name: str, value) -> None: >>> try: ... trans_bas.random_attribute_name = "some value" ... except ValueError as e: - ... print(e) - Only setting _basis or existing attributes of _basis is allowed. + ... print(repr(e)) + ValueError('Only setting _basis or existing attributes of _basis is allowed.') """ # allow self._basis = basis if name == "_basis": @@ -366,8 +366,8 @@ def set_params(self, **parameters) -> TransformerBasis: >>> try: ... transformer_basis.set_params(_basis=BSplineBasis(10), n_basis_funcs=2) ... except ValueError as e: - ... print(e) - Set either new _basis object or parameters for existing _basis, not both. + ... print(repr(e)) + ValueError('Set either new _basis object or parameters for existing _basis, not both.') """ new_basis = parameters.pop("_basis", None) if new_basis is not None: From 5d1ca74731d6ed309c952f48237fff54e9df3ab2 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Fri, 27 Sep 2024 16:35:07 -0400 Subject: [PATCH 09/34] simplify pytest options --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2edf5dee..dcd24c96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,10 +109,6 @@ profile = "black" # Configure pytest [tool.pytest.ini_options] testpaths = ["tests"] # Specify the directory where test files are located -addopts = [ - "--doctest-modules", -] - [tool.coverage.run] omit = [ From cd25819cb456f3881ea78fc78c9540f8e2aaf9ea Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 11:55:25 -0400 Subject: [PATCH 10/34] added doctest to contributing --- CONTRIBUTING.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 +++ tox.ini | 2 +- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bcc3e63..bb669966 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,6 +99,10 @@ Lastly, you should make sure that the existing tests all run successfully and th ```bash # run tests and make sure they all pass pytest tests/ + +# run doctest (this runs all the examples in the docstrings and +# checks that the output matches expectations) +pytest --doctest-modules src/nemos/ # format the code base black src/ isort src @@ -197,7 +201,47 @@ but do not need to be as extensive. We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure. -2) **Examples/Tutorials** +2) **Doctest** + +Doctests are a great way to ensure that your examples in the documentation remain accurate as the code changes. +To add doctests, include example usage within your docstrings. These examples should be formatted as interactive Python sessions, showing inputs and expected outputs. The format should be as follows: + +```python +def add(a: float, b: float) -> float: + """ + Adds two numbers together. + + Parameters + ---------- + a : + The first number to add. + b : + The second number to add. + + Returns + ------- + : + The sum of `a` and `b`. + + Examples + -------- + >>> add(2, 3) + 5 + >>> add(0, 0) + 0 + """ + return a + b +``` + +To run doctests, use the following command: + +```sh +pytest --doctest-modules src/nemos/ +``` + +This command will find and run all doctests within the source files under src/nemos/. Be sure to test your doctests locally before committing any changes to avoid errors. + +3) **Examples/Tutorials** If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or a new example may need to be added. diff --git a/pyproject.toml b/pyproject.toml index dcd24c96..5de06bfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,9 @@ dev = [ "statsmodels", # Used to compare model pseudo-r2 in testing "scikit-learn", # Testing compatibility with CV & pipelines "matplotlib>=3.7", # Needed by doctest to run docstrings examples + "pooch", # Required by doctest for fetch module + "dandi", # Required by doctest for fetch module + "seaborn", # Required by doctest for _documentation_utils module ] docs = [ "mkdocs", # Documentation generator diff --git a/tox.ini b/tox.ini index 1c7794b6..d4c5df19 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ commands = isort docs/background --profile=black isort docs/tutorials --profile=black flake8 --config={toxinidir}/tox.ini src - pytest --doctest-modules src/nemos/ --ignore=src/nemos/_documentation_utils --ignore=src/nemos/fetch + pytest --doctest-modules src/nemos/ pytest --cov=nemos --cov-report=xml [gh-actions] From f985617a4b1ce96412e79036b977d60a5764a52f Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 11:56:58 -0400 Subject: [PATCH 11/34] added quotations --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb669966..b78bf1d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -239,7 +239,7 @@ To run doctests, use the following command: pytest --doctest-modules src/nemos/ ``` -This command will find and run all doctests within the source files under src/nemos/. Be sure to test your doctests locally before committing any changes to avoid errors. +This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. 3) **Examples/Tutorials** From 9342b0925391e184e435413df29b4fdfb80820d2 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 14:51:43 -0400 Subject: [PATCH 12/34] added tip --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b78bf1d7..fe2969e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,9 +100,9 @@ Lastly, you should make sure that the existing tests all run successfully and th # run tests and make sure they all pass pytest tests/ -# run doctest (this runs all the examples in the docstrings and -# checks that the output matches expectations) +# run doctest (run all examples in docstrings and match output) pytest --doctest-modules src/nemos/ + # format the code base black src/ isort src @@ -241,6 +241,8 @@ pytest --doctest-modules src/nemos/ This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. +> [!TIP] Include an example section in all docstrings of public functions and methods. + 3) **Examples/Tutorials** If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or From fcb47bf7b975547670be178685110f5a735bfe2e Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 14:52:39 -0400 Subject: [PATCH 13/34] fix syntax --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe2969e1..a4183f48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -241,7 +241,8 @@ pytest --doctest-modules src/nemos/ This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. -> [!TIP] Include an example section in all docstrings of public functions and methods. +> [!TIP] +> Include an example section in all docstrings of public functions and methods. 3) **Examples/Tutorials** From eaeacbffa799b78d40974edc4d885c4d4aa293a9 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 15:13:28 -0400 Subject: [PATCH 14/34] simplified example --- CONTRIBUTING.md | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4183f48..0d5e6e06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -204,33 +204,21 @@ We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions 2) **Doctest** Doctests are a great way to ensure that your examples in the documentation remain accurate as the code changes. -To add doctests, include example usage within your docstrings. These examples should be formatted as interactive Python sessions, showing inputs and expected outputs. The format should be as follows: +To add doctests, include an `Example` usage section within your docstrings. These examples should be formatted as interactive Python sessions, showing inputs and expected outputs. The format should be as follows: ```python -def add(a: float, b: float) -> float: - """ - Adds two numbers together. - - Parameters - ---------- - a : - The first number to add. - b : - The second number to add. - - Returns - ------- - : - The sum of `a` and `b`. - - Examples - -------- - >>> add(2, 3) - 5 - >>> add(0, 0) - 0 - """ - return a + b +""" +Examples +-------- +An expected output is required. +>>> 1 + 2 +3 + +Unless the output is captured. +>>> out = 1 + 2 + +""" + ``` To run doctests, use the following command: @@ -242,7 +230,7 @@ pytest --doctest-modules src/nemos/ This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. > [!TIP] -> Include an example section in all docstrings of public functions and methods. +> Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. 3) **Examples/Tutorials** From 1b60b75110bd52c9930840c02d2a17185b93d6d9 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 15:15:44 -0400 Subject: [PATCH 15/34] added a doctest to contributing --- CONTRIBUTING.md | 18 ++++++++++-------- tox.ini | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d5e6e06..1c4090ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -208,14 +208,16 @@ To add doctests, include an `Example` usage section within your docstrings. Thes ```python """ -Examples --------- -An expected output is required. ->>> 1 + 2 -3 - -Unless the output is captured. ->>> out = 1 + 2 + ... Docstrings content + + Examples + -------- + An expected output is required. + >>> 1 + 2 + 3 + + Unless the output is captured. + >>> out = 1 + 2 """ diff --git a/tox.ini b/tox.ini index d4c5df19..cd21a8cb 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,7 @@ commands = flake8 --config={toxinidir}/tox.ini src pytest --doctest-modules src/nemos/ pytest --cov=nemos --cov-report=xml + python -m doctest -v CONTRIBUTING.md [gh-actions] python = From 0661368fc9aa9fbd2af1d3f2071ce45f83c7262e Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 15:54:06 -0400 Subject: [PATCH 16/34] improved text --- CONTRIBUTING.md | 44 +++++++++++++++++++++++++++----------------- pyproject.toml | 2 +- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c4090ea..7c94be60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -203,25 +203,35 @@ We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions 2) **Doctest** -Doctests are a great way to ensure that your examples in the documentation remain accurate as the code changes. -To add doctests, include an `Example` usage section within your docstrings. These examples should be formatted as interactive Python sessions, showing inputs and expected outputs. The format should be as follows: - -```python -""" - ... Docstrings content - - Examples - -------- - An expected output is required. - >>> 1 + 2 - 3 +Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. + +- **Docstrings:** + You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. + ```python + """ + ... Docstrings content + + Examples + -------- + An expected output is required. + >>> 1 + 2 + 3 + + Unless the output is captured. + >>> out = 1 + 2 - Unless the output is captured. - >>> out = 1 + 2 + """ + ``` + +- **Documentation Pages:** + Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: + ```python + # Check any code + >>> x = 3**2 + >>> x + 1 + 10 + ``` -""" - -``` To run doctests, use the following command: diff --git a/pyproject.toml b/pyproject.toml index 5de06bfb..5ee288b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ testpaths = ["tests"] # Specify the directory where test files are l [tool.coverage.run] omit = [ "src/nemos/fetch/*", - "src/nemos/_documentation_utils/*" + "src/nemos/_documentation_utils/*", ] [tool.coverage.report] From a5fd71d398075ff3795fcd2864d7958ca561f666 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 15:56:06 -0400 Subject: [PATCH 17/34] fix test --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c94be60..92e8f422 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -227,9 +227,10 @@ Doctests are a great way to ensure that code examples in your documentation rema Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: ```python # Check any code - >>> x = 3**2 + >>> x = 3 ** 2 >>> x + 1 10 + ``` From 56f2ef5560e51a34e4a3edb9839d5381328c7ed7 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 15:58:59 -0400 Subject: [PATCH 18/34] indent --- CONTRIBUTING.md | 140 +++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 72 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92e8f422..e1a9193f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -188,82 +188,78 @@ properly documented as outlined below. #### Adding documentation -1) **Docstrings** +1. **Docstrings** -All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, -a medium-length description of the function / class and what it does, and a complete description of all arguments and return values. -Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature -should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary -for a user to use the code. - -Private functions and classes should have sufficient explanation that other developers know what the function / class does and how to use it, -but do not need to be as extensive. - -We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure. - -2) **Doctest** + All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, + a medium-length description of the function / class and what it does, and a complete description of all arguments and return values. + Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature + should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary + for a user to use the code. + + Private functions and classes should have sufficient explanation that other developers know what the function / class does and how to use it, + but do not need to be as extensive. + + We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure. -Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. +2. **Doctest** -- **Docstrings:** - You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. - ```python - """ - ... Docstrings content - - Examples - -------- - An expected output is required. - >>> 1 + 2 - 3 + Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. + + - **Docstrings:** + You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. + ```python + """ + ... Docstrings content + + Examples + -------- + An expected output is required. + >>> 1 + 2 + 3 + + Unless the output is captured. + >>> out = 1 + 2 - Unless the output is captured. - >>> out = 1 + 2 + """ + ``` + + - **Documentation Pages:** + Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: + ```python + # Check any code + >>> x = 3 ** 2 + >>> x + 1 + 10 + + ``` + To run doctests, use the following command: + ```sh + pytest --doctest-modules src/nemos/ + ``` + + This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. - """ - ``` - -- **Documentation Pages:** - Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: - ```python - # Check any code - >>> x = 3 ** 2 - >>> x + 1 - 10 - - ``` - - -To run doctests, use the following command: - -```sh -pytest --doctest-modules src/nemos/ -``` - -This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. - -> [!TIP] > Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. -3) **Examples/Tutorials** - -If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or -a new example may need to be added. - -All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to -notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example -gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/). - -We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control. - -To see if changes you have made break the current documentation, you can build the documentation locally. - -```bash -# Clear the cached documentation pages -# This step is only necessary if your changes affected the src/ directory -rm -r docs/generated -# build the docs within the nemos repo -mkdocs build -``` - -If the build fails, you will see line-specific errors that prompted the failure. +3. **Examples/Tutorials** + + If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or + a new example may need to be added. + + All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to + notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example + gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/). + + We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control. + + To see if changes you have made break the current documentation, you can build the documentation locally. + + ```bash + # Clear the cached documentation pages + # This step is only necessary if your changes affected the src/ directory + rm -r docs/generated + # build the docs within the nemos repo + mkdocs build + ``` + + If the build fails, you will see line-specific errors that prompted the failure. From c0d25eadc713aed6418ba9be847bd67d6cbb442d Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:02:41 -0400 Subject: [PATCH 19/34] indent --- CONTRIBUTING.md | 61 ++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1a9193f..1a4d98a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -206,40 +206,39 @@ properly documented as outlined below. Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. - **Docstrings:** - You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. - ```python - """ - ... Docstrings content - - Examples - -------- - An expected output is required. - >>> 1 + 2 - 3 - - Unless the output is captured. - >>> out = 1 + 2 + You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. + ```python + """ + ... Docstrings content - """ - ``` - - - **Documentation Pages:** - Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: - ```python - # Check any code - >>> x = 3 ** 2 - >>> x + 1 - 10 - - ``` - To run doctests, use the following command: - ```sh - pytest --doctest-modules src/nemos/ - ``` + Examples + -------- + An expected output is required. + >>> 1 + 2 + 3 + + Unless the output is captured. + >>> out = 1 + 2 - This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. + """ + ``` -> Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. + - **Documentation Pages:** + Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: + ```python + >>> # Add any code + >>> x = 3 ** 2 + >>> x + 1 + 10 + + ``` + To run doctests, use the following command: + ```sh + pytest --doctest-modules src/nemos/ + ``` + This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. + > [!TIP] + > Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. 3. **Examples/Tutorials** From 2314e94571af773fd3818be837b43a8c17e76fd6 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:10:10 -0400 Subject: [PATCH 20/34] indent fix --- CONTRIBUTING.md | 78 +++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a4d98a9..6ed55f90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,14 +190,9 @@ properly documented as outlined below. 1. **Docstrings** - All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, - a medium-length description of the function / class and what it does, and a complete description of all arguments and return values. - Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature - should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary - for a user to use the code. + All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, a medium-length description of the function/class and what it does, and a complete description of all arguments and return values. Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary for a user to use the code. - Private functions and classes should have sufficient explanation that other developers know what the function / class does and how to use it, - but do not need to be as extensive. + Private functions and classes should have sufficient explanation that other developers know what the function/class does and how to use it, but do not need to be as extensive. We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure. @@ -206,54 +201,55 @@ properly documented as outlined below. Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. - **Docstrings:** - You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. - ```python - """ - ... Docstrings content + You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. - Examples - -------- - An expected output is required. - >>> 1 + 2 - 3 + ``` + """ + ... Docstrings content - Unless the output is captured. - >>> out = 1 + 2 - - """ - ``` + Examples + -------- + An expected output is required. + >>> 1 + 2 + 3 + + Unless the output is captured. + >>> out = 1 + 2 + """ + ``` - **Documentation Pages:** - Doctests can also be included in text files like Markdown by using code blocks with the python language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: - ```python - >>> # Add any code - >>> x = 3 ** 2 - >>> x + 1 - 10 + Doctests can also be included in text files like Markdown by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: + + ``` + >>> # Add any code + >>> x = 3 ** 2 + >>> x + 1 + 10 + ``` + + To run doctests, use the following command: + + ``` + python -m doctest -v path-to-your-text-file/file.md + ``` - ``` - To run doctests, use the following command: - ```sh - pytest --doctest-modules src/nemos/ - ``` This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. + > [!TIP] > Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. 3. **Examples/Tutorials** + + If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or a new example may need to be added. - If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or - a new example may need to be added. - - All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to - notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example - gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/). + All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/). - We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control. + We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control. - To see if changes you have made break the current documentation, you can build the documentation locally. + To see if changes you have made break the current documentation, you can build the documentation locally. - ```bash + ``` # Clear the cached documentation pages # This step is only necessary if your changes affected the src/ directory rm -r docs/generated From f69d7d9a4325fec1928372a77cdf18fe8f0a3701 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:10:37 -0400 Subject: [PATCH 21/34] indent fix x2 --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ed55f90..82663c03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -236,8 +236,8 @@ properly documented as outlined below. This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. - > [!TIP] - > Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. +> [!TIP] +> Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. 3. **Examples/Tutorials** From fdae612c51e9ead4242a7ed2927e9d3d9c1ffbe6 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:12:33 -0400 Subject: [PATCH 22/34] add new lines to pass doctest --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82663c03..f36a99c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -215,6 +215,7 @@ properly documented as outlined below. Unless the output is captured. >>> out = 1 + 2 + """ ``` @@ -226,6 +227,7 @@ properly documented as outlined below. >>> x = 3 ** 2 >>> x + 1 10 + ``` To run doctests, use the following command: From 16dbe07f6a3a95918907f554234db9fa1cb3c267 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:17:09 -0400 Subject: [PATCH 23/34] add pytest --- CONTRIBUTING.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f36a99c6..649bb050 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -218,6 +218,12 @@ properly documented as outlined below. """ ``` + + To validate all your docstrings examples, run pytest `--doctest-module` flag, + + ``` + pytest --doctest-modules src/nemos/ + ``` - **Documentation Pages:** Doctests can also be included in text files like Markdown by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: @@ -230,10 +236,10 @@ properly documented as outlined below. ``` - To run doctests, use the following command: + To run doctests on a text file, use the following command: ``` - python -m doctest -v path-to-your-text-file/file.md + python -m doctest -v path-to-your-text-file/file_name.md ``` This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. From 0d13ba1fc4b12619b604257ff076c8d18623624e Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:18:35 -0400 Subject: [PATCH 24/34] add double ``` --- CONTRIBUTING.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 649bb050..43b3f72b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -203,7 +203,8 @@ properly documented as outlined below. - **Docstrings:** You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. - ``` + ```markdown + ```python """ ... Docstrings content @@ -218,6 +219,7 @@ properly documented as outlined below. """ ``` + ``` To validate all your docstrings examples, run pytest `--doctest-module` flag, @@ -228,12 +230,14 @@ properly documented as outlined below. - **Documentation Pages:** Doctests can also be included in text files like Markdown by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: - ``` + ```markdown + ```python >>> # Add any code >>> x = 3 ** 2 >>> x + 1 10 + ``` ``` To run doctests on a text file, use the following command: From ea3dd3b8b6c412113591ae292081512ffc09676c Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:20:49 -0400 Subject: [PATCH 25/34] indent --- CONTRIBUTING.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43b3f72b..3e86ae89 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -204,21 +204,21 @@ properly documented as outlined below. You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. ```markdown - ```python - """ - ... Docstrings content - - Examples - -------- - An expected output is required. - >>> 1 + 2 - 3 - - Unless the output is captured. - >>> out = 1 + 2 - - """ - ``` + ```python + """ + ... Docstrings content + + Examples + -------- + An expected output is required. + >>> 1 + 2 + 3 + + Unless the output is captured. + >>> out = 1 + 2 + + """ + ``` ``` To validate all your docstrings examples, run pytest `--doctest-module` flag, @@ -231,13 +231,13 @@ properly documented as outlined below. Doctests can also be included in text files like Markdown by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: ```markdown - ```python - >>> # Add any code - >>> x = 3 ** 2 - >>> x + 1 - 10 + ```python + >>> # Add any code + >>> x = 3 ** 2 + >>> x + 1 + 10 - ``` + ``` ``` To run doctests on a text file, use the following command: From a33b457f2ff9c9993ad059a49fe81009c4bf8a06 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:25:05 -0400 Subject: [PATCH 26/34] indent the docstrings --- CONTRIBUTING.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e86ae89..53349fbf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -205,19 +205,23 @@ properly documented as outlined below. ```markdown ```python - """ - ... Docstrings content - + def add(a, b): + """ + The sum of two numbers. + + ...Other docstrings sections (Parameters, Returns...) + Examples -------- An expected output is required. - >>> 1 + 2 + >>> add(1, 2) 3 - + Unless the output is captured. - >>> out = 1 + 2 - - """ + >>> out = add(1, 2) + + """ + return a + b ``` ``` From f164085ff7bdefe195506cf3a5110e0f37a08323 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:25:44 -0400 Subject: [PATCH 27/34] do not test contributing (docstrings only works in modules) --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index cd21a8cb..d4c5df19 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,6 @@ commands = flake8 --config={toxinidir}/tox.ini src pytest --doctest-modules src/nemos/ pytest --cov=nemos --cov-report=xml - python -m doctest -v CONTRIBUTING.md [gh-actions] python = From 278f9bce45745e075847bec271201435c338e00c Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Mon, 30 Sep 2024 16:27:05 -0400 Subject: [PATCH 28/34] add python --- CONTRIBUTING.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53349fbf..0b3f7367 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -203,26 +203,24 @@ properly documented as outlined below. - **Docstrings:** You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. - ```markdown - ```python - def add(a, b): - """ - The sum of two numbers. - - ...Other docstrings sections (Parameters, Returns...) + ```python + def add(a, b): + """ + The sum of two numbers. - Examples - -------- - An expected output is required. - >>> add(1, 2) - 3 + ...Other docstrings sections (Parameters, Returns...) - Unless the output is captured. - >>> out = add(1, 2) - - """ - return a + b - ``` + Examples + -------- + An expected output is required. + >>> add(1, 2) + 3 + + Unless the output is captured. + >>> out = add(1, 2) + + """ + return a + b ``` To validate all your docstrings examples, run pytest `--doctest-module` flag, From 3599288e6000c92b94c46c05b56afff75d0bbaa8 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Tue, 1 Oct 2024 11:53:32 -0400 Subject: [PATCH 29/34] swap order --- CONTRIBUTING.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b3f7367..810da9a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -196,7 +196,27 @@ properly documented as outlined below. We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure. -2. **Doctest** +2. **Examples/Tutorials** + + If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or a new example may need to be added. + + All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/). + + We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control. + + To see if changes you have made break the current documentation, you can build the documentation locally. + + ``` + # Clear the cached documentation pages + # This step is only necessary if your changes affected the src/ directory + rm -r docs/generated + # build the docs within the nemos repo + mkdocs build + ``` + + If the build fails, you will see line-specific errors that prompted the failure. + +3. **Doctest: Test the example code in your docs** Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. @@ -252,23 +272,3 @@ properly documented as outlined below. > [!TIP] > Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. - -3. **Examples/Tutorials** - - If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or a new example may need to be added. - - All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/). - - We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control. - - To see if changes you have made break the current documentation, you can build the documentation locally. - - ``` - # Clear the cached documentation pages - # This step is only necessary if your changes affected the src/ directory - rm -r docs/generated - # build the docs within the nemos repo - mkdocs build - ``` - - If the build fails, you will see line-specific errors that prompted the failure. From 24884620367751a12b5733ab07eb48dbba91ee82 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Tue, 1 Oct 2024 12:15:37 -0400 Subject: [PATCH 30/34] remove raw strings (r""") to docstrings whenever possible --- src/nemos/basis.py | 2 +- src/nemos/fetch/fetch_data.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nemos/basis.py b/src/nemos/basis.py index 8be3018f..f5907480 100644 --- a/src/nemos/basis.py +++ b/src/nemos/basis.py @@ -1351,7 +1351,7 @@ def _check_n_basis_min(self) -> None: class MSplineBasis(SplineBasis): - r""" + """ M-spline[$^{[1]}$](#references) basis functions for modeling and data transformation. M-splines are a type of spline basis function used for smooth curve fitting diff --git a/src/nemos/fetch/fetch_data.py b/src/nemos/fetch/fetch_data.py index 80d5f263..4ee9bbb9 100644 --- a/src/nemos/fetch/fetch_data.py +++ b/src/nemos/fetch/fetch_data.py @@ -126,7 +126,7 @@ def fetch_data( def download_dandi_data(dandiset_id: str, filepath: str) -> NWBHDF5IO: - r"""Download a dataset from the DANDI Archive (https://dandiarchive.org/) + """Download a dataset from the DANDI Archive (https://dandiarchive.org/) Parameters ---------- From dbb8d115c840704146566e038379b640b24a8206 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Tue, 1 Oct 2024 12:52:18 -0400 Subject: [PATCH 31/34] added support --- docs/assets/CCN-logo-wText.png | Bin 0 -> 52002 bytes docs/index.md | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/assets/CCN-logo-wText.png diff --git a/docs/assets/CCN-logo-wText.png b/docs/assets/CCN-logo-wText.png new file mode 100644 index 0000000000000000000000000000000000000000..206f7693b63d5e9e542aeaff535b83a79ea74b91 GIT binary patch literal 52002 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

Z(Ve*0{JkzD2>Ju$gX?S}eu_&2; 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 literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index 7c840e1b..562491b5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,3 +103,10 @@ We provide a **Poisson GLM** for analyzing spike counts, and a **Gamma GLM** for ## :material-scale-balance:{ .lg } License Open source, [licensed under MIT](https://github.com/flatironinstitute/nemos/blob/main/LICENSE). + + +## Support + +This package is supported by the Center for Computational Neuroscience, in the Flatiron Institute of the Simons Foundation. + +Flatiron Center for Computational Neuroscience logo. From 9c42db78072b147877eb53a1ccf34ec360a6ca57 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Tue, 1 Oct 2024 12:52:49 -0400 Subject: [PATCH 32/34] added support in the README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 40470e6e..e9abf895 100644 --- a/README.md +++ b/README.md @@ -177,3 +177,8 @@ We communicate via several channels on Github: In all cases, we request that you respect our [code of conduct](CODE_OF_CONDUCT.md). +## Support + +This package is supported by the Center for Computational Neuroscience, in the Flatiron Institute of the Simons Foundation. + +Flatiron Center for Computational Neuroscience logo. From 24da5930a067aa07863a68a2bef73c3847b9baac Mon Sep 17 00:00:00 2001 From: Edoardo Balzani Date: Wed, 2 Oct 2024 18:01:54 -0400 Subject: [PATCH 33/34] Update CONTRIBUTING.md Co-authored-by: William F. Broderick --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 810da9a0..6831e9bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -250,7 +250,7 @@ properly documented as outlined below. ``` - **Documentation Pages:** - Doctests can also be included in text files like Markdown by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: + Doctests can also be included in Markdown files by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: ```markdown ```python From f09c2efad8ae659dc14564aabec678fd9c122254 Mon Sep 17 00:00:00 2001 From: BalzaniEdoardo Date: Wed, 2 Oct 2024 18:13:06 -0400 Subject: [PATCH 34/34] edited CONTRIBUTING.md --- CONTRIBUTING.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 810da9a0..00f18270 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,7 +190,7 @@ properly documented as outlined below. 1. **Docstrings** - All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, a medium-length description of the function/class and what it does, and a complete description of all arguments and return values. Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary for a user to use the code. + All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, a medium-length description of the function/class and what it does, a complete description of all arguments and return values, and an example to illustrate usage. Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary for a user to use the code. Private functions and classes should have sufficient explanation that other developers know what the function/class does and how to use it, but do not need to be as extensive. @@ -218,10 +218,10 @@ properly documented as outlined below. 3. **Doctest: Test the example code in your docs** - Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. You can add doctests to docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. + Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. With doctests, we will test any docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions. - **Docstrings:** - You can include doctests in your function and class docstrings by adding an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. + To include doctests in your function and class docstrings you must add an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below. ```python def add(a, b): @@ -248,6 +248,8 @@ properly documented as outlined below. ``` pytest --doctest-modules src/nemos/ ``` + + This test is part of the Continuous Integration, every example must pass before we can merge a PR. - **Documentation Pages:** Doctests can also be included in text files like Markdown by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format: @@ -267,8 +269,5 @@ properly documented as outlined below. ``` python -m doctest -v path-to-your-text-file/file_name.md ``` - - This command will find and run all doctests within the source files under `src/nemos/`. Be sure to test your doctests locally before committing any changes to avoid errors. - -> [!TIP] -> Include an `Examples` section in all docstrings of public functions and methods. This will greatly enhance the code usability by showing concrete usage scenarios. + + All MarkDown files will be tested as part of the Continuous Integration.