├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── docs
├── Makefile
├── authors.rst
├── conf.py
├── contributing.rst
├── index.rst
├── installation.rst
├── make.bat
├── netlsd.kernels.compare.rst
├── netlsd.kernels.heat.rst
├── netlsd.kernels.netlsd.rst
├── netlsd.kernels.wave.rst
├── readme.rst
├── reference.rst
└── usage.rst
├── netlsd
├── __init__.py
├── kernels.py
└── util.py
├── requirements.txt
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled python modules.
2 | *.pyc
3 |
4 | # Setuptools distribution folder.
5 | /dist/
6 |
7 | # Python egg metadata, regenerated from source files by setuptools.
8 | /*.egg-info
9 | /*.egg
10 |
11 | # Sphinx
12 | docs/_*
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Anton Tsitsulin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===============================
2 | NetLSD
3 | ===============================
4 |
5 | NetLSD is a family of spectral graph descriptros. Given a graph, NetLSD computes a low-dimensional vector representation that can be used for different tasks.
6 |
7 | Quick start
8 | -----------
9 |
10 | .. code-block:: python
11 |
12 | import netlsd
13 | import networkx as nx
14 |
15 | g = nx.erdos_renyi_graph(100, 0.01) # create a random graph with 100 nodes
16 | descriptor = netlsd.heat(g) # compute the signature
17 |
18 | That's it! Then, signatures of two graphs can be compared easily. NetLSD supports `networkx `_, `graph_tool `_, and `igraph `_ packages natively.
19 |
20 | .. code-block:: python
21 |
22 | import netlsd
23 | import numpy as np
24 |
25 | distance = netlsd.compare(desc1, desc2) # compare the signatures using l2 distance
26 | distance = np.linalg.norm(desc1 - desc2) # equivalent
27 |
28 |
29 | For more advanced usage, check out `online documentation `_.
30 |
31 |
32 | Requirements
33 | ------------
34 | * numpy
35 | * scipy
36 |
37 |
38 | Installation
39 | ------------
40 | #. cd netlsd
41 | #. pip install -r requirements.txt
42 | #. python setup.py install
43 |
44 | Or simply ``pip install netlsd``
45 |
46 | Citing
47 | ------
48 | If you find NetLSD useful in your research, we ask that you cite the following paper::
49 |
50 | @inproceedings{Tsitsulin:2018:KDD,
51 | author={Tsitsulin, Anton and Mottin, Davide and Karras, Panagiotis and Bronstein, Alex and M{\"u}ller, Emmanuel},
52 | title={NetLSD: Hearing the Shape of a Graph},
53 | booktitle = {Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining},
54 | series = {KDD '18},
55 | year = {2018},
56 | }
57 |
58 | Misc
59 | ----
60 |
61 | NetLSD - Hearing the shape of graphs.
62 |
63 | * MIT license
64 | * Documentation: http://netlsd.readthedocs.org
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " applehelp to make an Apple Help Book"
34 | @echo " devhelp to make HTML files and a Devhelp project"
35 | @echo " epub to make an epub"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 |
51 | clean:
52 | rm -rf $(BUILDDIR)/*
53 |
54 | html:
55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
56 | @echo
57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
58 |
59 | dirhtml:
60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
61 | @echo
62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
63 |
64 | singlehtml:
65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
66 | @echo
67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
68 |
69 | pickle:
70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
71 | @echo
72 | @echo "Build finished; now you can process the pickle files."
73 |
74 | json:
75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
76 | @echo
77 | @echo "Build finished; now you can process the JSON files."
78 |
79 | htmlhelp:
80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
81 | @echo
82 | @echo "Build finished; now you can run HTML Help Workshop with the" \
83 | ".hhp project file in $(BUILDDIR)/htmlhelp."
84 |
85 | qthelp:
86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
87 | @echo
88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/NetLSD.qhcp"
91 | @echo "To view the help file:"
92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/NetLSD.qhc"
93 |
94 | applehelp:
95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
96 | @echo
97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
98 | @echo "N.B. You won't be able to view it unless you put it in" \
99 | "~/Library/Documentation/Help or install it in your application" \
100 | "bundle."
101 |
102 | devhelp:
103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
104 | @echo
105 | @echo "Build finished."
106 | @echo "To view the help file:"
107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/NetLSD"
108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/NetLSD"
109 | @echo "# devhelp"
110 |
111 | epub:
112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
113 | @echo
114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
115 |
116 | latex:
117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
118 | @echo
119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
121 | "(use \`make latexpdf' here to do that automatically)."
122 |
123 | latexpdf:
124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
125 | @echo "Running LaTeX files through pdflatex..."
126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
128 |
129 | latexpdfja:
130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
131 | @echo "Running LaTeX files through platex and dvipdfmx..."
132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
134 |
135 | text:
136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
137 | @echo
138 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
139 |
140 | man:
141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
142 | @echo
143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
144 |
145 | texinfo:
146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
147 | @echo
148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
149 | @echo "Run \`make' in that directory to run these through makeinfo" \
150 | "(use \`make info' here to do that automatically)."
151 |
152 | info:
153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
154 | @echo "Running Texinfo files through makeinfo..."
155 | make -C $(BUILDDIR)/texinfo info
156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
157 |
158 | gettext:
159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
160 | @echo
161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
162 |
163 | changes:
164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
165 | @echo
166 | @echo "The overview file is in $(BUILDDIR)/changes."
167 |
168 | linkcheck:
169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
170 | @echo
171 | @echo "Link check complete; look for any errors in the above output " \
172 | "or in $(BUILDDIR)/linkcheck/output.txt."
173 |
174 | doctest:
175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
176 | @echo "Testing of doctests in the sources finished, look at the " \
177 | "results in $(BUILDDIR)/doctest/output.txt."
178 |
179 | coverage:
180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
181 | @echo "Testing of coverage in the sources finished, look at the " \
182 | "results in $(BUILDDIR)/coverage/python.txt."
183 |
184 | xml:
185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
186 | @echo
187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
188 |
189 | pseudoxml:
190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
191 | @echo
192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
193 |
--------------------------------------------------------------------------------
/docs/authors.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Credits
3 | =======
4 |
5 | * Anton Tsitsulin
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # NetLSD documentation build configuration file, created by
5 | # sphinx-quickstart on Mon Jun 4 10:50:06 2018.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | import sys
17 | import os
18 | import shlex
19 | from recommonmark.parser import CommonMarkParser
20 |
21 | # If extensions (or modules to document with autodoc) are in another directory,
22 | # add these directories to sys.path here. If the directory is relative to the
23 | # documentation root, use os.path.abspath to make it absolute, like shown here.
24 | #sys.path.insert(0, os.path.abspath('.'))
25 |
26 | # -- General configuration ------------------------------------------------
27 |
28 | # If your documentation needs a minimal Sphinx version, state it here.
29 | #needs_sphinx = '1.0'
30 | source_parsers = {
31 | '.md': CommonMarkParser,
32 | }
33 |
34 |
35 | # Add any Sphinx extension module names here, as strings. They can be
36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
37 | # ones.
38 | extensions = [
39 | 'sphinx.ext.autosummary',
40 | 'sphinx.ext.autodoc',
41 | 'sphinx.ext.todo',
42 | 'sphinx.ext.coverage',
43 | 'sphinx.ext.mathjax',
44 | 'sphinx.ext.viewcode',
45 | ]
46 |
47 | # Add any paths that contain templates here, relative to this directory.
48 | templates_path = ['_templates']
49 |
50 | # The suffix(es) of source filenames.
51 | # You can specify multiple suffix as a list of string:
52 | # source_suffix = ['.rst', '.md']
53 | source_suffix = ['.rst', '.md']
54 |
55 | # The encoding of source files.
56 | #source_encoding = 'utf-8-sig'
57 |
58 | # The master toctree document.
59 | master_doc = 'index'
60 |
61 | # General information about the project.
62 | project = 'NetLSD'
63 | copyright = '2018, Anton Tsitsulin'
64 | author = 'Anton Tsitsulin'
65 |
66 | # The version info for the project you're documenting, acts as replacement for
67 | # |version| and |release|, also used in various other places throughout the
68 | # built documents.
69 | #
70 | # The short X.Y version.
71 | version = '0.1'
72 | # The full version, including alpha/beta/rc tags.
73 | release = '0.1'
74 |
75 | # The language for content autogenerated by Sphinx. Refer to documentation
76 | # for a list of supported languages.
77 | #
78 | # This is also used if you do content translation via gettext catalogs.
79 | # Usually you set "language" from the command line for these cases.
80 | language = None
81 |
82 | # There are two options for replacing |today|: either, you set today to some
83 | # non-false value, then it is used:
84 | #today = ''
85 | # Else, today_fmt is used as the format for a strftime call.
86 | #today_fmt = '%B %d, %Y'
87 |
88 | # List of patterns, relative to source directory, that match files and
89 | # directories to ignore when looking for source files.
90 | exclude_patterns = ['_build']
91 |
92 | # The reST default role (used for this markup: `text`) to use for all
93 | # documents.
94 | #default_role = None
95 |
96 | # If true, '()' will be appended to :func: etc. cross-reference text.
97 | #add_function_parentheses = True
98 |
99 | # If true, the current module name will be prepended to all description
100 | # unit titles (such as .. function::).
101 | #add_module_names = True
102 |
103 | # If true, sectionauthor and moduleauthor directives will be shown in the
104 | # output. They are ignored by default.
105 | #show_authors = False
106 |
107 | # The name of the Pygments (syntax highlighting) style to use.
108 | pygments_style = 'sphinx'
109 |
110 | # A list of ignored prefixes for module index sorting.
111 | #modindex_common_prefix = []
112 |
113 | # If true, keep warnings as "system message" paragraphs in the built documents.
114 | #keep_warnings = False
115 |
116 | # If true, `todo` and `todoList` produce output, else they produce nothing.
117 | todo_include_todos = True
118 |
119 |
120 | # -- Options for HTML output ----------------------------------------------
121 |
122 | # The theme to use for HTML and HTML Help pages. See the documentation for
123 | # a list of builtin themes.
124 | html_theme = 'sphinx_rtd_theme'
125 |
126 | # Theme options are theme-specific and customize the look and feel of a theme
127 | # further. For a list of options available for each theme, see the
128 | # documentation.
129 | #html_theme_options = {}
130 |
131 | # Add any paths that contain custom themes here, relative to this directory.
132 | #html_theme_path = []
133 |
134 | # The name for this set of Sphinx documents. If None, it defaults to
135 | # " v documentation".
136 | #html_title = None
137 |
138 | # A shorter title for the navigation bar. Default is the same as html_title.
139 | #html_short_title = None
140 |
141 | # The name of an image file (relative to this directory) to place at the top
142 | # of the sidebar.
143 | #html_logo = None
144 |
145 | # The name of an image file (within the static path) to use as favicon of the
146 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
147 | # pixels large.
148 | #html_favicon = None
149 |
150 | # Add any paths that contain custom static files (such as style sheets) here,
151 | # relative to this directory. They are copied after the builtin static files,
152 | # so a file named "default.css" will overwrite the builtin "default.css".
153 | html_static_path = ['_static']
154 |
155 | # Add any extra paths that contain custom files (such as robots.txt or
156 | # .htaccess) here, relative to this directory. These files are copied
157 | # directly to the root of the documentation.
158 | #html_extra_path = []
159 |
160 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
161 | # using the given strftime format.
162 | #html_last_updated_fmt = '%b %d, %Y'
163 |
164 | # If true, SmartyPants will be used to convert quotes and dashes to
165 | # typographically correct entities.
166 | #html_use_smartypants = True
167 |
168 | # Custom sidebar templates, maps document names to template names.
169 | #html_sidebars = {}
170 |
171 | # Additional templates that should be rendered to pages, maps page names to
172 | # template names.
173 | #html_additional_pages = {}
174 |
175 | # If false, no module index is generated.
176 | #html_domain_indices = True
177 |
178 | # If false, no index is generated.
179 | #html_use_index = True
180 |
181 | # If true, the index is split into individual pages for each letter.
182 | #html_split_index = False
183 |
184 | # If true, links to the reST sources are added to the pages.
185 | #html_show_sourcelink = True
186 |
187 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
188 | #html_show_sphinx = True
189 |
190 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
191 | #html_show_copyright = True
192 |
193 | # If true, an OpenSearch description file will be output, and all pages will
194 | # contain a tag referring to it. The value of this option must be the
195 | # base URL from which the finished HTML is served.
196 | #html_use_opensearch = ''
197 |
198 | # This is the file name suffix for HTML files (e.g. ".xhtml").
199 | #html_file_suffix = None
200 |
201 | # Language to be used for generating the HTML full-text search index.
202 | # Sphinx supports the following languages:
203 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
204 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr'
205 | #html_search_language = 'en'
206 |
207 | # A dictionary with options for the search language support, empty by default.
208 | # Now only 'ja' uses this config value
209 | #html_search_options = {'type': 'default'}
210 |
211 | # The name of a javascript file (relative to the configuration directory) that
212 | # implements a search results scorer. If empty, the default will be used.
213 | #html_search_scorer = 'scorer.js'
214 |
215 | # Output file base name for HTML help builder.
216 | htmlhelp_basename = 'NetLSDdoc'
217 |
218 | # -- Options for LaTeX output ---------------------------------------------
219 |
220 | latex_elements = {
221 | # The paper size ('letterpaper' or 'a4paper').
222 | #'papersize': 'letterpaper',
223 |
224 | # The font size ('10pt', '11pt' or '12pt').
225 | #'pointsize': '10pt',
226 |
227 | # Additional stuff for the LaTeX preamble.
228 | #'preamble': '',
229 |
230 | # Latex figure (float) alignment
231 | #'figure_align': 'htbp',
232 | }
233 |
234 | # Grouping the document tree into LaTeX files. List of tuples
235 | # (source start file, target name, title,
236 | # author, documentclass [howto, manual, or own class]).
237 | latex_documents = [
238 | (master_doc, 'NetLSD.tex', 'NetLSD Documentation',
239 | 'Anton Tsitsulin', 'manual'),
240 | ]
241 |
242 | # The name of an image file (relative to this directory) to place at the top of
243 | # the title page.
244 | #latex_logo = None
245 |
246 | # For "manual" documents, if this is true, then toplevel headings are parts,
247 | # not chapters.
248 | #latex_use_parts = False
249 |
250 | # If true, show page references after internal links.
251 | #latex_show_pagerefs = False
252 |
253 | # If true, show URL addresses after external links.
254 | #latex_show_urls = False
255 |
256 | # Documents to append as an appendix to all manuals.
257 | #latex_appendices = []
258 |
259 | # If false, no module index is generated.
260 | #latex_domain_indices = True
261 |
262 |
263 | # -- Options for manual page output ---------------------------------------
264 |
265 | # One entry per manual page. List of tuples
266 | # (source start file, name, description, authors, manual section).
267 | man_pages = [
268 | (master_doc, 'netlsd', 'NetLSD Documentation',
269 | [author], 1)
270 | ]
271 |
272 | # If true, show URL addresses after external links.
273 | #man_show_urls = False
274 |
275 |
276 | # -- Options for Texinfo output -------------------------------------------
277 |
278 | # Grouping the document tree into Texinfo files. List of tuples
279 | # (source start file, target name, title, author,
280 | # dir menu entry, description, category)
281 | texinfo_documents = [
282 | (master_doc, 'NetLSD', 'NetLSD Documentation',
283 | author, 'NetLSD', 'One line description of project.',
284 | 'Miscellaneous'),
285 | ]
286 |
287 | # Documents to append as an appendix to all manuals.
288 | #texinfo_appendices = []
289 |
290 | # If false, no module index is generated.
291 | #texinfo_domain_indices = True
292 |
293 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
294 | #texinfo_show_urls = 'footnote'
295 |
296 | # If true, do not generate a @detailmenu in the "Top" node's menu.
297 | #texinfo_no_detailmenu = False
298 |
--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Contributing
3 | ============
4 |
5 | Contributions are welcome, and they are greatly appreciated! Every
6 | little bit helps, and credit will always be given.
7 |
8 | You can contribute in many ways:
9 |
10 | Types of Contributions
11 | ----------------------
12 |
13 | Report Bugs
14 | ~~~~~~~~~~~
15 |
16 | Report bugs at https://github.com/xgfs/NetLSD/issues.
17 |
18 | If you are reporting a bug, please include:
19 |
20 | * Your operating system name and version.
21 | * Any details about your local setup that might be helpful in troubleshooting.
22 | * Detailed steps to reproduce the bug.
23 |
24 | Fix Bugs
25 | ~~~~~~~~
26 |
27 | Look through the GitHub issues for bugs. Anything tagged with "bug"
28 | is open to whoever wants to implement it.
29 |
30 | Implement Features
31 | ~~~~~~~~~~~~~~~~~~
32 |
33 | Look through the GitHub issues for features. Anything tagged with "feature"
34 | is open to whoever wants to implement it.
35 |
36 | Write Documentation
37 | ~~~~~~~~~~~~~~~~~~~
38 |
39 | NetLSD could always use more documentation, whether as part of the
40 | official NetLSD docs, in docstrings, or even on the web in blog posts,
41 | articles, and such.
42 |
43 | Submit Feedback
44 | ~~~~~~~~~~~~~~~
45 |
46 | The best way to send feedback is to file an issue at https://github.com/xgfs/NetLSD/issues.
47 |
48 | If you are proposing a feature:
49 |
50 | * Explain in detail how it would work.
51 | * Keep the scope as narrow as possible, to make it easier to implement.
52 | * Remember that this is a volunteer-driven project, and that contributions
53 | are welcome :)
54 |
55 | Get Started!
56 | ------------
57 |
58 | Ready to contribute? Here's how to set up `NetLSD` for local development.
59 |
60 | 1. Fork the `NetLSD` repo on GitHub.
61 | 2. Clone your fork locally::
62 |
63 | $ git clone git@github.com:your_name_here/NetLSD.git
64 |
65 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
66 |
67 | $ mkvirtualenv NetLSD
68 | $ cd NetLSD/
69 | $ python setup.py develop
70 |
71 | 4. Create a branch for local development::
72 |
73 | $ git checkout -b name-of-your-bugfix-or-feature
74 |
75 | Now you can make your changes locally.
76 |
77 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox::
78 |
79 | $ flake8 NetLSD tests
80 | $ python setup.py test
81 | $ tox
82 |
83 | To get flake8 and tox, just pip install them into your virtualenv.
84 |
85 | 6. Commit your changes and push your branch to GitHub::
86 |
87 | $ git add .
88 | $ git commit -m "Your detailed description of your changes."
89 | $ git push origin name-of-your-bugfix-or-feature
90 |
91 | 7. Submit a pull request through the GitHub website.
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. NetLSD documentation master file, created by
2 | sphinx-quickstart on Mon Jun 4 10:50:06 2018.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to NetLSD's documentation!
7 | ==================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | readme
15 | installation
16 | usage
17 | reference
18 | contributing
19 | authors
20 |
21 |
22 | Indices and tables
23 | ==================
24 |
25 | * :ref:`genindex`
26 | * :ref:`modindex`
27 | * :ref:`search`
28 |
29 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Installation
3 | ============
4 |
5 | At the command line::
6 |
7 | $ pip install netlsd
8 |
9 | Or, if you prefer to install from source::
10 |
11 | $ mkvirtualenv netlsd
12 | $ pip install netlsd
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | echo. coverage to run coverage check of the documentation if enabled
41 | goto end
42 | )
43 |
44 | if "%1" == "clean" (
45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
46 | del /q /s %BUILDDIR%\*
47 | goto end
48 | )
49 |
50 |
51 | REM Check if sphinx-build is available and fallback to Python version if any
52 | %SPHINXBUILD% 2> nul
53 | if errorlevel 9009 goto sphinx_python
54 | goto sphinx_ok
55 |
56 | :sphinx_python
57 |
58 | set SPHINXBUILD=python -m sphinx.__init__
59 | %SPHINXBUILD% 2> nul
60 | if errorlevel 9009 (
61 | echo.
62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
63 | echo.installed, then set the SPHINXBUILD environment variable to point
64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
65 | echo.may add the Sphinx directory to PATH.
66 | echo.
67 | echo.If you don't have Sphinx installed, grab it from
68 | echo.http://sphinx-doc.org/
69 | exit /b 1
70 | )
71 |
72 | :sphinx_ok
73 |
74 |
75 | if "%1" == "html" (
76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
80 | goto end
81 | )
82 |
83 | if "%1" == "dirhtml" (
84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
88 | goto end
89 | )
90 |
91 | if "%1" == "singlehtml" (
92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
93 | if errorlevel 1 exit /b 1
94 | echo.
95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
96 | goto end
97 | )
98 |
99 | if "%1" == "pickle" (
100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
101 | if errorlevel 1 exit /b 1
102 | echo.
103 | echo.Build finished; now you can process the pickle files.
104 | goto end
105 | )
106 |
107 | if "%1" == "json" (
108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
109 | if errorlevel 1 exit /b 1
110 | echo.
111 | echo.Build finished; now you can process the JSON files.
112 | goto end
113 | )
114 |
115 | if "%1" == "htmlhelp" (
116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
117 | if errorlevel 1 exit /b 1
118 | echo.
119 | echo.Build finished; now you can run HTML Help Workshop with the ^
120 | .hhp project file in %BUILDDIR%/htmlhelp.
121 | goto end
122 | )
123 |
124 | if "%1" == "qthelp" (
125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
129 | .qhcp project file in %BUILDDIR%/qthelp, like this:
130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\NetLSD.qhcp
131 | echo.To view the help file:
132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\NetLSD.ghc
133 | goto end
134 | )
135 |
136 | if "%1" == "devhelp" (
137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished.
141 | goto end
142 | )
143 |
144 | if "%1" == "epub" (
145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
149 | goto end
150 | )
151 |
152 | if "%1" == "latex" (
153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
157 | goto end
158 | )
159 |
160 | if "%1" == "latexpdf" (
161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
162 | cd %BUILDDIR%/latex
163 | make all-pdf
164 | cd %~dp0
165 | echo.
166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdfja" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf-ja
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "text" (
181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
182 | if errorlevel 1 exit /b 1
183 | echo.
184 | echo.Build finished. The text files are in %BUILDDIR%/text.
185 | goto end
186 | )
187 |
188 | if "%1" == "man" (
189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
190 | if errorlevel 1 exit /b 1
191 | echo.
192 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
193 | goto end
194 | )
195 |
196 | if "%1" == "texinfo" (
197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
198 | if errorlevel 1 exit /b 1
199 | echo.
200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
201 | goto end
202 | )
203 |
204 | if "%1" == "gettext" (
205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
206 | if errorlevel 1 exit /b 1
207 | echo.
208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
209 | goto end
210 | )
211 |
212 | if "%1" == "changes" (
213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
214 | if errorlevel 1 exit /b 1
215 | echo.
216 | echo.The overview file is in %BUILDDIR%/changes.
217 | goto end
218 | )
219 |
220 | if "%1" == "linkcheck" (
221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
222 | if errorlevel 1 exit /b 1
223 | echo.
224 | echo.Link check complete; look for any errors in the above output ^
225 | or in %BUILDDIR%/linkcheck/output.txt.
226 | goto end
227 | )
228 |
229 | if "%1" == "doctest" (
230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
231 | if errorlevel 1 exit /b 1
232 | echo.
233 | echo.Testing of doctests in the sources finished, look at the ^
234 | results in %BUILDDIR%/doctest/output.txt.
235 | goto end
236 | )
237 |
238 | if "%1" == "coverage" (
239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
240 | if errorlevel 1 exit /b 1
241 | echo.
242 | echo.Testing of coverage in the sources finished, look at the ^
243 | results in %BUILDDIR%/coverage/python.txt.
244 | goto end
245 | )
246 |
247 | if "%1" == "xml" (
248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
249 | if errorlevel 1 exit /b 1
250 | echo.
251 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
252 | goto end
253 | )
254 |
255 | if "%1" == "pseudoxml" (
256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
257 | if errorlevel 1 exit /b 1
258 | echo.
259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
260 | goto end
261 | )
262 |
263 | :end
264 |
--------------------------------------------------------------------------------
/docs/netlsd.kernels.compare.rst:
--------------------------------------------------------------------------------
1 | netlsd.kernels.compare
2 | ======================
3 |
4 | .. currentmodule:: netlsd.kernels
5 |
6 | .. autofunction:: compare
--------------------------------------------------------------------------------
/docs/netlsd.kernels.heat.rst:
--------------------------------------------------------------------------------
1 | netlsd.kernels.heat
2 | ===================
3 |
4 | .. currentmodule:: netlsd.kernels
5 |
6 | .. autofunction:: heat
--------------------------------------------------------------------------------
/docs/netlsd.kernels.netlsd.rst:
--------------------------------------------------------------------------------
1 | netlsd.kernels.netlsd
2 | =====================
3 |
4 | .. currentmodule:: netlsd.kernels
5 |
6 | .. autofunction:: netlsd
--------------------------------------------------------------------------------
/docs/netlsd.kernels.wave.rst:
--------------------------------------------------------------------------------
1 | netlsd.kernels.wave
2 | ===================
3 |
4 | .. currentmodule:: netlsd.kernels
5 |
6 | .. autofunction:: wave
--------------------------------------------------------------------------------
/docs/readme.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../README.rst
--------------------------------------------------------------------------------
/docs/reference.rst:
--------------------------------------------------------------------------------
1 | ****************
2 | Reference
3 | ****************
4 | .. currentmodule:: netlsd
5 |
6 | NetLSD
7 | ----------
8 | .. automodule:: netlsd.kernels
9 |
10 | .. autosummary::
11 | :toctree: .
12 |
13 | netlsd
14 | heat
15 | wave
16 | compare
17 |
18 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Usage
3 | ============
4 |
5 | Quick start usage
6 | ----------------------
7 |
8 | .. code-block:: python
9 |
10 | import netlsd
11 | import networkx as nx
12 |
13 | g = nx.erdos_renyi_graph(100, 0.01) # create a random graph with 100 nodes
14 | descriptor = netlsd.heat(g) # compute NetLSD signature
15 |
16 | That's it! Then, signatures of two graphs can be compared easily::
17 |
18 | .. code-block:: python
19 |
20 | import netlsd
21 |
22 | distance = netlsd.compare(sig1, sig2) # compare the signatures using l2 distance
23 |
24 | or, equivalently::
25 |
26 | .. code-block:: python
27 |
28 | import numpy as np
29 |
30 | distance = np.linalg.norm(sig1 - sig2) # compare the signatures using l2 distance in numpy
31 |
32 | Advanced usage
33 | ----------------------
34 |
35 | Here we outline different ways to get more out of NetLSD.
36 |
37 | Try the wave kernel
38 | ~~~~~~~~
39 |
40 | In the paper, we introduce two kernels: heat and wave.
41 | You can simply replace ``netlsd.heat`` with ``netlsd.wave`` to switch to wave kernel.
42 | Wave kernel is known to preserve symmetries and structures as it acts as a band-pass filter on the spectrum.
43 |
44 | Supply adjacency matrix directly
45 | ~~~~~~~~
46 |
47 | You do not need to use python's graph libraries to interface with NetLSD.
48 | One option is to use any type of a sparse matrix from scipy:
49 |
50 | .. code-block:: python
51 |
52 | import netlsd
53 | import scipy.sparse as sps
54 |
55 | A = sps.random(1000, 1000) # create a random adjacency matrix
56 | A = A + A.T # make sure it is undirected
57 | descriptor = netlsd.heat(A) # compute NetLSD signature
58 |
59 | In case you have already constructed a Laplacian, just pass it to the function.
60 |
61 | Scale things up with custom eigensolvers
62 | ~~~~~~~~
63 |
64 | If you want to use a different eigensolver routine, such as SLEPc, you can directly supply eigenvalues to NetLSD:
65 |
66 | .. code-block:: python
67 |
68 | import netlsd
69 | import fancy_eigensolver
70 |
71 | eigenvalues = fancy_eigensolver(graph)
72 | descriptor = netlsd.heat(eigenvalues) # compute NetLSD signature
--------------------------------------------------------------------------------
/netlsd/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | __author__ = 'Anton Tsitsulin'
4 | __email__ = 'anton.tsitsulin@hpi.de'
5 | __version__ = '0.0.1'
6 |
7 | from .kernels import heat, wave, netlsd, compare
--------------------------------------------------------------------------------
/netlsd/kernels.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from .util import check_1d, check_2d, eigenvalues_auto, graph_to_laplacian, mat_to_laplacian
3 |
4 |
5 | def compare(descriptor1, descriptor2):
6 | """
7 | Computes the distance between two NetLSD representations.
8 |
9 | Parameters
10 | ----------
11 | descriptor1: numpy.ndarray
12 | First signature to compare
13 | descriptor2: numpy.ndarray
14 | Second signature to compare
15 |
16 | Returns
17 | -------
18 | float
19 | NetLSD distance
20 |
21 | """
22 | return np.linalg.norm(descriptor1-descriptor2)
23 |
24 |
25 | def netlsd(inp, timescales=np.logspace(-2, 2, 250), kernel='heat', eigenvalues='auto', normalization='empty', normalized_laplacian=True):
26 | """
27 | Computes NetLSD signature from some given input, timescales, and normalization.
28 |
29 | Accepts matrices, common Python graph libraries' graphs, or vectors of eigenvalues.
30 | For precise definition, please refer to "NetLSD: Hearing the Shape of a Graph" by A. Tsitsulin, D. Mottin, P. Karras, A. Bronstein, E. Müller. Published at KDD'18.
31 |
32 | Parameters
33 | ----------
34 | inp: obj
35 | 2D numpy/scipy matrix, common Python graph libraries' graph, or vector of eigenvalues
36 | timescales : numpy.ndarray
37 | Vector of discrete timesteps for the kernel computation
38 | kernel : str
39 | Either 'heat' or 'wave'. Type of a kernel to use for computation.
40 | eigenvalues : str
41 | Either string or int or tuple
42 | Number of eigenvalues to compute / use for approximation.
43 | If string, we expect either 'full' or 'auto', otherwise error will be raised. 'auto' lets the program decide based on the faithful usage. 'full' computes all eigenvalues.
44 | If int, compute n_eivals eigenvalues from each side and approximate using linear growth approximation.
45 | If tuple, we expect two ints, first for lower part of approximation, and second for the upper part.
46 | normalization : str or numpy.ndarray
47 | Either 'empty', 'complete' or None.
48 | If None or any ther value, return unnormalized heat kernel trace.
49 | For the details how 'empty' and 'complete' are computed, please refer to the paper.
50 | If np.ndarray, they are treated as exact normalization constants
51 | normalized_laplacian: bool
52 | Defines whether the eigenvalues came from the normalized Laplacian. It only affects 'complete' normalization.
53 |
54 | Returns
55 | -------
56 | numpy.ndarray
57 | NetLSD signature
58 |
59 | """
60 | if kernel not in {'heat', 'wave'}:
61 | raise AttributeError('Unirecognized kernel type: expected one of [\'heat\', \'wave\'], got {0}'.format(kernel))
62 | if not isinstance(normalized_laplacian, bool):
63 | raise AttributeError('Unknown Laplacian type: expected bool, got {0}'.format(normalized_laplacian))
64 | if not isinstance(eigenvalues, (int, tuple, str)):
65 | raise AttributeError('Unirecognized requested eigenvalue number: expected type of [\'str\', \'tuple\', or \'int\'], got {0}'.format(type(eigenvalues)))
66 | if not isinstance(timescales, np.ndarray):
67 | raise AttributeError('Unirecognized timescales data type: expected np.ndarray, got {0}'.format(type(timescales)))
68 | if timescales.ndim != 1:
69 | raise AttributeError('Unirecognized timescales dimensionality: expected a vector, got {0}-d array'.format(timescales.ndim))
70 | if normalization not in {'complete', 'empty', 'none', True, False, None}:
71 | if not isinstance(normalization, np.ndarray):
72 | raise AttributeError('Unirecognized normalization type: expected one of [\'complete\', \'empty\', None or np.ndarray], got {0}'.format(normalization))
73 | if normalization.ndim != 1:
74 | raise AttributeError('Unirecognized normalization dimensionality: expected a vector, got {0}-d array'.format(normalization.ndim))
75 | if timescales.shape[0] != normalization.shape[0]:
76 | raise AttributeError('Unirecognized normalization dimensionality: expected {0}-length vector, got length {1}'.format(timescales.shape[0], normalization.shape[0]))
77 |
78 | eivals = check_1d(inp)
79 | if eivals is None:
80 | mat = check_2d(inp)
81 | if mat is None:
82 | mat = graph_to_laplacian(inp, normalized_laplacian)
83 | if mat is None:
84 | raise ValueError('Unirecognized input type: expected one of [\'np.ndarray\', \'scipy.sparse\', \'networkx.Graph\',\' graph_tool.Graph,\' or \'igraph.Graph\'], got {0}'.format(type(inp)))
85 | else:
86 | mat = mat_to_laplacian(inp, normalized_laplacian)
87 | eivals = eigenvalues_auto(mat, eigenvalues)
88 | if kernel == 'heat':
89 | return _hkt(eivals, timescales, normalization, normalized_laplacian)
90 | else:
91 | return _wkt(eivals, timescales, normalization, normalized_laplacian)
92 |
93 |
94 | def heat(inp, timescales=np.logspace(-2, 2, 250), eigenvalues='auto', normalization='empty', normalized_laplacian=True):
95 | """
96 | Computes heat kernel trace from some given input, timescales, and normalization.
97 |
98 | Accepts matrices, common Python graph libraries' graphs, or vectors of eigenvalues.
99 | For precise definition, please refer to "NetLSD: Hearing the Shape of a Graph" by A. Tsitsulin, D. Mottin, P. Karras, A. Bronstein, E. Müller. Published at KDD'18.
100 |
101 | Parameters
102 | ----------
103 | inp: obj
104 | 2D numpy/scipy matrix, common Python graph libraries' graph, or vector of eigenvalues
105 | timescales : numpy.ndarray
106 | Vector of discrete timesteps for the kernel computation
107 | eigenvalues : str
108 | Either string or int or tuple
109 | Number of eigenvalues to compute / use for approximation.
110 | If string, we expect either 'full' or 'auto', otherwise error will be raised. 'auto' lets the program decide based on the faithful usage. 'full' computes all eigenvalues.
111 | If int, compute n_eivals eigenvalues from each side and approximate using linear growth approximation.
112 | If tuple, we expect two ints, first for lower part of approximation, and second for the upper part.
113 | normalization : str or numpy.ndarray
114 | Either 'empty', 'complete' or None.
115 | If None or any ther value, return unnormalized heat kernel trace.
116 | For the details how 'empty' and 'complete' are computed, please refer to the paper.
117 | If np.ndarray, they are treated as exact normalization constants
118 | normalized_laplacian: bool
119 | Defines whether the eigenvalues came from the normalized Laplacian. It only affects 'complete' normalization.
120 |
121 | Returns
122 | -------
123 | numpy.ndarray
124 | Heat kernel trace signature
125 |
126 | """
127 | return netlsd(inp, timescales, 'heat', eigenvalues, normalization, normalized_laplacian)
128 |
129 |
130 | def wave(inp, timescales=np.linspace(0, 2*np.pi, 250), eigenvalues='auto', normalization='empty', normalized_laplacian=True):
131 | """
132 | Computes wave kernel trace from some given input, timescales, and normalization.
133 |
134 | Accepts matrices, common Python graph libraries' graphs, or vectors of eigenvalues.
135 | For precise definition, please refer to "NetLSD: Hearing the Shape of a Graph" by A. Tsitsulin, D. Mottin, P. Karras, A. Bronstein, E. Müller. Published at KDD'18.
136 |
137 | Parameters
138 | ----------
139 | inp: obj
140 | 2D numpy/scipy matrix, common Python graph libraries' graph, or vector of eigenvalues
141 | timescales : numpy.ndarray
142 | Vector of discrete timesteps for the kernel computation
143 | eigenvalues : str
144 | Either string or int or tuple
145 | Number of eigenvalues to compute / use for approximation.
146 | If string, we expect either 'full' or 'auto', otherwise error will be raised. 'auto' lets the program decide based on the faithful usage. 'full' computes all eigenvalues.
147 | If int, compute n_eivals eigenvalues from each side and approximate using linear growth approximation.
148 | If tuple, we expect two ints, first for lower part of approximation, and second for the upper part.
149 | normalization : str or numpy.ndarray
150 | Either 'empty', 'complete' or None.
151 | If None or any ther value, return unnormalized wave kernel trace.
152 | For the details how 'empty' and 'complete' are computed, please refer to the paper.
153 | If np.ndarray, they are treated as exact normalization constants
154 | normalized_laplacian: bool
155 | Defines whether the eigenvalues came from the normalized Laplacian. It only affects 'complete' normalization.
156 |
157 | Returns
158 | -------
159 | numpy.ndarray
160 | Wave kernel trace signature
161 |
162 | """
163 | return netlsd(inp, timescales, 'wave', eigenvalues, normalization, normalized_laplacian)
164 |
165 |
166 | def _hkt(eivals, timescales, normalization, normalized_laplacian):
167 | """
168 | Computes heat kernel trace from given eigenvalues, timescales, and normalization.
169 |
170 | For precise definition, please refer to "NetLSD: Hearing the Shape of a Graph" by A. Tsitsulin, D. Mottin, P. Karras, A. Bronstein, E. Müller. Published at KDD'18.
171 |
172 | Parameters
173 | ----------
174 | eivals : numpy.ndarray
175 | Eigenvalue vector
176 | timescales : numpy.ndarray
177 | Vector of discrete timesteps for the kernel computation
178 | normalization : str or numpy.ndarray
179 | Either 'empty', 'complete' or None.
180 | If None or any ther value, return unnormalized heat kernel trace.
181 | For the details how 'empty' and 'complete' are computed, please refer to the paper.
182 | If np.ndarray, they are treated as exact normalization constants
183 | normalized_laplacian: bool
184 | Defines whether the eigenvalues came from the normalized Laplacian. It only affects 'complete' normalization.
185 |
186 | Returns
187 | -------
188 | numpy.ndarray
189 | Heat kernel trace signature
190 |
191 | """
192 | nv = eivals.shape[0]
193 | hkt = np.zeros(timescales.shape)
194 | for idx, t in enumerate(timescales):
195 | hkt[idx] = np.sum(np.exp(-t * eivals))
196 | if isinstance(normalization, np.ndarray):
197 | return hkt / normalization
198 | if normalization == 'empty' or normalization == True:
199 | return hkt / nv
200 | if normalization == 'complete':
201 | if normalized_laplacian:
202 | return hkt / (1 + (nv - 1) * np.exp(-timescales))
203 | else:
204 | return hkt / (1 + nv * np.exp(-nv * timescales))
205 | return hkt
206 |
207 |
208 | def _wkt(eivals, timescales, normalization, normalized_laplacian):
209 | """
210 | Computes wave kernel trace from given eigenvalues, timescales, and normalization.
211 |
212 | For precise definition, please refer to "NetLSD: Hearing the Shape of a Graph" by A. Tsitsulin, D. Mottin, P. Karras, A. Bronstein, E. Müller. Published at KDD'18.
213 |
214 | Parameters
215 | ----------
216 | eivals : numpy.ndarray
217 | Eigenvalue vector
218 | timescales : numpy.ndarray
219 | Vector of discrete timesteps for the kernel computation
220 | normalization : str or numpy.ndarray
221 | Either 'empty', 'complete' or None.
222 | If None or any ther value, return unnormalized wave kernel trace.
223 | For the details how 'empty' and 'complete' are computed, please refer to the paper.
224 | If np.ndarray, they are treated as exact normalization constants
225 | normalized_laplacian: bool
226 | Defines whether the eigenvalues came from the normalized Laplacian. It only affects 'complete' normalization.
227 |
228 | Returns
229 | -------
230 | numpy.ndarray
231 | Wave kernel trace signature
232 |
233 | """
234 | nv = eivals.shape[0]
235 | wkt = np.zeros(timescales.shape)
236 | for idx, t in enumerate(timescales):
237 | wkt[idx] = np.sum(np.exp(-1j * t * eivals))
238 | if isinstance(normalization, np.ndarray):
239 | return wkt / normalization
240 | if normalization == 'empty' or normalization == True:
241 | return wkt / nv
242 | if normalization == 'complete':
243 | if normalized_laplacian:
244 | return wkt / (1 + (nv - 1) * np.cos(timescales))
245 | else:
246 | return wkt / (1 + (nv - 1) * np.cos(nv * timescales))
247 | return wkt
248 |
--------------------------------------------------------------------------------
/netlsd/util.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.linalg as spl
3 | import scipy.sparse as sps
4 | import scipy.sparse.linalg as spsl
5 |
6 |
7 | def check_1d(inp):
8 | """
9 | Check input to be a vector. Converts lists to np.ndarray.
10 |
11 | Parameters
12 | ----------
13 | inp : obj
14 | Input vector
15 |
16 | Returns
17 | -------
18 | numpy.ndarray or None
19 | Input vector or None
20 |
21 | Examples
22 | --------
23 | >>> check_1d([0, 1, 2, 3])
24 | [0, 1, 2, 3]
25 |
26 | >>> check_1d('test')
27 | None
28 |
29 | """
30 | if isinstance(inp, list):
31 | return check_1d(np.array(inp))
32 | if isinstance(inp, np.ndarray):
33 | if inp.ndim == 1: # input is a vector
34 | return inp
35 |
36 |
37 | def check_2d(inp):
38 | """
39 | Check input to be a matrix. Converts lists of lists to np.ndarray.
40 |
41 | Also allows the input to be a scipy sparse matrix.
42 |
43 | Parameters
44 | ----------
45 | inp : obj
46 | Input matrix
47 |
48 | Returns
49 | -------
50 | numpy.ndarray, scipy.sparse or None
51 | Input matrix or None
52 |
53 | Examples
54 | --------
55 | >>> check_2d([[0, 1], [2, 3]])
56 | [[0, 1], [2, 3]]
57 |
58 | >>> check_2d('test')
59 | None
60 |
61 | """
62 | if isinstance(inp, list):
63 | return check_2d(np.array(inp))
64 | if isinstance(inp, (np.ndarray, np.matrixlib.defmatrix.matrix)):
65 | if inp.ndim == 2: # input is a dense matrix
66 | return inp
67 | if sps.issparse(inp):
68 | if inp.ndim == 2: # input is a sparse matrix
69 | return inp
70 |
71 |
72 | def graph_to_laplacian(G, normalized=True):
73 | """
74 | Converts a graph from popular Python packages to Laplacian representation.
75 |
76 | Currently support NetworkX, graph_tool and igraph.
77 |
78 | Parameters
79 | ----------
80 | G : obj
81 | Input graph
82 | normalized : bool
83 | Whether to use normalized Laplacian.
84 | Normalized and unnormalized Laplacians capture different properties of graphs, e.g. normalized Laplacian spectrum can determine whether a graph is bipartite, but not the number of its edges. We recommend using normalized Laplacian.
85 |
86 | Returns
87 | -------
88 | scipy.sparse
89 | Laplacian matrix of the input graph
90 |
91 | Examples
92 | --------
93 | >>> graph_to_laplacian(nx.complete_graph(3), 'unnormalized').todense()
94 | [[ 2, -1, -1], [-1, 2, -1], [-1, -1, 2]]
95 |
96 | >>> graph_to_laplacian('test')
97 | None
98 |
99 | """
100 | try:
101 | import networkx as nx
102 | if isinstance(G, nx.Graph):
103 | if normalized:
104 | return nx.normalized_laplacian_matrix(G)
105 | else:
106 | return nx.laplacian_matrix(G)
107 | except ImportError:
108 | pass
109 | try:
110 | import graph_tool.all as gt
111 | if isinstance(G, gt.Graph):
112 | if normalized:
113 | return gt.laplacian_type(G, normalized=True)
114 | else:
115 | return gt.laplacian(G)
116 | except ImportError:
117 | pass
118 | try:
119 | import igraph as ig
120 | if isinstance(G, ig.Graph):
121 | if normalized:
122 | return np.array(G.laplacian(normalized=True))
123 | else:
124 | return np.array(G.laplacian())
125 | except ImportError:
126 | pass
127 |
128 |
129 | def mat_to_laplacian(mat, normalized):
130 | """
131 | Converts a sparse or dence adjacency matrix to Laplacian.
132 |
133 | Parameters
134 | ----------
135 | mat : obj
136 | Input adjacency matrix. If it is a Laplacian matrix already, return it.
137 | normalized : bool
138 | Whether to use normalized Laplacian.
139 | Normalized and unnormalized Laplacians capture different properties of graphs, e.g. normalized Laplacian spectrum can determine whether a graph is bipartite, but not the number of its edges. We recommend using normalized Laplacian.
140 |
141 | Returns
142 | -------
143 | obj
144 | Laplacian of the input adjacency matrix
145 |
146 | Examples
147 | --------
148 | >>> mat_to_laplacian(numpy.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]), False)
149 | [[ 2, -1, -1], [-1, 2, -1], [-1, -1, 2]]
150 |
151 | """
152 | if sps.issparse(mat):
153 | if np.all(mat.diagonal()>=0): # Check diagonal
154 | if np.all((mat-sps.diags(mat.diagonal())).data <= 0): # Check off-diagonal elements
155 | return mat
156 | else:
157 | if np.all(np.diag(mat)>=0): # Check diagonal
158 | if np.all(mat - np.diag(mat) <= 0): # Check off-diagonal elements
159 | return mat
160 | deg = np.squeeze(np.asarray(mat.sum(axis=1)))
161 | if sps.issparse(mat):
162 | L = sps.diags(deg) - mat
163 | else:
164 | L = np.diag(deg) - mat
165 | if not normalized:
166 | return L
167 | with np.errstate(divide='ignore'):
168 | sqrt_deg = 1.0 / np.sqrt(deg)
169 | sqrt_deg[sqrt_deg==np.inf] = 0
170 | if sps.issparse(mat):
171 | sqrt_deg_mat = sps.diags(sqrt_deg)
172 | else:
173 | sqrt_deg_mat = np.diag(sqrt_deg)
174 | return sqrt_deg_mat.dot(L).dot(sqrt_deg_mat)
175 |
176 |
177 | def updown_linear_approx(eigvals_lower, eigvals_upper, nv):
178 | """
179 | Approximates Laplacian spectrum using upper and lower parts of the eigenspectrum.
180 |
181 | Parameters
182 | ----------
183 | eigvals_lower : numpy.ndarray
184 | Lower part of the spectrum, sorted
185 | eigvals_upper : numpy.ndarray
186 | Upper part of the spectrum, sorted
187 | nv : int
188 | Total number of nodes (eigenvalues) in the graph.
189 |
190 | Returns
191 | -------
192 | numpy.ndarray
193 | Vector of approximated eigenvalues
194 |
195 | Examples
196 | --------
197 | >>> updown_linear_approx([1, 2, 3], [7, 8, 9], 9)
198 | array([1, 2, 3, 4, 5, 6, 7, 8, 9])
199 |
200 | """
201 | nal = len(eigvals_lower)
202 | nau = len(eigvals_upper)
203 | if nv < nal + nau:
204 | raise ValueError('Number of supplied eigenvalues ({0} lower and {1} upper) is higher than number of nodes ({2})!'.format(nal, nau, nv))
205 | ret = np.zeros(nv)
206 | ret[:nal] = eigvals_lower
207 | ret[-nau:] = eigvals_upper
208 | ret[nal-1:-nau+1] = np.linspace(eigvals_lower[-1], eigvals_upper[0], nv-nal-nau+2)
209 | return ret
210 |
211 |
212 | def eigenvalues_auto(mat, n_eivals='auto'):
213 | """
214 | Automatically computes the spectrum of a given Laplacian matrix.
215 |
216 | Parameters
217 | ----------
218 | mat : numpy.ndarray or scipy.sparse
219 | Laplacian matrix
220 | n_eivals : string or int or tuple
221 | Number of eigenvalues to compute / use for approximation.
222 | If string, we expect either 'full' or 'auto', otherwise error will be raised. 'auto' lets the program decide based on the faithful usage. 'full' computes all eigenvalues.
223 | If int, compute n_eivals eigenvalues from each side and approximate using linear growth approximation.
224 | If tuple, we expect two ints, first for lower part of approximation, and second for the upper part.
225 |
226 | Returns
227 | -------
228 | np.ndarray
229 | Vector of approximated eigenvalues
230 |
231 | Examples
232 | --------
233 | >>> eigenvalues_auto(numpy.array([[ 2, -1, -1], [-1, 2, -1], [-1, -1, 2]]), 'auto')
234 | array([0, 3, 3])
235 |
236 | """
237 | do_full = True
238 | n_lower = 150
239 | n_upper = 150
240 | nv = mat.shape[0]
241 | if n_eivals == 'auto':
242 | if mat.shape[0] > 1024:
243 | do_full = False
244 | if n_eivals == 'full':
245 | do_full = True
246 | if isinstance(n_eivals, int):
247 | n_lower = n_upper = n_eivals
248 | do_full = False
249 | if isinstance(n_eivals, tuple):
250 | n_lower, n_upper = n_eivals
251 | do_full = False
252 | if do_full and sps.issparse(mat):
253 | mat = mat.todense()
254 | if sps.issparse(mat):
255 | if n_lower == n_upper:
256 | tr_eivals = spsl.eigsh(mat, 2*n_lower, which='BE', return_eigenvectors=False)
257 | return updown_linear_approx(tr_eivals[:n_upper], tr_eivals[n_upper:], nv)
258 | else:
259 | lo_eivals = spsl.eigsh(mat, n_lower, which='SM', return_eigenvectors=False)[::-1]
260 | up_eivals = spsl.eigsh(mat, n_upper, which='LM', return_eigenvectors=False)
261 | return updown_linear_approx(lo_eivals, up_eivals, nv)
262 | else:
263 | if do_full:
264 | return spl.eigvalsh(mat)
265 | else:
266 | lo_eivals = spl.eigvalsh(mat, eigvals=(0, n_lower-1))
267 | up_eivals = spl.eigvalsh(mat, eigvals=(nv-n_upper-1, nv-1))
268 | return updown_linear_approx(lo_eivals, up_eivals, nv)
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | scipy
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.rst
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | import sys
5 |
6 |
7 | try:
8 | from setuptools import setup
9 | except ImportError:
10 | from distutils.core import setup
11 |
12 | setup(
13 | name='NetLSD',
14 | version='1.0.2',
15 | author='Anton Tsitsulin',
16 | author_email='anton.tsitsulin@hpi.de',
17 | description='NetLSD descriptors for graphs. Compare and analyze graph structure on multiple levels!',
18 | packages=['netlsd',],
19 | url='http://github.com/xgfs/netlsd',
20 | download_url = 'https://github.com/xgfs/netlsd/archive/0.1.tar.gz',
21 | license='MIT',
22 | install_requires=[
23 | 'numpy',
24 | 'scipy'
25 | ],
26 | classifiers=[
27 | 'Development Status :: 3 - Alpha',
28 | 'License :: OSI Approved :: MIT License',
29 | 'Programming Language :: Python :: 2.7',
30 | 'Programming Language :: Python :: 3.5',
31 | 'Topic :: Scientific/Engineering :: Mathematics',
32 | 'Topic :: Scientific/Engineering :: Artificial Intelligence',
33 | ],
34 | long_description=open('README.rst').read(),
35 | )
--------------------------------------------------------------------------------