diff --git a/.travis.yml b/.travis.yml index 4e9a2d3..26140a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ before_install: source venv/bin/activate install: - cmake similarity_search && make -j 4 - - travis_wait travis_retry pip install -r python_bindings/requirements.txt scipy six + - travis_wait travis_retry pip install -r python_bindings/requirements.txt scipy six flake8 - travis_retry cd python_bindings && python setup.py build install && cd .. script: @@ -40,6 +40,7 @@ script: fi cd python_bindings python setup.py test + flake8 cd .. cache: - apt diff --git a/python_bindings/docs/Makefile b/python_bindings/docs/Makefile new file mode 100644 index 0000000..47a6693 --- /dev/null +++ b/python_bindings/docs/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(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/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nmslib.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nmslib.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/nmslib" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/nmslib" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/python_bindings/docs/api.rst b/python_bindings/docs/api.rst new file mode 100644 index 0000000..ef91ce4 --- /dev/null +++ b/python_bindings/docs/api.rst @@ -0,0 +1,47 @@ +API Reference +============= + +nmslib.init +----------- + +This function acts act the main entry point into NMS lib. This +function should be called first before calling any other method. + +.. autofunction:: nmslib.init + + +.. class:: nmslib.DistType + + .. attribute:: FLOAT + .. attribute:: DOUBLE + .. attribute:: INT + +.. class:: nmslib.DataType + + .. attribute:: DENSE_VECTOR + .. attribute:: OBJECT_AS_STRING + .. attribute:: SPARSE_VECTOR + +nmslib.FloatIndex +----------------- + +nmslib.dist.FloatIndex + +.. autoclass:: nmslib.dist.FloatIndex + :members: + +nmslib.DoubleIndex +------------------ + +nmslib.dist.DoubleIndex + +.. autoclass:: nmslib.dist.DoubleIndex + :members: + +nmslib.IntIndex +--------------- + +nmslib.dist.IntIndex + +.. autoclass:: nmslib.dist.IntIndex + :members: diff --git a/python_bindings/docs/conf.py b/python_bindings/docs/conf.py new file mode 100644 index 0000000..4901fcc --- /dev/null +++ b/python_bindings/docs/conf.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +# +# nmslib documentation build configuration file, created by +# sphinx-quickstart on Mon Aug 7 17:30:15 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# 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. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.githubpages', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'nmslib' +copyright = u'2017, Bilegsaikhan Naidan, Leonid Boytsov, Yury Malkov. With contributions from David Novak, Lawrence Cayton, Wei Dong, Avrelin Nikita, Ben Frederickson, Dmitry Yashunin, Bob Poekert, @orgoro, Maxim Andreev, Daniel Lemire, Nathan Kurz, Alexander Ponomarenko.' +author = u'Bilegsaikhan Naidan, Leonid Boytsov, Yury Malkov. With contributions from David Novak, Lawrence Cayton, Wei Dong, Avrelin Nikita, Ben Frederickson, Dmitry Yashunin, Bob Poekert, @orgoro, Maxim Andreev, Daniel Lemire, Nathan Kurz, Alexander Ponomarenko.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +import nmslib # noqa +version = '.'.join(nmslib.__version__.split(".")[:2]) + +# The full version, including alpha/beta/rc tags. +release = nmslib.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- 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 = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# 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 any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'nmslibdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'nmslib.tex', u'nmslib Documentation', + u'Bilegsaikhan Naidan, Leonid Boytsov, Yury Malkov. With contributions from David Novak, Lawrence Cayton, Wei Dong, Avrelin Nikita, Ben Frederickson, Dmitry Yashunin, Bob Poekert, @orgoro, Maxim Andreev, Daniel Lemire, Nathan Kurz, Alexander Ponomarenko.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'nmslib', u'nmslib Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'nmslib', u'nmslib Documentation', + author, 'nmslib', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/python_bindings/docs/index.rst b/python_bindings/docs/index.rst new file mode 100644 index 0000000..7429c67 --- /dev/null +++ b/python_bindings/docs/index.rst @@ -0,0 +1,25 @@ +.. nmslib documentation master file, created by + sphinx-quickstart on Mon Aug 7 17:30:15 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Non-Metric Space Library (NMSLIB) +================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + Quickstart + API Reference + Logging + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/python_bindings/docs/logging.rst b/python_bindings/docs/logging.rst new file mode 100644 index 0000000..74b41bf --- /dev/null +++ b/python_bindings/docs/logging.rst @@ -0,0 +1,32 @@ +Configuring Logging for NMSLIB +============================== + +This library logs to a Python logger named ``nmslib``. This lets +you fully control the log messages produced by nmslib in Python. + +For instance, to log everything produced by nmslib to a default +python logger: + +.. code-block:: python + + # setup basic python logging + import logging + logging.basicConfig(level=logging.DEBUG) + + # importing nmslib logs some debug messages on startup, that + # that will be output to the python log handler created above + import nmslib + +To quiet these messages you can just set the level for nmslib +as appropiate: + +.. code-block:: python + + # setup basic python logging + import logging + logging.basicConfig(level=logging.DEBUG) + + # Only log WARNING messages and above from nmslib + logging.getLogger('nmslib').setLevel(logging.WARNING) + + import nmslib diff --git a/python_bindings/docs/make.bat b/python_bindings/docs/make.bat new file mode 100644 index 0000000..cf7ff07 --- /dev/null +++ b/python_bindings/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 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.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\nmslib.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\nmslib.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/python_bindings/docs/quickstart.rst b/python_bindings/docs/quickstart.rst new file mode 100644 index 0000000..8d9e7ba --- /dev/null +++ b/python_bindings/docs/quickstart.rst @@ -0,0 +1,39 @@ +Python bindings for NMSLIB +==================================== + +Installation +------------ + +This project works with Python on version 2.7+ and 3.5+, and on Linux, OSX and the Windows operating systems. To install: + +``pip install nmslib`` + +You may need to install Python dev-files. On Ubuntu, you can do it as follows: + +``sudo apt-get install python3-dev`` + +Building on Windows requires Visual Studio 2015, see this project for more information. + +Example Usage +------------- + +.. code-block:: python + + import nmslib + import numpy + + # create a random matrix to index + data = numpy.random.randn(10000, 100).astype(numpy.float32) + + # initialize a new index, using a HNSW index on Cosine Similarity + index = nmslib.init(method='hnsw', space='cosinesimil') + index.addDataPointBatch(data) + index.createIndex({'post': 2}, print_progress=True) + + # query for the nearest neighbours of the first datapoint + ids, distances = index.knnQuery(data[0], k=10) + + # get all nearest neighbours for all the datapoint + # using a pool of 4 threads to compute + neighbours = index.knnQueryBatch(data, k=10, num_threads=4) + diff --git a/python_bindings/nmslib.cc b/python_bindings/nmslib.cc index 079ddf9..e24edfa 100644 --- a/python_bindings/nmslib.cc +++ b/python_bindings/nmslib.cc @@ -317,9 +317,47 @@ struct IndexWrapper { ObjectVector data; }; +class PythonLogger + : public Logger { + public: + py::object inner; + explicit PythonLogger(const py::object & inner) + : inner(inner) { + } + + void log(LogSeverity severity, + const char * file, + int line, + const char * function, + const std::string & message) { + py::gil_scoped_acquire l; + switch(severity) { + case LIB_DEBUG: + inner.attr("debug")(message); + break; + case LIB_INFO: + inner.attr("info")(message); + break; + case LIB_WARNING: + inner.attr("warning")(message); + break; + case LIB_ERROR: + inner.attr("error")(message); + break; + case LIB_FATAL: + inner.attr("critical")(message); + break; + } + } +}; + PYBIND11_PLUGIN(nmslib) { - // TODO(@benfred): configurable logging - initLibrary(LIB_LOGSTDERR, NULL); + // Log using the python logger, instead of defaults built in here + py::module logging = py::module::import("logging"); + py::module nmslibLogger = logging.attr("getLogger")("nmslib"); + setGlobalLogger(new PythonLogger(nmslibLogger)); + + initLibrary(LIB_LOGCUSTOM, NULL); py::module m(module_name, "Bindings for Non-Metric Space Library (NMSLIB)"); @@ -438,7 +476,7 @@ void exportIndex(py::module * m) { .def("knnQueryBatch", &IndexWrapper::knnQueryBatch, py::arg("queries"), py::arg("k") = 10, py::arg("num_threads") = 0, "Performs multiple queries on the index, distributing the work over \n" - "a thread pool\n" + "a thread pool\n\n" "Parameters\n" "----------\n" "input: list\n" @@ -485,7 +523,7 @@ void exportIndex(py::module * m) { .def("addDataPoint", &IndexWrapper::addDataPoint, py::arg("id"), py::arg("data"), - "Adds a single data point to the index\n\n" + "Adds a single datapoint to the index\n\n" "Parameters\n" "----------\n" "id: int\n" @@ -500,7 +538,7 @@ void exportIndex(py::module * m) { .def("addDataPointBatch", &IndexWrapper::addDataPointBatch, py::arg("data"), py::arg("ids") = py::none(), - "Adds several data points to the index\n\n" + "Adds multiple datapoints to the index\n\n" "Parameters\n" "----------\n" "data: object\n" diff --git a/python_bindings/setup.cfg b/python_bindings/setup.cfg new file mode 100644 index 0000000..4e14725 --- /dev/null +++ b/python_bindings/setup.cfg @@ -0,0 +1,6 @@ +[metadata] +description-file = README.md + +[flake8] +max-line-length = 100 +exclude = build,.eggs,.tox,integration_tests,docs diff --git a/python_bindings/setup.py b/python_bindings/setup.py index 1951786..5253243 100755 --- a/python_bindings/setup.py +++ b/python_bindings/setup.py @@ -129,9 +129,12 @@ def build_extensions(self): description='Non-Metric Space Library (NMSLIB)', author='Leonid Boytsov', url='https://github.com/searchivarius/nmslib', - long_description="""Non-Metric Space Library (NMSLIB) is an efficient cross-platform similarity search library and a toolkit for evaluation of similarity search methods. -The goal of the project is to create an effective and comprehensive toolkit for searching in generic non-metric spaces. Being comprehensive is important, because no single method -is likely to be sufficient in all cases. Also note that exact solutions are hardly efficient in high dimensions and/or non-metric spaces. Hence, the main focus is on approximate me thods.""", + long_description="""Non-Metric Space Library (NMSLIB) is an efficient cross-platform + similarity search library and a toolkit for evaluation of similarity search methods. The + goal of the project is to create an effective and comprehensive toolkit for searching in + generic non-metric spaces. Being comprehensive is important, because no single method is + likely to be sufficient in all cases. Also note that exact solutions are hardly efficient in + high dimensions and/or non-metric spaces. Hence, the main focus is on approximate methods.""", ext_modules=ext_modules, install_requires=['pybind11>=2.0', 'numpy'], cmdclass={'build_ext': BuildExt}, diff --git a/python_bindings/tests/legacy_test.py b/python_bindings/tests/legacy_test.py index 3acaf7e..91fd498 100755 --- a/python_bindings/tests/legacy_test.py +++ b/python_bindings/tests/legacy_test.py @@ -13,6 +13,7 @@ import nmslib + class DenseTests(unittest.TestCase): def setUp(self): diff --git a/python_bindings/tox.ini b/python_bindings/tox.ini index b1af162..6bed36e 100644 --- a/python_bindings/tox.ini +++ b/python_bindings/tox.ini @@ -5,3 +5,14 @@ envlist = py27, py35, py36 commands = {envpython} setup.py test deps = -rrequirements.txt + +[testenv:style] +deps = + -rrequirements.txt + flake8 + +commands = + flake8 + +[flake8] +max-line-length = 100 diff --git a/similarity_search/apps/tune_vptree.cc b/similarity_search/apps/tune_vptree.cc index 5d04b1c..bd323d6 100644 --- a/similarity_search/apps/tune_vptree.cc +++ b/similarity_search/apps/tune_vptree.cc @@ -231,7 +231,7 @@ void RunExper(unsigned AddRestartQty, } LOG(LIB_INFO) << "Recall: " << recall; LOG(LIB_INFO) << "Best time: " << time_best; - LOG(LIB_INFO) << "Best impr. " << impr_best << " (" << getOptimMetricName(metric) << ")" << endl; + LOG(LIB_INFO) << "Best impr. " << impr_best << " (" << getOptimMetricName(metric) << ")"; LOG(LIB_INFO) << "alpha_left: " << alpha_left; LOG(LIB_INFO) << "exp_left: " << exp_left; LOG(LIB_INFO) << "alpha_right: " << alpha_right; diff --git a/similarity_search/include/logging.h b/similarity_search/include/logging.h index c5aa935..25a60c0 100644 --- a/similarity_search/include/logging.h +++ b/similarity_search/include/logging.h @@ -5,8 +5,8 @@ * With contributions from Lawrence Cayton (http://lcayton.com/) and others. * * For the complete list of contributors and further details see: - * https://github.com/searchivarius/NonMetricSpaceLib - * + * https://github.com/searchivarius/NonMetricSpaceLib + * * Copyright (c) 2014 * * This code is released under the @@ -30,30 +30,82 @@ using std::stringstream; using std::runtime_error; using std::string; -enum LogSeverity {LIB_INFO, LIB_WARNING, LIB_ERROR, LIB_FATAL}; -enum LogChoice {LIB_LOGNONE, LIB_LOGFILE, LIB_LOGSTDERR}; +enum LogSeverity {LIB_DEBUG, LIB_INFO, LIB_WARNING, LIB_ERROR, LIB_FATAL}; +enum LogChoice {LIB_LOGNONE, LIB_LOGFILE, LIB_LOGSTDERR, LIB_LOGCUSTOM}; std::string LibGetCurrentTime(); // write log to file void InitializeLogger(LogChoice choice = LIB_LOGNONE, const char* logfile = NULL); +// Abstract base class that all loggers must override class Logger { public: - Logger(LogSeverity severity, const std::string& file, int line, const char* function); - ~Logger(); + virtual ~Logger(); + virtual void log(LogSeverity severity, + const char * file, + int line, + const char * function, + const std::string & message) = 0; +}; - static std::ostream& stream() { return *currstrm_ ; } +void setGlobalLogger(Logger * logger); +Logger * getGlobalLogger(); - private: - LogSeverity severity_; +class StdErrLogger + : public Logger { + public: + void log(LogSeverity severity, + const char * file, + int line, + const char * function, + const std::string & message); +}; + +class FileLogger + : public Logger { + public: + FileLogger(const char * logfile); + void log(LogSeverity severity, + const char * file, + int line, + const char * function, + const std::string & message); + protected: + std::ofstream logfile; +}; - static ofstream logfile_; +// A single entry in the log +class LogItem { + public: + LogItem(LogSeverity severity, const char * file, int line, const char * function, + Logger * logger) + : severity(severity), file(file), line(line), function(function), logger(logger) { + } - static ostream* currstrm_; + template + LogItem & operator << (T t) { + message << t; + return *this; + } - // If choice != LIB_LOGFILE the second argument is ignored - friend void InitializeLogger(LogChoice choice, const char* logfile); + ~LogItem() { + if (logger) { + logger->log(severity, file, line, function, message.str()); + } + // TODO: probably better to throw an exception here rather than die outright + // but this matches previous behaviour + if (severity == LIB_FATAL) { + exit(1); + } + } + + LogSeverity severity; + const char * file; + int line; + const char * function; + Logger * logger; + std::stringstream message; }; class RuntimeErrorWrapper { @@ -66,9 +118,9 @@ class RuntimeErrorWrapper { stringstream currstrm_; }; - +// TODO: zero cost log abstraction #define LOG(severity) \ - Logger(severity, __FILE__, __LINE__, __FUNCTION__).stream() + LogItem(severity, __FILE__, __LINE__, __FUNCTION__, getGlobalLogger()) #define CHECK(condition) \ if (!(condition)) {\ diff --git a/similarity_search/lshkit/include/lshkit/multiprobelsh-fitdata.h b/similarity_search/lshkit/include/lshkit/multiprobelsh-fitdata.h index c4db8a5..9c96b68 100644 --- a/similarity_search/lshkit/include/lshkit/multiprobelsh-fitdata.h +++ b/similarity_search/lshkit/include/lshkit/multiprobelsh-fitdata.h @@ -71,7 +71,7 @@ std::string FitData(const FloatMatrix& data, unsigned F // divide the sample to F folds ) { - LOG(LIB_INFO) << "started running FitData" << std::endl; + LOG(LIB_INFO) << "started running FitData"; std::vector idx(data.getSize()); for (size_t i = 0; i < idx.size(); ++i) idx[i] = i; diff --git a/similarity_search/src/init.cc b/similarity_search/src/init.cc index a1504ee..9e43957 100644 --- a/similarity_search/src/init.cc +++ b/similarity_search/src/init.cc @@ -35,5 +35,4 @@ void initLibrary(LogChoice choice, const char* pLogFile) { initSpaces(); initMethods(); } - } diff --git a/similarity_search/src/logging.cc b/similarity_search/src/logging.cc index ddbac5f..15552a9 100644 --- a/similarity_search/src/logging.cc +++ b/similarity_search/src/logging.cc @@ -24,23 +24,29 @@ #include #include +#include #include #include "logging.h" -const char* log_severity[] = {"INFO", "WARNING", "ERROR", "FATAL"}; +const char* log_severity[] = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; using namespace std; -// allocate the static member -std::ofstream Logger::logfile_; +namespace { + std::unique_ptr global_log(new StdErrLogger()); +} -static struct voidstream : public ostream { - template ostream& operator<< (T) { return *this; } - template ostream& operator<< (T*) { return *this; } - template ostream& operator<< (T&) { return *this; } - voidstream() : ostream(0) {} -} voidstream_; +void setGlobalLogger(Logger * logger) { + global_log.reset(logger); +} + +Logger * getGlobalLogger() { + return global_log.get(); +} + +Logger::~Logger() { +} std::string LibGetCurrentTime() { time_t now; @@ -51,48 +57,59 @@ std::string LibGetCurrentTime() { return std::string(time_string); } -ostream* Logger::currstrm_ = &cerr; - -Logger::Logger(LogSeverity severity, const std::string& _file, int line, const char* function) - : severity_(severity) { +template +void defaultOutput(T & stream, LogSeverity severity, + const std::string& _file, int line, const char* function, + const std::string & message) { std::string file = _file; size_t n = file.rfind('/'); if (n != std::string::npos) { file.erase(file.begin(), file.begin() + n + 1); } - stream() << LibGetCurrentTime() << " " << file << ":" << line - << " (" << function << ") [" << log_severity[severity] << "] "; + stream << LibGetCurrentTime() << " " << file << ":" << line + << " (" << function << ") [" << log_severity[severity] << "] " << message << std::endl; } -Logger::~Logger() { - stream() << '\n'; - if (severity_ == LIB_FATAL) { - stream().flush(); - if (logfile_.is_open()) - logfile_.close(); - // TODO(@leo/@bileg) do we want to abort here? - //abort(); - exit(1); +void StdErrLogger::log(LogSeverity severity, + const char * file, + int line, + const char * function, + const std::string & message) { + defaultOutput(std::cerr, severity, file, line, function, message); +} + +FileLogger::FileLogger(const char * logfilename) + : logfile(logfilename) { + if (!logfile) { + LOG(LIB_FATAL) << "Can't open the logfile: '" << logfilename << "'"; } } +void FileLogger::log(LogSeverity severity, + const char * file, + int line, + const char * function, + const std::string & message) { + defaultOutput(logfile, severity, file, line, function, message); +} + void InitializeLogger(LogChoice choice, const char* logfile) { - if (choice == LIB_LOGNONE) { - Logger::currstrm_ = &voidstream_; - } - if (choice == LIB_LOGFILE) { - Logger::logfile_.open(logfile); - if (!Logger::logfile_) { - LOG(LIB_FATAL) << "Can't open the logfile: '" << logfile << "'"; - } - Logger::currstrm_ = &Logger::logfile_; - } - if (choice == LIB_LOGSTDERR) { - Logger::currstrm_ = &cerr; + switch (choice) { + case LIB_LOGNONE: + setGlobalLogger(NULL); + break; + case LIB_LOGSTDERR: + setGlobalLogger(new StdErrLogger()); + break; + case LIB_LOGFILE: + setGlobalLogger(new FileLogger(logfile)); + break; + case LIB_LOGCUSTOM: + // don't set a logger here: setGlobalLogger will be called already + break; } } - RuntimeErrorWrapper::RuntimeErrorWrapper(const std::string& _file, int line, const char* function) { std::string file = _file; size_t n = file.rfind('/'); diff --git a/similarity_search/src/space/space_bit_hamming.cc b/similarity_search/src/space/space_bit_hamming.cc index 0fe1ced..84aad92 100644 --- a/similarity_search/src/space/space_bit_hamming.cc +++ b/similarity_search/src/space/space_bit_hamming.cc @@ -65,9 +65,9 @@ void SpaceBitHamming::ReadBitMaskVect(std::string line, LabelType& label, std::v v.push_back(val); } } catch (const std::exception &e) { - LOG(LIB_ERROR) << "Exception: " << e.what() << std::endl; + LOG(LIB_ERROR) << "Exception: " << e.what(); PREPARE_RUNTIME_ERR(err) << "Failed to parse the line: '" << line << "'"; - LOG(LIB_ERROR) << err.stream().str() << std::endl; + LOG(LIB_ERROR) << err.stream().str(); THROW_RUNTIME_ERR(err); } Binarize(v, 1, binVect); // Create the binary vector diff --git a/similarity_search/src/space/space_dummy.cc b/similarity_search/src/space/space_dummy.cc index 1366d4a..abbaaac 100644 --- a/similarity_search/src/space/space_dummy.cc +++ b/similarity_search/src/space/space_dummy.cc @@ -26,7 +26,7 @@ namespace similarity { template dist_t SpaceDummy::HiddenDistance(const Object* obj1, const Object* obj2) const { - LOG(LIB_INFO) << "Calculating the distance between objects: " << obj1->id() << " and " << obj2->id() << endl; + LOG(LIB_INFO) << "Calculating the distance between objects: " << obj1->id() << " and " << obj2->id(); CHECK(obj1->datalength() > 0); CHECK(obj1->datalength() == obj2->datalength()); /* @@ -83,7 +83,7 @@ bool SpaceDummy::ReadNextObjStr(DataFileInputState &inpStateBase, string template void SpaceDummy::WriteNextObj(const Object& obj, const string& externId, DataFileOutputState &outState) const { string s = CreateStrFromObj(&obj, externId); - outState.out_file_ << s << endl; + outState.out_file_ << s; } /** End of standard functions to read/write/create objects */ diff --git a/similarity_search/src/space/space_sparse_vector.cc b/similarity_search/src/space/space_sparse_vector.cc index 95556b1..077a70f 100644 --- a/similarity_search/src/space/space_sparse_vector.cc +++ b/similarity_search/src/space/space_sparse_vector.cc @@ -70,9 +70,9 @@ void SpaceSparseVector::ReadSparseVec(std::string line, size_t line_num, } } } catch (const std::exception &e) { - LOG(LIB_ERROR) << "Exception: " << e.what() << std::endl; + LOG(LIB_ERROR) << "Exception: " << e.what(); PREPARE_RUNTIME_ERR(err) << "Failed to parse the line # " << line_num << ": '" << line << "'" << std::endl; - LOG(LIB_ERROR) << err.stream().str() << std::endl; + LOG(LIB_ERROR) << err.stream().str(); THROW_RUNTIME_ERR(err); } diff --git a/similarity_search/test/test_distfunc.cc b/similarity_search/test/test_distfunc.cc index ef293c5..0cb4698 100644 --- a/similarity_search/test/test_distfunc.cc +++ b/similarity_search/test/test_distfunc.cc @@ -912,8 +912,8 @@ bool TestPivotIndex(const string& spaceName, const string& dataFile, size_t dataQty, const string& pivotFile, size_t pivotQty) { - LOG(LIB_INFO) << "space: " << spaceName << " real pivot index?: " << !useDummyIndex << endl << - " dataFile: " << dataFile << endl << + LOG(LIB_INFO) << "space: " << spaceName << " real pivot index?: " << !useDummyIndex << " " << + " dataFile: " << dataFile << " " << " pivotFile: " << pivotFile; try { typedef float T;