Full Code of rafguns/linkpred for AI

main 9fb5d5d94315 cached
49 files
167.1 KB
56.1k tokens
319 symbols
1 requests
Download .txt
Repository: rafguns/linkpred
Branch: main
Commit: 9fb5d5d94315
Files: 49
Total size: 167.1 KB

Directory structure:
gitextract_c3o8axzv/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── coverage.yml
│       ├── publish-to-pypi.yml
│       └── tox.yml
├── .gitignore
├── CHANGELOG.rst
├── LICENSE
├── README.rst
├── examples/
│   ├── inf1990-2004.net
│   └── inf2005-2009.net
├── linkpred/
│   ├── __init__.py
│   ├── cli.py
│   ├── evaluation/
│   │   ├── __init__.py
│   │   ├── listeners.py
│   │   ├── scoresheet.py
│   │   └── static.py
│   ├── exceptions.py
│   ├── linkpred.py
│   ├── network/
│   │   ├── __init__.py
│   │   ├── addremove.py
│   │   └── algorithms.py
│   ├── predictors/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── eigenvector.py
│   │   ├── misc.py
│   │   ├── neighbour.py
│   │   ├── path.py
│   │   └── util.py
│   ├── preprocess.py
│   └── util.py
├── pyproject.toml
├── pytest.ini
├── tests/
│   ├── __init__.py
│   ├── test_addremove.py
│   ├── test_cli.py
│   ├── test_evaluation_static.py
│   ├── test_functional.py
│   ├── test_linkpred.py
│   ├── test_listeners.py
│   ├── test_predictors_base.py
│   ├── test_predictors_eigenvector.py
│   ├── test_predictors_misc.py
│   ├── test_predictors_neighbour.py
│   ├── test_predictors_path.py
│   ├── test_preprocess.py
│   ├── test_scoresheet.py
│   ├── test_util.py
│   └── utils.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10


================================================
FILE: .github/workflows/coverage.yml
================================================
name: Calculate test coverage

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Check out
      uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: "3.12"
    - uses: actions/cache@v2
      name: Configure pip caching
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
        restore-keys: |
          ${{ runner.os }}-pip-
    - name: Install Python dependencies
      run: |
        pip install --upgrade pip
        pip install -e .[community]
        pip install pytest pytest-cov
    - name: Run tests
      run: |-
        pytest --cov=linkpred --cov-report xml:coverage.xml --cov-report term
    - name: Upload coverage report
      uses: codecov/codecov-action@v1
      with:
        token: ${{ secrets.CODECOV_TOKEN }}
        file: coverage.xml


================================================
FILE: .github/workflows/publish-to-pypi.yml
================================================
name: Publish 📦 to PyPI
on: push

