From 6f23111d239b2f69fb3a2b63761ed4311656320f Mon Sep 17 00:00:00 2001 From: Nina Miolane Date: Mon, 22 May 2023 09:05:58 -1000 Subject: [PATCH 1/3] Rm deprecated docs website --- .github/workflows/docs.yml | 45 ++++++++++++++ docs/Makefile | 20 ------ docs/make.bat | 35 ----------- docs/source/conf.py | 62 ------------------- docs/source/index.rst | 23 ------- docs/source/quick_start/index.rst | 8 --- docs/source/quick_start/installation.rst | 4 -- docs/source/quick_start/toy_example.rst | 4 -- docs/source/reference/index.rst | 7 --- docs/source/reference/models.rst | 4 -- docs/source/user_guide/index.rst | 8 --- docs/source/user_guide/simplices.rst | 4 -- .../user_guide/simplicial_complexes.rst | 7 --- 13 files changed, 45 insertions(+), 186 deletions(-) create mode 100644 .github/workflows/docs.yml delete mode 100644 docs/Makefile delete mode 100644 docs/make.bat delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/quick_start/index.rst delete mode 100644 docs/source/quick_start/installation.rst delete mode 100644 docs/source/quick_start/toy_example.rst delete mode 100644 docs/source/reference/index.rst delete mode 100644 docs/source/reference/models.rst delete mode 100644 docs/source/user_guide/index.rst delete mode 100644 docs/source/user_guide/simplices.rst delete mode 100644 docs/source/user_guide/simplicial_complexes.rst diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..e084575f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: "Docs: Check and Deploy" + +on: + push: + branches: [main, github-actions-test] + pull_request: + branches: [main] +permissions: + contents: write + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.10.11] + + steps: + - uses: actions/checkout@v3 + - name: Build using Python ${{matrix.python-version}} + uses: actions/setup-python@v4 + with: + python-version: ${{matrix.python-version}} + cache: "pip" + cache-dependency-path: "pyproject.toml" + - name: Install dependencies [pip] + run: | + pip install --upgrade pip setuptools wheel + pip install -e .[doc] + - name: Install Pandoc [apt-get] + run: | + sudo apt-get -y install pandoc + - name: Generate Docs [Sphinx] + run: | + sphinx-build -b html -D version=latest -D release=latest docs docs/_build + - name: Deploy Docs + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: main + folder: docs/_build + token: ${{ secrets.TMX_DOCUMENTATION_KEY }} + repository-name: pyt-team/pyt-team.github.io + target-folder: topomodelx + clean: true \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d0c3cbf1..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 061f32f9..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index b42dce49..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,62 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = "stnets" -copyright = "2022, Mustafa Hajij" -author = "Mustafa Hajij" - -# The full version, including alpha/beta/rc tags -release = "0.1" - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "classic" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# Add table of contents in the sidebar -# https://stackoverflow.com/questions/18969093/how-to-include-the-toctree-in-the-sidebar-of-each-page -# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_sidebars - -html_sidebars = { - "**": ["globaltoc.html", "searchbox.html"], -} diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 43ea0363..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. stnets documentation master file, created by - sphinx-quickstart on Sun Jan 16 17:18:22 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to stnets's documentation! -================================== - -**stnets** is a Python package for simplicial tensor networks. - -.. toctree:: - :maxdepth: 2 - - quick_start/index - user_guide/index - reference/index - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/source/quick_start/index.rst b/docs/source/quick_start/index.rst deleted file mode 100644 index 90d65c57..00000000 --- a/docs/source/quick_start/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -Quick start -================================== - -.. toctree:: - :maxdepth: 1 - - installation - toy_example diff --git a/docs/source/quick_start/installation.rst b/docs/source/quick_start/installation.rst deleted file mode 100644 index 23ecd668..00000000 --- a/docs/source/quick_start/installation.rst +++ /dev/null @@ -1,4 +0,0 @@ -Installation -================================== - -Installation instructions go here. diff --git a/docs/source/quick_start/toy_example.rst b/docs/source/quick_start/toy_example.rst deleted file mode 100644 index 91bbae58..00000000 --- a/docs/source/quick_start/toy_example.rst +++ /dev/null @@ -1,4 +0,0 @@ -Toy example -================================== - -Toy example goes here. diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst deleted file mode 100644 index 0b7babf4..00000000 --- a/docs/source/reference/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -stnets reference -================================== - -.. toctree:: - :maxdepth: 1 - - models diff --git a/docs/source/reference/models.rst b/docs/source/reference/models.rst deleted file mode 100644 index 9a63c523..00000000 --- a/docs/source/reference/models.rst +++ /dev/null @@ -1,4 +0,0 @@ -Models -================================== - -Model references go here. diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst deleted file mode 100644 index aa0d1732..00000000 --- a/docs/source/user_guide/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -User guide -================================== - -.. toctree:: - :maxdepth: 1 - - simplices - simplicial_complexes diff --git a/docs/source/user_guide/simplices.rst b/docs/source/user_guide/simplices.rst deleted file mode 100644 index 0ac3c958..00000000 --- a/docs/source/user_guide/simplices.rst +++ /dev/null @@ -1,4 +0,0 @@ -Simplices -================================== - -Simplices go here. diff --git a/docs/source/user_guide/simplicial_complexes.rst b/docs/source/user_guide/simplicial_complexes.rst deleted file mode 100644 index c373b030..00000000 --- a/docs/source/user_guide/simplicial_complexes.rst +++ /dev/null @@ -1,7 +0,0 @@ -Simplicial complexes -================================== - -Simplicial complexes go here. - -Some subtitle -********************************** From d0547548686b4589a04354031a8b7fab9e98a90a Mon Sep 17 00:00:00 2001 From: Nina Miolane Date: Mon, 22 May 2023 09:06:55 -1000 Subject: [PATCH 2/3] Add doc website pages --- docs/Makefile | 19 ++ docs/api/base.rst | 12 + docs/api/index.rst | 18 ++ docs/api/nn.rst | 15 ++ docs/api/utils.rst | 6 + docs/conf.py | 94 +++++++ docs/contributing/index.rst | 399 +++++++++++++++++++++++++++ docs/index.rst | 13 + docs/notebooks/convcxn_train.ipynb | 401 ++++++++++++++++++++++++++++ docs/notebooks/hsn_train.ipynb | 251 +++++++++++++++++ docs/notebooks/template_train.ipynb | 244 +++++++++++++++++ docs/tutorials/index.rst | 11 + 12 files changed, 1483 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/api/base.rst create mode 100644 docs/api/index.rst create mode 100644 docs/api/nn.rst create mode 100644 docs/api/utils.rst create mode 100644 docs/conf.py create mode 100644 docs/contributing/index.rst create mode 100644 docs/index.rst create mode 100644 docs/notebooks/convcxn_train.ipynb create mode 100644 docs/notebooks/hsn_train.ipynb create mode 100644 docs/notebooks/template_train.ipynb create mode 100644 docs/tutorials/index.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..51285967 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/api/base.rst b/docs/api/base.rst new file mode 100644 index 00000000..4598cc51 --- /dev/null +++ b/docs/api/base.rst @@ -0,0 +1,12 @@ +**** +Base +**** + +.. automodule:: topomodelx.base.aggregation + :members: + +.. automodule:: topomodelx.base.conv + :members: + +.. automodule:: topomodelx.base.message_passing + :members: \ No newline at end of file diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 00000000..0248de8d --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,18 @@ +============= +API Reference +============= + +The API reference gives an overview of `TopoModelX`, which consists of several modules: + +- `base` implements the core mathematical concepts of the package, such as the concept of message passing. +- `nn` implements neural network layers, organized by topological domains. +- `utils` implements utilities such as computing with sparse tensors. + + +.. toctree:: + :maxdepth: 2 + :caption: Packages & Modules + + base + nn + utils diff --git a/docs/api/nn.rst b/docs/api/nn.rst new file mode 100644 index 00000000..e942df42 --- /dev/null +++ b/docs/api/nn.rst @@ -0,0 +1,15 @@ +*************** +Neural Networks +*************** + +Simplicial Complex Neural Networks +================================ + +.. automodule:: topomodelx.nn.simplicial + :members: + +Cellular Complex Neural Networks +================================ + +.. automodule:: topomodelx.nn.cellular + :members: diff --git a/docs/api/utils.rst b/docs/api/utils.rst new file mode 100644 index 00000000..2c1734d6 --- /dev/null +++ b/docs/api/utils.rst @@ -0,0 +1,6 @@ +***** +Utils +***** + +.. automodule:: topomodelx.utils.scatter + :members: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..3379745e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,94 @@ +"""Sphinx configuration file.""" + +project = "TopoModelX" +copyright = "2022-2023, PyT-Team, Inc." +author = "PyT-Team Authors" + +extensions = [ + "nbsphinx", + "nbsphinx_link", + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.githubpages", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_gallery.load_style", +] + +# Configure napoleon for numpy docstring +napoleon_google_docstring = False +napoleon_numpy_docstring = True +napoleon_use_param = False +napoleon_use_ivar = False +napoleon_use_rtype = False +napoleon_include_init_with_doc = False + +# Configure nbsphinx for notebooks execution +nbsphinx_execute_arguments = [ + "--InlineBackend.figure_formats={'svg', 'pdf'}", + "--InlineBackend.rc={'figure.dpi': 96}", +] + +nbsphinx_execute = "never" + +# To get a prompt similar to the Classic Notebook, use +nbsphinx_input_prompt = " In [%s]:" +nbsphinx_output_prompt = " Out [%s]:" + +nbsphinx_allow_errors = True + +templates_path = ["_templates"] + +source_suffix = [".rst"] + +master_doc = "index" + +language = None + +nbsphinx_prolog = r""" +{% set docname = env.doc2path(env.docname, base=None) %} + +.. raw:: latex + \nbsphinxstartnotebook{\scriptsize\noindent\strut + \textcolor{gray}{The following section was generated from + \sphinxcode{\sphinxupquote{\strut {{ docname | escape_latex }}}} \dotfill}} + """ +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] + +pygments_style = None + +html_baseurl = "pyt-team.github.io" +htmlhelp_basename = "pyt-teamdoc" +html_last_updated_fmt = "%c" + +latex_elements = {} + + +latex_documents = [ + ( + master_doc, + "topomodelx.tex", + "TopoModelX Documentation", + "PyT-Team", + "manual", + ), +] + +man_pages = [(master_doc, "topomodelx", "TopoModelX Documentation", [author], 1)] + +texinfo_documents = [ + ( + master_doc, + "topomodelx", + "TopoModelX Documentation", + author, + "topomodelx", + "One line description of project.", + "Miscellaneous", + ), +] + +epub_title = project +epub_exclude_files = ["search.html"] diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst new file mode 100644 index 00000000..3432f1b8 --- /dev/null +++ b/docs/contributing/index.rst @@ -0,0 +1,399 @@ +.. _contributing: + +================== +Contributing Guide +================== + +This is an exhaustive guide to ease the contribution +process for both novice and experienced contributors. + +`TopoModelX `_ is a +community effort, and everyone is welcome to contribute. + +.. _run tests + +Run the tests +-------------- + +TopoModelX tests can be run using `pytest `_. +To run tests with `pytest`, first install the required packages: + + .. code-block:: bash + + $ pip install -e .[dev] + + +Then run all tests using: + + .. code-block:: bash + + $ pytest test + + +Optionally, run a particular test file using: + + .. code-block:: bash + + $ pytest test/algorithms/ + + +Testing +======== + +Test Driven Development +------------------------- + +High-quality `unit testing `_ +is a corner-stone of `TopoModelX` development process. + +The tests consist of classes appropriately named, located in the `test`` +subdirectory, that check the validity of the algorithms and the +different options of the code. + + +TDD with pytest +----------------- + +TopoModelX uses the `pytest` Python tool for testing different functions and features. +Install the test requirements using: + + .. code-block:: bash + + $ pip install -e .[dev] + +By convention all test functions should be located in files with file names +that start with `test_`. For example a unit test that exercises the Python +addition functionality can be written as follows: + + .. code-block:: bash + + # test_add.py + + def add(x, y): + return x + y + + def test_capital_case(): + assert add(4, 5) == 9 + +Use an `assert` statement to check that the function under test returns +the correct output. Then run the test using: + + .. code-block:: bash + + $ pytest test_add.py + + +Workflow of a contribution +=========================== + +The best way to start contributing is by finding a part of the project that is more familiar to you. + +Alternatively, if everything is new to you and you would like to contribute while learning, look at some of the existing GitHub Issues. + + +.. _new-contributors: + + +Making changes +--------------- + +The preferred way to contribute to topomodelx is to fork the `upstream +repository `__ and submit a "pull request" (PR). + +Follow these steps before submitting a PR: + +#. Synchronize your main branch with the upstream main branch: + + .. code-block:: bash + + $ git checkout main + $ git pull upstream main + +#. | Create a feature branch to hold your development changes: + + .. code-block:: bash + + $ git checkout -b + +#. Make changes. + +#. When you're done editing, add changed files using ``git add`` and then ``git commit``: + + .. code-block:: bash + + $ git add + $ git commit -m "Add my feature" + + to record your changes. Your commit message should respect the `good + commit messages guidelines `_. (`How to Write a Git Commit Message `_ also provides good advice.) + + .. note:: + Before commit, make sure you have run the `black `_ and + `flake8 `_ tools for proper code formatting. + + Then push the changes to your fork of `TopoNextX` with: + + .. code-block:: bash + + $ git push origin + + Use the `-u` flag if the branch does not exist yet remotely. + +#. Follow `these `_ + instructions to create a pull request from your fork. + +#. Repeat 3. and 4. following the reviewers requests. + + +It is often helpful to keep your local feature branch synchronized with the +latest changes of the main topomodelx repository. Bring remote changes locally: + + .. code-block:: bash + + $ git checkout main + $ git pull upstream main + +And then merge them into your branch: + + .. code-block:: bash + + $ git checkout + $ git merge main + + +Pull Request Checklist +---------------------- + +In order to ease the reviewing process, we recommend that your contribution +complies with the following rules. The **bolded** ones are especially important: + +#. **Give your pull request a helpful title.** This summarises what your + contribution does. This title will often become the commit message once + merged so it should summarise your contribution for posterity. In some + cases `Fix ` is enough. `Fix #` is never a + good title. + +#. **Submit your code with associated unit tests**. High-quality + `unit testing `_ + is a corner-stone of the topomodelx development process. + +#. **Make sure your code passes all unit tests**. First, + run the tests related to your changes. + +#. **Make sure that your PR follows Python international style guidelines**, + `PEP8 `_. The `flake8` package + automatically checks for style violations when you + submit your PR. + + To prevent adding commits which fail to adhere to the PEP8 guidelines, we + include a `pre-commit `_ config, which immediately + invokes flake8 on all files staged for commit when running `git commit`. To + enable the hook, simply run `pre-commit install` after installing + `pre-commit` either manually via `pip` or as part of the development requirements. + + Please avoid reformatting parts of the file that your pull request doesn't + change, as it distracts during code reviews. + +#. **Make sure that your PR follows topomodelx coding style and API** (see :ref:`coding-guidelines`). Ensuring style consistency throughout + topomodelx allows using tools to automatically parse the codebase, + for example searching all instances where a given function is used, + or use automatic find-and-replace during code's refactorizations. It + also speeds up the code review and acceptance of PR, as the maintainers + do not spend time getting used to new conventions and coding preferences. + +#. **Make sure your code is properly documented**, and **make + sure the documentation renders properly**. To build the documentation, please + see our :ref:`contribute_documentation` guidelines. The plugin + flake8-docstrings automatically checks that your the documentation follows + our guidelines when you submit a PR. + +#. Often pull requests resolve one or more other issues (or pull requests). + If merging your pull request means that some other issues/PRs should + be closed, you should `use keywords to create link to them + `_ + (e.g., ``fixes #1234``; multiple issues/PRs are allowed as long as each + one is preceded by a keyword). Upon merging, those issues/PRs will + automatically be closed by GitHub. If your pull request is simply + related to some other issues/PRs, create a link to them without using + the keywords (e.g., ``see also #1234``). + +#. **Each PR needs to be accepted by a core developer** before being merged. + + +.. _contribute_documentation: + +Documentation +============= + +We are glad to accept any sort of documentation: function docstrings, +reStructuredText documents (like this one), tutorials, etc. reStructuredText +documents live in the source code repository under the ``docs/`` directory. + +Building the Documentation +-------------------------- + +Building the documentation requires installing specific requirements:: + + pip install -e .[doc] + + +Writing Docstrings +------------------- + +Intro to Docstrings +~~~~~~~~~~~~~~~~~~~ + + +A docstring is a well-formatted description of your function/class/module which includes +its purpose, usage, and other information. + +There are different markdown languages/formats used for docstrings in Python. The most common +three are reStructuredText, numpy, and google docstring styles. For toponet, we are +using the numpy docstring standard. +When writing up your docstrings, please review the `NumPy docstring guide `_ +to understand the role and syntax of each section. Following this syntax is important not only for readability, +it is also required for automated parsing for inclusion into our generated API Reference. + +You can look at these for any object by printing out the ``__doc__`` attribute. +Try this out with the np.array class and the np.mean function to see good examples:: + + >>> import numpy as np + >>> print(np.mean.__doc__) + +The Anatomy of a Docstring +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These are some of the most common elements for functions (and ones we’d like you to add where appropriate): + +#. Summary - a one-line (here <79 char) description of the object + + a. Begins immediately after the first """ with a capital letter, ends with a period + + b. If describing a function, use a verb with the imperative mood (e.g. **Compute** vs Computes) + + c. Use a verb which is as specific as possible, but default to Compute when uncertain (as opposed to Calculate or Evaluate, for example) + +#. Description - a more informative multi-line description of the function + + a. Separated from the summary line by a blank line + + b. Begins with a capital letter and ends with period + +#. Parameters - a formatted list of arguments with type information and description + + a. On the first line, state the parameter name, type, and shape when appropriate. The parameter name should be separated from the rest of the line by a ``:`` (with a space on either side). If a parameter is optional, write ``Optional, default: default_value.`` as a separate line in the description. + b. On the next line, indent and write a summary of the parameter beginning with a capital letter and ending with a period. + + c. See :ref:`docstring-examples`. + +#. Returns (esp. for functions) - a formatted list of returned objects type information and description + + a. The syntax here is the same as in the parameters section above. + + b. See :ref:`docstring-examples`. + +If documenting a class, you would also want to include an Attributes section. +There are many other optional sections you can include which are very helpful. +For example: Raises, See Also, Notes, Examples, References, etc. + +N.B. Within Notes, you can + - include LaTex code + - cite references in text using ids placed in References + + +.. _docstring-examples: + +Docstring Examples +~~~~~~~~~~~~~~~~~~ + +Here's a generic docstring template:: + + def my_method(self, my_param_1, my_param_2="vector"): + """Write a one-line summary for the method. + + Write a description of the method, including "big O" + (:math:`O\left(g\left(n\right)\right)`) complexities. + + Parameters + ---------- + my_param_1 : array-like, shape=[..., dim] + Write a short description of parameter my_param_1. + my_param_2 : str, {"vector", "matrix"} + Write a short description of parameter my_param_2. + Optional, default: "vector". + + Returns + ------- + my_result : array-like, shape=[..., dim, dim] + Write a short description of the result returned by the method. + + Notes + ----- + If relevant, provide equations with (:math:) + describing computations performed in the method. + + Example + ------- + Provide code snippets showing how the method is used. + You can link to scripts of the examples/ directory. + + Reference + --------- + If relevant, provide a reference with associated pdf or + wikipedia page. + """ + +And here's a filled-in example from the Scikit-Learn project, modified to our syntax:: + + def fit_predict(self, X, y=None, sample_weight=None): + """Compute cluster centers and predict cluster index for each sample. + + Convenience method; equivalent to calling fit(X) followed by + predict(X). + + Parameters + ---------- + X : {array-like, sparse_matrix} of shape=[..., n_features] + New data to transform. + y : Ignored + Not used, present here for API consistency by convention. + sample_weight : array-like, shape [...,], optional + The weights for each observation in X. If None, all observations + are assigned equal weight (default: None). + + Returns + ------- + labels : array, shape=[...,] + Index of the cluster each sample belongs to. + """ + return self.fit(X, sample_weight=sample_weight).labels_ + +In general, have the following in mind: + + #. Use built-in Python types. (``bool`` instead of ``boolean``) + + #. Use ``[`` for defining shapes: ``array-like, shape=[..., dim]`` + + #. If a shape can vary, use a list-like notation: + ``array-like, shape=[dimension[:axis], n, dimension[axis:]]`` + + #. For strings with multiple options, use brackets: + ``input: str, {"log", "squared", "multinomial"}`` + + #. 1D or 2D data can be a subset of + ``{array-like, ndarray, sparse matrix, dataframe}``. Note that + ``array-like`` can also be a ``list``, while ``ndarray`` is explicitly + only a ``numpy.ndarray``. + + #. Add "See Also" in docstrings for related classes/functions. + "See Also" in docstrings should be one line per reference, + with a colon and an explanation. + +For Class and Module Examples see the `scikit-learn _weight_boosting.py module +`_. +The class AdaBoost has a great example using the elements we’ve discussed here. +Of course, these examples are rather verbose, but they’re good for +understanding the components. + +When editing reStructuredText (``.rst``) files, try to keep line length under +80 characters (exceptions include links and tables). \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..d54c341d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,13 @@ +TopoModelX +========== + +Deep Learning on Topological Domains. + +Welcome! Check out our tutorials to get started. + +.. toctree:: + :maxdepth: 1 + :hidden: + + api/index + contributing/index \ No newline at end of file diff --git a/docs/notebooks/convcxn_train.ipynb b/docs/notebooks/convcxn_train.ipynb new file mode 100644 index 00000000..498dfc53 --- /dev/null +++ b/docs/notebooks/convcxn_train.ipynb @@ -0,0 +1,401 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial: Set-up, create and train a convolutional CXN\n", + "\n", + "In this notebook, we create and train a simplified, non-attentional version of a CXN network, originally proposed in the paper by Hajij et. al : Cell Complex Neural Networks (https://arxiv.org/pdf/2010.00743.pdf). We will load a cellular complex dataset from the web and train the model to perform classificaiton on this dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import numpy as np\n", + "import argparse\n", + "import random\n", + "from toponetx import CellComplex\n", + "\n", + "from topomodelx.nn.cellular.convcxn_layer import ConvCXNLayer" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If GPU's are available, we will make use of them. Otherwise, this will run on CPU." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cpu\n" + ] + } + ], + "source": [ + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(device)\n", + "device = torch.device(device)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We specify the hyperparameters for training." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "parser.add_argument(\"--lr\", default=1e-3, type=float)\n", + "parser.add_argument(\"--num_epochs\", default=3, type=int)\n", + "parser.add_argument(\"--with_rotation\", default=1, type=int, choices=[0, 1])\n", + "\n", + "args, unknown = parser.parse_known_args()\n", + "training_cfg = {\n", + " \"lr\": args.lr,\n", + " \"num_epochs\": args.num_epochs,\n", + "}" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pre-processing\n", + "\n", + "## Create synthetic data and load its neighborhood structures\n", + "\n", + "We start by creating the cellular complex on which the neural network will operate." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cell Complex with 6 nodes, 8 edges and 1 2-cells \n", + "8\n" + ] + } + ], + "source": [ + "edge_set = [\n", + " [1, 2],\n", + " [1, 3],\n", + " [2, 4],\n", + " [3, 4],\n", + " [4, 5],\n", + " [1, 6],\n", + "] # two edges stick out on diag\n", + "node_set = [1, 2, 3, 4, 5, 6]\n", + "face_set = [[1, 2, 3, 4]]\n", + "complex = CellComplex(edge_set + node_set + face_set)\n", + "print(complex)\n", + "print(len(complex.edges))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will need the adjacency matrix $A_{\\uparrow, 0}$ and the coboundary matrix $B_2^T$." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "incidence_2_t = complex.incidence_matrix(rank=2).T\n", + "adjacency_0 = complex.adjacency_matrix(rank=0)\n", + "incidence_2_t = torch.from_numpy(incidence_2_t.todense()).to_sparse().float()\n", + "adjacency_0 = torch.from_numpy(adjacency_0.todense()).to_sparse().float()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we create data on this complex. Specifically, we need node features and edge features for both train and test datasets." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "x_0_train = []\n", + "num_features_0 = 4\n", + "for _ in range(100):\n", + " x_0_train.append(torch.Tensor(np.random.rand(len(complex.nodes), num_features_0)))\n", + "\n", + "x_1_train = []\n", + "num_features_1 = 5\n", + "for _ in range(100):\n", + " x_1_train.append(torch.Tensor(np.random.rand(len(complex.edges), num_features_1)))\n", + "\n", + "x_0_test = []\n", + "num_features_0 = 4\n", + "for _ in range(10):\n", + " x_0_test.append(torch.Tensor(np.random.rand(len(complex.nodes), num_features_0)))\n", + "\n", + "x_1_test = []\n", + "num_features_1 = 5\n", + "for _ in range(10):\n", + " x_1_test.append(torch.Tensor(np.random.rand(len(complex.edges), num_features_1)))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we must define labels associated to these datasets, as we will perform binary node classification. For the purposes of the tutorial, we leave these completely random." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "labels_train = [random.randint(0, 1) for _ in range(100)]\n", + "labels_test = [random.randint(0, 1) for _ in range(10)]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's define the input feature dimensions as channel dimensions. We will use this to define our model." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "in_ch_v 4 in_ch_e 5 in_ch_f 5\n" + ] + } + ], + "source": [ + "in_ch_0 = num_features_0\n", + "in_ch_1 = num_features_1\n", + "in_ch_2 = 5\n", + "print(f\"in_ch_v {in_ch_0} in_ch_e {in_ch_1} in_ch_f {in_ch_2}\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create the Neural Network\n", + "\n", + "Using the ConvCXNLayer class, we create a neural network with stacked layers. We define the amount of channels on the face and edge ranks to be different, making this a heterogenous network." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "class ConvCXN(torch.nn.Module):\n", + " \"\"\"Convolutional CXN.\n", + "\n", + " Parameters\n", + " ----------\n", + " in_ch_0 : int\n", + " Dimension of input features on nodes.\n", + " in_ch_1 : int\n", + " Dimension of input features on edges.\n", + " in_ch_2 : int\n", + " Dimension of input features on faces.\n", + " num_classes : int\n", + " Number of classes.\n", + " n_layers : int\n", + " Number of CXN layers.\n", + " \"\"\"\n", + "\n", + " def __init__(self, in_ch_0, in_ch_1, in_ch_2, num_classes, n_layers=2):\n", + " super().__init__()\n", + " layers = []\n", + " for _ in range(n_layers):\n", + " layers.append(\n", + " ConvCXNLayer(\n", + " in_channels_0=in_ch_0,\n", + " in_channels_1=in_ch_1,\n", + " in_channels_2=in_ch_2,\n", + " )\n", + " )\n", + " self.layers = layers\n", + " self.lin_0 = torch.nn.Linear(in_ch_0, num_classes)\n", + " self.lin_1 = torch.nn.Linear(in_ch_1, num_classes)\n", + " self.lin_2 = torch.nn.Linear(in_ch_2, num_classes)\n", + "\n", + " def forward(self, x_0, x_1, neighborhood_0_to_0, neighborhood_1_to_2):\n", + " \"\"\"Forward computation through ConvCXN layers then linear layers.\"\"\"\n", + " for layer in self.layers:\n", + " x_0, x_1, x_2 = layer(x_0, x_1, neighborhood_0_to_0, neighborhood_1_to_2)\n", + " x_0 = self.lin_0(x_0)\n", + " x_1 = self.lin_1(x_1)\n", + " x_2 = self.lin_2(x_2)\n", + " return torch.mean(x_2, dim=0) + torch.mean(x_1, dim=0) + torch.mean(x_0, dim=0)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train the Neural Network\n", + "\n", + "We specify the model, initialize loss, and specify an optimizer." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "model = ConvCXN(in_ch_0, in_ch_1, in_ch_2, num_classes=2, n_layers=2)\n", + "model = model.to(device)\n", + "crit = torch.nn.CrossEntropyLoss()\n", + "opt = torch.optim.Adam(model.parameters(), lr=args.lr)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell performs the training, looping over the network for 5 epochs and testing after every 2 epochs." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 1 loss: 0.2939 Train_acc: 0.9800\n", + "Epoch: 2 loss: 0.0628 Train_acc: 1.0000\n", + "Test_acc: 1.0000\n", + "Epoch: 3 loss: 0.0297 Train_acc: 1.0000\n" + ] + } + ], + "source": [ + "test_interval = 2\n", + "for epoch_i in range(1, args.num_epochs + 1):\n", + " epoch_loss = []\n", + " num_samples = 0\n", + " correct = 0\n", + " model.train()\n", + " for x_0, x_1, y in zip(x_0_train, x_1_train, labels_train):\n", + "\n", + " opt.zero_grad()\n", + "\n", + " y_hat = model(\n", + " x_0.float(), x_1.float(), adjacency_0.float(), incidence_2_t.float()\n", + " )\n", + " y = torch.tensor(y).long()\n", + " loss = crit(y_hat, y)\n", + " correct += (y_hat.argmax() == y).sum().item()\n", + " num_samples += 1\n", + " loss.backward()\n", + " opt.step()\n", + " epoch_loss.append(loss.item())\n", + " train_acc = correct / num_samples\n", + " print(\n", + " f\"Epoch: {epoch_i} loss: {np.mean(epoch_loss):.4f} Train_acc: {train_acc:.4f}\",\n", + " flush=True,\n", + " )\n", + " # wandb.log({\"loss\": np.mean(epoch_loss), \"Train_acc\": train_acc, \"epoch\": epoch_i})\n", + " if epoch_i % test_interval == 0:\n", + " with torch.no_grad():\n", + " num_samples = 0\n", + " correct = 0\n", + " for x_0, x_1, y in zip(x_0_test, x_1_test, labels_test):\n", + " y = torch.tensor(y).long()\n", + " y_hat = model(x_0, x_1, adjacency_0, incidence_2_t)\n", + "\n", + " correct += (y_hat.argmax() == y).sum().item()\n", + " num_samples += 1\n", + " test_acc = correct / num_samples\n", + " print(f\"Test_acc: {test_acc:.4f}\", flush=True)\n", + " # wandb.log({\"Test_acc\": test_acc})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "tmx", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/hsn_train.ipynb b/docs/notebooks/hsn_train.ipynb new file mode 100644 index 00000000..646b0f33 --- /dev/null +++ b/docs/notebooks/hsn_train.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial: Set-up, create, and train a High-Skip Network (HSN)\n", + "\n", + "In this notebook, we will create and train a High Skip Network in the simplicial complex domain, as proposed in the paper by Hajij et. al: High Skip Networks: A Higher Order Generalization of Skip Connections (https://openreview.net/pdf?id=Sc8glB-k6e9). We will build a simple toy dataset from scratch using TopoNetX. We train the model to perform binary node classification. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import numpy as np\n", + "from toponetx import SimplicialComplex\n", + "from topomodelx.nn.simplicial.hsn_layer import HSNLayer" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pre-processing\n", + "\n", + "## Create domain ##\n", + "\n", + "The first step is to define the topological domain on which the TNN will operate, as well as the neighborhod structures characterizing this domain. We will only define the neighborhood matrices that we plan on using.\n", + "\n", + "Here, we build a simple simplicial complex domain. TopoNetX is capable of defining a regular simplicial complex using only a set of given edges. In this case, we define two edges between three nodes. As such, the domain will be endowed with three nodes. We use the propoerty .simplices to list the cells of the domain." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SimplexView([(1,), (2,), (3,), (1, 2), (1, 3)])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "edge_set = [[1, 2], [1, 3]]\n", + "\n", + "domain = SimplicialComplex(edge_set)\n", + "domain.simplices" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create neighborhood structures. ##\n", + "\n", + "Now we retrieve the neighborhood structures (i.e. their representative matrices) that we will use to send messges on the domain. In this case, we need the boundary matrix (or incidence matrix) $B_1$ and the adjacency matrix $A_{\\uparrow,0}$ on the nodes. For a santiy check, we show that the shape of the $B_1 = n_\\text{nodes} \\times n_\\text{edges}$ and $A_{\\uparrow,0} = n_\\text{nodes} \\times n_\\text{nodes}$." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "incidence_1\n", + " [[-1. -1.]\n", + " [ 1. 0.]\n", + " [ 0. 1.]]\n", + "adjacency_0\n", + " [[0. 1. 1.]\n", + " [1. 0. 0.]\n", + " [1. 0. 0.]]\n" + ] + } + ], + "source": [ + "incidence_1 = domain.incidence_matrix(rank=1)\n", + "adjacency_0 = domain.adjacency_matrix(rank=0)\n", + "\n", + "print(\"incidence_1\\n\", incidence_1.todense())\n", + "print(\"adjacency_0\\n\", adjacency_0.todense())" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we convert the neighborhood structures to torch tensors." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "incidence_1 = torch.from_numpy(incidence_1.todense()).to_sparse()\n", + "adjacency_0 = torch.from_numpy(adjacency_0.todense()).to_sparse()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create signal ##\n", + "\n", + "Since our task will be node classification, we must define an input signal (at least one datapoint) on the nodes. The signal will have shape $n_\\text{nodes} \\times$ in_channels, where in_channels is the dimension of each cell's feature. Here, we take in_channels = channels_nodes $ = 2$." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "x_nodes = torch.tensor([[1.0, 1.0], [2.0, 2.0], [1.0, 1.0]])\n", + "channels_nodes = x_nodes.shape[-1]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create the Neural Network\n", + "\n", + "Using the HSNLayer class, we create a neural network with stacked layers." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "class HSN(torch.nn.Module):\n", + " def __init__(self, channels, n_layers=2):\n", + " super().__init__()\n", + " layers = []\n", + " for _ in range(n_layers):\n", + " layers.append(\n", + " HSNLayer(\n", + " channels=channels,\n", + " )\n", + " )\n", + " self.linear = torch.nn.Linear(channels, 1)\n", + " self.layers = layers\n", + "\n", + " def forward(self, x_0, incidence_1, adjacency_0):\n", + " for layer in self.layers:\n", + " x_0 = layer(x_0, incidence_1, adjacency_0)\n", + " return self.linear(x_0)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train the Neural Network\n", + "\n", + "We specify the model with our pre-made neighborhood structures, assign ground truth labels for the classification task, and specify an optimizer." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model = HSN(\n", + " channels=channels_nodes,\n", + " n_layers=2,\n", + ")\n", + "nodes_gt_labels = torch.Tensor([[0], [1], [1]])\n", + "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell performs the training, looping over the network for 5 epochs." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "for epoch in range(5):\n", + " optimizer.zero_grad()\n", + " nodes_pred_labels = model(x_nodes, incidence_1, adjacency_0)\n", + " loss = torch.nn.functional.binary_cross_entropy_with_logits(\n", + " nodes_pred_labels, nodes_gt_labels\n", + " )\n", + " loss.backward()\n", + " optimizer.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "tmx", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/template_train.ipynb b/docs/notebooks/template_train.ipynb new file mode 100644 index 00000000..e3d3d170 --- /dev/null +++ b/docs/notebooks/template_train.ipynb @@ -0,0 +1,244 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial: Set-up, create, and train a two-step message passing network (Template)\n", + "\n", + "In this notebook, we will create and train a two-step message passing network in the simplicial complex domain. We will build a simple toy dataset from scratch using TopoNetX. We train the model to perform binary node classification. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import numpy as np\n", + "from toponetx import SimplicialComplex\n", + "from topomodelx.nn.simplicial.template_layer import TemplateLayer" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pre-processing\n", + "\n", + "## Create domain ##\n", + "\n", + "The first step is to define the topological domain on which the TNN will operate, as well as the neighborhod structures characterizing this domain. We will only define the neighborhood matrices that we plan on using.\n", + "\n", + "Here, we build a simple simplicial complex domain. Our domain is comprised of 5 nodes, which form two faces. We specify two edges in the domain, and TopoNetX adds edges along the faces to ensure the cell is regular." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "edge_set = [[1, 2], [1, 3]]\n", + "face_set = [[2, 3, 4], [2, 4, 5]]\n", + "\n", + "domain = SimplicialComplex(edge_set + face_set)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create neighborhood structures. ##\n", + "\n", + "Now we retrieve the neighborhood structures (i.e. their representative matrices) that we will use to send messges on the domain. In this case, we need the boundary matrix (or incidence matrix) $B_2$. For a santiy check, we show that the shape of the $B_2 = n_\\text{edges} \\times n_\\text{faces}$." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "incidence_2\n", + " [[ 0. 0.]\n", + " [ 0. 0.]\n", + " [ 1. 0.]\n", + " [-1. 1.]\n", + " [ 0. -1.]\n", + " [ 1. 0.]\n", + " [ 0. 1.]]\n" + ] + } + ], + "source": [ + "incidence_2 = domain.incidence_matrix(rank=2)\n", + "print(\"incidence_2\\n\", incidence_2.todense())" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We convert the neighborhood matrix to tensor format." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "incidence_2_torch = torch.from_numpy(incidence_2.todense()).to_sparse()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create data ##\n", + "\n", + "Since our task will be node classification, we must define an input signal (at least one datapoint) on the nodes. The signal will have shape $n_\\text{faces} \\times$ in_channels, where in_channels is the dimension of each cell's feature. Here, we take in_channels = channels_nodes $ = 2$." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "x_2 = torch.tensor([[1.0, 1.0], [2.0, 2.0]])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create the Neural Network\n", + "\n", + "Using the TemplateLayer class, we create a neural network with stacked layers. We define the amount of channels on the face and edge ranks to be different, making this a heterogenous network." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "channels_face = np.array(x_2.shape[1])\n", + "channels_edge = 4" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "class TemplateNN(torch.nn.Module):\n", + " def __init__(self, channels_face, channels_edge, n_layers=2):\n", + " super().__init__()\n", + " layers = []\n", + " for _ in range(n_layers):\n", + " layers.append(\n", + " TemplateLayer(\n", + " in_channels=channels_face,\n", + " intermediate_channels=channels_edge,\n", + " out_channels=channels_face,\n", + " )\n", + " )\n", + " self.layers = layers\n", + " self.linear = torch.nn.Linear(channels_face, 1)\n", + "\n", + " def forward(self, x_2, incidence_2_torch):\n", + " for layer in self.layers:\n", + " x_2 = layer(x_2, incidence_2_torch)\n", + " return self.linear(x_2)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train the Neural Network\n", + "\n", + "We specify the model, assign ground truth labels for the classification task, and specify an optimizer." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "model = TemplateNN(channels_face, channels_edge, n_layers=2)\n", + "faces_gt_labels = torch.Tensor([[0], [1]]) # (n_faces, 1)\n", + "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell performs the training, looping over the network for 5 epochs." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "for epoch in range(5):\n", + " optimizer.zero_grad()\n", + " faces_pred_labels = model(x_2, incidence_2_torch)\n", + " loss = torch.nn.functional.binary_cross_entropy_with_logits(\n", + " faces_pred_labels, faces_gt_labels\n", + " )\n", + " loss.backward()\n", + " optimizer.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "tmx", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst new file mode 100644 index 00000000..6a3b066c --- /dev/null +++ b/docs/tutorials/index.rst @@ -0,0 +1,11 @@ +.. _tutorials: + +========= +Tutorials +========= + +.. nbgallery:: + :maxdepth: 1 + :glob: + + ../notebooks/* From f9a7e560bf4479cad6c043146ce1c9f3e466c7fa Mon Sep 17 00:00:00 2001 From: Nina Miolane Date: Mon, 22 May 2023 09:10:39 -1000 Subject: [PATCH 3/3] Change back to DOCUMENTATION_KEY --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e084575f..4e2c09a4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -39,7 +39,7 @@ jobs: with: branch: main folder: docs/_build - token: ${{ secrets.TMX_DOCUMENTATION_KEY }} + token: ${{ secrets.DOCUMENTATION_KEY }} repository-name: pyt-team/pyt-team.github.io target-folder: topomodelx clean: true \ No newline at end of file