jobs:
  build:
    name: Build distribution 📦
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: "3.x"
    - name: Install pypa/build
      run: >-
        python3 -m
        pip install
        build
        --user
    - name: Build a binary wheel and a source tarball
      run: python3 -m build
    - name: Store the distribution packages
      uses: actions/upload-artifact@v3
      with:
        name: python-package-distributions
        path: dist/

  publish-to-pypi:
    name: >-
      Publish Python 🐍 distribution 📦 to PyPI
    if: startsWith(github.ref, 'refs/tags/')  # only publish to PyPI on tag pushes
    needs:
    - build
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/linkpred
    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1

  github-release:
    name: >-
      Sign the Python 🐍 distribution 📦 with Sigstore
      and upload them to GitHub Release
    needs:
    - publish-to-pypi
    runs-on: ubuntu-latest

    permissions:
      contents: write  # IMPORTANT: mandatory for making GitHub Releases
      id-token: write  # IMPORTANT: mandatory for sigstore

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v4
      with:
        name: python-package-distributions
        path: dist/
    - name: Sign the dists with Sigstore
      uses: sigstore/gh-action-sigstore-python@v3.0.0
      with:
        inputs: >-
          ./dist/*.tar.gz
          ./dist/*.whl
    - name: Create GitHub Release
      env:
        GITHUB_TOKEN: ${{ github.token }}
      run: >-
        gh release create
        '${{ github.ref_name }}'
        --repo '${{ github.repository }}'
        --notes ""
    - name: Upload artifact signatures to GitHub Release
      env:
        GITHUB_TOKEN: ${{ github.token }}
      # Upload to GitHub Release using the `gh` CLI.
      # `dist/` contains the built packages, and the
      # sigstore-produced signatures and certificates.
      run: >-
        gh release upload
        '${{ github.ref_name }}' dist/**
        --repo '${{ github.repository }}'


================================================
FILE: .github/workflows/tox.yml
================================================
name: Test with tox

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 5
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    - uses: actions/cache@v2
      name: Configure pip caching
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
        restore-keys: |
          ${{ runner.os }}-pip-
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install tox tox-gh-actions
    - name: Test with tox
      run: tox


================================================
FILE: .gitignore
================================================
*.py[cod]
*.swp

examples/*.pdf
examples/*.png
examples/*.txt

.ropeproject

# C extensions
*.so

# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox
nosetests.xml

# Translations
*.mo

# IDEs
.mr.developer.cfg
.project
.pydevproject
.idea
.vscode

.mypy_cache
.cache


================================================
FILE: CHANGELOG.rst
================================================
Changelog
=========

**Note**: I only started keeping this changelog from version 0.5 onwards.

Version 0.6
-----------

- Officially support Python versions 3.8-3.12

- Fix bug where linkpred could no longer be installed on Windows

- General modernization of code and especially infrastructure (CI, packaging etc.)

Version 0.5.1
-------------

(I botched the release of v0.5, hence 0.5.1)

- Python 3.8 officially supported!

- Behind-the-scenes work: testing is now done with pytest and tox, formatting is done by black, and we are based on the latest version of networkx (2.4).

- The Community predictor is now easier to use, also because its optional dependency (`python-louvain <https://github.com/taynaud/python-louvain>`_) is now on PyPI. If you want to use it, install as follows:

    $ pip install linkpred[all]

- Some bug fixes.


================================================
FILE: LICENSE
================================================
New BSD License

Copyright (c) 2013 The linkpred developers.
All rights reserved.


Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  a. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.
  b. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
  c. Neither the name of the linkpred developers nor the names of
     its contributors may be used to endorse or promote products
     derived from this software without specific prior written
     permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.




================================================
FILE: README.rst
================================================
⚠️ **Note: This package is in maintenance mode**.
Critical bugs will continue to be resolved,
but no new features will be implemented (`more information <https://github.com/rafguns/linkpred/issues/35>`_).

Linkpred
========

**Linkpred** is a Python package for link prediction: given a network, Linkpred provides a number of heuristics (known as *predictors*) that assess the likelihood of potential links in a future snapshot of the network.

While some predictors are fairly straightforward (e.g., if two people have a large number of mutual friends, it seems likely that eventually they will meet and become friends), others are more involved.

.. image:: https://codecov.io/gh/rafguns/linkpred/branch/master/graph/badge.svg?token=JVZIVHWJXY 
   :target: https://codecov.io/gh/rafguns/linkpred

**linkpred** can both be used as a command-line tool and as a Python library in your own code.


Installation
------------

**linkpred** (v0.6 and later) works under Python 3.8 to 3.12.
It depends on:

- matplotlib
- networkx
- numpy
- pyyaml
- scipy
- smokesignal

You should be able to install Linkpred and its dependencies using pip (``pip install linkpred`` or ``python -m pip install linkpred``).
If you do not yet have Python installed, I recommend starting with `Anaconda <https://www.continuum.io/downloads>`_,
which includes optimized versions of packages like numpy.
If you want to use the Community predictor, which relies on community structure of the network,
make sure you also have the `python-louvain <https://github.com/taynaud/python-louvain>`_ package by installing with ``pip install linkpred[community]``.


Example usage as command-line tool
----------------------------------

A good starting point is ``linkpred --help``, which lists all the available options. To save the predictions of the ``CommonNeighbours`` predictor, for instance, run::

    $ linkpred examples/inf1990-2004.net -p CommonNeighbours --output cache-predictions

where ``examples/inf1990-2004.net`` is a network file in Pajek format. Other supported formats include GML and GraphML. The full output looks like this:

.. code:: console

    $ linkpred examples/inf1990-2004.net -p CommonNeighbours --output cache-predictions
    16:43:13 - INFO - Reading file 'examples/inf1990-2004.net'...
    16:43:13 - INFO - Successfully read file.
    16:43:13 - INFO - Starting preprocessing...
    16:43:13 - INFO - Removed 35 nodes (degree < 1)
    16:43:13 - INFO - Finished preprocessing.
    16:43:13 - INFO - Executing CommonNeighbours...
    16:43:14 - INFO - Finished executing CommonNeighbours.
    16:43:14 - INFO - Prediction run finished

    $ head examples/inf1990-2004-CommonNeighbours-predictions_2016-04-22_16.43.txt
    "Ikogami, K"    "Ikegami, K"    5.0
    "Durand, T"     "Abd El Kader, M"       5.0
    "Sharma, L"     "Kumar, S"      4.0
    "Paul, A"       "Durand, T"     4.0
    "Paul, A"       "Dudognon, G"   4.0
    "Paul, A"       "Abd El Kader, M"       4.0
    "Karisiddippa, CR"      "Garg, KC"      4.0
    "Wu, YS"        "Kretschmer, H" 3.0
    "Veugelers, R"  "Deleus, F"     3.0
    "Veugelers, R"  "Andries, P"    3.0


Example usage within Python
---------------------------

.. code:: pycon

    >>> import linkpred
    >>> G = linkpred.read_network("examples/training.net")
    11:49:00 - INFO - Reading file 'examples/training.net'...
    11:49:00 - INFO - Successfully read file.
    >>> len(G)   # number of nodes
    632
    >>> # We exclude edges already present, to predict only new links
    >>> simrank = linkpred.predictors.SimRank(G, excluded=G.edges())
    >>> simrank_results = simrank.predict(c=0.5)
    >>> top = simrank_results.top(5)
    >>> for authors, score in top.items():
    ...    print(authors, score)
    ...
    Tomizawa, H - Fujigaki, Y 0.188686630053
    Shirabe, M - Hayashi, T 0.143866427916
    Garfield, E - Fuseler, EA 0.148097050146
    Persson, O - Larsen, IM 0.138516589957
    Vanleeuwen, TN - Noyons, ECM 0.185040358711


================================================
FILE: examples/inf1990-2004.net
================================================
*network Informetrics1990-2004
*vertices 632
1 "Pereira, JCR"
2 "Peters, HPF"
3 "Widhalm, C"
4 "Verbeek, A"
5 "Salvador, P"
6 "Van Den Besselaar, P"
7 "Koschatzky, K"
8 "Sreenivas, V"
9 "Dos Santos, NF"
10 "He, L"
11 "Lannes, D"
12 "Yuthavong, Y"
13 "Pant, N"
14 "Salman, S"
15 "Van Looy, B"
16 "Hood, WW"
17 "Garcia-zorita, C"
18 "Green, L"
19 "Bonzi, S"
20 "Zitt, M"
21 "Pereira, TS"
22 "Vargas-quesada, B"
23 "Dore, JC"
24 "Rivaud, D"
25 "Clarysse, B"
26 "Cherny, AI"
27 "Wright, B"
28 "Mcmillan, GS"
29 "Wu, Z"
30 "Li, J4"
31 "Mckenzie, G"
32 "Lihua, L"
33 "Chen, DQ"
34 "Schneider, JW"
35 "Zhu, XY"
36 "Legentil, M"
37 "Gurjeva, LG"
38 "Warner, J"
39 "Arora, J"
40 "Elalami, J"
41 "Wilkinson, D"
42 "Yemenu, D"
43 "De Los Santos-riog, M"
44 "Bauin, S"
45 "Agudelo, D"
46 "Watts, RJ"
47 "Havemann, F"
48 "Thijs, B"
49 "Rao, IKR"
50 "Lemarie, J"
51 "Dillon, M"
52 "Mccain, KW"
53 "Kovacs-nemeth, E"
54 "Shama, G"
55 "Dawson, G"
56 "Kundra, R"
57 "Callon, M"
58 "Maciaschapula, CA"
59 "Kandhari, R"
60 "Viedma, MI"
61 "Milisproost, G"
62 "Chaimovich, H"
63 "Shlesinger, MF"
64 "Fenton, MR"
65 "Kessler, C"
66 "Mahlck, P"
67 "Leemans, MJ"
68 "Oppenheim, C"
69 "Luukkonen, T"
70 "Bocock, D"
71 "Scaller, RR"
72 "Weingart, P"
73 "Eisemon, TO"
74 "Ahmed, T"
75 "Weber, M"
76 "Vaughan, L"
77 "Cunningham, S"
78 "Cunningham, P"
79 "Ren, SL"
80 "Prime, C"
81 "Butler, L"
82 "Deyong, C"
83 "Fujigaki, Y"
84 "Banerjee, P"
85 "Fuseler, EA"
86 "Navarro, A"
87 "Humenik, JA"
88 "Rey, J"
89 "Barrigon, S"
90 "Stanard, C"
91 "Kim, MS"
92 "Lapid, K"
93 "Heimeriks, G"
94 "Fawcett, G"
95 "Contreras, EJ"
96 "Nazer, N"
97 "Binns, R"
98 "Frigoletto, L"
99 "Martin-sempere, MJ"
100 "Melin, G"
101 "Galvez, L"
102 "Clement, F"
103 "Collazo-reyes, F"
104 "Engwall, L"
105 "Pudovkin, AI"
106 "Garciajover, F"
107 "Price, L"
108 "Szabadi-peresztegi, Z"
109 "Johnson, B"
110 "Etemad, S"
111 "Davis, CH"
112 "Moed, H"
113 "Buter, RK"
114 "Li, XM"
115 "Markusova, VA"
116 "Kuhlmann, S"
117 "Libkind, AN"
118 "Deleus, F"
119 "Uehara, M"
120 "Debackere, K"
121 "Sivertsen, G"
122 "Yu, DR"
123 "Solari, A"
124 "Buela-casal, G"
125 "Reedijk, J"
126 "Wolfram, D"
127 "Saavedra, F"
128 "Dastidar, PG"
129 "Niemi, T"
130 "Cheng, YR"
131 "Feillet, H"
132 "Wofchuk, S"
133 "Van Raan, AFJ"
134 "Boerner, K"
135 "Eberhart, HJ"
136 "Munoz, E"
137 "Mackenzie, MR"
138 "Lacasa, ID"
139 "Maczelka, H"
140 "Glaser, J"
141 "Davis, M"
142 "Meyer, M"
143 "Abd El Kader, M"
144 "Ugena, S"
145 "Lemarie, S"
146 "Klavans, R"
147 "Guo, YZ"
148 "Garzon-garcia, B"
149 "Persson, O"
150 "Schwarz, S"
151 "Claveria, LE"
152 "Ricoy, JR"
153 "Zhang, JG"
154 "Bobb, K"
155 "De Francisco, A"
156 "Lee, YS"
157 "Whitlow, ES"
158 "Hamilton, RD"
159 "Yagi, E"
160 "Linstone, HA"
161 "Wouters, P"
162 "White, HD"
163 "Bonitz, M"
164 "Weaverwozniak, S"
165 "Rey-rocha, J"
166 "Snyder, H"
167 "Ikogami, K"
168 "Ojasoo, T"
169 "Ebeling, W"
170 "Ikegami, K"
171 "Godoy, V"
172 "Harries, G"
173 "Cronin, B"
174 "Archambault, E"
175 "Garg, KC"
176 "Griffith, BC"
177 "Sanz, E"
178 "Chiu, WT"
179 "Larsen, B"
180 "Bar-ilan, J"
181 "Higashi, T"
182 "Narin, F"
183 "Courtial, JP"
184 "Schweighoffer, MG"
185 "Del Castillo, JM"
186 "Breitzman, T"
187 "Lascurain-sanchez, ML"
188 "Grant, J"
189 "Fairclough, R"
190 "Jepsen, ET"
191 "Maes, M"
192 "Gaillard, J"
193 "Magde, B"
194 "Park, YT"
195 "Munoz-fernandez, FJ"
196 "Mello, J"
197 "Muller, R"
198 "Bjorneborn, L"
199 "Neelameghan, A"
200 "Clausen, H"
201 "Lipworth, S"
202 "Gourdon, L"
203 "Viby-mogensen, J"
204 "Brocken, M"
205 "Peritz, BC"
206 "Gaudy, JF"
207 "Mason, B"
208 "Fang, Y"
209 "Meertens, RW"
210 "Braun, T"
211 "Paraje, G"
212 "Danell, R"
213 "Zulueta, MA"
214 "Noyons, ECM"
215 "Vermeulin, P"
216 "Ortega, JL"
217 "Arvanitis, R"
218 "Ramanana-rahary, S"
219 "Zimmermann, E"
220 "Aggarwal, BS"
221 "Geisler, E"
222 "Robert, C"
223 "Suarez-balseiro, CA"
224 "Scharnhorst, A"
225 "Granstrand, O"
226 "Durand, T"
227 "Thirion, B"
228 "Okubo, Y"
229 "Caridad, IG"
230 "Liang, LM"
231 "Small, H"
232 "Delgado, H"
233 "Narvaezberthelemot, N"
234 "Larsen, IM"
235 "Igic, R"
236 "Sotolongo-aguilar, GR"
237 "Satyanarayana, K"
238 "Almind, TC"
239 "De Moya-anegon, F"
240 "Tijssen, R"
241 "Skram, U"
242 "Callahan, E"
243 "Jagodzinskisigogneau, M"
244 "Bruckner, E"
245 "Okubo, T"
246 "Martin, J"
247 "Chinchilla-rodriguez, Z"
248 "Zsindely, S"
249 "Martin, A"
250 "Mutafov, HG"
251 "Poca, MA"
252 "Rodrigues, PS"
253 "Rubio, L"
254 "Gherbi, R"
255 "Hesselink, FT"
256 "Rubio, E"
257 "Stevens, KA"
258 "Meyer, JB"
259 "Detampel, MJ"
260 "Debruin, RE"
261 "Seiden, P"
262 "Levy, D"
263 "Schmoch, U"
264 "Velloso, A"
265 "Iversen, EJ"
266 "Downie, JS"
267 "Delgado-lopez-cozar, E"
268 "Wang, L1"
269 "Cluzeau, F"
270 "Rosas, AM"
271 "Pastor, A"
272 "Badash, L"
273 "Itoh, T"
274 "Matthiessen, CW"
275 "Kostoff, RN"
276 "Karisiddippa, CR"
277 "Leydesdorff, L"
278 "Hicks, D"
279 "Azerad, J"
280 "Jarvelin, K"
281 "Deroulede, A"
282 "Fonseca, L"
283 "Rumjanek, VM"
284 "Dutt, B"
285 "Waast, R"
286 "Figueira, I"
287 "Bourke, P"
288 "Agis, A"
289 "Hellgardt, K"
290 "Kongthon, A"
291 "Topolnik, M"
292 "Sinilainen, T"
293 "Laville, F"
294 "Sharma, SC"
295 "Vanhooydonk, G"
296 "Traynor, M"
297 "Medina, A"
298 "Van Vuren, HG"
299 "Smith, AG"
300 "Czerwon, HJ"
301 "Gupta, BM"
302 "Kobayashi, S"
303 "Fawcettjones, A"
304 "Sigogneau, A"
305 "Garfield, E"
306 "Tomizawa, H"
307 "Veugelers, R"
308 "Leta, J"
309 "Ho, YS"
310 "Bruil, J"
311 "Todorov, R"
312 "Coronini, R"
313 "Rodriguez-farre, E"
314 "Kretschmer, H"
315 "Pennebaker, JW"
316 "Machado, RDP"
317 "Demarco, RA"
318 "Wormell, I"
319 "Russell, JM"
320 "Georgel, A"
321 "Wu, YS"
322 "Minin, VA"
323 "Jacques, R"
324 "Charum, J"
325 "Martinson, A"
326 "Sauquillo, J"
327 "Lewison, G"
328 "Rong, YH"
329 "Park, HW"
330 "Jacobs, D"
331 "Devey, ME"
332 "Rowlands, I"
333 "Engelsman, EC"
334 "Xu, HD"
335 "Morillo, F"
336 "Visser, MS"
337 "Pal, C"
338 "Korevaar, JC"
339 "Zhang, YH"
340 "Pistorius, C"
341 "Tanaka, C"
342 "Limoges, C"
343 "Oneill, E"
344 "Shan, S"
345 "Seglen, PO"
346 "Gevaert, R"
347 "Del Rio, JA"
348 "Reinert, M"
349 "Meijer, RF"
350 "Yue, WP"
351 "Senkovska, ED"
352 "Grupp, H"
353 "Corera-alvarez, E"
354 "Torres, G"
355 "Kyvik, S"
356 "Pastor, R"
357 "Zhang, QQ"
358 "Karki, MMS"
359 "Paul, A"
360 "Cottrell, R"
361 "Zelman, A"
362 "Katz, JS"
363 "Yang, ZQ"
364 "Cothey, V"
365 "Wu, GZ"
366 "Pearson, S"
367 "Benichou, J"
368 "Niwa, F"
369 "Carretero-dios, H"
370 "Osareh, F"
371 "Jain, A"
372 "Ma, Z"
373 "Berg, J"
374 "Urdin, MC"
375 "Caldeira, MT"
376 "Ajiferuke, I"
377 "Bordons, M"
378 "Rojouan, F"
379 "Andries, P"
380 "Losiewicz, P"
381 "Gilyarevskii, RS"
382 "Gilbert, J"
383 "Pellenbarg, R"
384 "Schubert, GA"
385 "Borlund, P"
386 "Malpohl, G"
387 "Wagnerdobler, R"
388 "Toothman, DR"
389 "Bravo, C"
390 "Devillard, J"
391 "Huber, JC"
392 "Wilson, CS"
393 "Burrell, QL"
394 "Van Der Wurff, LJ"
395 "He, DG"
396 "Tomov, DT"
397 "Karypis, G"
398 "Schoepflin, U"
399 "Roussel, F"
400 "Barnett, GA"
401 "Hayashi, T"
402 "Zanetta, DMT"
403 "Rodea-castro, IP"
404 "Torricella-morales, RG"
405 "Davidse, RJ"
406 "Van Den Berghe, H"
407 "Rafferty, AM"
408 "Jiang, L"
409 "Gauthier, L"
410 "Delooze, MA"
411 "Aksnes, DW"
412 "Moreno, L"
413 "Welljamsdorof, A"
414 "Jacquemin, C"
415 "Hirvonen, L"
416 "Page-kennedy, T"
417 "Haritash, N"
418 "Pessot, R"
419 "Petard, JP"
420 "Bookstein, A"
421 "Horlesberger, M"
422 "Sehringer, R"
423 "Shaw, D"
424 "Cahlik, T"
425 "Van Hulle, MM"
426 "Bogaert, J"
427 "Winterhager, M"
428 "Lozano, S"
429 "Dudognon, G"
430 "Wilke, HAM"
431 "Heydari, A"
432 "Musgrove, PB"
433 "Guallar, E"
434 "Zhao, HZ"
435 "Matillon, Y"
436 "Carding, P"
437 "Martin-moreno, C"
438 "Kim, CS"
439 "Mangematin, V"
440 "Arikan, F"
441 "Herrero-solana, V"
442 "Gilmour, JE"
443 "Ranga, LM"
444 "Wellman, B"
445 "Chatelin, Y"
446 "Sarbolouki, MN"
447 "Lelu, A"
448 "Lustosa, P"
449 "Rousseau, R"
450 "Rousseau, S"
451 "Bernal, G"
452 "Harsanyi, MA"
453 "Prat, AM"
454 "Pan, YT"
455 "Schlemmer, B"
456 "Yamazaki, S"
457 "Lemarc, M"
458 "Garcia, EO"
459 "Rippon, I"
460 "Jansen, P"
461 "Delange, C"
462 "Albertini, R"
463 "Hsieh, WH"
464 "Krauskopf, V"
465 "Lakshmi, VV"
466 "Van Vijk, E"
467 "Schiebel, E"
468 "Yitzhaki, M"
469 "Wang, B"
470 "Boyack, KW"
471 "Kuntze, U"
472 "Ruiz-perez, R"
473 "Beckmann, M"
474 "Araujo-ruiz, JA"
475 "Kaloudis, A"
476 "Wang, Y"
477 "Srivastava, D"
478 "Olsen, TB"
479 "Cabrero, A"
480 "Hamilton, KS"
481 "Jin, BH"
482 "Jeannin, P"
483 "Zhang, HQ"
484 "Teixeira, N"
485 "Chungcharoen, A"
486 "Luwel, M"
487 "Cozzens, S"
488 "Pfeil, KM"
489 "Bailon-moreno, R"
490 "Kinnucan, MT"
491 "Ortiz-rivera, LA"
492 "Smeyers, M"
493 "Schubert, A"
494 "Bruins, EEW"
495 "Cambrosio, A"
496 "Breton-lopez, J"
497 "Xu, B"
498 "Schwarz, AW"
499 "Ruts, C"
500 "Turner, WA"
501 "Bhattacharya, S"
502 "Ramani, SV"
503 "Rosenbaum, H"
504 "Mehrotra, NN"
505 "Romero, F"
506 "De Vries, R"
507 "Krauskopf, M"
508 "Georghiou, L"
509 "Kopcsa, A"
510 "Houben, JA"
511 "Hooten, PA"
512 "Arapov, MV"
513 "Mely, B"
514 "Hartley, J"
515 "Jouve, O"
516 "Saitoh, Y"
517 "Mehrdad, M"
518 "Egghe, L"
519 "Kint, A"
520 "Harter, SP"
521 "Ruiz-palomo, F"
522 "Tang, R"
523 "Sudhakar, P"
524 "Albert, A"
525 "Find, S"
526 "Sebastian, J"
527 "Luna-morales, ME"
528 "Glanzel, W"
529 "Spurling, TH"
530 "Arreto, CD"
531 "Hullmann, A"
532 "Yu, G"
533 "Criado, E"
534 "Tseng, TM"
535 "Taxt, RE"
536 "Padhi, P"
537 "Laudel, G"
538 "Nagpaul, PS"
539 "Van Hecke, P"
540 "Schwechheimer, H"
541 "Stephens, D"
542 "Bassecoulard-zitt, E"
543 "Anegon, FD"
544 "Singh, SP"
545 "Danowski, JA"
546 "Nederhof, AJ"
547 "Shirabe, M"
548 "Faba-perez, C"
549 "Vera, MI"
550 "Ferreiro, L"
551 "Demeis, L"
552 "Sen, BK"
553 "Magri, MH"
554 "Kumar, S"
555 "Mallik, M"
556 "Laredo, P"
557 "Guo, H"
558 "Dillon, SM"
559 "Porter, AL"
560 "Christensen, FH"
561 "Cami, J"
562 "Ortega, C"
563 "Ingwersen, P"
564 "Jin, XY"
565 "Bali, A"
566 "Peck, C"
567 "Rinia, EJ"
568 "Hinze, S"
569 "Granadino, B"
570 "Granes, J"
571 "Solorio-lagunas, J"
572 "Cortes, HD"
573 "Crouch, D"
574 "Lui, JC"
575 "Seetharam, G"
576 "Liu, JW"
577 "Garvey, WD"
578 "Raina, D"
579 "Takahashi, K"
580 "Morris, SA"
581 "Mendez, A"
582 "Bedford, CD"
583 "Fischer, AL"
584 "Leger, MD"
585 "Fox, C"
586 "Mendez, I"
587 "Tshiteya, R"
588 "Michelet, B"
589 "Mijangos-nolasco, A"
590 "Sancho, R"
591 "Jiang, GH"
592 "Davenport, E"
593 "Mustar, P"
594 "Dutheuil, C"
595 "Plaza, LM"
596 "Banos, RR"
597 "Py, Y"
598 "Hoshuyama, T"
599 "Ramirez, AM"
600 "Phornsadja, K"
601 "Wagner, CS"
602 "Maisonneuve, H"
603 "Suma, MP"
604 "Conde, J"
605 "Heinz, M"
606 "Roy, A"
607 "Xu, XS"
608 "Roy, S"
609 "Sharma, P"
610 "He, TW"
611 "Bentamar, D"
612 "Vilanova, MR"
613 "Von Tunzelmann, N"
614 "Spruyt, E"
615 "Escuder, MML"
616 "Ibanez, JJ"
617 "Olivastro, D"
618 "Sharma, L"
619 "Sangam, SL"
620 "Oard, DW"
621 "Chiu, CH"
622 "Coates, V"
623 "Guo, HN"
624 "Zhu, L"
625 "Nicolaisen, J"
626 "Farooque, M"
627 "Vanleeuwen, TN"
628 "Miquel, JF"
629 "Corrochano, MD"
630 "Utecht, JT"
631 "Hysen, K"
632 "Shailendra, K"
*edges
1 402 1
1 615 3
1 583 1
3 75 1
3 467 1
3 509 1
3 291 1
4 307 1
4 118 1
4 120 4
4 379 1
4 15 1
4 196 1
4 219 3
5 581 1
6 421 1
6 93 1
7 352 1
7 263 1
8 477 1
8 237 1
9 283 1
10 30 1
10 395 1
10 483 1
11 551 2
11 308 1
11 264 1
12 111 1
12 485 1
12 600 1
12 73 1
13 538 1
14 580 1
14 29 1
14 42 1
14 82 1
15 120 1
15 307 1
15 196 1
15 219 1
16 392 7
16 141 1
17 187 1
17 177 1
17 437 1
18 207 2
18 188 2
19 166 1
20 80 1
20 542 1
20 115 1
20 484 1
20 293 1
20 228 1
20 218 3
21 149 1
21 225 1
22 247 1
22 441 1
22 195 1
22 353 1
23 359 1
23 40 2
23 281 1
23 382 1
23 594 2
23 226 1
23 262 1
23 429 1
23 228 5
23 168 7
23 628 8
23 602 1
23 143 1
24 410 1
24 50 1
25 120 1
26 381 2
26 176 1
26 115 1
27 420 1
28 158 1
29 580 1
29 42 1
29 82 1
30 395 1
30 483 1
31 173 1
31 253 1
31 164 1
32 230 1
33 481 1
33 35 1
33 153 1
34 385 1
35 481 1
35 153 1
36 553 1
36 410 1
36 312 1
36 482 1
37 161 1
39 501 1
39 337 1
40 281 1
40 594 1
40 262 1
40 382 1
40 628 2
41 107 3
41 97 1
41 189 1
41 114 1
41 416 1
41 172 4
42 580 1
42 82 1
43 369 1
43 124 1
44 183 1
44 184 1
44 243 1
44 588 1
44 215 1
44 131 1
45 496 3
45 124 3
46 559 1
47 605 1
47 300 1
48 455 2
48 528 4
49 449 1
49 199 1
49 603 1
49 518 2
49 575 1
50 410 2
51 541 1
51 420 1
51 343 1
53 210 2
53 108 2
54 289 1
54 68 1
55 327 1
56 396 1
56 230 2
56 314 3
57 293 1
57 183 4
57 424 1
57 304 1
58 571 1
58 193 1
58 233 1
58 403 1
58 589 1
59 578 1
59 301 1
60 428 1
60 354 1
60 297 1
60 171 1
60 124 1
61 295 1
62 252 1
62 308 1
62 282 1
63 275 2
63 587 1
63 386 1
64 393 1
65 327 1
65 303 1
66 149 1
67 449 1
67 191 1
67 499 1
68 289 1
68 109 1
68 566 1
68 74 1
69 149 1
69 121 1
70 77 1
71 275 1
72 422 1
72 427 1
73 111 1
73 485 1
73 600 1
74 566 1
74 109 1
75 467 1
75 509 1
75 291 1
76 631 1
76 114 1
76 364 1
76 423 1
76 299 1
77 558 1
78 327 1
79 449 1
81 529 1
81 287 3
81 140 1
82 580 1
83 547 1
83 401 1
84 175 1
84 301 1
85 105 1
86 246 1
87 388 2
87 275 4
87 458 1
87 135 1
87 210 1
87 347 1
87 587 1
87 488 1
87 599 1
88 595 3
88 616 1
88 99 1
88 586 1
89 377 6
89 389 1
89 581 1
89 106 1
89 505 1
89 213 2
90 268 1
90 559 1
90 334 1
90 442 1
90 564 1
91 194 1
92 340 1
92 559 1
92 146 1
92 626 1
92 622 1
92 160 1
93 421 1
94 188 1
94 269 1
94 360 1
95 543 1
95 596 2
95 183 2
95 472 2
95 267 3
95 489 2
96 444 1
96 162 1
97 432 1
97 107 1
97 416 2
97 172 1
98 628 1
99 595 1
99 165 5
99 148 1
100 149 5
100 212 2
100 475 1
101 451 1
101 590 1
103 527 2
103 319 1
104 149 1
104 212 1
105 305 1
106 377 1
107 416 1
107 189 1
107 522 1
107 172 3
108 210 2
109 566 1
110 517 1
110 446 1
110 431 1
111 600 1
111 485 1
111 173 1
112 398 1
112 528 1
112 362 1
113 627 1
113 214 1
114 364 1
114 299 1
115 392 2
115 542 1
115 381 1
115 176 2
115 141 1
117 512 1
117 322 1
118 120 1
118 379 1
118 425 1
118 219 1
119 579 1
119 341 1
119 598 1
120 307 2
120 379 1
120 613 1
120 528 1
120 346 1
120 443 1
120 196 1
120 219 3
121 149 1
121 411 1
122 532 1
123 553 1
124 369 2
124 297 1
124 354 1
124 428 1
124 496 4
124 171 1
125 627 2
126 490 1
126 376 2
127 507 1
127 418 1
127 137 1
129 563 1
129 415 1
129 280 2
130 520 1
131 183 1
131 243 1
132 551 1
132 282 2
133 336 3
133 546 1
133 394 1
133 627 10
133 494 2
133 214 3
133 466 1
133 298 4
134 470 1
135 388 4
135 275 4
135 383 1
136 604 1
136 356 1
136 561 1
136 313 1
136 151 1
136 152 1
136 521 1
136 433 1
137 507 1
137 418 1
138 352 1
138 263 1
139 210 3
139 248 1
139 528 2
140 529 1
140 537 1
141 392 4
141 147 1
141 230 1
143 513 1
143 168 1
143 429 1
143 228 2
143 628 1
144 550 1
145 439 1
146 340 1
146 559 1
146 626 1
146 622 1
146 160 1
147 230 2
147 314 1
148 165 1
149 225 1
149 212 6
149 528 2
149 473 2
149 355 1
149 475 1
150 498 1
151 604 1
151 356 1
151 561 1
151 313 1
151 152 1
151 521 1
151 433 1
152 604 1
152 356 1
152 561 1
152 313 1
152 521 1
152 433 1
153 481 1
155 459 1
155 201 2
155 327 2
156 178 1
156 463 1
156 309 1
157 182 1
159 272 1
160 340 1
160 559 1
160 626 1
160 622 1
161 277 4
161 506 1
162 444 1
163 224 4
163 244 3
164 173 1
164 253 1
166 592 1
166 173 1
167 598 1
167 579 1
167 273 1
167 245 1
167 181 1
168 359 1
168 435 1
168 226 1
168 429 1
168 228 4
168 628 4
168 602 2
169 224 1
169 244 1
170 598 1
170 579 1
170 273 1
170 245 1
170 181 1
171 428 1
171 354 1
171 297 1
172 189 1
172 416 1
173 325 2
173 592 2
173 423 3
173 242 1
173 503 1
173 253 1
173 366 1
175 609 4
175 301 1
175 284 2
175 371 2
175 536 6
175 554 1
175 565 1
175 358 5
175 618 1
176 381 1
176 577 1
177 491 1
177 437 1
177 236 1
177 581 1
177 223 1
177 187 1
178 621 1
178 463 1
178 309 3
178 534 1
179 563 1
179 203 1
179 241 1
180 205 4
181 273 2
181 598 2
181 579 2
181 245 2
182 480 1
182 617 2
182 257 1
183 424 1
183 489 2
183 351 1
183 596 2
183 342 1
183 293 2
183 243 1
183 588 1
183 495 1
183 457 1
183 202 1
183 304 1
183 419 1
183 597 1
184 215 1
184 588 1
185 441 1
186 617 1
187 437 1
188 460 1
188 360 1
188 327 1
188 207 2
188 269 1
190 563 1
190 198 1
190 261 1
191 449 1
191 499 1
192 217 2
192 233 1
192 285 2
192 319 1
193 571 1
195 247 1
195 441 1
195 353 1
196 307 1
196 219 1
197 314 1
198 563 3
198 261 1
200 318 2
201 459 1
201 327 2
203 563 1
203 241 1
204 210 1
204 567 1
204 528 1
206 222 1
206 279 1
206 530 1
208 449 1
209 430 1
210 388 1
210 275 1
210 581 1
210 352 2
210 528 16
210 248 1
210 567 1
211 327 1
212 528 2
212 475 1
213 377 8
213 561 2
213 505 1
213 581 1
215 588 1
217 270 1
217 285 2
217 319 2
217 445 1
217 233 1
218 293 1
219 307 1
219 379 1
220 554 1
220 301 1
222 279 1
222 530 1
223 491 1
224 244 4
226 429 1
226 228 1
226 628 1
227 399 1
228 359 1
228 513 1
228 429 2
228 628 6
229 561 1
230 576 1
230 624 1
230 449 1
230 434 1
230 314 3
230 476 1
230 321 1
231 305 1
232 319 1
233 285 1
233 403 1
233 319 2
234 355 1
235 327 1
237 477 1
238 563 1
239 548 1
241 563 1
242 325 1
242 503 1
245 598 2
245 579 2
245 273 2
247 353 1
247 441 1
249 479 1
249 288 1
251 326 1
251 256 1
251 440 1
252 282 1
254 584 1
254 500 1
254 414 1
256 326 1
256 440 1
257 617 1
258 570 1
258 445 1
258 324 1
259 559 1
261 563 1
262 281 1
262 594 1
262 382 1
262 628 1
263 568 1
263 352 4
263 471 1
264 551 1
267 543 1
267 472 2
268 564 1
268 334 1
268 442 1
268 559 1
269 360 1
270 319 1
271 590 1
271 533 1
273 598 2
273 579 2
274 525 1
274 498 2
275 388 5
275 397 1
275 347 2
275 488 1
275 620 1
275 582 1
275 572 1
275 317 1
275 599 1
275 587 2
275 383 1
275 458 1
275 380 1
275 386 1
276 619 1
276 618 1
276 554 3
276 609 1
276 301 9
277 612 1
277 361 1
277 409 1
277 487 1
277 601 1
279 530 1
280 563 1
280 415 1
281 594 1
281 382 1
281 628 1
282 448 1
282 551 1
282 316 1
282 375 1
283 308 1
284 565 1
285 319 1
286 551 1
286 323 2
286 308 2
288 479 1
290 559 1
290 574 1
291 467 1
291 509 1
292 630 1
293 342 1
293 495 1
294 504 1
294 301 1
295 404 1
295 474 1
296 407 2
296 327 1
297 428 1
297 354 1
298 494 2
298 627 4
299 364 1
300 398 1
300 528 4
301 619 1
301 609 2
301 618 1
301 417 1
301 449 1
301 578 2
301 314 1
301 554 6
301 504 1
302 306 1
302 516 1
303 327 1
305 413 2
306 368 1
306 547 2
306 516 1
308 327 1
308 551 2
308 323 2
309 621 1
309 463 1
309 534 1
311 427 2
312 515 1
312 410 1
312 482 1
312 439 1
312 606 1
312 553 1
312 348 1
313 604 1
313 356 1
313 561 1
313 521 1
313 433 1
314 449 1
314 501 1
314 528 1
315 585 1
315 514 1
316 448 1
316 375 1
318 563 2
318 560 1
319 527 1
320 447 1
320 500 1
321 591 1
321 557 1
321 339 1
321 372 1
321 454 1
321 528 1
321 434 1
321 449 1
321 497 1
321 363 1
321 476 1
322 512 1
323 551 1
324 570 1
324 445 1
325 592 1
325 503 1
326 440 1
327 460 1
327 407 1
327 436 1
327 331 1
327 459 1
328 532 1
329 438 1
329 400 1
330 563 2
334 564 1
334 442 1
334 559 1
335 377 4
336 546 1
336 627 4
336 466 1
337 501 1
339 557 1
339 454 1
339 483 1
339 372 1
339 497 1
339 363 1
340 559 1
340 626 1
340 622 1
341 579 1
341 598 1
342 495 1
343 541 1
343 420 1
344 591 2
344 408 2
344 607 1
345 478 1
345 411 2
347 397 1
347 582 1
347 572 1
347 599 2
347 458 2
348 515 1
348 606 1
350 392 1
351 419 1
351 597 1
351 457 1
352 568 3
352 471 1
352 528 2
353 441 1
354 428 1
356 604 1
356 561 1
356 521 1
356 433 1
357 449 1
358 609 1
359 628 1
362 398 1
362 528 1
363 557 1
363 454 1
363 372 1
363 497 1
367 399 1
370 392 3
371 554 1
371 609 1
372 557 1
372 454 1
372 497 1
373 387 3
374 595 1
374 562 1
375 448 1
377 389 1
377 581 4
377 505 1
377 561 1
378 500 1
380 620 1
382 594 1
382 628 1
383 388 1
384 493 1
390 482 1
394 627 1
395 483 1
397 582 1
397 572 1
398 528 7
400 545 1
400 438 1
402 615 1
404 474 1
406 519 1
406 510 2
406 614 1
407 555 1
408 591 2
408 607 1
410 482 1
410 553 1
411 535 1
411 478 1
412 590 1
413 507 1
413 464 1
413 549 1
414 584 1
414 500 1
416 432 1
418 507 1
419 597 1
419 457 1
420 541 1
420 468 1
422 427 1
426 449 1
426 539 1
427 528 1
427 540 2
429 513 1
429 628 1
431 517 1
431 446 1
433 604 1
433 561 1
433 521 1
434 476 1
435 602 1
442 564 1
442 559 1
443 613 1
445 570 1
446 517 1
447 500 1
449 591 1
449 554 1
449 499 1
449 450 3
449 492 1
449 528 1
449 576 1
449 539 1
449 518 11
449 468 1
451 590 1
452 520 1
453 507 1
454 557 1
454 497 1
455 528 2
456 483 1
457 597 1
458 599 2
461 528 3
462 507 1
462 549 1
464 507 1
464 549 1
465 552 1
466 627 1
467 509 1
468 611 1
468 518 1
469 481 1
482 553 1
483 623 1
485 600 1
488 587 1
489 596 2
494 627 2
497 557 1
498 525 1
500 584 1
501 544 1
501 523 1
507 549 3
510 519 1
510 614 1
511 520 1
514 585 1
515 606 1
521 604 1
521 561 1
523 544 1
524 595 1
528 591 1
528 567 1
533 590 1
534 621 1
538 608 1
538 618 2
543 629 1
546 627 1
552 632 1
554 619 1
554 609 2
556 593 1
559 574 1
559 626 1
559 622 1
559 564 1
560 563 3
561 604 1
562 595 1
572 582 1
579 598 3
583 615 1
586 616 1
591 607 1
594 628 2
609 618 1
622 626 1


================================================
FILE: examples/inf2005-2009.net
================================================
*network Informetrics2005-2009
*vertices 634
1 "Pereira, JCR"
2 "Harthorn, BH"
3 "Verbeek, A"
4 "Fischer, TC"
5 "Van Den Besselaar, P"
6 "Ottoson, JM"
7 "Bollen, J"
8 "Engels, A"
9 "Calo, WA"
10 "Diaz-perez, M"
11 "Barth, RB"
12 "Bhattacharya, S"
13 "Huang, Y"
14 "Zhou, P"
15 "Van Looy, B"
16 "Garcia-zorita, C"
17 "You, J"
18 "Zitt, M"
19 "Buxton, M"
20 "Wang, XH"
21 "Savanur, K"
22 "Van Raan, AFJ"
23 "Harirchi, G"
24 "Liu, NC"
25 "Leggat, PA"
26 "Klitkou, A"
27 "Tukeva, T"
28 "Ausloos, M"
29 "Rushenberg, RL"
30 "Sorenson, MM"
31 "Arora, P"
32 "Schneider, JW"
33 "Hurt, I"
34 "De Zeeuw, D"
35 "Wilkinson, D"
36 "Ruschenburg, T"
37 "Robinson, S"
38 "Kretschmer, H"
39 "Torres-salinas, D"
40 "Havemann, F"
41 "Thijs, B"
42 "Rao, IKR"
43 "Ho, YS"
44 "Yang, K"
45 "Mccain, KW"
46 "Johnson, SD"
47 "Izquierdo, LR"
48 "Kundra, R"
49 "Maciaschapula, CA"
50 "Wang, MH"
51 "Barden, EM"
52 "Corera-alvarez, E"
53 "Chaimovich, H"
54 "Buela-casal, G"
55 "Conti, J"
56 "Karageorgopoulos, DE"
57 "Beirlant, J"
58 "Salmeron, JL"
59 "Chaudet, H"
60 "Gehanno, JF"
61 "Oppenheim, C"
62 "Barriga, OA"
63 "Weingart, P"
64 "Wilcox-jay, K"
65 "Rogers, Y"
66 "Ahmed, T"
67 "Vaughan, L"
68 "Pearson, DC"
69 "Trillo-dominguez, M"
70 "Weber, J"
71 "Hardeman, S"
72 "Shapira, P"
73 "Payne, N"
74 "Krauskopf, E"
75 "Shi, F"
76 "Fujigaki, Y"
77 "Cincera, M"
78 "Krauskopf, M"
79 "Navarro, A"
80 "Humenik, JA"
81 "Garcia-santiago, L"
82 "Santa, S"
83 "Ramanana-rahary, S"
84 "Chu, DC"
85 "Contreras, EJ"
86 "Galvez, C"
87 "Fowler, JH"
88 "Martin-sempere, MJ"
89 "Melin, G"
90 "Geisler, E"
91 "Karpouzian, G"
92 "Antell, K"
93 "Johnson, B"
94 "Iversen, EJ"
95 "Summers, MAC"
96 "Yu, G"
97 "Li, XM"
98 "Markusova, VA"
99 "Marques-portella, C"
100 "Smith, DMS"
101 "Girji, RM"
102 "Redpath, S"
103 "Debackere, K"
104 "Hazelton, M"
105 "Yu, DR"
106 "Shlesinger, MF"
107 "Janssens, F"
108 "Ivanov, VV"
109 "Levitt, JM"
110 "Lynd, FE"
111 "Hamre, R"
112 "Rousseau, S"
113 "Tolles, WM"
114 "Alexandrov, M"
115 "Dastidar, PG"
116 "Groneberg-kloft, B"
117 "Heydari, A"
118 "Heinze, T"
119 "Saenz-feijoo, R"
120 "Niemi, T"
121 "Chen, SR"
122 "Boerner, K"
123 "Henadeera, K"
124 "Lai, KK"
125 "Cornelius, B"
126 "Zuccala, A"
127 "Leviton, L"
128 "Zhang, J2"
129 "Rollin, L"
130 "Campbell, D"
131 "Davis, M"
132 "Meyer, M"
133 "Bermudez-sanchez, MP"
134 "Cole, FTH"
135 "Origgi, G"
136 "Jeong, S"
137 "Levene, M"
138 "Dodbele, S"
139 "Arencibia-jorge, R"
140 "De Filippo, D"
141 "Dorta-contreras, AJ"
142 "Icenhour, AS"
143 "Persson, O"
144 "Ke, W"
145 "Okubo, Y"
146 "Grobbee, DE"
147 "Holden, G"
148 "Hou, HY"
149 "Liu, YX"
150 "Perianes-rodriguez, A"
151 "Kluver, R"
152 "Pecht, M"
153 "Li, LL"
154 "Medina, A"
155 "Lambiotte, R"
156 "Kuusi, O"
157 "De Sompel, HV"
158 "Ovalle-perandones, MA"
159 "Wouters, P"
160 "White, HD"
161 "Lee, W"
162 "Perakakis, P"
163 "Mauleon, E"
164 "Morillo, F"
165 "Rojas-sola, JI"
166 "Soteriades, ES"
167 "Harries, G"
168 "Laine, T"
169 "Briggs, MB"
170 "Somogyi, A"
171 "Archambault, E"
172 "Olmeda-gomez, C"
173 "Garg, KC"
174 "Sanz, E"
175 "Chiu, WT"
176 "Yalpani, M"
177 "Youtie, J"
178 "Frenken, K"
179 "Larsen, B"
180 "Hussinger, K"
181 "Bonitz, M"
182 "Mcmillan, GS"
183 "Meng, W"
184 "Granadino, B"
185 "Gutierrez-martinez, O"
186 "Glenisson, P"
187 "Chaichio-moreno, JA"
188 "Grant, J"
189 "Fairclough, R"
190 "Munoz-munoz, AM"
191 "Stuart, D"
192 "Block, JA"
193 "Bravo-vinaja, A"
194 "Yuan, XJ"
195 "Pereyra, AS"
196 "Csajbok, E"
197 "Tuunanen, T"
198 "Libkind, I"
199 "Mendez-vasquez, RI"
200 "Hullmann, A"
201 "Munoz-fernandez, FJ"
202 "De Osma, ER"
203 "Sahoo, BB"
204 "Gleiser, S"
205 "Bjorneborn, L"
206 "Famoye, F"
207 "Zhang, WW"
208 "Ramirez, AMU"
209 "De Moor, B"
210 "Kurtenbach, E"
211 "Czarnitzki, D"
212 "Yang, LY"
213 "Sen, BK"
214 "Oncu, S"
215 "Yi, H"
216 "Proot, G"
217 "Hoekman, J"
218 "Braun, T"
219 "Paraje, G"
220 "Han, W"
221 "Danell, R"
222 "Ye, FY"
223 "Noyons, ECM"
224 "Welte, T"
225 "Ortega, JL"
226 "Jian, WS"
227 "Fernandez-lopez, JA"
228 "Robert, C"
229 "Biswas, BC"
230 "Pellegrino, D"
231 "Kim, HG"
232 "Scharnhorst, A"
233 "Ajiferuke, I"
234 "Cahill, CL"
235 "Vasconcelos, SMR"
236 "Liang, LM"
237 "Jamal, T"
238 "Thelwall, M"
239 "Barker, K"
240 "Aguillo, IF"
241 "Gomez-crisostomo, R"
242 "Zimmerman, E"
243 "Maura-sardo, M"
244 "Berhidi, A"
245 "Marquiss, M"
246 "Bliziotis, IA"
247 "Cami, J"
248 "Martin, J"
249 "Martin, H"
250 "Torricella-morales, RG"
251 "Chinchilla-rodriguez, Z"
252 "Zsindely, S"
253 "Wu, SJ"
254 "Yamashita, Y"
255 "Valdes, CC"
256 "Dhiensa, R"
257 "Covert-vail, L"
258 "Michan, L"
259 "Schoon, ML"
260 "Morse, SA"
261 "Penumarthy, S"
262 "Pau, MRD"
263 "Navarrete-cortes, J"
264 "Gingras, Y"
265 "Devos, P"
266 "Schmoch, U"
267 "Peck, C"
268 "Nelson, MJ"
269 "Delgado-lopez-cozar, E"
270 "Ding, GH"
271 "Roessner, D"
272 "Cohen, SA"
273 "Rajendram, R"
274 "Hassan-montero, Y"
275 "Salvucci, LJ"
276 "Leydesdorff, L"
277 "Jarvelin, K"
278 "Dutt, B"
279 "Holloway, T"
280 "Sunen-pinyol, E"
281 "Figueira, I"
282 "Iribarren-maestro, I"
283 "Mendonca-araujo, K"
284 "Scutaru, C"
285 "Shin, J"
286 "Hamilton, RD"
287 "Pulgar, R"
288 "Paravic-klijn, T"
289 "Manjunath, M"
290 "Lecocq, C"
291 "Vanhooydonk, G"
292 "Le Jean, T"
293 "Suarez, E"
294 "Xi, Z"
295 "Mendoza-parra, S"
296 "Sadana, R"
297 "Quevedo-blasco, R"
298 "Smith, AG"
299 "Wooding, S"
300 "Gupta, BM"
301 "Cole, V"
302 "Chuang, KY"
303 "Must, U"
304 "Garfield, E"
305 "Tomizawa, H"
306 "Evanco, W"
307 "Veugelers, R"
308 "Smith, C"
309 "Nappila, T"
310 "Smith, A"
311 "Frame, I"
312 "Kretschmer, U"
313 "Kretschmer, T"
314 "Leta, J"
315 "Qian, WH"
316 "Jurado-alameda, E"
317 "Miguel, S"
318 "Li, T"
319 "Verner, JM"
320 "Ostrowski, AD"
321 "Li, Z"
322 "Barbati, AD"
323 "Del Jesus, MIV"
324 "Sun, XX"
325 "Oldenburg, BF"
326 "Casey, DL"
327 "Rodriguez, MA"
328 "De La Moneda, M"
329 "De La Potterie, BV"
330 "Russell, JM"
331 "Shachaf, P"
332 "Cervello, R"
333 "Kuhlmann, S"
334 "Cantu, AG"
335 "Garcia-martinez, AT"
336 "Gamber, T"
337 "Lewison, G"
338 "Perreault, M"
339 "Ramachandran, S"
340 "Park, HW"
341 "Rowlands, I"
342 "Visser, MS"
343 "Cronin, B"
344 "Chung, KF"
345 "Li, CY"
346 "Lucio-arias, D"
347 "Andrews, J"
348 "Shan, S"
349 "Gaudy, JF"
350 "Coleman, A"
351 "Murday, JS"
352 "Buchtel, HA"
353 "Karavasiou, AI"
354 "Rosenberg, G"
355 "Norris, M"
356 "Munoz-molina, A"
357 "Feng, N"
358 "Kipp, MEI"
359 "Grupp, H"
360 "Bowles, CA"
361 "Vasconcellos, JP"
362 "Torres, G"
363 "Kyvik, S"
364 "Benson, D"
365 "Friedrich-nishio, M"
366 "Liu, ZY"
367 "Upham, P"
368 "Diospatonyi, I"
369 "Harzing, AW"
370 "Franke, K"
371 "Van Der Wal, R"
372 "Dhawan, SM"
373 "Guns, R"
374 "Kusma, B"
375 "Michalopoulos, AS"
376 "Smith, DR"
377 "Cothey, V"
378 "Rodriguez, V"
379 "Rios, C"
380 "Senter, SK"
381 "Gil-montoya, JA"
382 "Osareh, F"
383 "Lovegrove, BG"
384 "Hislop, GW"
385 "Hellsten, I"
386 "Ma, Z"
387 "Hong, HD"
388 "Carbonez, A"
389 "Callaert, J"
390 "Bordons, M"
391 "Salmela, R"
392 "Rojo, R"
393 "Vasudevan, R"
394 "Ao, XL"
395 "Borlund, P"
396 "Malpohl, G"
397 "Liu, L"
398 "Devillard, J"
399 "Donovan, C"
400 "Stump, JA"
401 "Wilson, CS"
402 "Mustar, P"
403 "Burrell, QL"
404 "Van Der Wurff, LJ"
405 "Yoon, B"
406 "Zador, E"
407 "Luna-morales, ME"
408 "Leemans, H"
409 "Mogoutov, A"
410 "Checa, P"
411 "Hayashi, T"
412 "Beltran, CL"
413 "Wu, YS"
414 "Liberman, S"
415 "Ambre, S"
416 "Koley, S"
417 "Daim, TU"
418 "Aksnes, DW"
419 "Moreno, L"
420 "Jirina, M"
421 "Kouranos, VD"
422 "Korn, A"
423 "Vann, K"
424 "Lau, CGY"
425 "Lisee, C"
426 "Ball, R"
427 "Horlesberger, M"
428 "Butler, L"
429 "Gouveia, FC"
430 "Cahlik, T"
431 "Zhou, F"
432 "Rafols, I"
433 "Lozano, S"
434 "Bar-ilan, J"
435 "Shaw, W"
436 "Guerrero-bote, VP"
437 "Mendlowicz, M"
438 "Costas, R"
439 "De Pedro-cuesta, J"
440 "Chen, Y"
441 "Martin-moreno, C"
442 "Frandsen, TF"
443 "Rey-rocha, J"
444 "Heerspink, HJL"
445 "Herrero-solana, V"
446 "Gulbrandsen, M"
447 "Lal, K"
448 "Lelu, A"
449 "Courtial, JP"
450 "Mercier, S"
451 "Rousseau, R"
452 "Lopez-illescas, C"
453 "Braam, RR"
454 "Boell, SK"
455 "Jeannin, P"
456 "Teng, LR"
457 "Xie, SD"
458 "Tijssen, R"
459 "Prieto, JA"
460 "Van Zeebroeck, N"
461 "Furusawa, L"
462 "Senker, J"
463 "Meho, LI"
464 "Holmberg, K"
465 "Paley, WB"
466 "Gerdsri, P"
467 "Pan, YT"
468 "Schlemmer, B"
469 "Coutinho, E"
470 "Lima, M"
471 "Clay, MA"
472 "Louvel, A"
473 "Klavans, R"
474 "Lee, S"
475 "Tunger, D"
476 "Echermane, A"
477 "Schoeneck, DJ"
478 "Lascurain-sanchez, ML"
479 "Jansen, D"
480 "Schiebel, E"
481 "Nygaard, S"
482 "Angus, E"
483 "Chen, L2"
484 "Boyack, KW"
485 "Meiss, M"
486 "Ruiz-perez, R"
487 "Crisostomo, RG"
488 "Jonkers, K"
489 "Araujo-ruiz, JA"
490 "Inzelt, A"
491 "Mcallister, RRJ"
492 "Wang, Y"
493 "Srivastava, D"
494 "Chou, CT"
495 "Olsen, TB"
496 "Nikodym, KF"
497 "Jin, BH"
498 "Llamas, G"
499 "Chiang, CH"
500 "Kushmerick, A"
501 "Onghena, P"
502 "Bailon-moreno, R"
503 "Donnadieu, S"
504 "Schubert, T"
505 "Ladner, J"
506 "Heimeriks, G"
507 "Hu, ZH"
508 "Du Plessis, M"
509 "Hanney, S"
510 "Cambrosio, A"
511 "Zamora, H"
512 "Schubert, M"
513 "Laudel, G"
514 "Vargas-quesada, B"
515 "Lin, A"
516 "Cabizuca, M"
517 "Porter, AL"
518 "Mancini, J"
519 "Beery, WL"
520 "Knol, MJ"
521 "Gao, YJ"
522 "Ortiz, AP"
523 "Hartley, J"
524 "Utrilla, AM"
525 "Mehrdad, M"
526 "Egghe, L"
527 "Zych, I"
528 "Tang, R"
529 "Tang, P"
530 "Albert, A"
531 "Karypis, G"
532 "Glanzel, W"
533 "Nicholas, D"
534 "Arreto, CD"
535 "Vasas, L"
536 "Buter, RK"
537 "Wolfram, D"
538 "Yu, H"
539 "Lee, JY"
540 "Glaser, J"
541 "Danell, JAB"
542 "Hoorens, S"
543 "Zapico-alonso, F"
544 "Janssen, MA"
545 "Taylor, M"
546 "Anegon, FD"
547 "Prabowo, R"
548 "Panos, G"
549 "Fernandez, MT"
550 "Burgoon, J"
551 "Marti-lahera, Y"
552 "Faba-perez, C"
553 "Fieschi, M"
554 "Song, IY"
555 "Lariviere, V"
556 "Wald, A"
557 "Landstrom, H"
558 "Pfeil, KM"
559 "Kousha, K"
560 "Dinh, QT"
561 "Small, H"
562 "Wen, HC"
563 "Nicol, MB"
564 "Ingwersen, P"
565 "Lang, PB"
566 "Bauer, G"
567 "Guo, R"
568 "Zhang, L"
569 "De Craen, AJM"
570 "Bontems, V"
571 "Kostoff, RN"
572 "Cortes, HD"
573 "Bahl, M"
574 "Eowles, CA"
575 "Wanless, S"
576 "Takahashi, K"
577 "Telcs, A"
578 "Falagas, ME"
579 "Wu, CZ"
580 "Mendez, B"
581 "Koytcheff, RG"
582 "Madhavi, Y"
583 "Young, T"
584 "Del Rio, JA"
585 "Larowe, G"
586 "Varshavskii, AE"
587 "Pepe, A"
588 "Barjak, F"
589 "Kahane, B"
590 "Shaw, D"
591 "Sancho, R"
592 "Caillieux, N"
593 "Preedy, VR"
594 "Greenwald, HP"
595 "Espinosa-calvo, ME"
596 "Collazo-reyes, F"
597 "Plaza, LM"
598 "Banos, RR"
599 "Vignola-gagne, E"
600 "Paraschakis, K"
601 "Vergidis, PI"
602 "Nebelong-bonnevie, E"
603 "Vadillo-munoz, O"
604 "Da Luz, MP"
605 "Wagner, CS"
606 "Hardy, E"
607 "Horowitz, M"
608 "Heinz, M"
609 "Braga, RJ"
610 "Roy, A"
611 "Cote, G"
612 "Jansz, CNM"
613 "Perez-angon, MA"
614 "Roy, S"
615 "He, TW"
616 "Cassiman, B"
617 "Kumar, S"
618 "Tshiteya, R"
619 "Quarcoo, D"
620 "Kim, H"
621 "Sangam, SL"
622 "Etemad, S"
623 "Magerman, T"
624 "Guo, HC"
625 "Keating, P"
626 "Panzarasa, P"
627 "Nicolaisen, J"
628 "Cruset, AL"
629 "Vanleeuwen, TN"
630 "Hoffmann, U"
631 "Utecht, JT"
632 "Hsu, YHE"
633 "Van Mackelenbergh, M"
634 "Rivett, DA"
*edges
1 53 1
1 322 1
1 314 1
1 361 1
1 461 1
2 33 1
2 55 1
2 249 1
2 320 1
3 103 2
3 41 1
3 389 1
3 15 1
4 619 1
4 116 1
4 374 1
4 284 1
4 224 1
5 506 1
6 111 1
6 594 1
6 127 1
6 380 1
6 234 1
6 519 1
6 68 1
7 327 2
7 157 1
8 63 1
8 36 1
9 243 1
9 522 1
9 293 1
11 571 6
11 360 3
11 138 3
11 169 2
11 496 3
11 12 3
11 424 2
11 142 4
11 152 3
11 29 3
11 574 1
12 571 4
12 360 2
12 138 3
12 31 1
12 496 3
12 152 3
12 142 3
12 169 1
12 29 2
12 574 1
13 394 1
14 41 1
14 276 3
14 532 1
15 389 2
15 41 1
15 623 1
15 103 3
15 458 1
15 290 1
15 186 1
15 616 1
16 478 2
16 174 3
16 441 2
17 67 2
18 451 1
18 448 1
18 83 2
19 337 1
19 299 1
19 311 1
19 509 2
19 583 1
19 188 2
20 105 1
20 96 1
21 621 2
21 289 2
21 393 1
22 390 1
22 266 1
22 629 1
22 438 1
23 622 1
23 89 1
24 397 1
25 376 1
26 94 1
26 446 1
26 481 1
27 631 1
27 508 1
28 232 1
28 238 1
28 155 2
28 334 1
28 385 1
29 571 3
29 360 2
29 138 2
29 169 2
29 152 3
29 142 3
29 496 2
29 574 1
30 314 1
30 235 1
32 179 1
32 564 1
32 395 2
33 249 1
33 55 1
33 320 1
34 444 1
34 629 1
34 146 1
34 520 1
35 97 2
36 63 1
37 97 1
37 588 1
38 148 1
38 312 1
38 313 3
38 366 1
38 630 1
38 588 1
39 502 1
39 598 1
39 269 2
39 85 2
40 608 1
40 236 1
41 389 1
41 103 4
41 314 2
41 468 2
41 532 13
41 107 1
42 451 1
42 203 2
42 526 2
42 149 1
43 579 1
43 121 1
43 153 1
43 624 1
43 321 1
43 357 1
43 315 1
43 632 1
43 302 1
43 50 1
43 318 1
43 457 1
43 270 1
43 215 1
43 207 1
43 394 1
43 431 1
43 226 1
43 175 3
43 562 1
43 345 1
45 306 1
45 382 1
45 301 1
45 384 1
45 275 1
45 319 1
46 383 1
47 544 1
47 491 1
47 100 1
48 337 1
50 357 1
50 270 1
50 153 1
51 212 1
52 356 2
52 251 2
52 445 2
52 201 2
52 514 2
53 314 1
54 297 1
54 527 1
54 162 1
54 133 1
54 185 1
54 362 1
54 433 1
54 410 1
54 187 1
54 603 1
54 323 1
54 379 1
54 154 1
54 263 1
54 545 1
55 249 1
55 320 1
56 578 1
56 421 1
57 388 1
57 408 1
57 532 1
58 433 1
59 518 1
59 553 1
60 505 1
60 70 1
60 265 1
60 576 2
60 129 1
60 435 1
60 292 1
60 376 1
60 472 1
61 390 1
61 355 1
61 126 2
61 163 1
61 256 2
61 93 1
61 66 1
61 95 1
61 267 1
62 190 1
62 85 1
62 295 1
62 288 1
64 188 1
64 337 1
64 299 1
66 267 1
66 93 1
67 521 2
67 358 1
67 590 2
67 532 1
68 111 1
68 594 1
68 127 1
68 380 1
68 234 1
68 519 1
70 576 1
71 178 1
71 217 1
72 477 1
72 517 2
72 462 1
72 118 1
72 333 1
72 177 3
74 78 2
74 580 1
75 451 1
75 236 1
77 307 1
77 329 1
78 580 1
79 110 1
79 248 1
80 571 1
80 618 1
80 531 1
80 558 1
82 381 1
82 263 1
82 287 1
83 451 1
84 494 1
84 499 1
85 202 1
85 449 1
85 288 1
85 486 5
85 598 3
85 316 1
85 445 1
85 269 4
85 190 1
85 295 1
85 502 3
85 328 1
87 418 1
88 443 2
89 622 1
90 571 1
91 571 1
91 396 1
93 267 1
94 446 1
96 105 4
96 567 2
97 588 2
98 108 1
98 198 1
98 586 1
98 612 1
99 281 1
99 604 1
99 204 1
100 544 1
100 491 1
101 621 1
102 575 1
102 245 1
102 371 1
103 389 2
103 623 1
103 532 4
103 458 1
103 107 3
103 209 3
103 378 3
104 376 1
105 567 1
106 571 1
107 314 1
107 532 4
107 186 1
107 209 6
107 378 3
111 594 1
111 127 1
111 380 1
111 234 1
111 519 1
112 451 2
112 526 1
113 571 1
113 351 1
113 424 1
113 400 1
114 547 1
115 143 1
115 339 2
116 374 1
116 560 2
116 224 3
116 284 3
116 344 2
116 619 3
117 525 1
117 176 1
118 333 1
118 462 1
118 566 1
119 349 1
119 228 1
119 534 1
120 277 1
120 309 1
121 175 1
122 544 1
122 606 1
122 485 1
122 465 1
122 473 2
122 585 1
122 550 1
122 259 1
122 484 2
122 144 3
122 279 1
122 261 1
122 232 1
122 415 1
123 428 1
123 563 1
124 253 1
125 557 1
125 143 2
126 256 2
126 171 1
126 555 1
127 380 1
127 234 1
127 519 1
127 594 1
128 194 1
128 554 1
129 292 1
129 472 1
129 435 1
130 264 1
130 171 1
130 555 1
131 401 2
131 134 2
131 538 1
131 454 1
131 160 1
132 529 1
133 603 1
133 185 1
134 401 2
134 160 1
134 538 1
134 454 1
135 513 1
136 231 1
136 474 1
137 515 1
137 434 1
138 571 3
138 360 2
138 169 1
138 496 3
138 142 3
138 152 2
138 574 1
139 489 1
139 291 1
139 250 1
140 591 1
140 164 2
141 489 1
141 551 1
142 571 4
142 360 3
142 169 2
142 496 3
142 574 1
142 152 3
143 557 1
143 186 1
143 532 1
144 544 1
144 585 1
144 550 1
144 259 1
144 261 1
144 485 1
144 415 1
145 254 2
146 444 1
146 629 1
146 520 1
147 354 6
147 239 6
147 257 1
147 501 1
148 230 1
148 366 2
148 607 1
148 440 1
149 451 3
150 172 3
150 546 1
150 514 1
150 251 1
150 158 2
151 340 1
152 571 4
152 360 2
152 169 2
152 496 2
152 574 1
153 357 1
153 270 1
154 433 1
154 362 1
154 527 1
154 323 1
155 232 1
155 626 1
155 385 1
155 238 1
157 327 1
158 172 2
158 546 1
159 442 1
160 401 1
160 454 1
161 285 1
162 410 1
162 545 1
163 390 2
164 390 2
164 591 1
165 227 1
165 187 1
165 263 1
166 578 2
166 246 1
167 191 1
168 633 1
168 536 1
168 223 1
169 571 2
169 360 2
169 496 1
171 264 4
171 555 7
171 599 2
171 611 1
171 425 1
172 546 1
172 514 1
172 251 1
173 447 1
173 237 1
173 582 1
173 278 1
173 300 1
173 573 1
173 617 5
173 614 1
174 193 1
174 441 2
174 282 2
174 478 2
174 439 1
174 262 1
176 525 1
177 477 1
177 517 3
178 217 1
179 564 2
180 211 1
180 532 1
182 326 2
182 286 1
183 507 1
184 225 1
184 530 1
184 498 1
184 597 1
184 459 1
185 603 1
186 532 2
186 616 1
186 209 1
187 227 1
187 297 1
187 263 2
187 379 1
188 337 2
188 299 2
188 583 1
188 542 1
188 509 2
188 311 1
189 423 1
190 295 1
190 288 1
191 482 1
192 571 1
194 554 1
195 412 1
195 258 1
195 628 1
195 330 1
196 535 1
196 244 1
197 571 1
197 618 1
197 360 1
198 586 1
198 612 1
199 247 1
201 445 2
201 356 2
201 251 2
201 514 2
202 598 1
202 328 1
202 502 1
203 526 1
204 281 1
204 604 1
206 537 1
206 233 1
207 315 1
208 225 1
208 549 1
209 314 1
209 532 3
209 378 3
210 429 1
211 532 1
213 416 1
213 610 1
213 229 1
214 571 1
214 260 1
215 294 1
215 394 1
216 526 1
218 368 7
218 406 3
218 252 3
218 532 1
219 296 1
219 391 1
220 460 1
220 329 1
221 541 1
222 451 1
223 536 1
223 255 1
223 452 1
223 342 1
223 633 1
224 374 1
224 560 2
224 284 3
224 344 2
224 619 3
225 232 1
225 524 1
225 549 2
225 240 3
225 459 2
225 511 1
225 377 1
226 632 1
226 562 1
227 263 1
228 592 1
228 401 6
228 349 6
228 503 2
228 534 7
229 610 1
230 607 1
230 366 1
230 440 1
231 474 1
232 385 1
232 377 1
232 240 1
233 537 2
234 380 1
234 519 1
234 594 1
235 314 1
236 451 3
236 608 1
237 300 1
237 617 1
237 614 1
239 354 6
239 257 1
239 501 1
240 377 1
240 459 1
241 543 1
241 595 1
242 434 1
242 532 1
243 522 1
243 293 1
244 535 1
245 575 1
245 371 1
246 353 3
246 601 1
246 578 4
246 600 1
247 332 1
247 280 1
249 320 1
250 489 1
250 291 1
251 356 2
251 514 3
251 445 2
252 368 3
252 406 3
257 354 1
258 412 1
258 628 1
258 330 1
259 544 1
260 571 1
261 485 1
262 282 1
262 439 1
263 297 1
263 381 1
263 287 1
263 379 1
264 570 1
264 555 4
264 599 2
264 611 1
265 505 1
266 504 2
266 479 1
266 556 1
266 370 1
269 486 2
269 598 1
269 502 1
270 357 1
271 517 1
271 338 1
271 272 1
272 517 1
272 338 1
273 593 1
273 337 1
274 335 1
274 436 1
276 571 2
276 531 2
276 308 2
276 346 2
276 310 2
276 584 2
276 572 2
276 340 2
276 385 1
276 618 2
276 387 1
276 526 1
276 605 1
276 432 1
276 396 2
277 309 1
278 617 1
279 465 1
279 606 1
280 332 1
281 516 1
281 609 1
281 469 1
281 604 1
281 437 1
282 438 1
282 439 1
283 314 1
284 374 1
284 560 2
284 344 2
284 619 3
287 381 1
288 295 1
289 621 2
289 393 1
291 489 1
292 472 1
292 435 1
293 522 1
296 391 1
297 379 1
299 337 1
299 509 1
300 617 1
300 614 1
301 306 1
301 319 1
301 384 1
305 411 1
306 319 1
306 384 1
307 329 1
307 532 1
308 571 2
308 531 2
308 310 2
308 584 2
308 572 2
308 618 2
308 396 2
310 571 2
310 531 2
310 584 2
310 572 2
310 618 2
310 396 2
311 583 1
311 337 1
311 509 1
312 313 1
313 630 1
314 429 1
314 532 3
314 565 1
316 502 4
316 449 4
316 598 3
317 445 2
318 345 1
319 384 1
322 361 1
322 461 1
323 433 1
323 362 1
323 527 1
324 451 1
324 497 1
325 471 1
325 399 1
325 428 1
327 587 1
328 502 1
328 598 1
329 460 2
330 596 1
330 407 1
330 628 1
330 412 1
330 470 1
330 414 1
330 613 1
331 590 1
333 462 1
335 436 1
336 365 1
336 359 1
337 593 1
337 450 1
337 625 1
337 409 1
337 509 1
337 510 1
337 583 1
337 493 1
337 523 1
338 517 1
340 387 1
340 620 1
341 451 1
341 533 1
341 442 1
342 452 2
342 428 1
343 590 1
343 463 1
344 560 2
344 619 2
347 558 1
347 352 1
347 571 1
349 592 1
349 401 5
349 503 2
349 534 6
351 571 1
351 424 1
351 400 1
352 558 1
352 571 1
353 578 3
353 601 1
353 600 1
354 501 1
356 445 2
356 514 2
358 521 1
359 365 1
360 571 4
360 496 2
360 618 1
361 461 1
362 433 1
362 527 1
363 495 1
364 561 1
364 500 1
366 607 1
366 440 1
367 561 1
368 406 3
369 371 1
370 479 1
370 556 1
370 504 1
371 575 1
373 451 1
374 619 1
375 578 1
376 576 1
376 634 1
380 519 1
380 594 1
386 451 1
386 492 1
386 413 1
386 467 1
388 408 1
388 532 1
389 458 1
390 438 8
390 629 1
390 419 1
393 621 1
396 571 3
396 584 2
396 572 2
396 531 2
396 618 2
398 455 1
399 471 1
399 428 2
400 571 1
400 424 1
401 538 1
401 454 1
401 534 6
401 592 1
401 503 2
402 625 1
402 409 1
402 510 1
404 629 1
404 569 1
405 474 1
407 596 1
407 613 1
408 532 1
409 450 1
409 625 2
409 510 2
409 589 1
410 545 1
412 628 1
413 451 1
413 467 1
413 492 1
414 470 1
415 585 1
415 550 1
417 466 1
419 438 1
420 430 1
421 578 1
422 577 1
424 571 9
424 581 6
425 555 1
426 532 1
426 475 1
427 480 1
428 563 1
428 471 1
429 565 1
431 579 1
431 624 1
433 527 1
434 515 1
434 476 1
434 532 1
435 472 1
437 516 1
437 609 1
437 469 1
438 629 1
440 607 1
441 478 2
442 451 1
442 627 2
442 602 1
444 629 1
444 520 1
445 514 2
447 617 1
449 502 4
449 598 3
450 625 1
450 510 1
451 483 1
451 467 1
451 532 1
451 526 3
451 492 1
451 497 2
451 568 1
452 546 1
456 615 1
458 488 1
465 606 1
467 492 1
468 532 3
469 516 1
469 609 1
473 484 6
477 517 1
479 504 1
479 556 1
487 543 1
487 595 1
489 551 1
490 512 1
491 544 1
494 499 1
496 571 3
496 574 1
500 561 1
502 598 5
503 534 2
504 556 1
508 631 1
509 583 1
510 625 2
511 524 1
511 549 1
516 609 1
518 553 1
519 594 1
520 629 1
524 549 1
530 597 2
531 571 3
531 558 1
531 572 2
531 618 3
531 584 2
534 592 1
536 633 1
539 620 1
543 595 2
543 552 1
548 578 1
550 585 1
555 599 2
555 611 1
558 571 2
558 618 1
560 619 2
562 632 1
569 629 1
571 584 2
571 572 2
571 574 1
571 581 6
571 618 4
572 584 2
572 618 2
573 617 1
573 582 1
578 600 1
578 601 1
579 624 1
582 617 1
584 618 2
586 612 1
596 613 1
599 611 1
600 601 1
614 617 1


================================================
FILE: linkpred/__init__.py
================================================
"""linkpred, a Python package for link prediction"""
from .linkpred import *

__version__ = "0.6"


================================================
FILE: linkpred/cli.py
================================================
"""CLI handling"""
import argparse
import json
import logging
import sys

import yaml

from .exceptions import LinkPredError
from .linkpred import LinkPred
from .predictors import all_predictors

log = logging.getLogger("linkpred")

__all__ = ["load_profile", "get_config", "handle_arguments"]


def setup_logger():
    streamhandler = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter(
        "%(asctime)s - %(levelname)s - %(message)s", "%H:%M:%S"
    )
    streamhandler.setFormatter(formatter)
    log.addHandler(streamhandler)


def load_profile(fname):
    """Load the JSON or YAML profile with the given filename"""
    try:
        with open(fname) as f:
            if fname.endswith((".yaml", ".yml")):
                return yaml.safe_load(f)
            return json.load(f)
    except Exception as err:
        msg = f"Encountered error while loading profile '{fname}'. "
        raise LinkPredError(msg) from err


def get_config(args=None):
    """Get configuration as supplied by the user

    If a YAML-or JSON-based profile is supplied, any settinsg therein take
    priority over command-line arguments.

    """
    args = handle_arguments(args)

    profile = args.pop("profile")

    config = {}
    predictorlist = [{"name": predictor} for predictor in args.pop("predictors")]
    config["predictors"] = predictorlist
    config.update(args)

    if profile:
        config.update(load_profile(profile))

    return config


def handle_arguments(args=None):
    """Get nice CLI interface and return arguments."""

    parser = argparse.ArgumentParser(
        description="Easy link prediction tool",
        usage="%(prog)s training-file [test-file] [options]",
    )

    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "--debug",
        action="store_true",
        dest="debug",
        default=False,
        help="Show debug messages",
    )
    group.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        dest="quiet",
        default=False,
        help="Don't show info messages",
    )

    # TODO allow case-insensitive match
    output_types = [
        "recall-precision",
        "f-score",
        "roc",
        "cache-predictions",
        "cache-evaluations",
        "fmax",
    ]
    output_help = (
        "Type of output(s) to produce (default: recall-precision). "
        "Allowed values are: " + ", ".join(output_types)
    )
    parser.add_argument(
        "-o",
        "--output",
        help=output_help,
        nargs="*",
        choices=output_types,
        default=output_types[0:1],
        metavar="OUTPUT",
    )

    # TODO allow case-insensitive match
    parser.add_argument(
        "-f",
        "--chart-filetype",
        default="pdf",
        help="File type for charts (default: %(default)s)",
    )

    parser.add_argument(
        "-i",
        "--no-interpolation",
        dest="interpolation",
        help="Do not interpolate recall-precision charts",
        action="store_false",
        default=True,
    )

    # TODO allow case-insensitive match
    predictors = [p.__name__ for p in all_predictors()]
    predictor_help = (
        "Predictor(s) to use for link prediction. "
        "Allowed values are: " + ", ".join(predictors)
    )
    parser.add_argument(
        "-p",
        "--predictors",
        nargs="*",
        choices=predictors,
        default=[],
        help=predictor_help,
        metavar="PREDICTOR",
    )

    all_help = "Predict all links, including ones present in the training network"
    parser.add_argument(
        "-a",
        "--all",
        action="store_const",
        dest="exclude",
        const="",
        default="old",
        help=all_help,
    )

    parser.add_argument("-P", "--profile", help="JSON/YAML profile file")

    parser.add_argument("training-file", help="File with the training network")
    parser.add_argument("test-file", nargs="?", help="File with the test network")

    results = parser.parse_args(args)

    if results.debug:
        log.setLevel(logging.DEBUG)
    elif results.quiet:
        log.setLevel(logging.WARNING)
    else:
        log.setLevel(logging.INFO)

    # Return as plain dictionary
    return vars(results)


def main(args=None):
    """Main function

    This gets called if one invokes linkpred from the command-line
    """
    config = get_config(args)
    setup_logger()
    linkpred = LinkPred(config)
    linkpred.preprocess()
    linkpred.predict_all()
    linkpred.setup_output()
    linkpred.process_predictions()


================================================
FILE: linkpred/evaluation/__init__.py
================================================
"""Module for evaluating link prediction results"""
from .scoresheet import *
from .static import *


================================================
FILE: linkpred/evaluation/listeners.py
================================================
import copy
import logging
from time import localtime, strftime

import smokesignal

from ..util import interpolate
from .static import EvaluationSheet

log = logging.getLogger(__name__)

__all__ = [
    "EvaluatingListener",
    "CachePredictionListener",
    "Listener",
    "Plotter",
    "CacheEvaluationListener",
    "FMaxListener",
    "RecallPrecisionPlotter",
    "FScorePlotter",
    "ROCPlotter",
    "PrecisionAtKListener",
    "MarkednessPlotter",
]


def _timestamped_filename(basename, ext="txt"):
    return basename + strftime("_%Y-%m-%d_%H.%M.", localtime()) + ext


class Listener:
    def __init__(self):
        smokesignal.on("dataset_finished", self.on_dataset_finished)
        smokesignal.on("run_finished", self.on_run_finished)

    def on_dataset_finished(self, dataset):
        pass

    def on_run_finished(self):
        pass


class EvaluatingListener(Listener):
    def __init__(self, **kwargs):
        smokesignal.on("prediction_finished", self.on_prediction_finished)
        self.params = kwargs

        super().__init__()

    def on_prediction_finished(self, scoresheet, dataset, predictor):
        evaluation = EvaluationSheet(scoresheet, **self.params)
        smokesignal.emit(
            "evaluation_finished",
            evaluation=evaluation,
            dataset=dataset,
            predictor=predictor,
        )


class CachePredictionListener(Listener):
    def __init__(self):
        smokesignal.on("prediction_finished", self.on_prediction_finished)
        super().__init__()
        self.encoding = "utf-8"

    def on_prediction_finished(self, scoresheet, dataset, predictor):
        self.fname = _timestamped_filename(f"{dataset}-{predictor}-predictions")
        scoresheet.to_file(self.fname)


class CacheEvaluationListener(Listener):
    def __init__(self):
        smokesignal.on("evaluation_finished", self.on_evaluation_finished)
        super().__init__()

    def on_evaluation_finished(self, evaluation, dataset, predictor):
        self.fname = _timestamped_filename(f"{dataset}-{predictor}-predictions")
        evaluation.to_file(self.fname)


class FMaxListener(Listener):
    def __init__(self, name, beta=1):
        self.beta = beta
        self.fname = _timestamped_filename("%s-Fmax" % name)

        smokesignal.on("evaluation_finished", self.on_evaluation_finished)
        super().__init__()

    def on_evaluation_finished(self, evaluation, dataset, predictor):
        fmax = evaluation.f_score(self.beta).max()

        status = f"{dataset}\t{predictor}\t{fmax:.4f}\n"

        with open(self.fname, "a") as f:
            f.write(status)
        log.info("Evaluation finished: %s", status)


class PrecisionAtKListener(Listener):
    def __init__(self, name, k=10):
        self.k = k
        self.fname = _timestamped_filename("%s-precision-at-%d" % (name, self.k))

        smokesignal.on("evaluation_finished", self.on_evaluation_finished)
        super().__init__()

    def on_evaluation_finished(self, evaluation, dataset, predictor):
        precision = evaluation.precision()[self.k]

        status = f"{dataset}\t{predictor}\t{precision:.4f}\n"
        with open(self.fname, "a") as f:
            f.write(status)
        log.info("Evaluation finished: %s", status)


GENERIC_CHART_LOOKS = [
    "k-",
    "k--",
    "k.-",
    "k:",
    "r-",
    "r--",
    "r.-",
    "r:",
    "b-",
    "b--",
    "b.-",
    "b:",
    "g-",
    "g--",
    "g.-",
    "g:",
    "c-",
    "c--",
    "c.-",
    "c:",
    "y-",
    "y--",
    "y.-",
    "y:",
]


class Plotter(Listener):
    def __init__(self, name, xlabel="", ylabel="", filetype="pdf", chart_looks=None):
        import matplotlib.pyplot as plt

        self.name = name
        self.filetype = filetype
        self.chart_looks = chart_looks
        self._charttype = ""
        self._legend_props = {"prop": {"size": "x-small"}}
        self.fig = plt.figure()
        ax = self.fig.add_axes([0.1, 0.1, 0.8, 0.8], xlabel=xlabel, ylabel=ylabel)
        ax.set_ylim((0, 1))
        self._x = []
        self._y = []

        smokesignal.on("evaluation_finished", self.on_evaluation_finished)
        super().__init__()

    def add_line(self, predictor=""):
        ax = self.fig.axes[0]
        ax.plot(self._x, self._y, self.chart_look(), label=predictor)

        log.debug(
            "Added line with %d points: start = (%.2f, %.2f), end = (%.2f, %.2f)",
            len(self._x),
            self._x[0],
            self._y[0],
            self._x[-1],
            self._y[-1],
        )

    def chart_look(self, default=None):
        if not self.chart_looks:
            if not default:
                default = GENERIC_CHART_LOOKS
            self.chart_looks = copy.copy(default)
        return self.chart_looks.pop(0)

    def on_evaluation_finished(self, evaluation, dataset, predictor):
        self.setup_coords(evaluation)
        self.add_line(predictor)

    def on_run_finished(self):
        # Fix looks
        for ax in self.fig.axes:
            ax.legend(**self._legend_props)

        # Save to file
        self.fname = _timestamped_filename(
            f"{self.name}-{self._charttype}", self.filetype
        )
        self.fig.savefig(self.fname)


class RecallPrecisionPlotter(Plotter):
    def __init__(
        self, name, xlabel="Recall", ylabel="Precision", *, interpolation=True, **kwargs
    ):
        super().__init__(name, xlabel, ylabel, **kwargs)
        self._charttype = "recall-precision"
        self.interpolation = interpolation

    def add_line(self, predictor=""):
        if self.interpolation:
            self._y = interpolate(self._y)
        Plotter.add_line(self, predictor)

    def setup_coords(self, evaluation):
        self._x = evaluation.recall()
        self._y = evaluation.precision()


class FScorePlotter(Plotter):
    def __init__(self, name, xlabel="#", ylabel="F-score", beta=1, **kwargs):
        super().__init__(name, xlabel, ylabel, **kwargs)
        self._charttype = "F-Score"
        self.beta = beta

    def setup_coords(self, evaluation):
        self._x = range(len(evaluation))
        self._y = evaluation.f_score(self.beta)


class ROCPlotter(Plotter):
    def __init__(
        self, name, xlabel="False pos. rate", ylabel="True pos. rate", **kwargs
    ):
        super().__init__(name, xlabel, ylabel, **kwargs)
        self._charttype = "ROC"

    def setup_coords(self, evaluation):
        self._x = evaluation.fallout()
        self._y = evaluation.recall()


class MarkednessPlotter(Plotter):
    def __init__(self, name, xlabel="Miss", ylabel="Precision", **kwargs):
        super().__init__(name, xlabel, ylabel, **kwargs)
        self._charttype = "Markedness"
        self._legend_props["loc"] = "upper left"

    def setup_coords(self, evaluation):
        self._x = evaluation.miss()
        self._y = evaluation.precision()


================================================
FILE: linkpred/evaluation/scoresheet.py
================================================
import logging
from collections import defaultdict

import networkx as nx
from networkx.readwrite.pajek import make_qstr

log = logging.getLogger(__name__)
__all__ = ["Pair", "BaseScoresheet", "Scoresheet"]


class BaseScoresheet(defaultdict):
    """Score sheet for evaluation of IR and similar

    This is a simple dict-like object, whose values are numeric
    (floats). It adds the methods `ranked_items` and `top`.

    Example
    -------
    >>> data = {('a', 'b'): 0.8, ('b', 'c'): 0.5, ('c', 'a'): 0.2}
    >>> sheet = Scoresheet(data)
    >>> for (x, y), score in sheet.ranked_items():
    ...     print("{}-{}: {}".format(x, y, score))
    b-a: 0.8
    c-b: 0.5
    c-a: 0.2

    """

    def __init__(self, data=None):
        defaultdict.__init__(self, float)
        if data:
            self.update(self.process_data(data))

    def __setitem__(self, key, val):
        dict.__setitem__(self, key, float(val))

    def process_data(self, data):
        """Can be overridden by child classes"""
        return data

    def ranked_items(self, threshold=None):
        """Return items in decreasing order of their score

        Arguments
        ---------
        threshold : int
            Maximum number of items to return (in total)

        Returns
        -------
        (item, score) : tuple of item and score

        """
        threshold = threshold or len(self)
        log.debug("Called Scoresheet.ranked_items(): threshold=%d", threshold)

        # Sort first by score, then by key. This way, we always get the same
        # ranking, even in case of ties.
        # We use the tmp structure because it is much faster than
        # itemgetter(1, 0).
        tmp = ((score, key) for key, score in self.items())
        ranked_data = sorted(tmp, reverse=True)

        for score, key in ranked_data[:threshold]:
            yield key, score

    def top(self, n=10):
        return dict(self.ranked_items(threshold=n))

    @staticmethod
    def from_record(line, delimiter="\t"):
        line = line.rstrip("\n")
        return line.rstrip("\n").split(delimiter)

    @staticmethod
    def to_record(key, value, delimiter="\t"):
        key, value = map(make_qstr, (key, value))
        return f"{key}{delimiter}{value}\n"

    @classmethod
    def from_file(cls, fname, delimiter="\t", encoding="utf-8"):
        """Create new instance from CSV file *fname*"""
        d = cls()
        with open(fname, "rb") as fh:
            for line in fh:
                key, score = cls.from_record(line.decode(encoding), delimiter)
                d[key] = score
        return d

    def to_file(self, fname, delimiter="\t", encoding="utf-8"):
        """Save to CSV file *fname*"""
        with open(fname, "wb") as fh:
            for key, score in self.ranked_items():
                fh.write(self.to_record(key, score, delimiter).encode(encoding))


class Pair:
    """An unsorted pair of things.

    We could probably also use frozenset for this, but a Pair class opens
    possibilities for the future, such as extensions to 'directed' pairs
    (where the order is important) or to self-loops (where the two elements
    are the same).

    Example
    -------
    >>> t = ('a', 'b')
    >>> Pair(t) == Pair(*t) == Pair('b', 'a')
    True

    """

    def __init__(self, *args):
        if len(args) == 1:
            key = args[0]
            if isinstance(key, Pair):
                a, b = key.elements
            elif isinstance(key, tuple) and len(key) == 2:
                a, b = key
            else:
                raise TypeError("Key '%s' is not a Pair or tuple." % (key))
        elif len(args) == 2:
            a, b = args
        else:
            msg = "__init__() takes 1 or 2 arguments in addition to self"
            raise TypeError(msg)
        # For link prediction, a and b are two different nodes
        assert a != b, f"Predicted link ({a}, {b}) is a self-loop!"
        self.elements = self._sorted_tuple((a, b))

    @staticmethod
    def _sorted_tuple(t):
        a, b = t
        try:
            return (a, b) if a > b else (b, a)
        except TypeError:
            # Different node types. This does not hande all possible edge
            # cases but should be enough for most real-world scenarios.
            return (a, b) if str(a) > str(b) else (b, a)

    def __eq__(self, other):
        try:
            return self.elements == other.elements
        except AttributeError:
            return self.elements == self._sorted_tuple(other)

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        try:
            return self.elements < other.elements
        except AttributeError:
            return self.elements < self._sorted_tuple(other)

    def __gt__(self, other):
        try:
            return self.elements > other.elements
        except AttributeError:
            return self.elements > self._sorted_tuple(other)

    def __le__(self, other):
        return self < other or self == other

    def __ge__(self, other):
        return self > other or self == other

    def __getitem__(self, idx):
        return self.elements[idx]

    def __hash__(self):
        return hash(self.elements)

    def __str__(self):
        return "{} - {}".format(*self.elements)

    def __repr__(self):
        return "Pair%s" % repr(self.elements)

    def __iter__(self):
        return iter(self.elements)

    def __len__(self):
        return len(self.elements)


class Scoresheet(BaseScoresheet):
    """Scoresheet for link prediction

    Scoresheet's keys are always Pairs.

    """

    def __getitem__(self, key):
        return BaseScoresheet.__getitem__(self, Pair(key))

    def __setitem__(self, key, val):
        BaseScoresheet.__setitem__(self, Pair(key), float(val))

    def __delitem__(self, key):
        return dict.__delitem__(self, Pair(key))

    def process_data(self, data, weight="weight"):
        if isinstance(data, dict):
            return {Pair(k): float(v) for k, v in data.items()}
        if isinstance(data, nx.Graph):
            return {Pair(u, v): float(d[weight]) for u, v, d in data.edges(data=True)}
        # We assume that data is some sort of iterable, like a list or tuple
        return {Pair(k): float(v) for k, v in data}

    @staticmethod
    def from_record(line, delimiter="\t"):
        u, v, score = line.rstrip("\n").split(delimiter)
        return (u, v), score

    @staticmethod
    def to_record(key, value, delimiter="\t"):
        u, v = key
        u, v, score = map(make_qstr, (u, v, value))
        return f"{u}{delimiter}{v}{delimiter}{score}\n"


================================================
FILE: linkpred/evaluation/static.py
================================================
import logging

import numpy as np

from .scoresheet import BaseScoresheet

log = logging.getLogger(__name__)

__all__ = ["EvaluationSheet", "StaticEvaluation", "UndefinedError"]


class UndefinedError(Exception):
    """Raised when the method's result is undefined"""


class StaticEvaluation:
    """Static evaluation of IR"""

    def __init__(self, retrieved=None, relevant=None, universe=None):
        """
        Initialize IR evaluation.

        We determine the following table:

        +--------------+---------------+
        | tp           | fp            |
        | ret & rel    | ret & ~rel    |
        +--------------+---------------+
        | fn           | tn            |
        | ~ret & rel   | ~ret & ~rel   |
        +--------------+---------------+

        Arguments
        ---------
        retrieved : a list or set
            iterable of the retrieved items

        relevant : a list or set
            iterable of the relevant items

        universe : a list or set, an int or None
            If universe is an iterable, it is interpreted as the set of all
            items in the system. If universe is an int, it is interpreted as
            the *number* of items in the system. This allows for fewer checks
            but is more memory-efficient. If universe is None, it is supposed
            to be unknown. This still allows for some measures, including
            precision and recall, to be calculated.

        """
        retrieved = set(retrieved) if retrieved else set()
        relevant = set(relevant) if relevant else set()

        self.fp = retrieved - relevant
        self.fn = relevant - retrieved
        self.tp = retrieved & relevant
        if universe is None:
            self.tn = None
            self.num_universe = -1
        elif isinstance(universe, int):
            self.tn = None
            self.num_universe = universe
            if len(retrieved) > self.num_universe or len(relevant) > self.num_universe:
                msg = "Retrieved cannot be larger than universe."
                raise ValueError(msg)
        else:
            universe = set(universe)
            if not (retrieved <= universe and relevant <= universe):
                msg = "Retrieved and relevant should be subsets of universe."
                raise ValueError(msg)
            self.num_universe = len(universe)
            self.tn = universe - retrieved - relevant
            del universe
        self.update_counts()

    def update_counts(self):
        self.num_fp = len(self.fp)
        self.num_fn = len(self.fn)
        self.num_tp = len(self.tp)
        if self.tn is not None:
            self.num_tn = len(self.tn)
        elif self.num_universe == -1:
            self.num_tn = -1
        else:
            self.num_tn = self.num_universe - self.num_fp - self.num_fn - self.num_tp
            assert self.num_tn >= 0

    def add_retrieved_item(self, item):
        self.update_retrieved({item})

    def update_retrieved(self, new):
        new = set(new)

        if not (new.isdisjoint(self.tp) and new.isdisjoint(self.fp)):
            msg = "One or more elements in `new` have already been retrieved."
            raise ValueError(msg)

        relevant_new = new & self.fn
        nonrelevant_new = new - relevant_new

        self.tp |= relevant_new
        self.fp |= nonrelevant_new
        if self.tn:
            if not new <= self.fn | self.tn:
                msg = (
                    "Newly retrieved items should be a subset "
                    "of currently unretrieved items."
                )
                raise ValueError(msg)
            self.tn -= nonrelevant_new
        self.fn -= relevant_new
        self.update_counts()


def ensure_defined(func):
    def _wrapper(self, *args, **kwargs):
        if self.data.shape[0] == 0:
            msg = "Measure is undefined if there are no relevant or retrieved items"
            raise UndefinedError(msg)
        return func(self, *args, **kwargs)

    return _wrapper


def ensure_universe_known(func):
    def _wrapper(self, *args, **kwargs):
        # If tn is -1 somewhere, we know that universe is not defined.
        if np.where(self.tn == -1, True, False).any():
            msg = "Measure is undefined if universe is unknown"
            raise UndefinedError(msg)
        return func(self, *args, **kwargs)

    return _wrapper


class EvaluationSheet:
    def __init__(self, data=None, relevant=None, universe=None):
        if isinstance(data, BaseScoresheet):
            if relevant is None:
                msg = (
                    "Cannot create evaluation sheet from "
                    "scoresheet without set of relevant items"
                )
                raise TypeError(msg)
            log.debug("Counting for evaluation sheet...")
            static = StaticEvaluation(relevant=relevant, universe=universe)
            # Initialize empty array of right dimensions
            # 4 columns for tp, fp, fn, tn
            self.data = np.empty((len(data), 4))
            for i, (prediction, _) in enumerate(data.ranked_items()):
                static.add_retrieved_item(prediction)
                self.data[i] = (
                    static.num_tp,
                    static.num_fp,
                    static.num_fn,
                    static.num_tn,
                )
            log.debug("Finished counting evaluation sheet...")
        elif isinstance(data, np.ndarray):
            self.data = data
        else:
            msg = f"Cannot create evaluation sheet from unknown data type {type(data)}."
            raise TypeError(msg)

    def __len__(self):
        return len(self.data)

    @property
    def tp(self):
        return self.data[:, 0]

    @property
    def fp(self):
        return self.data[:, 1]

    @property
    def fn(self):
        return self.data[:, 2]

    @property
    def tn(self):
        return self.data[:, 3]

    def to_file(self, fname, *args, **kwargs):
        np.savetxt(fname, self.data, *args, **kwargs)

    @classmethod
    def from_file(cls, fname, *args, **kwargs):
        data = np.loadtxt(fname, *args, **kwargs)
        return cls(data)

    @ensure_defined
    def precision(self):
        return self.tp / (self.tp + self.fp)

    @ensure_defined
    def recall(self):
        return self.tp / (self.tp + self.fn)

    @ensure_defined
    @ensure_universe_known
    def fallout(self):
        return self.fp / (self.fp + self.tn)

    @ensure_defined
    @ensure_universe_known
    def miss(self):
        return self.fn / (self.fn + self.tn)

    @ensure_defined
    @ensure_universe_known
    def accuracy(self):
        return (self.tp + self.tn) / self.data.sum(axis=1)

    @ensure_defined
    def f_score(self, beta=1):
        r"""Compute F-score

        F is the harmonic mean of precision and recall:

            F = 2PR / (P + R)

        We use the generalized form:

            F = (beta^2 + 1)PR / (beta^2 P + R)
              = (beta^2 + 1)tp / ((beta^2 + 1)tp + beta^2fn + fp)

        The parameter beta allows assigning more weight to precision or recall.
        If beta > 1, recall is emphasized over precision. If beta < 1,
        precision is emphasized over recall.

        """
        beta2 = beta**2
        beta2_tp = (beta2 + 1) * self.tp
        return beta2_tp / (beta2_tp + beta2 * self.fn + self.fp)

    @ensure_defined
    @ensure_universe_known
    def generality(self):
        """Compute generality of the query

        Generality G is defined as:

            G = (tp + fn) / (tp + fn + fp + tp)

        Returns
        -------

        G : float

        """
        # Return single number: this is constant wrt what is retrieved
        return ((self.tp + self.fn) / self.data.sum(axis=1))[0]


================================================
FILE: linkpred/exceptions.py
================================================
"""Package-specific exceptions"""


class LinkPredError(Exception):
    """Link prediction error"""


================================================
FILE: linkpred/linkpred.py
================================================
"""linkpred main module"""
import contextlib
import logging
import os

import networkx as nx
import smokesignal

from . import predictors
from .evaluation import Pair
from .evaluation import listeners as l
from .exceptions import LinkPredError
from .preprocess import (
    without_low_degree_nodes,
    without_selfloops,
    without_uncommon_nodes,
)

log = logging.getLogger(__name__)

__all__ = ["LinkPred", "read_network"]


def for_comparison(G, exclude=None):
    """Return the result in a format, suitable for comparison.

    In practice this means we return it as a set of Pairs.

    """
    if not exclude:
        return set(G.edges())

    exclude = {Pair(u, v) for u, v in exclude}
    return {Pair(u, v) for u, v in G.edges()} - exclude


def pretty_print(name, params=None):
    """Pretty print a predictor name

    Arguments
    ---------
    name : string
        predictor name

    params : dict or None
        dictionary of parameter name -> value

    """
    if not params:
        return name

    pretty_params = ", ".join(f"{k} = {str(v)}" for k, v in params.items())
    return f"{name} ({pretty_params})"


def _read_pajek(*args, **kwargs):
    """Read Pajek file and make sure that we get an nx.Graph or nx.DiGraph"""
    G = nx.read_pajek(*args, **kwargs)
    edges = G.edges()
    if len(set(edges)) < len(edges):  # multiple edges
        log.warning("Network contains multiple edges. These will be ignored.")

    return nx.DiGraph(G) if G.is_directed() else nx.Graph(G)


FILETYPE_READERS = {
    ".net": _read_pajek,
    ".gml": nx.read_gml,
    ".graphml": nx.read_graphml,
    ".gexf": nx.read_gexf,
    ".edgelist": nx.read_edgelist,
    ".adjlist": nx.read_adjlist,
}


def read_network(fh):
    """Read the network file and return as nx.Graph or nx.DiGraph

    Arguments
    ---------
    fh : string
        file handle or file name

    """
    try:
        fname = fh.name
    except AttributeError:
        # fh is a string or path
        fname = fh

    ext = os.path.splitext(fname.lower())[1]
    try:
        read = FILETYPE_READERS[ext]
        log.info("Reading file '%s'...", fname)
        network = read(fh)
        log.info("Successfully read file.")
    except KeyError as err:
        msg = (
            f"File '{fname}' is of an unknown type. "
            f"Known types are: {', '.join(FILETYPE_READERS)}."
        )
        raise LinkPredError(msg) from err

    return network


class LinkPred:

    """linkpred main object

    LinkPred stores all configuration and provides a high-level interface to
    most functionality.

    """

    def __init__(self, config=None):
        # default config
        self.config = {
            "chart_filetype": "pdf",
            "eligible": None,
            "interpolation": False,
            "label": "",
            "min_degree": 1,
            "exclude": "old",
            "output": ["recall-precision"],
            "predictors": [],
            "test-file": None,
            "training-file": None,
        }
        if config:
            self.config.update(config)
        log.debug("Config: %s", self.config)

        if not self.config["predictors"]:
            msg = "No predictor specified. Aborting..."
            raise LinkPredError(msg)

        self.label = (
            self.config["label"] or os.path.splitext(self.config["training-file"])[0]
        )
        self.training = self.network("training-file")
        self.test = self.network("test-file")
        self.evaluator = None
        self.listeners = []

    @property
    def excluded(self):
        """Get set of links that should not be predicted"""
        exclude = self.config["exclude"]
        if not exclude:
            return set()  # No nodes are excluded
        if exclude == "old":
            return set(self.training.edges())
        if exclude == "new":
            return set(nx.non_edges(self.training))

        msg = (
            f"Value '{exclude}' for exclude is unexpected. Use either 'old', 'new' or "
            "empty string '' (for no exclusions)"
        )
        raise LinkPredError(msg)

    def network(self, key):
        """Get network for given key"""
        with contextlib.suppress(KeyError):
            network_file = self.config[key]
        if network_file:
            return read_network(network_file)
        return None

    def preprocess(self):
        """Preprocess all networks according to configuration"""

        log.info("Starting preprocessing...")

        def preprocessed(G):
            return without_low_degree_nodes(
                without_selfloops(G), minimum=self.config["min_degree"]
            )

        if self.test:
            networks = [preprocessed(G) for G in (self.training, self.test)]
            self.training, self.test = without_uncommon_nodes(networks)
        else:  # Only a training network
            self.training = preprocessed(self.training)

        log.info("Finished preprocessing.")

    def setup_output(self):
        """Configure listeners"""
        filetype = self.config["chart_filetype"]
        interpolation = self.config["interpolation"]

        listeners = {
            "cache-predictions": (l.CachePredictionListener, False, {}),
            "recall-precision": (
                l.RecallPrecisionPlotter,
                True,
                {
                    "name": self.label,
                    "filetype": filetype,
                    "interpolation": interpolation,
                },
            ),
            "f-score": (
                l.FScorePlotter,
                True,
                {"name": self.label, "filetype": filetype},
            ),
            "roc": (l.ROCPlotter, True, {"name": self.label, "filetype": filetype}),
            "fmax": (l.FMaxListener, True, {"name": self.label}),
            "cache-evaluations": (l.CacheEvaluationListener, True, {}),
        }

        for output in self.config["output"]:
            name = output.lower()
            listener, evaluating, kwargs = listeners[name]

            if evaluating:
                if not self.test:
                    msg = f"Cannot evaluate ({output}) without test network"
                    raise LinkPredError(msg)

                # Set up an 'evaluator': a listener that routes predictions
                # and turns them into evaluations
                if not self.evaluator:
                    test_set = for_comparison(self.test, exclude=self.excluded)
                    n = len(self.test)
                    # Universe = all possible edges, except for the ones that
                    # we no longer consider because they're excluded
                    # Make sure we get an int here.
                    num_universe = n * (n - 1) // 2 - len(self.excluded)
                    self.evaluator = l.EvaluatingListener(
                        relevant=test_set, universe=num_universe
                    )

            self.listeners.append(listener(**kwargs))
            log.debug("Added listener for '%s'", output)

    def do_predict_all(self):
        """Generator that yields predictions based on training network

        Yields
        ------
        (label, scoresheet) : a 2-tuple
            2-tuple consisting of a string (label of the prediction) and
            a Scoresheet (actual predictions)

        """
        for predictor_profile in self.config["predictors"]:
            params = predictor_profile.get("parameters", {})
            name = predictor_profile["name"]
            predictor_class = getattr(predictors, name)
            label = predictor_profile.get("displayname", pretty_print(name, params))

            log.info("Executing %s...", label)
            predictor = predictor_class(
                self.training, eligible=self.config["eligible"], excluded=self.excluded
            )
            scoresheet = predictor.predict(**params)
            log.info("Finished executing %s.", label)

            # XXX TODO Do we need name?
            yield name, scoresheet

    def predict_all(self):
        """Perform all predictions according to configuration

        The predictions are only executed when `process_predictions` is called
        or when `LinkPred.predictions` is accessed in some other way.

        """
        self.predictions = self.do_predict_all()
        return self.predictions

    def process_predictions(self):
        """Process (evaluate, log...) all predictions according to config"""

        # The following loop actually executes the predictors
        for predictorname, scoresheet in self.predictions:
            log.debug(
                "Predictor '%s' yields %d predictions", predictorname, len(scoresheet)
            )
            smokesignal.emit(
                "prediction_finished",
                scoresheet=scoresheet,
                dataset=self.label,
                predictor=predictorname,
            )

        smokesignal.emit("dataset_finished", dataset=self.label)
        smokesignal.emit("run_finished")
        log.info("Prediction run finished")


================================================
FILE: linkpred/network/__init__.py
================================================
from .addremove import *
from .algorithms import *


================================================
FILE: linkpred/network/addremove.py
================================================
import logging
import random

import networkx as nx

log = logging.getLogger(__name__)

__all__ = ["add_random_edges", "remove_random_edges", "add_remove_random_edges"]


def assert_is_percentage(pct):
    if not 0 <= pct <= 1:
        msg = "Percentage should be float between 0 and 1"
        raise ValueError(msg)


def add_random_edges(G, pct):
    """Add `n` random edges to G (`n` = fraction of current edge count)

    Parameters
    ----------
    G : a networkx.Graph
        the network

    pct : float
        A percentage (between 0 and 1)
    """
    assert_is_percentage(pct)
    m = G.size()
    to_add = int(m * pct)
    log.debug("Will add %d edges to %d (%f)", to_add, m, pct)

    new_edges = set(nx.non_edges(G))
    G.add_edges_from(random.sample(list(new_edges), to_add), weight=1)


def remove_random_edges(G, pct):
    """Randomly remove `n` edges from G (`n` = fraction of current edge count)

    Parameters
    ----------
    G : a networkx.Graph
        the network

    pct : float
        A percentage (between 0 and 1)
    """
    assert_is_percentage(pct)
    edges = G.edges()
    m = len(edges)
    to_remove = int(m * pct)

    log.debug("Will remove %d edges of %d (%f)", to_remove, m, pct)
    G.remove_edges_from(random.sample(list(edges), to_remove))


def add_remove_random_edges(G, pct_add, pct_remove):
    """Randomly add edges to and remove edges from G

    Parameters
    ----------
    G : a networkx.Graph
        the network

    pct_add : float
        A percentage (between 0 and 1)

    pct_remove : float
        A percentage (between 0 and 1)
    """
    assert_is_percentage(pct_add)
    assert_is_percentage(pct_remove)
    edges = G.edges()
    m = len(edges)
    to_add = int(m * pct_add)
    to_remove = int(m * pct_remove)
    log.debug(
        "Will add %d (%f) edges to and remove %d (%f) edges of %d",
        to_add,
        pct_add,
        to_remove,
        pct_remove,
        m,
    )

    new_edges = set(nx.non_edges(G))
    G.remove_edges_from(random.sample(list(edges), to_remove))
    G.add_edges_from(random.sample(list(new_edges), to_add))


================================================
FILE: linkpred/network/algorithms.py
================================================
import logging

import networkx as nx
import numpy as np

log = logging.getLogger(__name__)

__all__ = ["rooted_pagerank", "simrank"]


def rooted_pagerank(G, root, alpha=0.85, beta=0, weight="weight"):
    """Return the rooted PageRank of all nodes with respect to node `root`

    Parameters
    ----------

    G : a networkx.(Di)Graph
        network to compute PR on

    root : a node from the network
        the node that will be the starting point of all random walks

    alpha : float
        PageRank probability that we will advance to a neighbour of the
        current node in a random walk

    beta : float or int
        Normally, we return to the root node with probability 1 - alpha.
        With this parameter, we can also advance to a random other node in the
        network with probability beta. Thus, we get back to the root node with
        probability 1 - alpha - beta. This is off (0) by default.

    weight : string or None
        The edge attribute that holds the numerical value used for
        the edge weight.  If None then treat as unweighted.

    """
    personalization = dict.fromkeys(G, beta)
    personalization[root] = 1 - beta

    return nx.pagerank(G, alpha, personalization, weight=weight)


def simrank(G, nodelist=None, c=0.8, num_iterations=10, weight="weight"):
    r"""Calculate SimRank matrix for nodes in nodelist

    SimRank is defined as:

    .. math ::

        sim(u, v) = \frac{c}{|N(u)| |N(v)|} \sum_{p \in N(u)}
                    \sum_{q \in N(v)} sim(p, q)

    Parameters
    ----------
    G : a networkx.Graph
        network

    nodelist : collection of nodes, optional
        nodes to calculate SimRank for (default: all)

    c : float, optional
        decay factor, determines how quickly similarity decreases

    num_iterations : int, optional
        number of iterations to calculate

    weight: string or None, optional
        If None, all edge weights are considered equal.
        Otherwise holds the name of the edge attribute used as weight.

    """
    n = len(G)
    M = raw_google_matrix(G, nodelist=nodelist, weight=weight)
    sim = np.identity(n, dtype=np.float32)
    for i in range(num_iterations):
        log.debug("Starting SimRank iteration %d", i)
        temp = c * M.T @ sim @ M
        sim = temp + np.identity(n) - np.diag(np.diag(temp))
    return sim


def raw_google_matrix(G, nodelist=None, weight="weight"):
    """Calculate the raw Google matrix (stochastic without teleportation)"""
    n = len(G)
    if n == 0:
        msg = "Empty network, cannot calculate Google matrix"
        raise ValueError(msg)
    M = nx.to_numpy_array(G, nodelist=nodelist, dtype=np.float32, weight=weight)

    # Find 'dangling' nodes, i.e. nodes whose row's sum = 0
    dangling = np.where(M.sum(axis=1) == 0)
    # add constant to dangling nodes' row
    for d in dangling[0]:
        M[d] = 1.0 / n
    # Normalize. We now have the 'raw' Google matrix (cf. example on p. 11 of
    # Langville & Meyer (2006)).
    M = M / M.sum(axis=1)
    return M


================================================
FILE: linkpred/predictors/__init__.py
================================================
from .base import *
from .eigenvector import *
from .misc import *
from .neighbour import *
from .path import *


================================================
FILE: linkpred/predictors/base.py
================================================
import contextlib

from .util import neighbourhood

__all__ = ["Predictor", "all_predictors"]


class Predictor:
    """
    Predictor based on graph structure

    This can also be used for bipartite networks or other networks
    involving nodes that should not be included in the predictions.
    To distinguish between 'eligible' and 'non-eligible' nodes, the
    graph can set a node attribute that returns true for eligible
    nodes and false for non-eligible ones.

    For instance:

    >>> import networkx as nx
    >>> B = nx.Graph()
    >>> # Add the node attribute "bipartite"
    >>> B.add_nodes_from([1,2,3,4], bipartite=0)
    >>> B.add_nodes_from(['a','b','c'], bipartite=1)
    >>> B.add_edges_from([(1,'a'), (1,'b'), (2,'b'), (2,'c'),
    ...                   (3,'c'), (4,'a')])
    >>> p = Predictor(B, eligible='bipartite')
    >>> p.eligible_node(1)
    0
    >>> sorted(p.eligible_nodes())
    ['a', 'b', 'c']

    """

    def __init__(self, G, eligible=None, excluded=None):
        """
        Initialize predictor

        Arguments
        ---------
        G : nx.Graph
            a graph

        eligible : a string or None
            If this is a string, it is used to distinguish between eligible
            and non-eligible nodes. We only try to predict links between
            two eligible nodes.

        excluded : iterable or None
            A list or iterable of node pairs that should be excluded (i.e., not
            predicted). This is useful to, for instance, make sure that we only
            predict new links that are not currently in G.

        """
        self.G = G
        self.eligible_attr = eligible
        self.name = self.__class__.__name__
        self.excluded = [] if excluded is None else excluded

        # Add a decorator to predict(), to do the necessary postprocessing for
        # filtering out links if `excluded` is not empty. We do this in
        # __init__() such that child classes need not be changed.
        def add_postprocessing(func):
            def predict_and_postprocess(*args, **kwargs):
                scoresheet = func(*args, **kwargs)
                for u, v in self.excluded:
                    with contextlib.suppress(KeyError):
                        del scoresheet[(u, v)]
                return scoresheet

            predict_and_postprocess.__name__ = func.__name__
            predict_and_postprocess.__doc__ = func.__doc__
            predict_and_postprocess.__dict__.update(func.__dict__)
            return predict_and_postprocess

        self.predict = add_postprocessing(self.predict)

    def __str__(self):
        return self.name

    def __call__(self, *args, **kwargs):
        return self.predict(*args, **kwargs)

    def predict(self, *args, **kwargs):
        raise NotImplementedError

    def eligible(self, u, v):
        """Check if link between nodes u and v is eligible

        Eligibility allows us to ignore some nodes/links for link prediction.

        """
        return self.eligible_node(u) and self.eligible_node(v) and u != v

    def eligible_node(self, v):
        """Check if node v is eligible

        Eligibility allows us to ignore some nodes/links for link prediction.

        """
        if self.eligible_attr is None:
            return True
        return self.G.nodes[v][self.eligible_attr]

    def eligible_nodes(self):
        """Get list of eligible nodes

        Eligibility allows us to ignore some nodes/links for link prediction.

        """
        return [v for v in self.G if self.eligible_node(v)]

    def likely_pairs(self, k=2):
        """
        Yield node pairs from the same neighbourhood

        Arguments
        ---------
        k : int
            size of the neighbourhood (e.g., if k = 2, the neighbourhood
            consists of all nodes that are two links away)

        """
        for a in self.G.nodes():
            if not self.eligible_node(a):
                continue
            for b in neighbourhood(self.G, a, k):
                if not self.eligible_node(b):
                    continue
                yield (a, b)


def all_predictors():
    """Returns a list of all predictors"""
    from operator import itemgetter

    from ..util import itersubclasses

    predictors = sorted(
        ((s, s.__name__) for s in itersubclasses(Predictor)), key=itemgetter(1)
    )
    return list(zip(*predictors))[0]


================================================
FILE: linkpred/predictors/eigenvector.py
================================================
import networkx as nx

from ..evaluation import Scoresheet
from ..network import rooted_pagerank, simrank
from ..util import progressbar
from .base import Predictor


class RootedPageRank(Predictor):
    def predict(self, nbunch=None, alpha=0.85, beta=0, weight="weight", k=None):
        """Predict using rooted PageRank.

        Parameters
        ----------

        nbunch : iterable collection of nodes, optional
            node(s) to calculate PR for (default: all)

        alpha : float, optional
            PageRank probability that we will advance to a neighbour of the
            current node in a random walk

        beta : float, optional
            Normally, we return to the root node with probability 1 - alpha.
            With this parameter, we can also advance to a random other node in
            the network with probability beta. Thus, we get back to the root
            node with probability 1 - alpha - beta. This is off (0) by default.

        weight : string or None, optional
            The edge attribute that holds the numerical value used for
            the edge weight.  If None then treat as unweighted.

        k : int or None, optional
            If `k` is `None`, this predictor is applied to the entire network.
            If `k` is an int, the predictor is applied to a subgraph consisting
            of the k-neighbourhood of the current node.
            Results are often very similar but much faster.

        See documentation for linkpred.network.rooted_pagerank for these
        parameters.

        """
        res = Scoresheet()
        if nbunch is None:
            nbunch = self.G.nodes()
        for u in progressbar(nbunch):
            if not self.eligible_node(u):
                continue
            # Restrict to the k-neighbourhood subgraph if k is defined
            G = self.G if k is None else nx.ego_graph(self.G, u, radius=k)

            pagerank_scores = rooted_pagerank(G, u, alpha, beta, weight)
            for v, w in pagerank_scores.items():
                if w > 0 and u != v and self.eligible_node(v):
                    res[(u, v)] += w
        return res


class SimRank(Predictor):
    def predict(self, c=0.8, num_iterations=10, weight="weight"):
        r"""Predict using SimRank

        .. math ::
            sim(u, v) = \frac{c}{|N(u)| \cdot |N(v)|} \sum_{p \in N(u)}
                        \sum_{q \in N(v)} sim(p, q)

        where `N(v)` is the set of neighbours of node `v`.

        Parameters
        ----------
        c : float, optional
            decay factor, determines how quickly similarity decreases

        num_iterations : int, optional
            number of iterations to calculate

        weight: string or None, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        nodelist = list(self.G.nodes)
        sim = simrank(self.G, nodelist, c, num_iterations, weight)
        (m, n) = sim.shape
        assert m == n

        for i in range(m):
            # sim(a, b) = sim(b, a), leading to a 'mirrored' matrix.
            # We start the column range at i + 1, such that we only look at the
            # upper triangle in the matrix, excluding the diagonal:
            # sim(a, a) = 1.
            u = nodelist[i]
            for j in range(i + 1, n):
                if sim[i, j] > 0:
                    v = nodelist[j]
                    if self.eligible(u, v):
                        res[(u, v)] = sim[i, j]
        return res


================================================
FILE: linkpred/predictors/misc.py
================================================
import random
from collections import defaultdict

from ..evaluation import Scoresheet
from ..util import all_pairs
from .base import Predictor

__all__ = ["Community", "Copy", "Random"]


class Community(Predictor):
    def predict(self):  # pylint:disable=E0202
        """Predict using community structure

        If two nodes belong to the same community, they are predicted to form
        a link. This uses the Louvain algorithm, which determines communities
        at different granularity levels: the finer grained the community, the
        higher the resulting score.

        This needs the python-louvain package. Install linkpred as follows:

        $ pip install linkpred[community]

        """
        try:
            import community
        except ImportError as err:
            msg = (
                "Module 'community' could not be found. Please install linkpred with: "
                "$ pip install linkpred[community]"
            )
            raise ImportError(msg) from err

        res = Scoresheet()
        dendogram = community.generate_dendrogram(self.G)

        for i in range(len(dendogram)):
            partition = community.partition_at_level(dendogram, i)
            communities = defaultdict(list)
            weight = len(dendogram) - i  # Lower i, smaller communities

            for n, com in partition.items():
                communities[com].append(n)
            for nodes in communities.values():
                for u, v in all_pairs(nodes):
                    if not self.eligible(u, v):
                        continue
                    res[(u, v)] += weight
        return res


class Copy(Predictor):
    def predict(self, weight=None):  # pylint:disable=E0202
        """Predict by copying the training network

        If weights are used, the likelihood score is equal to the link weight.

        This predictor is mostly intended as a sort of baseline. By definition,
        it only yields predictions if we do not exclude links from the training
        network (with `excluded`).

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        if weight is None:
            return Scoresheet.fromkeys(self.G.edges(), 1)
        return Scoresheet(((u, v), d[weight]) for u, v, d in self.G.edges(data=True))


class Random(Predictor):
    def predict(self):  # pylint:disable=E0202
        """Predict randomly

        This predictor can be used as a baseline.

        """
        res = Scoresheet()
        for a, b in all_pairs(self.eligible_nodes()):
            res[(a, b)] = random.random()
        return res


================================================
FILE: linkpred/predictors/neighbour.py
================================================
import math

from ..evaluation import Scoresheet
from ..util import all_pairs
from .base import Predictor
from .util import (
    neighbourhood,
    neighbourhood_intersection_size,
    neighbourhood_size,
    neighbourhood_union_size,
)

__all__ = [
    "AdamicAdar",
    "AssociationStrength",
    "CommonNeighbours",
    "Cosine",
    "DegreeProduct",
    "Jaccard",
    "MaxOverlap",
    "MinOverlap",
    "NMeasure",
    "Pearson",
    "ResourceAllocation",
]


class AdamicAdar(Predictor):
    def predict(self, weight=None):
        """Predict by Adamic/Adar measure of neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            intersection = set(neighbourhood(self.G, a)) & set(neighbourhood(self.G, b))
            w = 0
            for c in intersection:
                if weight is not None:
                    numerator = self.G[a][c][weight] * self.G[b][c][weight]
                else:
                    numerator = 1
                w += numerator / math.log(neighbourhood_size(self.G, c, weight))
            if w > 0:
                res[(a, b)] = w
        return res


class AssociationStrength(Predictor):
    def predict(self, weight=None):
        """Predict by association strength of neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            w = neighbourhood_intersection_size(self.G, a, b, weight) / (
                neighbourhood_size(self.G, a, weight)
                * neighbourhood_size(self.G, b, weight)
            )
            if w > 0:
                res[(a, b)] = w
        return res


class CommonNeighbours(Predictor):
    def predict(self, alpha=1.0, weight=None):
        r"""Predict using common neighbours

        This is loosely based on Opsahl et al. (2010):

        .. math ::

            k(u, v) = |N(u) \cap N(v)|
            s(u, v) = \sum_{i=1}^n x_i \cdot y_i
            W(u, v) = k(u, v)^{1 - \alpha} \cdot s(u, v)^{\alpha}

        Parameters
        ----------
        alpha : float, optional
            If alpha = 0, weights are ignored. If alpha = 1, only weights are
            used (ignoring the number of intermediary nodes).

        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            if weight is None or alpha == 0.0:
                w = neighbourhood_intersection_size(self.G, a, b, weight=None)
            elif alpha == 1.0:
                w = neighbourhood_intersection_size(self.G, a, b, weight=weight)
            else:
                k = neighbourhood_intersection_size(self.G, a, b, weight=None)
                s = neighbourhood_intersection_size(self.G, a, b, weight=weight)
                w = (k ** (1.0 - alpha)) * (s**alpha)
            if w > 0:
                res[(a, b)] = w
        return res


class Cosine(Predictor):
    def predict(self, weight=None):
        """Predict by cosine measure of neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            w = neighbourhood_intersection_size(self.G, a, b, weight) / math.sqrt(
                neighbourhood_size(self.G, a, weight)
                * neighbourhood_size(self.G, b, weight)
            )
            if w > 0:
                res[(a, b)] = w
        return res


class DegreeProduct(Predictor):
    def predict(self, weight=None, minimum=1):
        """Predict by degree product (preferential attachment)

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        minimum : int, optional (default = 1)
            If the degree product is below this minimum, the corresponding
            prediction is ignored.

        """
        res = Scoresheet()
        for a, b in all_pairs(self.eligible_nodes()):
            w = neighbourhood_size(self.G, a, weight) * neighbourhood_size(
                self.G, b, weight
            )
            if w >= minimum:
                res[(a, b)] = w
        return res


class Jaccard(Predictor):
    def predict(self, weight=None):
        """Predict by Jaccard index of neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            # Best performance: weighted numerator, unweighted denominator.
            numerator = neighbourhood_intersection_size(self.G, a, b, weight)
            denominator = neighbourhood_union_size(self.G, a, b, weight)
            w = numerator / denominator
            if w > 0:
                res[(a, b)] = w
        return res


class NMeasure(Predictor):
    def predict(self, weight=None):
        """Predict by N measure of neighbours

        The N measure was defined by Egghe (2009).

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            w = (
                math.sqrt(2)
                * neighbourhood_intersection_size(self.G, a, b, weight)
                / math.sqrt(
                    neighbourhood_size(self.G, a, weight) ** 2
                    + neighbourhood_size(self.G, b, weight) ** 2
                )
            )
            if w > 0:
                res[(a, b)] = w
        return res


def _predict_overlap(predictor, function, weight=None):
    res = Scoresheet()
    for a, b in predictor.likely_pairs():
        # Best performance: weighted numerator, unweighted denominator.
        numerator = neighbourhood_intersection_size(predictor.G, a, b, weight)
        denominator = function(
            neighbourhood_size(predictor.G, a, weight),
            neighbourhood_size(predictor.G, b, weight),
        )
        w = numerator / denominator
        if w > 0:
            res[(a, b)] = w
    return res


class MaxOverlap(Predictor):
    def predict(self, weight=None):
        """Predict by maximum overlap between neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        return _predict_overlap(self, max, weight)


class MinOverlap(Predictor):
    def predict(self, weight=None):
        """Predict by minimum overlap between neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        return _predict_overlap(self, min, weight)


class Pearson(Predictor):
    def predict(self, weight=None):
        """Predict by Pearson correlation between neighbours

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        # 'Full' Pearson looks at all possible pairs. Since those are likely
        # of little value for link prediction, we restrict ourselves to pairs
        # with at least one common neighbour.
        for a, b in self.likely_pairs():
            n = len(self.G)
            a_l2norm = neighbourhood_size(self.G, a, weight)
            b_l2norm = neighbourhood_size(self.G, b, weight)
            a_l1norm = neighbourhood_size(self.G, a, weight, power=1)
            b_l1norm = neighbourhood_size(self.G, b, weight, power=1)
            intersect = neighbourhood_intersection_size(self.G, a, b, weight)

            numerator = (n * intersect) - (a_l1norm * b_l1norm)
            denominator = math.sqrt(n * a_l2norm - a_l1norm**2) * math.sqrt(
                n * b_l2norm - b_l1norm**2
            )

            w = numerator / denominator
            if w > 0:
                res[(a, b)] = w
        return res


class ResourceAllocation(Predictor):
    def predict(self, weight=None):
        """Predict with resource allocation index of neighbours

        Resource allocation was defined by Zhou, Lu & Zhang (2009, Eur. Phys.
        J. B, 71, 623).

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        """
        res = Scoresheet()
        for a, b in self.likely_pairs():
            intersection = set(neighbourhood(self.G, a)) & set(neighbourhood(self.G, b))
            w = 0
            for c in intersection:
                if weight is not None:
                    numerator = float(self.G[a][c][weight] * self.G[b][c][weight])
                else:
                    numerator = 1
                w += numerator / neighbourhood_size(self.G, c, weight)
            if w > 0:
                res[(a, b)] = w
        return res


================================================
FILE: linkpred/predictors/path.py
================================================
import networkx as nx

from ..evaluation import Scoresheet
from ..util import progressbar
from .base import Predictor

__all__ = ["GraphDistance", "Katz"]


class GraphDistance(Predictor):
    def predict(self, weight="weight", alpha=1):
        r"""Predict by graph distance

        This is based on the dissimilarity measures of Egghe & Rousseau (2003):

        $d(i, j) = \min(\sum 1/w_k)$

        The parameter alpha was introduced by Opsahl et al. (2010):

        $d_\alpha(i, j) = \min(\sum 1 / w_k^\alpha)$

        If alpha = 0 or weight is None, we determine unweighted graph distance,
        i.e. only keep track of number of intermediate nodes and not of edge
        weights. If alpha = 1, we only keep track of edge weights and not of
        the number of intermediate nodes. (In practice, setting alpha equal to
        around 0.1 seems to yield the best results.)

        Parameters
        ----------
        weight : None or string, optional
            If None, all edge weights are considered equal.
            Otherwise holds the name of the edge attribute used as weight.

        alpha : float
            Parameter to determine relative importance of intermediate
            link strength

        """
        res = Scoresheet()

        if weight is None:
            G = self.G
        else:
            # We assume that edge weights denote proximities
            G = nx.Graph()
            G.add_weighted_edges_from(
                (u, v, 1 / d[weight] ** alpha) for u, v, d in self.G.edges(data=True)
            )

        dist = nx.shortest_path_length(G, weight=weight)
        for a, others in dist:
            if not self.eligible_node(a):
                continue
            for b, length in others.items():
                if a == b or not self.eligible_node(b):
                    continue
                w = 1 / length
                res[(a, b)] = w
        return res


def matrix_power(arr, n):
    """Matrix power: perform matrix multiplication n times"""
    if n < 1:
        msg = "Expected power equal to 1 or higher"
        raise ValueError(msg)
    if n == 1:
        return arr

    return arr @ matrix_power(arr, n - 1)


class Katz(Predictor):
    def predict(self, beta=0.001, max_power=5, weight="weight", dtype=None):
        """Predict by Katz (1953) measure

        Let `A` be an adjacency matrix for the directed network `G`.
        Then, each element `a_{ij}` of `A^k` (the `k`-th power of `A`) has a
        value equal to the number of walks with length `k` from `i` to `j`.

        The probability of a link rapidly decreases as the walks grow longer.
        Katz therefore introduces an extra parameter (here beta) to weigh
        longer walks less.

        Parameters
        ----------
        beta : a float
            the value of beta in the formula of the Katz equation

        max_power : an int
            the maximum number of powers to take into account

        weight : string or None
            The edge attribute that holds the numerical value used for
            the edge weight.  If None then treat as unweighted.

        dtype : a data type
            data type of edge weights

        """
        nodelist = list(self.G.nodes)
        adj = nx.to_scipy_sparse_array(self.G, dtype=dtype, weight=weight)
        res = Scoresheet()

        for k in progressbar(range(1, max_power + 1), "Computing matrix powers: "):
            # The below method is found to be fastest for iterating through a
            # sparse matrix, see
            # http://stackoverflow.com/questions/4319014/
            matrix = matrix_power(adj, k).tocoo()
            for i, j, d in zip(matrix.row, matrix.col, matrix.data):
                if i == j:
                    continue
                u, v = nodelist[i], nodelist[j]
                if self.eligible(u, v):
                    w = d * (beta**k)
                    res[(u, v)] += w

        # We count double in case of undirected networks ((i, j) and (j, i))
        if not self.G.is_directed():
            for pair in res:
                res[pair] /= 2

        return res


================================================
FILE: linkpred/predictors/util.py
================================================
import networkx as nx


def neighbourhood(G, n, k=1):
    """Get k-neighbourhood of node n"""
    if k == 1:
        return G[n]
    dist = nx.single_source_shortest_path_length(G, n, k)
    del dist[n]
    return dist.keys()


def neighbourhood_intersection_size(G, a, b, weight=None, k=1):
    """Get the summed weight of the common neighbours of a and b

    If weighted, we use the sum of the weight products. This is equivalent
    to the vector-based interpretation (dot product of the two vectors).

    """
    common_neighbours = set(neighbourhood(G, a, k)) & set(neighbourhood(G, b, k))
    if weight:
        return sum(G[a][n][weight] * G[b][n][weight] for n in common_neighbours)

    return len(common_neighbours)


def neighbourhood_size(G, u, weight=None, k=1, power=2):
    """Get the weight of the neighbours of u

    If weighted, we use the sum of the squared edge weight for compatibility
    with the vector-based measures.

    """
    # The fast route for default options
    if weight is None and k == 1:
        return len(G[u])
    # The slow route for everything else
    neighbours = neighbourhood(G, u, k)
    return (
        sum(G[u][v][weight] ** power for v in neighbours) if weight else len(neighbours)
    )


def neighbourhood_union_size(G, a, b, weight=None, k=1, power=2):
    """Get the weight of the neighbours union of a and b"""
    a_neighbours = set(neighbourhood(G, a, k))
    b_neighbours = set(neighbourhood(G, b, k))
    if weight:
        return (
            sum(G[a][n][weight] ** power for n in a_neighbours)
            + sum(G[b][n][weight] ** power for n in b_neighbours)
            - sum(
                G[a][n][weight] * G[b][n][weight] for n in a_neighbours & b_neighbours
            )
        )

    return len(a_neighbours | b_neighbours)


================================================
FILE: linkpred/preprocess.py
================================================
import logging

import networkx as nx

log = logging.getLogger(__name__)


def without_low_degree_nodes(G, minimum=1, eligible=None):
    """Return a copy of the graph without nodes with degree below minimum

    arguments
    ---------
    g : a networkx.graph

    minimum : int
        minimum node degree

    eligible : none or string
        only eligible nodes are considered for removal

    """

    def low_degree(G, threshold):
        """Get eligible nodes whose degree is below the threshold"""
        if eligible is None:
            return [n for n, d in G.degree() if d < threshold]

        return [n for n, d in G.degree() if d < threshold and G.nodes[n][eligible]]

    to_remove = low_degree(G, minimum)
    H = G.copy()
    H.remove_nodes_from(to_remove)
    log.info("Removed %d nodes (degree < %d)", len(to_remove), minimum)

    return H


def without_uncommon_nodes(networks, eligible=None):
    """Return list of networks without nodes not common to all

    Arguments
    ---------
    networks : an iterable of `networkx.Graph`s

    eligible : None or string
        only eligible nodes are considered for removal

    Example
    -------
    >>> import networkx as nx
    >>> A, B = nx.Graph(), nx.Graph()
    >>> A.add_nodes_from('abcd')
    >>> B.add_nodes_from('cdef')
    >>> A2, B2 = without_uncommon_nodes((A, B))
    >>> sorted(A2.nodes())
    ['c', 'd']
    >>> sorted(B2.nodes())
    ['c', 'd']

    """

    def items_outside(G, nbunch):
        """Get eligible nodes outside nbunch"""
        if eligible is None:
            return [n for n in G.nodes() if n not in nbunch]

        return [n for n in G.nodes() if G.nodes[n][eligible] and n not in nbunch]

    common = set.intersection(*[set(G) for G in networks])
    new_networks = []
    for G in networks:
        to_remove = items_outside(G, common)
        H = G.copy()
        H.remove_nodes_from(to_remove)
        new_networks.append(H)
        log.info("Removed %d nodes (not common)", len(to_remove))

    return new_networks


def without_selfloops(G):
    """return copy of G without selfloop edges"""
    H = G.copy()
    num_loops = nx.number_of_selfloops(G)

    if num_loops:
        log.warning("Network contains %d self-loops. Removing...", num_loops)
        H.remove_edges_from(nx.selfloop_edges(G))

    return H


================================================
FILE: linkpred/util.py
================================================
import itertools
import sys


def all_pairs(iterable):
    """Return iterator over all possible pairs in l"""
    return itertools.combinations(iterable, 2)


def progressbar(it, prefix="", size=60):
    """Show progress bar

    http://code.activestate.com/recipes/576986-progress-bar-for-console-programs-as-iterator/

    """
    count = len(it)

    def _show(_i):
        x = int(size * _i / count)
        sys.stdout.write(
            "%s[%s%s] %i/%i\r" % (prefix, "#" * x, "." * (size - x), _i, count)
        )
        sys.stdout.flush()

    _show(0)
    for i, item in enumerate(it, start=1):
        yield item
        _show(i)
    sys.stdout.write("\n")
    sys.stdout.flush()


def load_function(full_functionname):
    """Return the function given by full_functionname

    This loads function names of the form 'module.submodule.function'

    """
    try:
        modulename, functionname = full_functionname.rsplit(".", 1)
    except ValueError:
        msg = f"No module name given in {full_functionname}"
        raise ValueError(msg) from None

    # Dynamically load module and function
    __import__(modulename)
    module = sys.modules[modulename]
    return getattr(module, functionname)


def interpolate(curve):
    """Make curve decrease."""
    for i in range(-1, -len(curve), -1):
        if curve[i] > curve[i - 1]:
            curve[i - 1] = curve[i]
    return curve


def itersubclasses(cls, _seen=None):
    """Generator over all subclasses of a given class, in depth first order.

    Based on:
    http://code.activestate.com/recipes/576949-find-all-subclasses-of-a-given-class/

    """
    if _seen is None:
        _seen = set()
    try:
        subs = cls.__subclasses__()
    except TypeError:  # fails only when cls is type
        subs = cls.__subclasses__(cls)
    for sub in subs:
        if sub not in _seen:
            _seen.add(sub)
            yield sub
            for sub2 in itersubclasses(sub, _seen):
                yield sub2


================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "linkpred"
authors = [{name = "Raf Guns", email = "raf.guns@uantwerpen.be"}]
readme = "README.rst"
dynamic = ["version", "description"]
classifiers = [
        "Intended Audience :: Science/Research",
        "Intended Audience :: Developers",
        "License :: OSI Approved",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Programming Language :: Python :: 3.12",
        "Topic :: Software Development",
        "Topic :: Scientific/Engineering",
        "Development Status :: 4 - Beta",
        "Natural Language :: English",
        "Operating System :: OS Independent",
]
requires-python=">=3.8"
dependencies = [
        "matplotlib>=3.5",
        "networkx>=3.0",
        "numpy>=1.23",
        "pyyaml>=3.0",
        "scipy>=1.10",
        "smokesignal>=0.7",
]

[project.scripts]
linkpred = "linkpred.cli:main"

[project.optional-dependencies]
dev = ["pytest >=7.1", "pytest-cov", "tox>=4.4"]
community = ["python-louvain"]
all = ["pytest >=7.1", "pytest-cov", "tox>=4.4", "python-louvain"]

[project.urls]
Home = "https://github.com/rafguns/linkpred/"

[tool.flit.module]
name = "linkpred"

[tool.ruff]
target-version = "py38"
# See https://beta.ruff.rs/docs/rules/
select = [
    "A", # builtin shadowing
    "ARG", # unsued arguments
    "B", # bugbear
    "C4", # comprehensions
    "C90", # mccabe complexity
    "E", # style errors
    "EM", # error messages
    "F", # flakes
    "FBT", # boolean trap
    "G", # logging format
    "I", # import sorting
    "ISC", # string concatenation
    "N", # naming
    "PGH", # pygrep-hooks
    "PIE", # miscellaneous
    "PL", # pylint
    "PT", # pytest style
    "Q", # quotes
    "RET", # return
    "RSE", # raise
    "RUF", # Ruff
    "SIM", # simplify
    "T20", # print
    "UP", # upgrade
    "W", # style warnings
    "YTT", # sys.version
]

ignore = [
    "N803", # Argument name `G` should be lowercase
    "N806", # "Variable `G` in function should be lowercase"
    "PLR0913", # Too many arguments to function call
]

[tool.ruff.per-file-ignores]
# Ignore unused imports in __init__.py
"__init__.py" = ["F401", "F403"]


================================================
FILE: pytest.ini
================================================
[pytest]
filterwarnings=
    # will be fixed in networkx 2.5
    ignore:.*is deprecated and will be removed in SciPy 2.0.0.*::networkx


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/test_addremove.py
================================================
import networkx as nx
import pytest

from linkpred.network.addremove import (
    add_random_edges,
    add_remove_random_edges,
    remove_random_edges,
)


def test_add_random_edges():
    G = nx.star_graph(10)
    edges = list(G.edges())

    add_random_edges(G, 0)
    assert edges == list(G.edges())

    add_random_edges(G, 0.5)
    assert G.size() == 15
    assert set(edges) < set(G.edges())

    with pytest.raises(ValueError):
        add_random_edges(G, 1.2)


def test_remove_random_edges():
    G = nx.star_graph(10)
    edges = list(G.edges())

    remove_random_edges(G, 0)
    assert edges == list(G.edges())

    remove_random_edges(G, 0.5)
    assert G.size() == 5
    assert set(G.edges()) < set(edges)

    with pytest.raises(ValueError):
        remove_random_edges(G, 10)


def test_add_remove_random_edges():
    G = nx.star_graph(10)
    edges = list(G.edges())

    add_remove_random_edges(G, 0, 0)
    assert edges == list(G.edges())

    add_remove_random_edges(G, 0.3, 0.4)
    assert G.size() == 9
    assert len(set(edges) & set(G.edges())) == 6

    with pytest.raises(ValueError):
        add_remove_random_edges(G, 0, 1.2)
    with pytest.raises(ValueError):
        add_remove_random_edges(G, 1.2, 0)


================================================
FILE: tests/test_cli.py
================================================
import os
import tempfile
from contextlib import contextmanager

import pytest

from linkpred.cli import get_config, handle_arguments, load_profile


@contextmanager
def temp_empty_file():
    fh = tempfile.NamedTemporaryFile("r", delete=False)
    yield fh.name
    assert fh.read() == ""
    fh.close()


class TestProfileFile:
    def setup_method(self):
        self.yaml_fd, self.yaml_fname = tempfile.mkstemp(suffix=".yaml")
        self.json_fd, self.json_fname = tempfile.mkstemp(suffix=".json")
        self.expected = {
            "predictors": [
                {"name": "CommonNeighbours", "displayname": "Common neighbours"},
                {"name": "Cosine"},
            ],
            "interpolation": True,
        }

    def teardown_method(self):
        for fd, fname in (
            (self.yaml_fd, self.yaml_fname),
            (self.json_fd, self.json_fname),
        ):
            os.close(fd)
            os.unlink(fname)

    def test_load_profile_yaml(self):
        with open(self.yaml_fname, "w") as fh:
            fh.write(
                """predictors:
- name: CommonNeighbours
  displayname: Common neighbours
- name: Cosine
interpolation: true"""
            )
        profile = load_profile(self.yaml_fname)
        assert profile == self.expected

    def test_load_profile_json(self):
        with open(self.json_fname, "w") as fh:
            fh.write(
                """{"predictors":
                [{"name": "CommonNeighbours",
                "displayname": "Common neighbours"},
                {"name": "Cosine"}],
                "interpolation": true}"""
            )
        profile = load_profile(self.json_fname)
        assert profile == self.expected

    def test_load_profile_error(self):
        with open(self.json_fname, "w") as fh:
            fh.write("foobar")
        with pytest.raises(Exception):
            load_profile(self.json_fname)

        with open(self.yaml_fname, "w") as fh:
            fh.write("{foobar")
        with pytest.raises(Exception):
            load_profile(self.yaml_fname)

    def test_get_config(self):
        with open(self.yaml_fname, "w") as fh:
            fh.write(
                """predictors:
- name: CommonNeighbours
  displayname: Common neighbours
- name: Cosine
interpolation: true"""
            )

        fh = tempfile.NamedTemporaryFile("r", delete=False)
        with temp_empty_file() as training:
            config = get_config([training, "-P", self.yaml_fname])
            for k, v in self.expected.items():
                assert config[k] == v

        with temp_empty_file() as training:
            config = get_config([fh.name, "-P", self.yaml_fname, "-p", "Katz", "-i"])
            # Profile gets priority
            for k, v in self.expected.items():
                assert config[k] == v
        fh.close()


def test_no_training_file():
    with pytest.raises(SystemExit):
        handle_arguments([])


def test_nonexisting_predictor():
    with pytest.raises(SystemExit):
        handle_arguments(["some-network", "-p", "Aargh"])


def test_handle_arguments():
    expected = {
        "debug": False,
        "quiet": False,
        "output": ["recall-precision"],
        "chart_filetype": "pdf",
        "interpolation": True,
        "predictors": [],
        "exclude": "old",
        "profile": None,
        "training-file": "training",
    }

    args = handle_arguments(["training"])
    for k, v in expected.items():
        assert args[k] == v

    args = handle_arguments(["training", "test"])
    for k, v in expected.items():
        assert args[k] == v
    assert args["test-file"] == "test"

    argstr = "-p CommonNeighbours Cosine -o fmax " "recall-precision -- training"
    args = handle_arguments(argstr.split())
    expected_special = {
        "predictors": ["CommonNeighbours", "Cosine"],
        "output": ["fmax", "recall-precision"],
    }
    for k, v in expected_special.items():
        assert args[k] == v

    args = handle_arguments(["training", "-i"])
    assert args["interpolation"] is False

    args = handle_arguments(["training", "-a"])
    assert args["exclude"] == ""

    args = handle_arguments(["training", "-P", "foo.json"])
    assert args["profile"] == "foo.json"

    args = handle_arguments(["training", "-f", "eps"])
    assert args["chart_filetype"] == "eps"


================================================
FILE: tests/test_evaluation_static.py
================================================
import numpy as np
import pytest

from linkpred.evaluation import (
    BaseScoresheet,
    EvaluationSheet,
    Scoresheet,
    StaticEvaluation,
    UndefinedError,
)

from .utils import assert_array_equal, temp_file


class TestStaticEvaluation:
    def setup_method(self):
        self.ret = range(5)
        self.rel = [3, 4, 5, 6]
        self.num_universe = 20
        self.universe = range(self.num_universe)

    def test_init(self):
        e = StaticEvaluation(self.ret, self.rel, self.universe)
        assert len(e.tp) == 2
        assert len(e.fp) == 3
        assert len(e.tn) == 13
        assert len(e.fn) == 2

        e_no_universe = StaticEvaluation(self.ret, self.rel)
        assert len(e.tp) == len(e_no_universe.tp)
        assert len(e.fp) == len(e_no_universe.fp)
        assert len(e.fn) == len(e_no_universe.fn)
        assert e_no_universe.tn is None

        e_num_universe = StaticEvaluation(self.ret, self.rel, self.num_universe)
        assert len(e_num_universe.tp) == 2
        assert len(e_num_universe.fp) == 3
        assert len(e_num_universe.fn) == 2
        assert len(e_num_universe.tp) == e_num_universe.num_tp
        assert len(e_num_universe.fp) == e_num_universe.num_fp
        assert len(e_num_universe.fn) == e_num_universe.num_fn
        assert e_num_universe.num_tn == 13

    def test_update_retrieved(self):
        e = StaticEvaluation(self.ret, self.rel, self.universe)
        e.update_retrieved([6, 7])
        assert len(e.tp) == 3
        assert len(e.fp) == 4
        assert len(e.tn) == 12
        assert len(e.fn) == 1

        with pytest.raises(ValueError):
            e.update_retrieved([1])  # fp
        with pytest.raises(ValueError):
            e.update_retrieved([3])  # tp
        with pytest.raises(ValueError):
            e.update_retrieved(["a"])

    def test_update_retrieved_num_universe(self):
        e = StaticEvaluation(self.ret, self.rel, self.num_universe)
        e.update_retrieved([6, 7])
        assert len(e.tp) == 3
        assert len(e.fp) == 4
        assert len(e.fn) == 1
        assert e.num_tp == 3
        assert e.num_fp == 4
        assert e.num_tn == 12
        assert e.num_fn == 1

        with pytest.raises(ValueError):
            e.update_retrieved([1])  # fp
        with pytest.raises(ValueError):
            e.update_retrieved([3])  # tp

    def test_update_retrieved_full(self):
        e = StaticEvaluation(relevant=range(5), universe=20)
        e.update_retrieved(range(10))
        e.update_retrieved(range(10, 20))
        assert e.num_tp == 5
        assert e.num_fp == 15
        assert e.num_fn == 0
        assert e.num_tn == 0

    def test_ret_no_universe_subset(self):
        with pytest.raises(ValueError):
            StaticEvaluation([1, 2, "a"], [2, 3], range(10))

    def test_rel_no_universe_subset(self):
        with pytest.raises(ValueError):
            StaticEvaluation([1, 2], [2, 3, "a"], range(10))

    def test_ret_larger_than_universe(self):
        with pytest.raises(ValueError):
            StaticEvaluation(range(11), [2, 3], 10)

    def test_rel_larger_than_universe(self):
        with pytest.raises(ValueError):
            StaticEvaluation([1, 2], range(11), 10)


class TestEvaluationSheet:
    def setup_method(self):
        self.rel = [3, 4, 5, 6]
        self.scores = BaseScoresheet(
            {7: 0.9, 4: 0.8, 6: 0.7, 1: 0.6, 3: 0.5, 5: 0.2, 2: 0.1}
        )
        self.num_universe = 20
        self.universe = range(self.num_universe)

    def test_init(self):
        sheet = EvaluationSheet(self.scores, relevant=self.rel)
        expected = np.array(
            [
                [0, 1, 2, 2, 3, 4, 4],
                [1, 1, 1, 2, 2, 2, 3],
                [4, 3, 2, 2, 1, 0, 0],
                [-1, -1, -1, -1, -1, -1, -1],
            ]
        ).T
        assert_array_equal(sheet.data, expected)

        sheet = EvaluationSheet(self.scores, relevant=self.rel, universe=self.universe)
        expected = np.array(
            [
                [0, 1, 2, 2, 3, 4, 4],
                [1, 1, 1, 2, 2, 2, 3],
                [4, 3, 2, 2, 1, 0, 0],
                [15, 15, 15, 14, 14, 14, 13],
            ]
        ).T
        assert_array_equal(sheet.data, expected)

        sheet = EvaluationSheet(
            self.scores, relevant=self.rel, universe=self.num_universe
        )
        # Same expected applies as above
        assert_array_equal(sheet.data, expected)

        data = np.array([[1, 0, 0, 1], [1, 1, 0, 0]])
        sheet = EvaluationSheet(data)
        assert_array_equal(sheet.data, data)

    def test_to_file_from_file(self):
        data = np.array([[1, 0, 0, 1], [1, 1, 0, 0]])
        sheet = EvaluationSheet(data)

        with temp_file() as fname:
            sheet.to_file(fname)
            newsheet = EvaluationSheet.from_file(fname)
            assert_array_equal(sheet.data, newsheet.data)

    def test_measures(self):
        sheet_num_universe = EvaluationSheet(
            self.scores, relevant=self.rel, universe=self.num_universe
        )
        sheet_universe = EvaluationSheet(
            self.scores, relevant=self.rel, universe=self.universe
        )
        sheet_no_universe = EvaluationSheet(self.scores, relevant=self.rel)

        # Measures that don't require universe

        for sheet in (sheet_num_universe, sheet_universe, sheet_no_universe):
            assert_array_equal(
                sheet.precision(), np.array([0, 0.5, 2 / 3, 0.5, 3 / 5, 2 / 3, 4 / 7])
            )
            assert_array_equal(
                sheet.recall(), np.array([0, 0.25, 0.5, 0.5, 0.75, 1, 1])
            )

        # Measures that do require universe

        for sheet in (sheet_num_universe, sheet_universe):
            # XXX The following ones look wrong?!
            expected = np.array([1 / 16, 1 / 16, 1 / 16, 1 / 8, 1 / 8, 1 / 8, 3 / 16])
            assert_array_equal(sheet.fallout(), expected)
            expected = np.array([4 / 19, 3 / 18, 2 / 17, 2 / 16, 1 / 15, 0, 0])
            assert_array_equal(sheet.miss(), expected)
            expected = np.array([0.75, 0.8, 17 / 20, 0.8, 17 / 20, 0.9, 17 / 20])
            assert_array_equal(sheet.accuracy(), expected)
            assert_array_equal(sheet.generality(), 0.2)

        with pytest.raises(UndefinedError):
            sheet_no_universe.fallout()
        with pytest.raises(UndefinedError):
            sheet_no_universe.miss()
        with pytest.raises(UndefinedError):
            sheet_no_universe.accuracy()
        with pytest.raises(UndefinedError):
            sheet_no_universe.generality()

    def test_measures_with_empty_rel_and_ret(self):
        sheet1 = EvaluationSheet(Scoresheet(), [], [])
        sheet2 = EvaluationSheet(Scoresheet(), [], 10)
        sheet3 = EvaluationSheet(Scoresheet(), [])

        for sheet in (sheet1, sheet2, sheet3):
            for method in [
                "precision",
                "recall",
                "f_score",
                "fallout",
                "miss",
                "accuracy",
                "generality",
            ]:
                with pytest.raises(UndefinedError):
                    getattr(sheet, method)()

    def test_f_score(self):
        sheet = EvaluationSheet(self.scores, relevant=self.rel)
        expected = np.array([0, 2 / 6, 4 / 7, 4 / 8, 6 / 9, 8 / 10, 8 / 11])
        assert_array_equal(sheet.f_score(), expected)
        # $F_\beta = \frac{\beta^2 + 1 |rel \cap ret|}{\beta^2 |rel| + |ret|}$
        expected = np.array(
            [
                0,
                1.25 * 1 / (0.25 * 4 + 2),
                1.25 * 2 / (0.25 * 4 + 3),
                1.25 * 2 / (0.25 * 4 + 4),
                1.25 * 3 / (0.25 * 4 + 5),
                1.25 * 4 / (0.25 * 4 + 6),
                1.25 * 4 / (0.25 * 4 + 7),
            ]
        )
        assert_array_equal(sheet.f_score(0.5), expected)
        expected = np.array(
            [
                0,
                5 * 1 / (4 * 4 + 2),
                5 * 2 / (4 * 4 + 3),
                5 * 2 / (4 * 4 + 4),
                5 * 3 / (4 * 4 + 5),
                5 * 4 / (4 * 4 + 6),
                5 * 4 / (4 * 4 + 7),
            ]
        )
        assert_array_equal(sheet.f_score(2), expected)


================================================
FILE: tests/test_functional.py
================================================
# Should be at start of file
import matplotlib

matplotlib.use("Agg")

import os

from linkpred.cli import main


def test_simple_run():
    # TODO do this test in a temp directory and clean up afterwards
    num_files = len(os.listdir("examples"))
    main(
        "examples/inf1990-2004.net examples/inf2005-2009.net"
        " -p CommonNeighbours --quiet".split()
    )
    assert len(os.listdir("examples")) == num_files + 1


================================================
FILE: tests/test_linkpred.py
================================================
# Should be at start of file
import io

import matplotlib
import networkx as nx
import pytest
import smokesignal

import linkpred
from linkpred.evaluation.listeners import (
    CacheEvaluationListener,
    FMaxListener,
    FScorePlotter,
    RecallPrecisionPlotter,
    ROCPlotter,
)

from .utils import temp_file

matplotlib.use("Agg")


def test_imports():
    linkpred.LinkPred
    linkpred.read_network
    linkpred.network
    linkpred.exceptions
    linkpred.evaluation
    linkpred.predictors


def test_for_comparison():
    from linkpred.evaluation import Pair
    from linkpred.linkpred import for_comparison

    G = nx.path_graph(10)
    expected = {(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)}
    assert for_comparison(G) == expected

    to_delete = [Pair(2, 3), Pair(8, 9)]
    expected = {Pair(t) for t in expected}
    expected = expected.difference(to_delete)
    assert for_comparison(G, exclude=to_delete) == expected


def test_pretty_print():
    from linkpred.linkpred import pretty_print

    name = "foo"
    assert pretty_print(name) == "foo"
    params = {"bar": 0.1, "baz": 5}

    # 2 possibilities because of hash randomization
    assert pretty_print(name, params) in [
        "foo (baz = 5, bar = 0.1)",
        "foo (bar = 0.1, baz = 5)",
    ]


def test_read_unknown_network_type():
    with temp_file(suffix=".foo") as fname:
        with pytest.raises(linkpred.exceptions.LinkPredError):
            linkpred.read_network(fname)


def test_read_network():
    with temp_file(suffix=".net") as fname:
        with open(fname, "w") as fh:
            fh.write(
                """*vertices 2
1 "A"
2 "B"
*arcs 2
1 2
2 1"""
            )
        expected = nx.DiGraph()
        expected.add_edges_from([("A", "B"), ("B", "A")])

        G = linkpred.read_network(fname)
        assert set(G.edges()) == set(expected.edges())

        with open(fname) as fh:
            G = linkpred.read_network(fname)
            assert set(G.edges()) == set(expected.edges())


def test_read_pajek():
    from linkpred.linkpred import _read_pajek

    with temp_file(suffix=".net") as fname:
        with open(fname, "w") as fh:
            fh.write(
                """*vertices 2
1 "A"
2 "B"
*arcs 2
1 2
1 2"""
            )
        expected = nx.DiGraph()
        expected.add_edges_from([("A", "B")])

        G = _read_pajek(fname)
        assert isinstance(G, nx.DiGraph)
        assert sorted(G.edges()) == sorted(expected.edges())

        with open(fname, "w") as fh:
            fh.write(
                """*vertices 2
1 "A"
2 "B"
*edges 2
1 2
1 2"""
            )
        expected = nx.Graph()
        expected.add_edges_from([("A", "B")])

        G = _read_pajek(fname)
        assert isinstance(G, nx.Graph)
        assert sorted(G.edges()) == sorted(expected.edges())


def test_LinkPred_without_predictors():
    with pytest.raises(linkpred.exceptions.LinkPredError):
        linkpred.LinkPred()


class TestLinkpred:
    def teardown_method(self):
        smokesignal.clear_all()

    def config_file(self, training=False, test=False, **kwargs):
        config = {"predictors": ["Random"], "label": "testing"}

        # add training and test files, if needed
        for var, name, fname, data in (
            (
                training,
                "training",
                "foo.net",
                b"*Vertices 3\n1 A\n2 B\n3 C\n" b"*Edges 1\n1 2 1\n",
            ),
            (
                test,
                "test",
                "bar.net",
                b"*Vertices 3\n1 A\n2 B\n3 C\n" b"*Edges 1\n3 2 1\n",
            ),
        ):
            if var:
                fh = io.BytesIO()
                fh.name = fname
                fh.write(data)
                fh.seek(0)
                config[f"{name}-file"] = fh

        config.update(kwargs)
        return config

    def test_init(self):
        lp = linkpred.LinkPred(self.config_file())
        assert lp.config["label"] == "testing"
        assert lp.training is None

        lp = linkpred.LinkPred(self.config_file(training=True))
        assert isinstance(lp.training, nx.Graph)
        assert len(lp.training.nodes()) == 3
        assert len(lp.training.edges()) == 1
        assert lp.test is None

    def test_excluded(self):
        for value, expected in zip(
            ("", "old", "new"), (set(), {("A", "B")}, {("B", "C"), ("A", "C")})
        ):
            lp = linkpred.LinkPred(self.config_file(training=True, exclude=value))
            assert {tuple(sorted(p)) for p in lp.excluded} == expected
        with pytest.raises(linkpred.exceptions.LinkPredError):
            lp = linkpred.LinkPred(self.config_file(exclude="bla"))
            lp.excluded

    def test_preprocess_only_training(self):
        lp = linkpred.LinkPred(self.config_file(training=True))
        lp.preprocess()
        assert set(lp.training.nodes()) == set("AB")

    def test_preprocess_training_and_test(self):
        lp = linkpred.LinkPred(self.config_file(training=True, test=True))
        lp.preprocess()
        assert set(lp.training.nodes()) == {"B"}
        assert set(lp.test.nodes()) == {"B"}

    def test_setup_output_evaluating_without_test(self):
        lp = linkpred.LinkPred(self.config_file(training=True))
        with pytest.raises(linkpred.exceptions.LinkPredError):
            lp.setup_output()

    def test_setup_output(self):
        # Make sure this also works is $DISPLAY is not set.
        # Should probably mock this out...

        for name, klass in (
            ("recall-precision", RecallPrecisionPlotter),
            ("f-score", FScorePlotter),
            # Should be able to handle uppercase
            ("ROC", ROCPlotter),
            ("fmax", FMaxListener),
            ("cache-evaluations", CacheEvaluationListener),
        ):
            config = self.config_file(training=True, test=True, output=[name])
            lp = linkpred.LinkPred(config)
            lp.setup_output()
            assert isinstance(lp.listeners[0], klass)
            smokesignal.clear_all()
        # Has an evaluator been set up?
        assert len(lp.evaluator.params["relevant"]) == 1
        assert lp.evaluator.params["universe"] == 2
        assert isinstance(lp.evaluator.params["universe"], int)

    def test_predict_all(self):
        # Mock out linkpred.predictors
        class Stub:
            def __init__(self, training, eligible, excluded):
                self.training = training
                self.eligible = eligible
                self.excluded = excluded

            def predict(self, **params):
                self.params = params
                return "scoresheet"

        linkpred.predictors.A = Stub
        linkpred.predictors.B = Stub

        config = self.config_file(training=True)
        config["predictors"] = [
            {"name": "A", "parameters": {"X": "x"}, "displayname": "prettyA"},
            {"name": "B", "displayname": "prettyB"},
        ]
        lp = linkpred.LinkPred(config)
        results = list(lp.predict_all())
        assert results == [("A", "scoresheet"), ("B", "scoresheet")]

    def test_process_predictions(self):
        @smokesignal.on("prediction_finished")
        def a(scoresheet, dataset, predictor):
            assert scoresheet.startswith("scoresheet")
            assert predictor.startswith("pred")
            assert dataset == "testing"
            a.called = True

        @smokesignal.on("dataset_finished")
        def b(dataset):
            assert dataset == "testing"
            b.called = True

        @smokesignal.on("run_finished")
        def c():
            c.called = True

        a.called = b.called = c.called = False
        lp = linkpred.LinkPred(self.config_file())
        lp.predictions = [("pred1", "scoresheet1"), ("pred2", "scoresheet2")]
        lp.process_predictions()
        assert a.called
        assert b.called
        assert c.called
        smokesignal.clear_all()


================================================
FILE: tests/test_listeners.py
================================================
import os
import re

import smokesignal

from linkpred.evaluation import BaseScoresheet, EvaluationSheet
from linkpred.evaluation.listeners import (
    CacheEvaluationListener,
    CachePredictionListener,
    EvaluatingListener,
    _timestamped_filename,
)

from .utils import assert_array_equal


def test_timestamped_filename():
    fname = _timestamped_filename("test")
    assert re.match(r"test_\d{4}-\d{2}-\d{2}_\d{2}.\d{2}.txt", fname)
    fname = _timestamped_filename("test", "foo")
    assert re.match(r"test_\d{4}-\d{2}-\d{2}_\d{2}.\d{2}.foo", fname)


def test_EvaluatingListener():
    @smokesignal.on("evaluation_finished")
    def t(evaluation, dataset, predictor):
        assert dataset == "dataset"
        assert isinstance(evaluation, EvaluationSheet)
        assert_array_equal(evaluation.tp, [1, 1, 2, 2])
        assert_array_equal(evaluation.fp, [0, 1, 1, 2])
        assert_array_equal(evaluation.fn, [1, 1, 0, 0])
        assert_array_equal(evaluation.tn, [2, 1, 1, 0])
        assert predictor == "predictor"
        t.called = True

    t.called = False
    relevant = {1, 2}
    universe = {1, 2, 3, 4}
    scoresheet = BaseScoresheet({1: 10, 3: 5, 2: 2, 4: 1})
    EvaluatingListener(relevant=relevant, universe=universe)
    smokesignal.emit(
        "prediction_finished",
        scoresheet=scoresheet,
        dataset="dataset",
        predictor="predictor",
    )
    assert t.called
    smokesignal.clear_all()


def test_CachePredictionListener():
    l = CachePredictionListener()
    scoresheet = BaseScoresheet({1: 10, 2: 5, 3: 2, 4: 1})
    smokesignal.emit("prediction_finished", scoresheet, "d", "p")

    with open(l.fname) as fh:
        # Line endings may be different across platforms
        assert fh.read().replace("\r\n", "\n") == "1\t10\n2\t5\n3\t2\n4\t1\n"
    smokesignal.clear_all()
    os.unlink(l.fname)


def test_CacheEvaluationListener():
    l = CacheEvaluationListener()
    scores = BaseScoresheet({1: 10, 2: 5})
    ev = EvaluationSheet(scores, {1})
    smokesignal.emit("evaluation_finished", ev, "d", "p")

    ev2 = EvaluationSheet.from_file(l.fname)
    assert_array_equal(ev.data, ev2.data)
    smokesignal.clear_all()
    os.unlink(l.fname)


================================================
FILE: tests/test_predictors_base.py
================================================
import networkx as nx

from linkpred.evaluation import Pair
from linkpred.predictors import CommonNeighbours, Copy, Predictor, all_predictors


def test_bipartite_common_neighbour():
    B = nx.Graph()
    B.add_nodes_from(range(1, 5), eligible=0)
    B.add_nodes_from("abc", eligible=1)
    B.add_edges_from(
        [(1, "a"), (1, "b"), (2, "a"), (2, "b"), (2, "c"), (3, "c"), (4, "a")]
    )

    expected = {Pair("a", "b"): 2, Pair("b", "c"): 1, Pair("a", "c"): 1}
    assert CommonNeighbours(B, eligible="eligible").predict() == expected


def test_bipartite_common_neighbours_equivalent_projection():
    B = nx.bipartite.random_graph(30, 50, 0.1)
    nodes = [v for v in B if B.nodes[v]["bipartite"]]
    G = nx.bipartite.weighted_projected_graph(B, nodes)

    expected = CommonNeighbours(B, eligible="bipartite")()
    assert Copy(G).predict(weight="weight") == expected


def test_postprocessing():
    G = nx.karate_club_graph()
    prediction_all_links = CommonNeighbours(G)()
    prediction_only_new_links = CommonNeighbours(G, excluded=G.edges())()

    for link, score in prediction_all_links.items():
        if G.has_edge(*link):
            assert link not in prediction_only_new_links
        else:
            assert score == prediction_only_new_links[link]


def test_all_predictors():
    predlist = all_predictors()
    assert len(predlist) > 0
    for p in predlist:
        assert p.__base__ == Predictor


================================================
FILE: tests/test_predictors_eigenvector.py
================================================
import networkx as nx

from linkpred.predictors.eigenvector import RootedPageRank, SimRank


class TestEigenvectorRuns:
    """Test if eigenvector methods run

    This is a bit of a temporary hack to avoid regressions.We're not quite
    sure of the correct output of, especially, SimRank, and so having tests
    that at least run the code are better than nothing.

    """

    def setup_method(self):
        self.n = 20
        self.G = nx.gnm_random_graph(self.n, self.n * 3)

    def test_rooted_pagerank_runs(self):
        pred = RootedPageRank(self.G).predict()
        assert len(pred) <= self.n * (self.n - 1) // 2

    def test_simrank_runs(self):
        pred = SimRank(self.G).predict()
        assert len(pred) == self.n * (self.n - 1) // 2


class TestEigenVector:
    def test_rooted_pagerank(self):
        pass

    def test_rooted_pagerank_weighted(self):
        pass

    def test_rooted_pagerank_alpha(self):
        pass

    def test_rooted_pagerank_beta(self):
        pass

    def test_rooted_pagerank_k(self):
        pass

    def test_simrank(self):
        pass

    def test_simrank_c(self):
        pass

    def test_simrank_weighted(self):
        pass


================================================
FILE: tests/test_predictors_misc.py
================================================
import networkx as nx

from linkpred.evaluation import Pair
from linkpred.predictors.misc import Community, Copy, Random


class TestCopy:
    def setup_method(self):
        self.G = nx.Graph()
        self.G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])

    def test_copy_unweighted(self):
        expected = {Pair(0, 1): 1, Pair(1, 2): 1}
        assert Copy(self.G).predict() == expected

    def test_copy_weighted(self):
        expected = {Pair(0, 1): 3.0, Pair(1, 2): 7.5}
        assert Copy(self.G).predict(weight="weight") == expected


def test_community():
    G = nx.erdos_renyi_graph(20, 0.1)
    prediction = Community(G).predict()
    assert len(prediction) <= 190


def test_community_exclude_noneligible():
    G = nx.erdos_renyi_graph(20, 0.1)
    G.add_nodes_from(range(10), eligible=True)
    G.add_nodes_from(range(10, 20), eligible=False)

    prediction = Community(G, eligible="eligible").predict()
    assert len(prediction) <= 45
    for pair in prediction:
        for v in pair:
            assert v in range(10)


def test_random():
    G = nx.Graph()
    G.add_nodes_from(range(10), eligible=True)
    prediction = Random(G).predict()
    assert len(prediction) == 45


def test_random_exclude_noneligible():
    G = nx.Graph()
    G.add_nodes_from(range(5), eligible=True)
    G.add_nodes_from(range(5, 10), eligible=False)
    prediction = Random(G, eligible="eligible").predict()
    assert len(prediction) == 10
    for i in range(5):
        for j in range(5):
            if i != j:
                assert Pair(i, j) in prediction


================================================
FILE: tests/test_predictors_neighbour.py
================================================
from math import log, sqrt

import networkx as nx
import pytest

import linkpred.predictors.neighbour as nbr
from linkpred.evaluation import Scoresheet


class TestUnweighted:
    def setup_method(self):
        self.G = nx.Graph()
        self.G.add_edges_from([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5)])

    def test_adamic_adar(self):
        known = {
            (1, 5): 1 / log(3),
            (2, 3): 2 / log(2),
            (1, 4): 1 / log(2) + 1 / log(3),
            (4, 5): 1 / log(3),
        }
        found = nbr.AdamicAdar(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_association_strength(self):
        known = {(1, 5): 0.5, (2, 3): 1 / 3, (1, 4): 0.5, (4, 5): 0.5}
        found = nbr.AssociationStrength(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_common_neighbours(self):
        known = {(1, 5): 1, (2, 3): 2, (1, 4): 2, (4, 5): 1}
        found = nbr.CommonNeighbours(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_cosine(self):
        known = {
            (1, 5): 1 / sqrt(2),
            (2, 3): 2 / sqrt(6),
            (1, 4): 1,
            (4, 5): 1 / sqrt(2),
        }
        found = nbr.Cosine(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_degree_product(self):
        known = {
            (1, 2): 4,
            (1, 3): 6,
            (1, 4): 4,
            (1, 5): 2,
            (2, 3): 6,
            (2, 4): 4,
            (2, 5): 2,
            (3, 4): 6,
            (3, 5): 3,
            (4, 5): 2,
        }
        found = nbr.DegreeProduct(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_jaccard(self):
        known = {(1, 5): 0.5, (2, 3): 2 / 3, (1, 4): 1, (4, 5): 0.5}
        found = nbr.Jaccard(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_nmeasure(self):
        known = {
            (1, 5): sqrt(2 / 5),
            (2, 3): sqrt(8 / 13),
            (1, 4): 1,
            (4, 5): sqrt(2 / 5),
        }
        found = nbr.NMeasure(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_maxoverlap(self):
        known = {(1, 5): 0.5, (2, 3): 2 / 3, (1, 4): 1, (4, 5): 0.5}
        found = nbr.MaxOverlap(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_minoverlap(self):
        known = {(1, 5): 1, (2, 3): 1, (1, 4): 1, (4, 5): 1}
        found = nbr.MinOverlap(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_pearson(self):
        known = {(1, 5): 0.61237243, (2, 3): 2 / 3, (1, 4): 1, (4, 5): 0.61237243}
        found = nbr.Pearson(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))

    def test_resource_allocation(self):
        known = {(1, 5): 1 / 3, (2, 3): 1, (1, 4): 5 / 6, (4, 5): 1 / 3}
        found = nbr.ResourceAllocation(self.G).predict()
        assert found == pytest.approx(Scoresheet(known))


class TestWeighted:
    def setup_method(self):
        self.G = nx.Graph()
        self.G.add_weighted_edges_from(
            [(1, 2, 1), (1, 3, 5), (2, 4, 2), (3, 4, 1), (3, 5, 2)]
        )

    def test_adamic_adar(self):
        known = {
            (1, 5): 10 / log(30),
            (1, 4): 2 / log(5) + 5 / log(30),
            (2, 3): 2 / log(5) + 5 / log(26),
            (4, 5): 2 / log(30),
        }
        found = nbr.AdamicAdar(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_association_strength(self):
        known = {(1, 5): 5 / 52, (2, 3): 7 / 150, (1, 4): 7 / 130, (4, 5): 0.1}
        found = nbr.AssociationStrength(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_common_neighbours(self):
        known = {(1, 5): 10, (2, 3): 7, (1, 4): 7, (4, 5): 2}
        found = nbr.CommonNeighbours(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_cosine(self):
        known = {
            (1, 5): 10 / sqrt(104),
            (2, 3): 7 / sqrt(150),
            (1, 4): 7 / sqrt(130),
            (4, 5): 2 / sqrt(20),
        }
        found = nbr.Cosine(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_degree_product(self):
        known = {
            (1, 2): 130,
            (1, 3): 780,
            (1, 4): 130,
            (1, 5): 104,
            (2, 3): 150,
            (2, 4): 25,
            (2, 5): 20,
            (3, 4): 150,
            (3, 5): 120,
            (4, 5): 20,
        }
        found = nbr.DegreeProduct(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_jaccard(self):
        known = {(1, 5): 0.5, (2, 3): 7 / 28, (1, 4): 7 / 24, (4, 5): 2 / 7}
        found = nbr.Jaccard(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_nmeasure(self):
        known = {
            (1, 5): sqrt(50 / 173),
            (2, 3): sqrt(98 / 925),
            (1, 4): sqrt(98 / 701),
            (4, 5): sqrt(8 / 41),
        }
        found = nbr.NMeasure(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_maxoverlap(self):
        known = {(1, 5): 5 / 13, (2, 3): 7 / 30, (1, 4): 7 / 26, (4, 5): 0.4}
        found = nbr.MaxOverlap(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_minoverlap(self):
        known = {(1, 5): 2.5, (2, 3): 1.4, (1, 4): 1.4, (4, 5): 0.5}
        found = nbr.MinOverlap(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_pearson(self):
        known = {(1, 5): 0.9798502, (2, 3): 0.2965401, (1, 4): 0.4383540, (4, 5): 0.25}
        found = nbr.Pearson(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))

    def test_resource_allocation(self):
        known = {(1, 5): 1 / 3, (2, 3): 77 / 130, (1, 4): 17 / 30, (4, 5): 1 / 15}
        found = nbr.ResourceAllocation(self.G).predict(weight="weight")
        assert found == pytest.approx(Scoresheet(known))


================================================
FILE: tests/test_predictors_path.py
================================================
import networkx as nx
import numpy as np
import pytest

from linkpred.evaluation import Scoresheet
from linkpred.predictors.path import GraphDistance, Katz


def test_katz():
    G = nx.Graph()
    G.add_weighted_edges_from(
        [(1, 2, 1), (0, 2, 5), (2, 3, 1), (0, 4, 2), (1, 4, 1), (3, 5, 1), (4, 5, 3)]
    )

    beta = 0.01
    I = np.identity(6)
    for weight in ("weight", None):
        katz = Katz(G).predict(beta=beta, weight=weight)

        nodes = list(G.nodes())
        M = nx.to_numpy_array(G, nodelist=nodes, weight=weight)
        K = np.linalg.matrix_power(I - beta * M, -1) - I

        x, y = np.asarray(K).nonzero()
        for i, j in zip(x, y):
            if i == j:
                continue
            u, v = nodes[i], nodes[j]
            assert K[i, j] == pytest.approx(katz[(u, v)], abs=1e-5)


class TestGraphDistance:
    def setup_method(self):
        self.G = nx.Graph()
        self.G.add_weighted_edges_from(
            [(0, 1, 1), (0, 2, 3), (1, 2, 1), (1, 3, 2), (2, 4, 1)]
        )

    def test_unweighted(self):
        known = {
            (0, 1): 1,
            (0, 2): 1,
            (1, 2): 1,
            (1, 3): 1,
            (2, 4): 1,
            (0, 3): 0.5,
            (0, 4): 0.5,
            (1, 4): 0.5,
            (2, 3): 0.5,
            (3, 4): 1 / 3,
        }
        known = Scoresheet(known)
        graph_distance = GraphDistance(self.G).predict(weight=None)
        assert graph_distance == pytest.approx(known)

        graph_distance = GraphDistance(self.G).predict(alpha=0)
        assert graph_distance == pytest.approx(known)

    def test_weighted(self):
        known = {
            (0, 1): 1,
            (0, 2): 3,
            (1, 2): 1,
            (1, 3): 2,
            (2, 4): 1,
            (0, 3): 2 / 3,
            (0, 4): 0.75,
            (1, 4): 0.5,
            (2, 3): 2 / 3,
            (3, 4): 0.4,
        }
        known = Scoresheet(known)
        graph_distance = GraphDistance(self.G).predict()
        assert graph_distance == pytest.approx(known)

    def test_weighted_alpha(self):
        from math import sqrt

        known = {
            (0, 1): 1,
            (0, 2): sqrt(3),
            (1, 2): 1,
            (1, 3): sqrt(2),
            (2, 4): 1,
            (0, 3): 1 / (1 + 1 / sqrt(2)),
            (0, 4): 1 / (1 + 1 / sqrt(3)),
            (1, 4): 0.5,
            (2, 3): 1 / (1 + 1 / sqrt(2)),
            (3, 4): 1 / (2 + 1 / sqrt(2)),
        }
        known = Scoresheet(known)
        graph_distance = GraphDistance(self.G).predict(alpha=0.5)
        assert graph_distance == pytest.approx(known)


================================================
FILE: tests/test_preprocess.py
================================================
import networkx as nx

from linkpred.preprocess import (
    without_low_degree_nodes,
    without_selfloops,
    without_uncommon_nodes,
)


def test_without_uncommon_nodes():
    G1 = nx.erdos_renyi_graph(50, 0.1)
    G2 = nx.erdos_renyi_graph(50, 0.1)
    G1, G2 = without_uncommon_nodes([G1, G2])
    assert len(G1) <= 50
    assert len(G2) == len(G1)

    node_sets = [range(5), range(1, 7)]
    graphs = []
    for nodes in node_sets:
        G = nx.Graph()
        G.add_nodes_from(nodes)
        for n in G:
            G.nodes[n]["eligible"] = n % 2 == 0
        graphs.append(G)

    for G in without_uncommon_nodes(graphs):
        assert sorted(n for n in G if G.nodes[n]["eligible"]) == [2, 4]


def test_without_low_degree_nodes():
    G = nx.star_graph(4)
    G.add_edge(1, 2)
    G = without_low_degree_nodes(G, minimum=2)
    assert sorted(G) == [0, 1, 2]

    edges = [(0, 1), (0, 5), (2, 3), (2, 5), (4, 3)]
    G = nx.Graph()
    G.add_edges_from(edges)
    for n in G:
        G.nodes[n]["eligible"] = n % 2 == 0
    G = without_low_degree_nodes(G, minimum=2)
    assert sorted(n for n in G if G.nodes[n]["eligible"]) == [0, 2]


def test_without_selfloops():
    G = nx.Graph()
    G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 2)])
    G = without_selfloops(G)
    assert sorted(G.edges()) == [(0, 1), (1, 2), (1, 3)]


================================================
FILE: tests/test_scoresheet.py
================================================
import networkx as nx
import pytest

from linkpred.evaluation.scoresheet import BaseScoresheet, Pair, Scoresheet

from .utils import temp_file


class TestBaseScoresheet:
    def setup_method(self):
        self.scoresheet = BaseScoresheet(zip("abcdefghijklmnopqrstuvwx", range(24)))

    def test_ranked_items(self):
        d = dict(self.scoresheet.ranked_items())
        assert d == dict(self.scoresheet)

        s = self.scoresheet.ranked_items()
        assert next(s) == ("x", 23)
        assert next(s) == ("w", 22)
        assert next(s) == ("v", 21)

    def test_sets_with_threshold(self):
        threshold = 12
        d = dict(self.scoresheet.ranked_items(threshold=threshold))
        assert d == dict(zip("xwvutsrqponm", reversed(range(12, 24))))

    def test_with_too_large_threshold(self):
        threshold = 25
        for s in self.scoresheet.ranked_items(threshold=threshold):
            assert len(s) < threshold

    def test_top(self):
        top = self.scoresheet.top()
        assert top == dict(zip("opqrstuvwx", range(14, 24)))

        top = self.scoresheet.top(2)
        assert top == dict(zip("wx", range(22, 24)))

        top = self.scoresheet.top(100)
        assert len(top) == 24

    def test_to_file_from_file(self):
        with temp_file() as fname:
            self.scoresheet.to_file(fname)

            newsheet = BaseScoresheet.from_file(fname)
            assert self.scoresheet == newsheet


class TestScoresheetFile:
    def setup_method(self):
        self.sheet = Scoresheet()
        self.sheet[("a", "b")] = 2.0
        self.sheet[("b", "\xe9")] = 1.0
        self.expected = b"b\ta\t2.0\n\xc3\xa9\tb\t1.0\n"

    def test_to_file(self):
        with temp_file() as fname:
            self.sheet.to_file(fname)

            with open(fname, "rb") as fh:
                assert fh.read() == self.expected

    def test_from_file(self):
        with temp_file() as fname:
            with open(fname, "wb") as fh:
                fh.write(self.expected)

            sheet = Scoresheet.from_file(fname)
            assert sheet == self.sheet


def test_pair():
    t = ("a", "b")
    pair = Pair(t)
    assert pair == Pair(*t)
    assert pair == t
    assert pair == Pair("b", "a")
    assert pair == eval(repr(pair))
    assert str(pair) == "b - a"

    # Test unicode (C4 87 -> latin small letter C with acute)
    pair = Pair("a", "\xc4\x87")
    assert str(pair) == "\xc4\x87 - a"


def test_pair_identical_elements():
    with pytest.raises(AssertionError):
        Pair("a", "a")


def test_pair_different_types():
    # Should not raise an error
    assert Pair("a", 1) == Pair(1, "a")


def test_scoresheet():
    sheet = Scoresheet()
    t = ("a", "b")
    sheet[t] = 5
    assert len(sheet) == 1
    assert list(sheet.items()) == [(Pair("a", "b"), 5.0)]
    assert sheet[t] == 5.0
    del sheet[t]
    assert len(sheet) == 0


def test_scoresheet_process_data():
    t = ("a", "b")
    d = {t: 5}
    G = nx.Graph()
    G.add_edge(*t, weight=5)
    s = [(t, 5)]

    for x in (d, G, s):
        sheet = Scoresheet(x)
        assert sheet[t] == 5.0


================================================
FILE: tests/test_util.py
================================================
import pytest

import linkpred.util as u


def test_all_pairs():
    s = [1, 2, 3, 4]
    expected = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
    assert sorted(u.all_pairs(s)) == expected


def test_load_function():
    import os

    assert u.load_function("os.path.join") == os.path.join


def test_load_function_no_modulename():
    with pytest.raises(ValueError):
        u.load_function("join")


def test_interpolate():
    a = [10, 8, 9, 6, 6, 7, 3, 5, 6, 2, 1, 2]
    assert u.interpolate(a) == [10, 9, 9, 7, 7, 7, 6, 6, 6, 2, 2, 2]

    a = list(range(5))
    assert u.interpolate(a) == [4] * 5


def test_itersubclasses():
    class A:
        pass

    class Aa(A):
        pass

    class Ab(A):
        pass

    class Aaa(Aa):
        pass

    def name(x):
        return x.__name__

    assert list(map(name, u.itersubclasses(A))) == ["Aa", "Aaa", "Ab"]


# This is silly but hey... 100% test coverage for this file :-)
def test_itersubclasses_from_type():
    list(u.itersubclasses(type))


================================================
FILE: tests/utils.py
================================================
import contextlib
import os
import tempfile


def assert_array_equal(a1, a2):
    try:
        if not (a1 == a2).all():
            raise AssertionError(f"{a1} != {a2}")
    except AttributeError:  # a1 and a2 are lists or empty ndarrays
        assert a1 == a2


@contextlib.contextmanager
def temp_file(suffix=".tmp"):
    fd, fname = tempfile.mkstemp(suffix)
    yield fname
    os.close(fd)
    os.unlink(fname)


================================================
FILE: tox.ini
================================================
# tox (https://tox.readthedocs.io/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.

[tox]
envlist = py38,py39,py310,py311,py312

[testenv]
deps =
    pytest
    pytest-cov
    python-louvain
commands =
    python -m pytest --cov=linkpred
Download .txt
gitextract_c3o8axzv/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── coverage.yml
│       ├── publish-to-pypi.yml
│       └── tox.yml
├── .gitignore
├── CHANGELOG.rst
├── LICENSE
├── README.rst
├── examples/
│   ├── inf1990-2004.net
│   └── inf2005-2009.net
├── linkpred/
│   ├── __init__.py
│   ├── cli.py
│   ├── evaluation/
│   │   ├── __init__.py
│   │   ├── listeners.py
│   │   ├── scoresheet.py
│   │   └── static.py
│   ├── exceptions.py
│   ├── linkpred.py
│   ├── network/
│   │   ├── __init__.py
│   │   ├── addremove.py
│   │   └── algorithms.py
│   ├── predictors/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── eigenvector.py
│   │   ├── misc.py
│   │   ├── neighbour.py
│   │   ├── path.py
│   │   └── util.py
│   ├── preprocess.py
│   └── util.py
├── pyproject.toml
├── pytest.ini
├── tests/
│   ├── __init__.py
│   ├── test_addremove.py
│   ├── test_cli.py
│   ├── test_evaluation_static.py
│   ├── test_functional.py
│   ├── test_linkpred.py
│   ├── test_listeners.py
│   ├── test_predictors_base.py
│   ├── test_predictors_eigenvector.py
│   ├── test_predictors_misc.py
│   ├── test_predictors_neighbour.py
│   ├── test_predictors_path.py
│   ├── test_preprocess.py
│   ├── test_scoresheet.py
│   ├── test_util.py
│   └── utils.py
└── tox.ini
Download .txt
SYMBOL INDEX (319 symbols across 31 files)

FILE: linkpred/cli.py
  function setup_logger (line 18) | def setup_logger():
  function load_profile (line 27) | def load_profile(fname):
  function get_config (line 39) | def get_config(args=None):
  function handle_arguments (line 61) | def handle_arguments(args=None):
  function main (line 171) | def main(args=None):

FILE: linkpred/evaluation/listeners.py
  function _timestamped_filename (line 27) | def _timestamped_filename(basename, ext="txt"):
  class Listener (line 31) | class Listener:
    method __init__ (line 32) | def __init__(self):
    method on_dataset_finished (line 36) | def on_dataset_finished(self, dataset):
    method on_run_finished (line 39) | def on_run_finished(self):
  class EvaluatingListener (line 43) | class EvaluatingListener(Listener):
    method __init__ (line 44) | def __init__(self, **kwargs):
    method on_prediction_finished (line 50) | def on_prediction_finished(self, scoresheet, dataset, predictor):
  class CachePredictionListener (line 60) | class CachePredictionListener(Listener):
    method __init__ (line 61) | def __init__(self):
    method on_prediction_finished (line 66) | def on_prediction_finished(self, scoresheet, dataset, predictor):
  class CacheEvaluationListener (line 71) | class CacheEvaluationListener(Listener):
    method __init__ (line 72) | def __init__(self):
    method on_evaluation_finished (line 76) | def on_evaluation_finished(self, evaluation, dataset, predictor):
  class FMaxListener (line 81) | class FMaxListener(Listener):
    method __init__ (line 82) | def __init__(self, name, beta=1):
    method on_evaluation_finished (line 89) | def on_evaluation_finished(self, evaluation, dataset, predictor):
  class PrecisionAtKListener (line 99) | class PrecisionAtKListener(Listener):
    method __init__ (line 100) | def __init__(self, name, k=10):
    method on_evaluation_finished (line 107) | def on_evaluation_finished(self, evaluation, dataset, predictor):
  class Plotter (line 144) | class Plotter(Listener):
    method __init__ (line 145) | def __init__(self, name, xlabel="", ylabel="", filetype="pdf", chart_l...
    method add_line (line 162) | def add_line(self, predictor=""):
    method chart_look (line 175) | def chart_look(self, default=None):
    method on_evaluation_finished (line 182) | def on_evaluation_finished(self, evaluation, dataset, predictor):
    method on_run_finished (line 186) | def on_run_finished(self):
  class RecallPrecisionPlotter (line 198) | class RecallPrecisionPlotter(Plotter):
    method __init__ (line 199) | def __init__(
    method add_line (line 206) | def add_line(self, predictor=""):
    method setup_coords (line 211) | def setup_coords(self, evaluation):
  class FScorePlotter (line 216) | class FScorePlotter(Plotter):
    method __init__ (line 217) | def __init__(self, name, xlabel="#", ylabel="F-score", beta=1, **kwargs):
    method setup_coords (line 222) | def setup_coords(self, evaluation):
  class ROCPlotter (line 227) | class ROCPlotter(Plotter):
    method __init__ (line 228) | def __init__(
    method setup_coords (line 234) | def setup_coords(self, evaluation):
  class MarkednessPlotter (line 239) | class MarkednessPlotter(Plotter):
    method __init__ (line 240) | def __init__(self, name, xlabel="Miss", ylabel="Precision", **kwargs):
    method setup_coords (line 245) | def setup_coords(self, evaluation):

FILE: linkpred/evaluation/scoresheet.py
  class BaseScoresheet (line 11) | class BaseScoresheet(defaultdict):
    method __init__ (line 29) | def __init__(self, data=None):
    method __setitem__ (line 34) | def __setitem__(self, key, val):
    method process_data (line 37) | def process_data(self, data):
    method ranked_items (line 41) | def ranked_items(self, threshold=None):
    method top (line 67) | def top(self, n=10):
    method from_record (line 71) | def from_record(line, delimiter="\t"):
    method to_record (line 76) | def to_record(key, value, delimiter="\t"):
    method from_file (line 81) | def from_file(cls, fname, delimiter="\t", encoding="utf-8"):
    method to_file (line 90) | def to_file(self, fname, delimiter="\t", encoding="utf-8"):
  class Pair (line 97) | class Pair:
    method __init__ (line 113) | def __init__(self, *args):
    method _sorted_tuple (line 132) | def _sorted_tuple(t):
    method __eq__ (line 141) | def __eq__(self, other):
    method __ne__ (line 147) | def __ne__(self, other):
    method __lt__ (line 150) | def __lt__(self, other):
    method __gt__ (line 156) | def __gt__(self, other):
    method __le__ (line 162) | def __le__(self, other):
    method __ge__ (line 165) | def __ge__(self, other):
    method __getitem__ (line 168) | def __getitem__(self, idx):
    method __hash__ (line 171) | def __hash__(self):
    method __str__ (line 174) | def __str__(self):
    method __repr__ (line 177) | def __repr__(self):
    method __iter__ (line 180) | def __iter__(self):
    method __len__ (line 183) | def __len__(self):
  class Scoresheet (line 187) | class Scoresheet(BaseScoresheet):
    method __getitem__ (line 194) | def __getitem__(self, key):
    method __setitem__ (line 197) | def __setitem__(self, key, val):
    method __delitem__ (line 200) | def __delitem__(self, key):
    method process_data (line 203) | def process_data(self, data, weight="weight"):
    method from_record (line 212) | def from_record(line, delimiter="\t"):
    method to_record (line 217) | def to_record(key, value, delimiter="\t"):

FILE: linkpred/evaluation/static.py
  class UndefinedError (line 12) | class UndefinedError(Exception):
  class StaticEvaluation (line 16) | class StaticEvaluation:
    method __init__ (line 19) | def __init__(self, retrieved=None, relevant=None, universe=None):
    method update_counts (line 75) | def update_counts(self):
    method add_retrieved_item (line 87) | def add_retrieved_item(self, item):
    method update_retrieved (line 90) | def update_retrieved(self, new):
  function ensure_defined (line 114) | def ensure_defined(func):
  function ensure_universe_known (line 124) | def ensure_universe_known(func):
  class EvaluationSheet (line 135) | class EvaluationSheet:
    method __init__ (line 136) | def __init__(self, data=None, relevant=None, universe=None):
    method __len__ (line 164) | def __len__(self):
    method tp (line 168) | def tp(self):
    method fp (line 172) | def fp(self):
    method fn (line 176) | def fn(self):
    method tn (line 180) | def tn(self):
    method to_file (line 183) | def to_file(self, fname, *args, **kwargs):
    method from_file (line 187) | def from_file(cls, fname, *args, **kwargs):
    method precision (line 192) | def precision(self):
    method recall (line 196) | def recall(self):
    method fallout (line 201) | def fallout(self):
    method miss (line 206) | def miss(self):
    method accuracy (line 211) | def accuracy(self):
    method f_score (line 215) | def f_score(self, beta=1):
    method generality (line 238) | def generality(self):

FILE: linkpred/exceptions.py
  class LinkPredError (line 4) | class LinkPredError(Exception):

FILE: linkpred/linkpred.py
  function for_comparison (line 24) | def for_comparison(G, exclude=None):
  function pretty_print (line 37) | def pretty_print(name, params=None):
  function _read_pajek (line 56) | def _read_pajek(*args, **kwargs):
  function read_network (line 76) | def read_network(fh):
  class LinkPred (line 107) | class LinkPred:
    method __init__ (line 116) | def __init__(self, config=None):
    method excluded (line 147) | def excluded(self):
    method network (line 163) | def network(self, key):
    method preprocess (line 171) | def preprocess(self):
    method setup_output (line 189) | def setup_output(self):
    method do_predict_all (line 240) | def do_predict_all(self):
    method predict_all (line 266) | def predict_all(self):
    method process_predictions (line 276) | def process_predictions(self):

FILE: linkpred/network/addremove.py
  function assert_is_percentage (line 11) | def assert_is_percentage(pct):
  function add_random_edges (line 17) | def add_random_edges(G, pct):
  function remove_random_edges (line 37) | def remove_random_edges(G, pct):
  function add_remove_random_edges (line 57) | def add_remove_random_edges(G, pct_add, pct_remove):

FILE: linkpred/network/algorithms.py
  function rooted_pagerank (line 11) | def rooted_pagerank(G, root, alpha=0.85, beta=0, weight="weight"):
  function simrank (line 44) | def simrank(G, nodelist=None, c=0.8, num_iterations=10, weight="weight"):
  function raw_google_matrix (line 83) | def raw_google_matrix(G, nodelist=None, weight="weight"):

FILE: linkpred/predictors/base.py
  class Predictor (line 8) | class Predictor:
    method __init__ (line 35) | def __init__(self, G, eligible=None, excluded=None):
    method __str__ (line 78) | def __str__(self):
    method __call__ (line 81) | def __call__(self, *args, **kwargs):
    method predict (line 84) | def predict(self, *args, **kwargs):
    method eligible (line 87) | def eligible(self, u, v):
    method eligible_node (line 95) | def eligible_node(self, v):
    method eligible_nodes (line 105) | def eligible_nodes(self):
    method likely_pairs (line 113) | def likely_pairs(self, k=2):
  function all_predictors (line 133) | def all_predictors():

FILE: linkpred/predictors/eigenvector.py
  class RootedPageRank (line 9) | class RootedPageRank(Predictor):
    method predict (line 10) | def predict(self, nbunch=None, alpha=0.85, beta=0, weight="weight", k=...
  class SimRank (line 59) | class SimRank(Predictor):
    method predict (line 60) | def predict(self, c=0.8, num_iterations=10, weight="weight"):

FILE: linkpred/predictors/misc.py
  class Community (line 11) | class Community(Predictor):
    method predict (line 12) | def predict(self):  # pylint:disable=E0202
  class Copy (line 52) | class Copy(Predictor):
    method predict (line 53) | def predict(self, weight=None):  # pylint:disable=E0202
  class Random (line 74) | class Random(Predictor):
    method predict (line 75) | def predict(self):  # pylint:disable=E0202

FILE: linkpred/predictors/neighbour.py
  class AdamicAdar (line 28) | class AdamicAdar(Predictor):
    method predict (line 29) | def predict(self, weight=None):
  class AssociationStrength (line 54) | class AssociationStrength(Predictor):
    method predict (line 55) | def predict(self, weight=None):
  class CommonNeighbours (line 76) | class CommonNeighbours(Predictor):
    method predict (line 77) | def predict(self, alpha=1.0, weight=None):
  class Cosine (line 114) | class Cosine(Predictor):
    method predict (line 115) | def predict(self, weight=None):
  class DegreeProduct (line 136) | class DegreeProduct(Predictor):
    method predict (line 137) | def predict(self, weight=None, minimum=1):
  class Jaccard (line 161) | class Jaccard(Predictor):
    method predict (line 162) | def predict(self, weight=None):
  class NMeasure (line 183) | class NMeasure(Predictor):
    method predict (line 184) | def predict(self, weight=None):
  function _predict_overlap (line 211) | def _predict_overlap(predictor, function, weight=None):
  class MaxOverlap (line 226) | class MaxOverlap(Predictor):
    method predict (line 227) | def predict(self, weight=None):
  class MinOverlap (line 240) | class MinOverlap(Predictor):
    method predict (line 241) | def predict(self, weight=None):
  class Pearson (line 254) | class Pearson(Predictor):
    method predict (line 255) | def predict(self, weight=None):
  class ResourceAllocation (line 288) | class ResourceAllocation(Predictor):
    method predict (line 289) | def predict(self, weight=None):

FILE: linkpred/predictors/path.py
  class GraphDistance (line 10) | class GraphDistance(Predictor):
    method predict (line 11) | def predict(self, weight="weight", alpha=1):
  function matrix_power (line 62) | def matrix_power(arr, n):
  class Katz (line 73) | class Katz(Predictor):
    method predict (line 74) | def predict(self, beta=0.001, max_power=5, weight="weight", dtype=None):

FILE: linkpred/predictors/util.py
  function neighbourhood (line 4) | def neighbourhood(G, n, k=1):
  function neighbourhood_intersection_size (line 13) | def neighbourhood_intersection_size(G, a, b, weight=None, k=1):
  function neighbourhood_size (line 27) | def neighbourhood_size(G, u, weight=None, k=1, power=2):
  function neighbourhood_union_size (line 44) | def neighbourhood_union_size(G, a, b, weight=None, k=1, power=2):

FILE: linkpred/preprocess.py
  function without_low_degree_nodes (line 8) | def without_low_degree_nodes(G, minimum=1, eligible=None):
  function without_uncommon_nodes (line 38) | def without_uncommon_nodes(networks, eligible=None):
  function without_selfloops (line 81) | def without_selfloops(G):

FILE: linkpred/util.py
  function all_pairs (line 5) | def all_pairs(iterable):
  function progressbar (line 10) | def progressbar(it, prefix="", size=60):
  function load_function (line 33) | def load_function(full_functionname):
  function interpolate (line 51) | def interpolate(curve):
  function itersubclasses (line 59) | def itersubclasses(cls, _seen=None):

FILE: tests/test_addremove.py
  function test_add_random_edges (line 11) | def test_add_random_edges():
  function test_remove_random_edges (line 26) | def test_remove_random_edges():
  function test_add_remove_random_edges (line 41) | def test_add_remove_random_edges():

FILE: tests/test_cli.py
  function temp_empty_file (line 11) | def temp_empty_file():
  class TestProfileFile (line 18) | class TestProfileFile:
    method setup_method (line 19) | def setup_method(self):
    method teardown_method (line 30) | def teardown_method(self):
    method test_load_profile_yaml (line 38) | def test_load_profile_yaml(self):
    method test_load_profile_json (line 50) | def test_load_profile_json(self):
    method test_load_profile_error (line 62) | def test_load_profile_error(self):
    method test_get_config (line 73) | def test_get_config(self):
  function test_no_training_file (line 97) | def test_no_training_file():
  function test_nonexisting_predictor (line 102) | def test_nonexisting_predictor():
  function test_handle_arguments (line 107) | def test_handle_arguments():

FILE: tests/test_evaluation_static.py
  class TestStaticEvaluation (line 15) | class TestStaticEvaluation:
    method setup_method (line 16) | def setup_method(self):
    method test_init (line 22) | def test_init(self):
    method test_update_retrieved (line 44) | def test_update_retrieved(self):
    method test_update_retrieved_num_universe (line 59) | def test_update_retrieved_num_universe(self):
    method test_update_retrieved_full (line 75) | def test_update_retrieved_full(self):
    method test_ret_no_universe_subset (line 84) | def test_ret_no_universe_subset(self):
    method test_rel_no_universe_subset (line 88) | def test_rel_no_universe_subset(self):
    method test_ret_larger_than_universe (line 92) | def test_ret_larger_than_universe(self):
    method test_rel_larger_than_universe (line 96) | def test_rel_larger_than_universe(self):
  class TestEvaluationSheet (line 101) | class TestEvaluationSheet:
    method setup_method (line 102) | def setup_method(self):
    method test_init (line 110) | def test_init(self):
    method test_to_file_from_file (line 143) | def test_to_file_from_file(self):
    method test_measures (line 152) | def test_measures(self):
    method test_measures_with_empty_rel_and_ret (line 192) | def test_measures_with_empty_rel_and_ret(self):
    method test_f_score (line 210) | def test_f_score(self):

FILE: tests/test_functional.py
  function test_simple_run (line 11) | def test_simple_run():

FILE: tests/test_linkpred.py
  function test_imports (line 23) | def test_imports():
  function test_for_comparison (line 32) | def test_for_comparison():
  function test_pretty_print (line 46) | def test_pretty_print():
  function test_read_unknown_network_type (line 60) | def test_read_unknown_network_type():
  function test_read_network (line 66) | def test_read_network():
  function test_read_pajek (line 88) | def test_read_pajek():
  function test_LinkPred_without_predictors (line 125) | def test_LinkPred_without_predictors():
  class TestLinkpred (line 130) | class TestLinkpred:
    method teardown_method (line 131) | def teardown_method(self):
    method config_file (line 134) | def config_file(self, training=False, test=False, **kwargs):
    method test_init (line 162) | def test_init(self):
    method test_excluded (line 173) | def test_excluded(self):
    method test_preprocess_only_training (line 183) | def test_preprocess_only_training(self):
    method test_preprocess_training_and_test (line 188) | def test_preprocess_training_and_test(self):
    method test_setup_output_evaluating_without_test (line 194) | def test_setup_output_evaluating_without_test(self):
    method test_setup_output (line 199) | def test_setup_output(self):
    method test_predict_all (line 221) | def test_predict_all(self):
    method test_process_predictions (line 245) | def test_process_predictions(self):

FILE: tests/test_listeners.py
  function test_timestamped_filename (line 17) | def test_timestamped_filename():
  function test_EvaluatingListener (line 24) | def test_EvaluatingListener():
  function test_CachePredictionListener (line 51) | def test_CachePredictionListener():
  function test_CacheEvaluationListener (line 63) | def test_CacheEvaluationListener():

FILE: tests/test_predictors_base.py
  function test_bipartite_common_neighbour (line 7) | def test_bipartite_common_neighbour():
  function test_bipartite_common_neighbours_equivalent_projection (line 19) | def test_bipartite_common_neighbours_equivalent_projection():
  function test_postprocessing (line 28) | def test_postprocessing():
  function test_all_predictors (line 40) | def test_all_predictors():

FILE: tests/test_predictors_eigenvector.py
  class TestEigenvectorRuns (line 6) | class TestEigenvectorRuns:
    method setup_method (line 15) | def setup_method(self):
    method test_rooted_pagerank_runs (line 19) | def test_rooted_pagerank_runs(self):
    method test_simrank_runs (line 23) | def test_simrank_runs(self):
  class TestEigenVector (line 28) | class TestEigenVector:
    method test_rooted_pagerank (line 29) | def test_rooted_pagerank(self):
    method test_rooted_pagerank_weighted (line 32) | def test_rooted_pagerank_weighted(self):
    method test_rooted_pagerank_alpha (line 35) | def test_rooted_pagerank_alpha(self):
    method test_rooted_pagerank_beta (line 38) | def test_rooted_pagerank_beta(self):
    method test_rooted_pagerank_k (line 41) | def test_rooted_pagerank_k(self):
    method test_simrank (line 44) | def test_simrank(self):
    method test_simrank_c (line 47) | def test_simrank_c(self):
    method test_simrank_weighted (line 50) | def test_simrank_weighted(self):

FILE: tests/test_predictors_misc.py
  class TestCopy (line 7) | class TestCopy:
    method setup_method (line 8) | def setup_method(self):
    method test_copy_unweighted (line 12) | def test_copy_unweighted(self):
    method test_copy_weighted (line 16) | def test_copy_weighted(self):
  function test_community (line 21) | def test_community():
  function test_community_exclude_noneligible (line 27) | def test_community_exclude_noneligible():
  function test_random (line 39) | def test_random():
  function test_random_exclude_noneligible (line 46) | def test_random_exclude_noneligible():

FILE: tests/test_predictors_neighbour.py
  class TestUnweighted (line 10) | class TestUnweighted:
    method setup_method (line 11) | def setup_method(self):
    method test_adamic_adar (line 15) | def test_adamic_adar(self):
    method test_association_strength (line 25) | def test_association_strength(self):
    method test_common_neighbours (line 30) | def test_common_neighbours(self):
    method test_cosine (line 35) | def test_cosine(self):
    method test_degree_product (line 45) | def test_degree_product(self):
    method test_jaccard (line 61) | def test_jaccard(self):
    method test_nmeasure (line 66) | def test_nmeasure(self):
    method test_maxoverlap (line 76) | def test_maxoverlap(self):
    method test_minoverlap (line 81) | def test_minoverlap(self):
    method test_pearson (line 86) | def test_pearson(self):
    method test_resource_allocation (line 91) | def test_resource_allocation(self):
  class TestWeighted (line 97) | class TestWeighted:
    method setup_method (line 98) | def setup_method(self):
    method test_adamic_adar (line 104) | def test_adamic_adar(self):
    method test_association_strength (line 114) | def test_association_strength(self):
    method test_common_neighbours (line 119) | def test_common_neighbours(self):
    method test_cosine (line 124) | def test_cosine(self):
    method test_degree_product (line 134) | def test_degree_product(self):
    method test_jaccard (line 150) | def test_jaccard(self):
    method test_nmeasure (line 155) | def test_nmeasure(self):
    method test_maxoverlap (line 165) | def test_maxoverlap(self):
    method test_minoverlap (line 170) | def test_minoverlap(self):
    method test_pearson (line 175) | def test_pearson(self):
    method test_resource_allocation (line 180) | def test_resource_allocation(self):

FILE: tests/test_predictors_path.py
  function test_katz (line 9) | def test_katz():
  class TestGraphDistance (line 32) | class TestGraphDistance:
    method setup_method (line 33) | def setup_method(self):
    method test_unweighted (line 39) | def test_unweighted(self):
    method test_weighted (line 59) | def test_weighted(self):
    method test_weighted_alpha (line 76) | def test_weighted_alpha(self):

FILE: tests/test_preprocess.py
  function test_without_uncommon_nodes (line 10) | def test_without_uncommon_nodes():
  function test_without_low_degree_nodes (line 30) | def test_without_low_degree_nodes():
  function test_without_selfloops (line 45) | def test_without_selfloops():

FILE: tests/test_scoresheet.py
  class TestBaseScoresheet (line 9) | class TestBaseScoresheet:
    method setup_method (line 10) | def setup_method(self):
    method test_ranked_items (line 13) | def test_ranked_items(self):
    method test_sets_with_threshold (line 22) | def test_sets_with_threshold(self):
    method test_with_too_large_threshold (line 27) | def test_with_too_large_threshold(self):
    method test_top (line 32) | def test_top(self):
    method test_to_file_from_file (line 42) | def test_to_file_from_file(self):
  class TestScoresheetFile (line 50) | class TestScoresheetFile:
    method setup_method (line 51) | def setup_method(self):
    method test_to_file (line 57) | def test_to_file(self):
    method test_from_file (line 64) | def test_from_file(self):
  function test_pair (line 73) | def test_pair():
  function test_pair_identical_elements (line 87) | def test_pair_identical_elements():
  function test_pair_different_types (line 92) | def test_pair_different_types():
  function test_scoresheet (line 97) | def test_scoresheet():
  function test_scoresheet_process_data (line 108) | def test_scoresheet_process_data():

FILE: tests/test_util.py
  function test_all_pairs (line 6) | def test_all_pairs():
  function test_load_function (line 12) | def test_load_function():
  function test_load_function_no_modulename (line 18) | def test_load_function_no_modulename():
  function test_interpolate (line 23) | def test_interpolate():
  function test_itersubclasses (line 31) | def test_itersubclasses():
  function test_itersubclasses_from_type (line 51) | def test_itersubclasses_from_type():

FILE: tests/utils.py
  function assert_array_equal (line 6) | def assert_array_equal(a1, a2):
  function temp_file (line 15) | def temp_file(suffix=".tmp"):
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (186K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 125,
    "preview": "version: 2\nupdates:\n- package-ecosystem: pip\n  directory: \"/\"\n  schedule:\n    interval: daily\n  open-pull-requests-limit"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "chars": 915,
    "preview": "name: Calculate test coverage\n\non: [push]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Check out\n   "
  },
  {
    "path": ".github/workflows/publish-to-pypi.yml",
    "chars": 2524,
    "preview": "name: Publish 📦 to PyPI\non: push\n\njobs:\n  build:\n    name: Build distribution 📦\n    runs-on: ubuntu-latest\n\n    steps:\n "
  },
  {
    "path": ".github/workflows/tox.yml",
    "chars": 787,
    "preview": "name: Test with tox\n\non: [push]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      max-parallel: 5\n      matr"
  },
  {
    "path": ".gitignore",
    "chars": 395,
    "preview": "*.py[cod]\n*.swp\n\nexamples/*.pdf\nexamples/*.png\nexamples/*.txt\n\n.ropeproject\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg"
  },
  {
    "path": "CHANGELOG.rst",
    "chars": 844,
    "preview": "Changelog\n=========\n\n**Note**: I only started keeping this changelog from version 0.5 onwards.\n\nVersion 0.6\n-----------\n"
  },
  {
    "path": "LICENSE",
    "chars": 1543,
    "preview": "New BSD License\n\nCopyright (c) 2013 The linkpred developers.\nAll rights reserved.\n\n\nRedistribution and use in source and"
  },
  {
    "path": "README.rst",
    "chars": 3984,
    "preview": "⚠️ **Note: This package is in maintenance mode**.\nCritical bugs will continue to be resolved,\nbut no new features will b"
  },
  {
    "path": "examples/inf1990-2004.net",
    "chars": 20651,
    "preview": "*network Informetrics1990-2004\n*vertices 632\n1 \"Pereira, JCR\"\n2 \"Peters, HPF\"\n3 \"Widhalm, C\"\n4 \"Verbeek, A\"\n5 \"Salvador,"
  },
  {
    "path": "examples/inf2005-2009.net",
    "chars": 21247,
    "preview": "*network Informetrics2005-2009\n*vertices 634\n1 \"Pereira, JCR\"\n2 \"Harthorn, BH\"\n3 \"Verbeek, A\"\n4 \"Fischer, TC\"\n5 \"Van Den"
  },
  {
    "path": "linkpred/__init__.py",
    "chars": 98,
    "preview": "\"\"\"linkpred, a Python package for link prediction\"\"\"\nfrom .linkpred import *\n\n__version__ = \"0.6\"\n"
  },
  {
    "path": "linkpred/cli.py",
    "chars": 4580,
    "preview": "\"\"\"CLI handling\"\"\"\nimport argparse\nimport json\nimport logging\nimport sys\n\nimport yaml\n\nfrom .exceptions import LinkPredE"
  },
  {
    "path": "linkpred/evaluation/__init__.py",
    "chars": 100,
    "preview": "\"\"\"Module for evaluating link prediction results\"\"\"\nfrom .scoresheet import *\nfrom .static import *\n"
  },
  {
    "path": "linkpred/evaluation/listeners.py",
    "chars": 6878,
    "preview": "import copy\nimport logging\nfrom time import localtime, strftime\n\nimport smokesignal\n\nfrom ..util import interpolate\nfrom"
  },
  {
    "path": "linkpred/evaluation/scoresheet.py",
    "chars": 6632,
    "preview": "import logging\nfrom collections import defaultdict\n\nimport networkx as nx\nfrom networkx.readwrite.pajek import make_qstr"
  },
  {
    "path": "linkpred/evaluation/static.py",
    "chars": 7782,
    "preview": "import logging\n\nimport numpy as np\n\nfrom .scoresheet import BaseScoresheet\n\nlog = logging.getLogger(__name__)\n\n__all__ ="
  },
  {
    "path": "linkpred/exceptions.py",
    "chars": 100,
    "preview": "\"\"\"Package-specific exceptions\"\"\"\n\n\nclass LinkPredError(Exception):\n    \"\"\"Link prediction error\"\"\"\n"
  },
  {
    "path": "linkpred/linkpred.py",
    "chars": 9072,
    "preview": "\"\"\"linkpred main module\"\"\"\nimport contextlib\nimport logging\nimport os\n\nimport networkx as nx\nimport smokesignal\n\nfrom . "
  },
  {
    "path": "linkpred/network/__init__.py",
    "chars": 51,
    "preview": "from .addremove import *\nfrom .algorithms import *\n"
  },
  {
    "path": "linkpred/network/addremove.py",
    "chars": 2118,
    "preview": "import logging\nimport random\n\nimport networkx as nx\n\nlog = logging.getLogger(__name__)\n\n__all__ = [\"add_random_edges\", \""
  },
  {
    "path": "linkpred/network/algorithms.py",
    "chars": 3048,
    "preview": "import logging\n\nimport networkx as nx\nimport numpy as np\n\nlog = logging.getLogger(__name__)\n\n__all__ = [\"rooted_pagerank"
  },
  {
    "path": "linkpred/predictors/__init__.py",
    "chars": 112,
    "preview": "from .base import *\nfrom .eigenvector import *\nfrom .misc import *\nfrom .neighbour import *\nfrom .path import *\n"
  },
  {
    "path": "linkpred/predictors/base.py",
    "chars": 4411,
    "preview": "import contextlib\n\nfrom .util import neighbourhood\n\n__all__ = [\"Predictor\", \"all_predictors\"]\n\n\nclass Predictor:\n    \"\"\""
  },
  {
    "path": "linkpred/predictors/eigenvector.py",
    "chars": 3595,
    "preview": "import networkx as nx\n\nfrom ..evaluation import Scoresheet\nfrom ..network import rooted_pagerank, simrank\nfrom ..util im"
  },
  {
    "path": "linkpred/predictors/misc.py",
    "chars": 2762,
    "preview": "import random\nfrom collections import defaultdict\n\nfrom ..evaluation import Scoresheet\nfrom ..util import all_pairs\nfrom"
  },
  {
    "path": "linkpred/predictors/neighbour.py",
    "chars": 10163,
    "preview": "import math\n\nfrom ..evaluation import Scoresheet\nfrom ..util import all_pairs\nfrom .base import Predictor\nfrom .util imp"
  },
  {
    "path": "linkpred/predictors/path.py",
    "chars": 4124,
    "preview": "import networkx as nx\n\nfrom ..evaluation import Scoresheet\nfrom ..util import progressbar\nfrom .base import Predictor\n\n_"
  },
  {
    "path": "linkpred/predictors/util.py",
    "chars": 1803,
    "preview": "import networkx as nx\n\n\ndef neighbourhood(G, n, k=1):\n    \"\"\"Get k-neighbourhood of node n\"\"\"\n    if k == 1:\n        ret"
  },
  {
    "path": "linkpred/preprocess.py",
    "chars": 2330,
    "preview": "import logging\n\nimport networkx as nx\n\nlog = logging.getLogger(__name__)\n\n\ndef without_low_degree_nodes(G, minimum=1, el"
  },
  {
    "path": "linkpred/util.py",
    "chars": 1985,
    "preview": "import itertools\nimport sys\n\n\ndef all_pairs(iterable):\n    \"\"\"Return iterator over all possible pairs in l\"\"\"\n    return"
  },
  {
    "path": "pyproject.toml",
    "chars": 2356,
    "preview": "[build-system]\nrequires = [\"flit_core >=3.2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n\n[project]\nname = \"linkpred\"\nautho"
  },
  {
    "path": "pytest.ini",
    "chars": 135,
    "preview": "[pytest]\nfilterwarnings=\n    # will be fixed in networkx 2.5\n    ignore:.*is deprecated and will be removed in SciPy 2.0"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/test_addremove.py",
    "chars": 1235,
    "preview": "import networkx as nx\nimport pytest\n\nfrom linkpred.network.addremove import (\n    add_random_edges,\n    add_remove_rando"
  },
  {
    "path": "tests/test_cli.py",
    "chars": 4346,
    "preview": "import os\nimport tempfile\nfrom contextlib import contextmanager\n\nimport pytest\n\nfrom linkpred.cli import get_config, han"
  },
  {
    "path": "tests/test_evaluation_static.py",
    "chars": 8263,
    "preview": "import numpy as np\nimport pytest\n\nfrom linkpred.evaluation import (\n    BaseScoresheet,\n    EvaluationSheet,\n    Scoresh"
  },
  {
    "path": "tests/test_functional.py",
    "chars": 430,
    "preview": "# Should be at start of file\nimport matplotlib\n\nmatplotlib.use(\"Agg\")\n\nimport os\n\nfrom linkpred.cli import main\n\n\ndef te"
  },
  {
    "path": "tests/test_linkpred.py",
    "chars": 7971,
    "preview": "# Should be at start of file\nimport io\n\nimport matplotlib\nimport networkx as nx\nimport pytest\nimport smokesignal\n\nimport"
  },
  {
    "path": "tests/test_listeners.py",
    "chars": 2215,
    "preview": "import os\nimport re\n\nimport smokesignal\n\nfrom linkpred.evaluation import BaseScoresheet, EvaluationSheet\nfrom linkpred.e"
  },
  {
    "path": "tests/test_predictors_base.py",
    "chars": 1430,
    "preview": "import networkx as nx\n\nfrom linkpred.evaluation import Pair\nfrom linkpred.predictors import CommonNeighbours, Copy, Pred"
  },
  {
    "path": "tests/test_predictors_eigenvector.py",
    "chars": 1190,
    "preview": "import networkx as nx\n\nfrom linkpred.predictors.eigenvector import RootedPageRank, SimRank\n\n\nclass TestEigenvectorRuns:\n"
  },
  {
    "path": "tests/test_predictors_misc.py",
    "chars": 1576,
    "preview": "import networkx as nx\n\nfrom linkpred.evaluation import Pair\nfrom linkpred.predictors.misc import Community, Copy, Random"
  },
  {
    "path": "tests/test_predictors_neighbour.py",
    "chars": 6319,
    "preview": "from math import log, sqrt\n\nimport networkx as nx\nimport pytest\n\nimport linkpred.predictors.neighbour as nbr\nfrom linkpr"
  },
  {
    "path": "tests/test_predictors_path.py",
    "chars": 2628,
    "preview": "import networkx as nx\nimport numpy as np\nimport pytest\n\nfrom linkpred.evaluation import Scoresheet\nfrom linkpred.predict"
  },
  {
    "path": "tests/test_preprocess.py",
    "chars": 1341,
    "preview": "import networkx as nx\n\nfrom linkpred.preprocess import (\n    without_low_degree_nodes,\n    without_selfloops,\n    withou"
  },
  {
    "path": "tests/test_scoresheet.py",
    "chars": 3113,
    "preview": "import networkx as nx\nimport pytest\n\nfrom linkpred.evaluation.scoresheet import BaseScoresheet, Pair, Scoresheet\n\nfrom ."
  },
  {
    "path": "tests/test_util.py",
    "chars": 1014,
    "preview": "import pytest\n\nimport linkpred.util as u\n\n\ndef test_all_pairs():\n    s = [1, 2, 3, 4]\n    expected = [(1, 2), (1, 3), (1"
  },
  {
    "path": "tests/utils.py",
    "chars": 416,
    "preview": "import contextlib\nimport os\nimport tempfile\n\n\ndef assert_array_equal(a1, a2):\n    try:\n        if not (a1 == a2).all():\n"
  },
  {
    "path": "tox.ini",
    "chars": 401,
    "preview": "# tox (https://tox.readthedocs.io/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will "
  }
]

About this extraction

This page contains the full source code of the rafguns/linkpred GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (167.1 KB), approximately 56.1k tokens, and a symbol index with 319 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!