diff -pruN 0.9.6+ds-2/CHANGELOG.md 0.9.9+ds-1/CHANGELOG.md
--- 0.9.6+ds-2/CHANGELOG.md	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/CHANGELOG.md	2022-06-04 12:27:41.000000000 +0000
@@ -1,5 +1,84 @@
 # igraph C library changelog
 
+## [0.9.9] - 2022-06-04
+
+### Changed
+
+ - `igraph_community_walktrap()` now uses double precision floating point operations internally instead of single precision.
+ - In `igraph_community_leiden()`, the `nb_clusters` output parameter is now optional (i.e. it can be `NULL`).
+ - `igraph_read_graph_graphml()` no longer attempts to temporarily set the C locale, and will therefore not work correctly if the current locale uses a decimal comma.
+
+### Fixed
+
+ - `igraph_community_walktrap()` would return an invalid `modularity` vector when the `merges` matrix was not requested.
+ - `igraph_community_walktrap()` would return a `modularity` vector that was too long for disconnected graphs. This would cause a failure in some weighted graphs when the `membership` vector was requested.
+ - `igraph_community_walktrap()` now checks the weight vector: only non-negative weights are accepted, and all vertices must have non-zero strength.
+ - `igraph_community_walktrap()` now returns a modularity score of NaN for graphs with no edges.
+ - `igraph_community_fast_greedy()` now returns a modularity score of NaN for graphs with no edges.
+ - `igraph_community_edge_betweenness()` now returns a modularity vector with a single NaN entry for graph with no edges. Previously it returned a zero-length vector.
+ - `igraph_community_leading_eigenvector()` does not ignore non-ARPACK-related errors from `igraph_arpack_rssolve()` any more.
+ - `igraph_preference_game()` now works correctly when `fixed_size` is true and
+   `type_dist` is not given; earlier versions had a bug where more than half of
+   the vertices mistakenly ended up in group 0.
+ - Fixed a memory leak in `igraph_hrg_fit()` when using `start=1`.
+ - `igraph_write_graph_dot()` now outputs NaN values unchanged.
+ - `igraph_write_graph_dot()` no longer produces invalid DOT files when empty string attributes are present.
+ - `igraph_layout_fruchterman_reingold()` and `igraph_layout_kamada_kawai()`, as well as their 3D versions, did not respect vertex coordinate bounds (`xmin`, `xmax`, etc.) when minimum values were large or maximum values were small. This is now fixed.
+ - The initial coordinates of the Kamada-Kawai layout (`igraph_layout_kamada_kawai()` and `igraph_layout_kamada_kawai_3d()`) are chosen to be more in line with the original publication, improving the stability of the result. See isse #963. This changes the output of the function for the same graph, compared with previous versions. To obtain the same layout, initialize coordinates with `igraph_layout_circle()` (in 2D) or `igraph_layout_sphere()` (in 3D).
+ - Improved numerical stability in Kamada-Kawai layout.
+ - Corrected a problem in the calculation of displacements in `igraph_layout_fruchterman_reingold()` and its 3D version. This fixes using the "grid" variant of the algorithm on disconnected graphs.
+ - `igraph_sumtree_search()` would consider search interval opens on the left and closed on the right, contrary to the documentation. This is now corrected to closed on the left and open on the right. In some cases this lead to a zero-weight element being returned for a zero search value. See issue #2080.
+
+### Other
+
+ - Documentation improvements.
+
+## [0.9.8] - 2022-04-08
+
+### Fixed
+
+ - Assertion failure in `igraph_bfs()` when an empty `roots` or `restricted` vector was provided.
+ - `igraph_diversity()` now returns 0 for degree-1 vertices. Previously it incorrectly returned NaN or +-Inf depending on roundoff errors.
+ - `igraph_community_walktrap()` does not crash any more when provided with
+   `modularity=NULL` and `membership=NULL`.
+
+### Other
+
+ - Documentation improvements.
+
+## [0.9.7] - 2022-03-16
+
+### Changed
+
+ - `igraph_get_all_shortest_paths_dijsktra()` now uses tolerances when comparing path
+   lengths, and is thus robust to numerical roundoff errors.
+ - `igraph_vector_*_swap` and `igraph_matrix_swap` now take O(1) instead of O(n) and accept all sizes.
+
+### Fixed
+
+ - NCOL and LGL format writers no longer accept "name" and "weight" attributes
+   of invalid types.
+ - The LGL writer could not access numerical weight attributes, potentially leading
+   to crashes.
+ - External PLFIT libraries and their headers are now detected at their standard
+   installation location.
+ - `igraph_vector_init()` no longer accepts negative vector sizes.
+ - `igraph_assortativity_nominal()` crashed on the null graph.
+ - Label propagation now ensures that all labels are dominant.
+ - Fixed incorrect partition results for walktrap algorithm (issue #1927)
+ - Negative values returned by `igraph_rng_get_integer()` and `RNG_INTEGER()` were incorrect,
+   one larger than they should have been.
+ - `igraph_community_walktrap()` now checks its `steps` input argument.
+ - The first modularity value reported by `igraph_community_walktrap()` was
+   incorrect (it was always zero). This is now fixed.
+ - `igraph_correlated_game()` would return incorrect results, or exhaust the memory,
+    for most input graphs that were not generated with `igraph_erdos_renyi_game_gnp()`.
+
+### Other
+
+ - The C attribute handler now verifies attribute types when retrieving attributes.
+ - Documentation improvements.
+
 ## [0.9.6] - 2022-01-05
 
 ### Changed
@@ -519,7 +598,10 @@
  - Provide proper support for Windows, using `__declspec(dllexport)` and `__declspec(dllimport)` for `DLL`s and static usage by using `#define IGRAPH_STATIC 1`.
  - Provided integer versions of `dqueue` and `stack` data types.
 
-[Unreleased]: https://github.com/igraph/igraph/compare/0.9.5..HEAD
+[Unreleased]: https://github.com/igraph/igraph/compare/0.9.8..HEAD
+[0.9.8]: https://github.com/igraph/igraph/compare/0.9.7...0.9.8
+[0.9.7]: https://github.com/igraph/igraph/compare/0.9.6...0.9.7
+[0.9.6]: https://github.com/igraph/igraph/compare/0.9.5...0.9.6
 [0.9.5]: https://github.com/igraph/igraph/compare/0.9.4...0.9.5
 [0.9.4]: https://github.com/igraph/igraph/compare/0.9.3...0.9.4
 [0.9.3]: https://github.com/igraph/igraph/compare/0.9.2...0.9.3
diff -pruN 0.9.6+ds-2/CMakeLists.txt 0.9.9+ds-1/CMakeLists.txt
--- 0.9.6+ds-2/CMakeLists.txt	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/CMakeLists.txt	2022-06-04 12:27:41.000000000 +0000
@@ -101,7 +101,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_N
     EXECUTABLE "${CMAKE_COMMAND}" "--build" "${PROJECT_BINARY_DIR}" "--target" "check"
     # Generated files are excluded; apparently the CodeCoverage script has some
     # problems with them. Yes, the exclusion is correct, it refers to a nonexistent
-    # directory that somehow gets into the coverage resolts. /Applications is for
+    # directory that somehow gets into the coverage results. /Applications is for
     # macOS -- it excludes files from the macOS SDK.
     EXCLUDE "io/*.l" "io/parsers/*" "/Applications/Xcode*" "examples/*" "tests/*"
   )
diff -pruN 0.9.6+ds-2/debian/changelog 0.9.9+ds-1/debian/changelog
--- 0.9.6+ds-2/debian/changelog	2022-01-08 20:50:51.000000000 +0000
+++ 0.9.9+ds-1/debian/changelog	2022-06-04 12:45:17.000000000 +0000
@@ -1,3 +1,32 @@
+igraph (0.9.9+ds-1) unstable; urgency=medium
+
+  * New upstream nano version.
+  * Debianization:
+    - d/watch, now download directly from github.
+
+ -- Jerome Benoit <calculus@rezozer.net>  Sat, 04 Jun 2022 12:45:17 +0000
+
+igraph (0.9.8+ds-1) unstable; urgency=medium
+
+  * New upstream nano version.
+  * Debianization:
+    - d/libigraph2.symbols, update.
+
+ -- Jerome Benoit <calculus@rezozer.net>  Sat, 09 Apr 2022 13:45:34 +0000
+
+igraph (0.9.7+ds-1) unstable; urgency=medium
+
+  * New upstream nano version.
+  * Debianization:
+    - d/rules:
+      - explicitly disable LTO for the static library as LTO does
+        not really achieve anything for static libraries (thanks to
+        Timo Röhling <roehling@debian.org> for the hint).
+    - d/patches/*:
+      - d/p/upstream-external-plfit.patch, update (partially integrated).
+
+ -- Jerome Benoit <calculus@rezozer.net>  Wed, 16 Mar 2022 23:11:57 +0000
+
 igraph (0.9.6+ds-2) unstable; urgency=medium
 
   * Source only upload
diff -pruN 0.9.6+ds-2/debian/libigraph2.symbols 0.9.9+ds-1/debian/libigraph2.symbols
--- 0.9.6+ds-2/debian/libigraph2.symbols	2022-01-08 20:50:51.000000000 +0000
+++ 0.9.9+ds-1/debian/libigraph2.symbols	2022-04-09 13:13:12.000000000 +0000
@@ -474,13 +474,6 @@ libigraph.so.2 libigraph2 #MINVER#
  igraph_has_attribute_table@Base 0.9.4
  igraph_has_loop@Base 0.9.4
  igraph_has_multiple@Base 0.9.4
- igraph_hashtable_addset2@Base 0.9.4
- igraph_hashtable_addset@Base 0.9.4
- igraph_hashtable_destroy@Base 0.9.4
- igraph_hashtable_get@Base 0.9.4
- igraph_hashtable_getkeys@Base 0.9.4
- igraph_hashtable_init@Base 0.9.4
- igraph_hashtable_reset@Base 0.9.4
  igraph_heap_char_delete_top@Base 0.9.4
  igraph_heap_char_destroy@Base 0.9.4
  igraph_heap_char_empty@Base 0.9.4
diff -pruN 0.9.6+ds-2/debian/patches/debianization.patch 0.9.9+ds-1/debian/patches/debianization.patch
--- 0.9.6+ds-2/debian/patches/debianization.patch	2022-01-08 20:50:51.000000000 +0000
+++ 0.9.9+ds-1/debian/patches/debianization.patch	2022-06-04 12:30:09.000000000 +0000
@@ -8,7 +8,7 @@ Last-Update: 2022-01-06
 
 --- a/src/CMakeLists.txt
 +++ b/src/CMakeLists.txt
-@@ -283,20 +283,20 @@
+@@ -282,20 +282,20 @@
  
    version.c
  
diff -pruN 0.9.6+ds-2/debian/patches/upstream-external-plfit.patch 0.9.9+ds-1/debian/patches/upstream-external-plfit.patch
--- 0.9.6+ds-2/debian/patches/upstream-external-plfit.patch	2022-01-08 20:50:51.000000000 +0000
+++ 0.9.9+ds-1/debian/patches/upstream-external-plfit.patch	2022-03-16 17:44:54.000000000 +0000
@@ -4,18 +4,8 @@ Description: upstream: non-internal supp
 Origin: debian
 Forwarded: https://github.com/igraph/igraph/pull/1921
 Author: Jerome Benoit <calculus@rezozer.net>
-Last-Update: 2022-01-06
+Last-Update: 2022-03-16
 
---- a/etc/cmake/FindPLFIT.cmake
-+++ b/etc/cmake/FindPLFIT.cmake
-@@ -10,6 +10,7 @@
- 
- find_path(PLFIT_INCLUDE_DIR
-   NAMES plfit.h
-+  PATH_SUFFIXES plfit
- )
- 
- find_library(PLFIT_LIBRARY
 --- a/tests/benchmarks/igraph_power_law_fit.c
 +++ b/tests/benchmarks/igraph_power_law_fit.c
 @@ -1,5 +1,5 @@
diff -pruN 0.9.6+ds-2/debian/rules 0.9.9+ds-1/debian/rules
--- 0.9.6+ds-2/debian/rules	2022-01-08 20:50:51.000000000 +0000
+++ 0.9.9+ds-1/debian/rules	2022-03-16 23:06:31.000000000 +0000
@@ -13,8 +13,7 @@ BUILDIR_SHARED=_BUILD_SHARED
 #	--enable-debug
 
 ### disable TLS to fix warning with TLS and arpack
-CONF_FLAGS=\
-	-DIGRAPH_ENABLE_LTO=ON \
+CONF_FLAGS= \
 	-DIGRAPH_ENABLE_TLS=OFF \
 	-DIGRAPH_USE_INTERNAL_BLAS=OFF \
 	-DIGRAPH_USE_INTERNAL_LAPACK=OFF \
@@ -27,9 +26,11 @@ CONF_FLAGS=\
 	-DIGRAPH_GRAPHML_SUPPORT=ON \
 	-DIGRAPH_OPENMP_SUPPORT=ON
 
+
+### LTO does not really achieve anything for static libraries
 override_dh_auto_configure:
-	dh_auto_configure -B$(BUILDIR_SHARED) -- -DBUILD_SHARED_LIBS=ON  $(CONF_FLAGS)
-	dh_auto_configure -B$(BUILDIR_STATIC) -- -DBUILD_SHARED_LIBS=OFF $(CONF_FLAGS)
+	dh_auto_configure -B$(BUILDIR_SHARED) -- -DIGRAPH_ENABLE_LTO=ON  -DBUILD_SHARED_LIBS=ON  $(CONF_FLAGS)
+	dh_auto_configure -B$(BUILDIR_STATIC) -- -DIGRAPH_ENABLE_LTO=OFF -DBUILD_SHARED_LIBS=OFF $(CONF_FLAGS)
 
 override_dh_auto_build-indep:
 	make -C $(BUILDIR_SHARED)/doc pdf
diff -pruN 0.9.6+ds-2/debian/watch 0.9.9+ds-1/debian/watch
--- 0.9.6+ds-2/debian/watch	2022-01-08 20:50:51.000000000 +0000
+++ 0.9.9+ds-1/debian/watch	2022-06-04 12:18:39.000000000 +0000
@@ -1,7 +1,7 @@
 version=4
-
 opts="repacksuffix=+ds,dversionmangle=s/\+ds[\d]?//,repack,compression=xz" \
-  https://igraph.org/c/#downloads .*/releases/download/.*/igraph-(\d[\d.-]+)\.tar\.gz
+	https://github.com/igraph/igraph/releases .*/igraph-(\d[\d.-]+)\.tar\.gz
 
 # see https://github.com/igraph/igraph/issues/1377#issuecomment-620620166
 #  https://github.com/igraph/igraph/releases .*/(\d[\d.-]+)\.tar\.gz
+#  https://igraph.org/c/#downloads .*/releases/download/.*/igraph-(\d[\d.-]+)\.tar\.gz
diff -pruN 0.9.6+ds-2/doc/arpack.xxml 0.9.9+ds-1/doc/arpack.xxml
--- 0.9.6+ds-2/doc/arpack.xxml	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/doc/arpack.xxml	2022-06-04 12:27:41.000000000 +0000
@@ -12,6 +12,7 @@
 <section id="about-blas">
 <!-- doxrox-include about_blas -->
 <!-- doxrox-include igraph_blas_ddot -->
+<!-- doxrox-include igraph_blas_dnrm2 -->
 <!-- doxrox-include igraph_blas_dgemv -->
 <!-- doxrox-include igraph_blas_dgemv_array -->
 </section>
diff -pruN 0.9.6+ds-2/doc/installation.xml 0.9.9+ds-1/doc/installation.xml
--- 0.9.6+ds-2/doc/installation.xml	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/doc/installation.xml	2022-06-04 12:27:41.000000000 +0000
@@ -246,12 +246,6 @@ cmake --build . --target check --config
           with dynamic runtime linking using the
           <literal>x64-windows-static-md</literal> triplet.
         </para>
-        <para>
-          There are some known issues with igraph when using certain external packages
-          from vcpkg. When building against OpenBLAS, this results in a few differences
-          in some unit tests, see issue
-          <ulink url="https://github.com/igraph/igraph/issues/1491">#1491</ulink>.
-        </para>
       </section>
     </section>
       <section id="igraph-Installation-msys2">
diff -pruN 0.9.6+ds-2/doc/tutorial.xml 0.9.9+ds-1/doc/tutorial.xml
--- 0.9.6+ds-2/doc/tutorial.xml	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/doc/tutorial.xml	2022-06-04 12:27:41.000000000 +0000
@@ -57,7 +57,8 @@ target_link_libraries(igraph_test PUBLIC
 </para>
 
 <para>
-To compile the project, create a new directory called build, and switch to it:
+To compile the project, create a new directory called <filename>build</filename> in
+the root of the igraph source tree, and switch to it:
 <programlisting>
 mkdir build
 cd build
diff -pruN 0.9.6+ds-2/etc/cmake/FindPLFIT.cmake 0.9.9+ds-1/etc/cmake/FindPLFIT.cmake
--- 0.9.6+ds-2/etc/cmake/FindPLFIT.cmake	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/etc/cmake/FindPLFIT.cmake	2022-06-04 12:27:41.000000000 +0000
@@ -10,6 +10,7 @@
 
 find_path(PLFIT_INCLUDE_DIR
   NAMES plfit.h
+  PATH_SUFFIXES plfit
 )
 
 find_library(PLFIT_LIBRARY
diff -pruN 0.9.6+ds-2/examples/simple/igraph_community_fastgreedy.c 0.9.9+ds-1/examples/simple/igraph_community_fastgreedy.c
--- 0.9.6+ds-2/examples/simple/igraph_community_fastgreedy.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_community_fastgreedy.c	2022-06-04 12:27:41.000000000 +0000
@@ -32,7 +32,9 @@ void show_results(igraph_t *g, igraph_ve
 
     if (mod != 0) {
         i = igraph_vector_which_max(mod);
-        fprintf(f, "Modularity:  %f\n", VECTOR(*mod)[i]);
+        fprintf(f, "Modularity:  ");
+        igraph_real_fprintf(f, VECTOR(*mod)[i]);
+        fprintf(f, "\n");
     } else {
         fprintf(f, "Modularity:  ---\n");
     }
diff -pruN 0.9.6+ds-2/examples/simple/igraph_community_fastgreedy.out 0.9.9+ds-1/examples/simple/igraph_community_fastgreedy.out
--- 0.9.6+ds-2/examples/simple/igraph_community_fastgreedy.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_community_fastgreedy.out	2022-06-04 12:27:41.000000000 +0000
@@ -8,15 +8,15 @@ Modularity:  0.170858
 Membership: 1 1 1 0 0 0 
 Modularity:  0.380671
 Membership: 0 2 2 2 0 0 0 2 1 2 0 0 2 2 1 1 0 2 1 0 1 2 1 1 1 1 1 1 1 1 1 1 1 1 
-Modularity:  0.500000
+Modularity:  0.5
 Membership: 1 1 1 1 0 0 0 0 2 
-Modularity:  0.540000
+Modularity:  0.54
 Membership: 1 1 1 1 3 3 3 3 1 1 0 0 0 0 0 0 2 2 2 2 
-Modularity:  0.000000
+Modularity:  NaN
 Membership: 0 1 2 3 4 5 6 7 8 9 
-Modularity:  0.281250
+Modularity:  0.28125
 Membership: 0 1 1 2 2 0 
-Modularity:  0.500000
+Modularity:  0.5
 Membership: 0 1 
 Modularity:  ---
 Membership: 1 1 1 1 1 0 0 0 0 0 
diff -pruN 0.9.6+ds-2/examples/simple/igraph_community_fluid_communities.c 0.9.9+ds-1/examples/simple/igraph_community_fluid_communities.c
--- 0.9.6+ds-2/examples/simple/igraph_community_fluid_communities.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_community_fluid_communities.c	1970-01-01 00:00:00.000000000 +0000
@@ -1,94 +0,0 @@
-/* -*- mode: C -*-  */
-/* vim:set ts=4 sw=4 sts=4 et: */
-/*
-   IGraph library.
-   Copyright (C) 2007-2012  Gabor Csardi <csardi.gabor@gmail.com>
-   334 Harvard street, Cambridge, MA 02139 USA
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301 USA
-
-*/
-
-#include <igraph.h>
-
-#include "../../tests/unit/test_utilities.inc"
-
-int main() {
-    igraph_t g;
-    igraph_integer_t k;
-    igraph_vector_t membership;
-    igraph_real_t modularity;
-
-    igraph_rng_seed(igraph_rng_default(), 247);
-
-    /* Empty graph */
-    igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1);
-    igraph_vector_init(&membership, 0);
-    igraph_vector_push_back(&membership, 1);
-    igraph_community_fluid_communities(&g, 2, &membership, &modularity);
-    if (!igraph_is_nan(modularity) || igraph_vector_size(&membership) != 0) {
-        return 2;
-    }
-    igraph_vector_destroy(&membership);
-    igraph_destroy(&g);
-
-    /* Graph with one vertex only */
-    igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1);
-    igraph_vector_init(&membership, 0);
-    igraph_community_fluid_communities(&g, 2, &membership, &modularity);
-    if (!igraph_is_nan(modularity) || igraph_vector_size(&membership) != 1 || VECTOR(membership)[0] != 0) {
-        return 3;
-    }
-    igraph_vector_destroy(&membership);
-    igraph_destroy(&g);
-
-    /* Zachary Karate club -- this is just a quick smoke test */
-    igraph_small(&g, 0, IGRAPH_UNDIRECTED,
-                 0,  1,  0,  2,  0,  3,  0,  4,  0,  5,
-                 0,  6,  0,  7,  0,  8,  0, 10,  0, 11,
-                 0, 12,  0, 13,  0, 17,  0, 19,  0, 21,
-                 0, 31,  1,  2,  1,  3,  1,  7,  1, 13,
-                 1, 17,  1, 19,  1, 21,  1, 30,  2,  3,
-                 2,  7,  2,  8,  2,  9,  2, 13,  2, 27,
-                 2, 28,  2, 32,  3,  7,  3, 12,  3, 13,
-                 4,  6,  4, 10,  5,  6,  5, 10,  5, 16,
-                 6, 16,  8, 30,  8, 32,  8, 33,  9, 33,
-                 13, 33, 14, 32, 14, 33, 15, 32, 15, 33,
-                 18, 32, 18, 33, 19, 33, 20, 32, 20, 33,
-                 22, 32, 22, 33, 23, 25, 23, 27, 23, 29,
-                 23, 32, 23, 33, 24, 25, 24, 27, 24, 31,
-                 25, 31, 26, 29, 26, 33, 27, 33, 28, 31,
-                 28, 33, 29, 32, 29, 33, 30, 32, 30, 33,
-                 31, 32, 31, 33, 32, 33,
-                 -1);
-
-    igraph_vector_init(&membership, 0);
-    k = 2;
-    igraph_community_fluid_communities(&g, k, &membership,
-                                       /*modularity=*/ 0);
-    if (!igraph_vector_contains(&membership, 0) || !igraph_vector_contains(&membership, 1)) {
-        printf("Resulting graph does not have exactly 2 communities as expected.\n");
-        igraph_vector_print(&membership);
-        return 1;
-    }
-
-    igraph_destroy(&g);
-    igraph_vector_destroy(&membership);
-
-    VERIFY_FINALLY_STACK();
-
-    return 0;
-}
diff -pruN 0.9.6+ds-2/examples/simple/igraph_community_label_propagation.c 0.9.9+ds-1/examples/simple/igraph_community_label_propagation.c
--- 0.9.6+ds-2/examples/simple/igraph_community_label_propagation.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_community_label_propagation.c	2022-06-04 12:27:41.000000000 +0000
@@ -43,6 +43,9 @@ int main() {
            (long int) (igraph_vector_max(&membership) + 1),
            modularity);
 
+    printf("Communities membership: ");
+    igraph_vector_print(&membership);
+
     /* Destroy data structures at the end. */
     igraph_vector_destroy(&membership);
     igraph_destroy(&graph);
diff -pruN 0.9.6+ds-2/examples/simple/igraph_community_label_propagation.out 0.9.9+ds-1/examples/simple/igraph_community_label_propagation.out
--- 0.9.6+ds-2/examples/simple/igraph_community_label_propagation.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_community_label_propagation.out	2022-06-04 12:27:41.000000000 +0000
@@ -1 +1,2 @@
-3 communities found; modularity score is 0.39908.
+3 communities found; modularity score is 0.331279.
+Communities membership: 0 1 2 1 0 0 0 1 2 2 0 0 1 1 2 2 0 0 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2
diff -pruN 0.9.6+ds-2/examples/simple/igraph_feedback_arc_set.c 0.9.9+ds-1/examples/simple/igraph_feedback_arc_set.c
--- 0.9.6+ds-2/examples/simple/igraph_feedback_arc_set.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_feedback_arc_set.c	2022-06-04 12:27:41.000000000 +0000
@@ -71,6 +71,22 @@ int main() {
     }
     igraph_destroy(&g);
 
+    /* Null graph */
+    igraph_empty(&g, 0, IGRAPH_DIRECTED);
+    igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_APPROX_EADES);
+    if (igraph_vector_size(&result) != 0) {
+        return 4;
+    }
+    igraph_destroy(&g);
+
+    /* Singleton graph */
+    igraph_empty(&g, 1, IGRAPH_DIRECTED);
+    igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_APPROX_EADES);
+    if (igraph_vector_size(&result) != 0) {
+        return 5;
+    }
+    igraph_destroy(&g);
+
     igraph_vector_destroy(&result);
 
     return 0;
diff -pruN 0.9.6+ds-2/examples/simple/igraph_feedback_arc_set_ip.c 0.9.9+ds-1/examples/simple/igraph_feedback_arc_set_ip.c
--- 0.9.6+ds-2/examples/simple/igraph_feedback_arc_set_ip.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_feedback_arc_set_ip.c	2022-06-04 12:27:41.000000000 +0000
@@ -102,6 +102,22 @@ int main() {
     }
     igraph_destroy(&g);
 
+    /* Null graph */
+    igraph_empty(&g, 0, IGRAPH_DIRECTED);
+    igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_EXACT_IP);
+    if (igraph_vector_size(&result) != 0) {
+        return 6;
+    }
+    igraph_destroy(&g);
+
+    /* Singleton graph */
+    igraph_empty(&g, 1, IGRAPH_DIRECTED);
+    igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_EXACT_IP);
+    if (igraph_vector_size(&result) != 0) {
+        return 7;
+    }
+    igraph_destroy(&g);
+
     igraph_vector_destroy(&result);
 
     return 0;
diff -pruN 0.9.6+ds-2/examples/simple/igraph_write_graph_lgl.c 0.9.9+ds-1/examples/simple/igraph_write_graph_lgl.c
--- 0.9.6+ds-2/examples/simple/igraph_write_graph_lgl.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_write_graph_lgl.c	2022-06-04 12:27:41.000000000 +0000
@@ -2,42 +2,46 @@
 
 int main() {
 
-    igraph_t g;
-    igraph_strvector_t names, weights;
-    int i;
-    char str[2] = " ";
+    igraph_t graph;
+    igraph_strvector_t names;
+    igraph_vector_t weights;
+    igraph_integer_t i;
+    igraph_integer_t vcount, ecount;
+
     igraph_set_attribute_table(&igraph_cattribute_table);
-    igraph_small(&g, 7, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, -1);
+
+    igraph_small(&graph, 7, IGRAPH_UNDIRECTED,
+                 0,1, 1,3, 1,2, 2,0, 4,2, 3,4,
+                 -1);
+    vcount = igraph_vcount(&graph);
+    ecount = igraph_ecount(&graph);
 
 
     printf("Output without isolates:\n");
-    igraph_write_graph_lgl(&g, stdout, /*names*/ NULL, /*weights*/ NULL, /*isolates*/ 0);
+    igraph_write_graph_lgl(&graph, stdout, /*names*/ NULL, /*weights*/ NULL, /*isolates*/ 0);
 
 
     printf("\nOutput with isolates:\n");
-    igraph_write_graph_lgl(&g, stdout, /*names*/ NULL, /*weights*/ NULL, /*isolates*/ 1);
+    igraph_write_graph_lgl(&graph, stdout, /*names*/ NULL, /*weights*/ NULL, /*isolates*/ 1);
 
 
     printf("\nOutput vertex and edge labels:\n");
-    igraph_strvector_init(&names, 7);
-    for (i = 0; i < 7; i++) {
+    igraph_strvector_init(&names, vcount);
+    for (i = 0; i < vcount; i++) {
+        char str[2] = " "; /* initialize to ensure presence of null terminator */
         str[0] = 'A' + i;
         igraph_strvector_set(&names, i, str);
     }
-    SETVASV(&g, "names", &names);
+    SETVASV(&graph, "names", &names);
 
-    igraph_strvector_init(&weights, 6);
-    for (i = 0; i < 6; i++) {
-        str[0] = '3' + i;
-        igraph_strvector_set(&weights, i, str);
-    }
-    SETEASV(&g, "weights", &weights);
+    igraph_vector_init_seq(&weights, 1, ecount);
+    SETEANV(&graph, "weights", &weights);
 
-    igraph_write_graph_lgl(&g, stdout, "names", "weights", /*isolates*/ 0);
+    igraph_write_graph_lgl(&graph, stdout, "names", "weights", /*isolates*/ 0);
 
     igraph_strvector_destroy(&names);
-    igraph_strvector_destroy(&weights);
-    igraph_destroy(&g);
+    igraph_vector_destroy(&weights);
+    igraph_destroy(&graph);
 
     return 0;
 }
diff -pruN 0.9.6+ds-2/examples/simple/igraph_write_graph_lgl.out 0.9.9+ds-1/examples/simple/igraph_write_graph_lgl.out
--- 0.9.6+ds-2/examples/simple/igraph_write_graph_lgl.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/igraph_write_graph_lgl.out	2022-06-04 12:27:41.000000000 +0000
@@ -26,12 +26,12 @@ Output with isolates:
 
 Output vertex and edge labels:
 # A
-B 3
+B 1
 C 4
 # B
-C 5
-D 6
+C 3
+D 2
 # C
-E 7
+E 5
 # D
-E 8
+E 6
diff -pruN 0.9.6+ds-2/examples/simple/walktrap.c 0.9.9+ds-1/examples/simple/walktrap.c
--- 0.9.6+ds-2/examples/simple/walktrap.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/walktrap.c	2022-06-04 12:27:41.000000000 +0000
@@ -30,18 +30,17 @@ int main() {
     long int no_of_nodes;
     long int i;
 
-    igraph_rng_seed(igraph_rng_default(), 42);
-
     igraph_small(&g, 5, IGRAPH_UNDIRECTED,
                  0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4,
-                 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, 0, 5, -1);
+                 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, 0, 5, 4, 9, -1);
     igraph_vector_init(&modularity, 0);
     igraph_matrix_init(&merges, 0, 0);
 
-    igraph_community_walktrap(&g, 0 /* no weights */,
+    igraph_community_walktrap(&g,
+                              NULL /* no weights */,
                               4 /* steps */,
                               &merges, &modularity,
-                              /* membership=*/ 0);
+                              /* membership=*/ NULL);
 
     no_of_nodes = igraph_vcount(&g);
     printf("Merges:\n");
@@ -55,18 +54,8 @@ int main() {
 
     igraph_destroy(&g);
 
-    /* isolated vertices */
-    igraph_small(&g, 5, IGRAPH_UNDIRECTED, -1);
-    if (igraph_community_walktrap(&g, 0 /* no weights */, 4 /* steps */, &merges,
-                                  &modularity, /* membership = */ 0)) {
-        return 1;
-    }
-    if (igraph_vector_min(&modularity) != 0 || igraph_vector_max(&modularity) != 0) {
-        return 2;
-    }
-    igraph_destroy(&g);
-
     igraph_matrix_destroy(&merges);
     igraph_vector_destroy(&modularity);
+
     return 0;
 }
diff -pruN 0.9.6+ds-2/examples/simple/walktrap.out 0.9.9+ds-1/examples/simple/walktrap.out
--- 0.9.6+ds-2/examples/simple/walktrap.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/examples/simple/walktrap.out	1970-01-01 00:00:00.000000000 +0000
@@ -1,10 +0,0 @@
-Merges:
- 6 +  7 -> 10 (modularity 0.00)
- 2 +  4 -> 11 (modularity -0.07)
- 8 + 10 -> 12 (modularity -0.04)
- 3 + 11 -> 13 (modularity 0.02)
- 9 + 12 -> 14 (modularity 0.08)
- 1 + 13 -> 15 (modularity 0.16)
- 0 + 15 -> 16 (modularity 0.25)
- 5 + 14 -> 17 (modularity 0.35)
-16 + 17 -> 18 (modularity 0.45)
diff -pruN 0.9.6+ds-2/IGRAPH_VERSION 0.9.9+ds-1/IGRAPH_VERSION
--- 0.9.6+ds-2/IGRAPH_VERSION	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/IGRAPH_VERSION	2022-06-04 12:27:41.000000000 +0000
@@ -1 +1 @@
-0.9.6
\ No newline at end of file
+0.9.9
\ No newline at end of file
diff -pruN 0.9.6+ds-2/interfaces/functions.yaml 0.9.9+ds-1/interfaces/functions.yaml
--- 0.9.6+ds-2/interfaces/functions.yaml	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/interfaces/functions.yaml	2022-06-04 12:27:41.000000000 +0000
@@ -304,7 +304,7 @@ igraph_hsbm_list_game:
 igraph_correlated_game:
     PARAMS: |-
         GRAPH old_graph, OUT GRAPH new_graph,
-        REAL corr, REAL p=old.graph$p, VECTORM1_OR_0 permutation=NULL
+        REAL corr, REAL p=edge_density(old.graph), VECTORM1_OR_0 permutation=NULL
 
 igraph_correlated_pair_game:
     PARAMS: |-
@@ -601,6 +601,12 @@ igraph_neighborhood_graphs:
 igraph_topological_sorting:
     PARAMS: GRAPH graph, OUT VECTOR res, NEIMODE mode=OUT
 
+igraph_feedback_arc_set:
+    # Default algorithm is the approximate method because it is faster and the
+    # function is _not_ called igraph_minimum_feedback_arc_set
+    PARAMS: GRAPH graph, OUT EDGESET result, EDGEWEIGHTS weights=NULL, FAS_ALGORITHM algo=APPROX_EADES
+    DEPS: result ON graph, weights ON graph
+
 igraph_is_loop:
     PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGESET es=ALL
     DEPS: es ON graph
@@ -615,6 +621,9 @@ igraph_is_multiple:
     PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGESET es=ALL
     DEPS: es ON graph
 
+igraph_has_loop:
+    PARAMS: GRAPH graph, OUT BOOLEAN res
+
 igraph_has_multiple:
     PARAMS: GRAPH graph, OUT BOOLEAN res
 
@@ -680,14 +689,15 @@ igraph_is_mutual:
     DEPS: es ON graph
 
 igraph_maximum_cardinality_search:
-    PARAMS: GRAPH graph, OUT VERTEXSET alpha, OUT VECTORM1_OR_0 alpham1
-    DEPS: alpha ON graph, alpham1 ON graph
+    PARAMS: GRAPH graph, OPTIONAL OUT VECTORM1 alpha, OPTIONAL OUT VERTEXSET alpham1
+    DEPS: alpham1 ON graph
 
 igraph_is_chordal:
     PARAMS: |-
-        GRAPH graph, VECTORM1_OR_0 alpha=NULL, VECTORM1_OR_0 alpham1=NULL,
+        GRAPH graph, VECTORM1 alpha=NULL, OPTIONAL VECTORM1 alpham1=NULL,
         OPTIONAL OUT BOOLEAN chordal, OUT VECTORM1_OR_0 fillin,
         OUT GRAPH_OR_0 newgraph
+    DEPS: alpham1 ON graph
 
 igraph_avg_nearest_neighbor_degree:
     PARAMS: |-
@@ -715,9 +725,12 @@ igraph_centralization_degree:
         BOOLEAN normalized=True
 
 igraph_centralization_degree_tmax:
+    # The general consensus is that the 'loops' argument of this function
+    # should not have a default value; see this comment from @torfason:
+    # https://github.com/igraph/rigraph/issues/369#issuecomment-939893681
     PARAMS: |-
         GRAPH_OR_0 graph=NULL, INTEGER nodes=0, NEIMODE mode=ALL,
-        BOOLEAN loops=False, OUT REAL res
+        BOOLEAN loops, OUT REAL res
 
 igraph_centralization_betweenness:
     PARAMS: |-
@@ -804,18 +817,18 @@ igraph_random_edge_walk:
     DEPS: start ON graph, weights ON graph, edgewalk ON graph
 
 igraph_global_efficiency:
-    PARAMS: GRAPH graph, OUT REAL res, VERTEXWEIGHTS weights=NULL, BOOLEAN directed=True
+    PARAMS: GRAPH graph, OUT REAL res, EDGEWEIGHTS weights=NULL, BOOLEAN directed=True
     DEPS: weights ON graph
 
 igraph_local_efficiency:
     PARAMS: |-
         GRAPH graph, OUT VERTEXINDEX res, VERTEXSET vids=ALL,
-        VERTEXWEIGHTS weights=NULL, BOOLEAN directed=True, NEIMODE mode=ALL
+        EDGEWEIGHTS weights=NULL, BOOLEAN directed=True, NEIMODE mode=ALL
     DEPS: vids ON graph, weights ON graph, res ON graph vids
 
 igraph_average_local_efficiency:
     PARAMS: |-
-        GRAPH graph, OUT REAL res, VERTEXWEIGHTS weights=NULL,
+        GRAPH graph, OUT REAL res, EDGEWEIGHTS weights=NULL,
         BOOLEAN directed=True, NEIMODE mode=ALL
     DEPS: weights ON graph
 
@@ -1246,7 +1259,7 @@ igraph_community_walktrap:
 igraph_community_edge_betweenness:
     PARAMS: |-
         GRAPH graph, OUT VECTOR result, OUT VECTOR edge_betweenness,
-        OUT MATRIX merges, OUT VECTOR bridges,
+        OUT MATRIX merges, OUT VECTORM1 bridges,
         OUT VECTOR_OR_0 modularity, OUT VECTOR_OR_0 membership,
         BOOLEAN directed=True, EDGEWEIGHTS weights=NULL
     DEPS: weights ON graph
@@ -1254,7 +1267,7 @@ igraph_community_edge_betweenness:
 igraph_community_eb_get_merges:
     PARAMS: |-
         GRAPH graph, BOOLEAN directed, VECTOR edges, EDGEWEIGHTS weights=NULL,
-        OUT MATRIX merges, OUT VECTOR bridges,
+        OUT MATRIX merges, OUT VECTORM1 bridges,
         OUT VECTOR_OR_0 modularity, OUT VECTOR_OR_0 membership
     DEPS: weights ON graph
 
@@ -1645,6 +1658,15 @@ igraph_mincut:
         GRAPH graph, OUT REAL value, OUT VECTORM1 partition1,
         OUT VECTORM1 partition2, OUT VECTORM1 cut, VECTOR_OR_0 capacity
 
+igraph_st_mincut:
+    PARAMS: |-
+        GRAPH graph, OUT REAL value, OUT VECTORM1_OR_0 cut,
+        OPTIONAL OUT VERTEXSET partition1, OPTIONAL OUT VERTEXSET partition2,
+        VERTEX source, VERTEX target, EDGECAPACITY capacity=NULL
+    DEPS: |-
+        capacity ON graph, source ON graph, target ON graph,
+        partition1 ON graph, partition2 ON graph, cut ON graph
+
 igraph_st_vertex_connectivity:
     PARAMS: |-
         GRAPH graph, OUT INTEGER res, VERTEX source, VERTEX target,
@@ -1681,10 +1703,10 @@ igraph_cohesion:
 
 igraph_dominator_tree:
     PARAMS: |-
-        GRAPH graph, VERTEX root, OUT VERTEXSET dom,
+        GRAPH graph, VERTEX root, OUT VECTORM1 dom,
         OUT GRAPH_OR_0 domtree, OUT VERTEXSET leftout,
         NEIMODE mode=OUT
-    DEPS: root ON graph, dom ON graph, leftout ON graph
+    DEPS: root ON graph, leftout ON graph
 
 igraph_all_st_cuts:
     PARAMS: |-
diff -pruN 0.9.6+ds-2/interfaces/types.yaml 0.9.9+ds-1/interfaces/types.yaml
--- 0.9.6+ds-2/interfaces/types.yaml	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/interfaces/types.yaml	2022-06-04 12:27:41.000000000 +0000
@@ -376,6 +376,11 @@ EIGENWHICHPOS:
     CTYPE: igraph_eigen_which_position_t
     FLAGS: ENUM
 
+FAS_ALGORITHM:
+    # Enum representing feedback arc set algorithms
+    CTYPE: igraph_fas_algorithm_t
+    FLAGS: ENUM
+
 GETADJACENCY:
     # Enum storing how to retrieve the adjacency matrix from a graph
     CTYPE: igraph_get_adjacency_t
diff -pruN 0.9.6+ds-2/src/cliques/glet.c 0.9.9+ds-1/src/cliques/glet.c
--- 0.9.6+ds-2/src/cliques/glet.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/cliques/glet.c	2022-06-04 12:27:41.000000000 +0000
@@ -82,25 +82,19 @@ static void igraph_i_subclique_next_free
     int i;
     if (data->resultids) {
         for (i = 0; i < data->nc; i++) {
-            if (data->resultids + i) {
-                igraph_vector_int_destroy(data->resultids + i);
-            }
+            igraph_vector_int_destroy(&data->resultids[i]);
         }
         IGRAPH_FREE(data->resultids);
     }
     if (data->result) {
         for (i = 0; i < data->nc; i++) {
-            if (data->result + i) {
-                igraph_destroy(data->result + i);
-            }
+            igraph_destroy(&data->result[i]);
         }
         IGRAPH_FREE(data->result);
     }
     if (data->resultweights) {
         for (i = 0; i < data->nc; i++) {
-            if (data->resultweights + i) {
-                igraph_vector_destroy(data->resultweights + i);
-            }
+            igraph_vector_destroy(&data->resultweights[i]);
         }
         IGRAPH_FREE(data->resultweights);
     }
diff -pruN 0.9.6+ds-2/src/CMakeLists.txt 0.9.9+ds-1/src/CMakeLists.txt
--- 0.9.6+ds-2/src/CMakeLists.txt	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/CMakeLists.txt	2022-06-04 12:27:41.000000000 +0000
@@ -43,7 +43,6 @@ add_library(
   core/estack.c
   core/fixed_vectorlist.c
   core/grid.c
-  core/hashtable.c
   core/heap.c
   core/indheap.c
   core/interruption.c
diff -pruN 0.9.6+ds-2/src/community/community_misc.c 0.9.9+ds-1/src/community/community_misc.c
--- 0.9.6+ds-2/src/community/community_misc.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/community_misc.c	2022-06-04 12:27:41.000000000 +0000
@@ -46,10 +46,6 @@
 #include <string.h>
 #include <math.h>
 
-#ifdef USING_R
-    #include <R.h>
-#endif
-
 /**
  * \function igraph_community_to_membership
  * \brief Create membership vector from community structure dendrogram
diff -pruN 0.9.6+ds-2/src/community/edge_betweenness.c 0.9.9+ds-1/src/community/edge_betweenness.c
--- 0.9.6+ds-2/src/community/edge_betweenness.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/edge_betweenness.c	2022-06-04 12:27:41.000000000 +0000
@@ -241,7 +241,8 @@ int igraph_community_eb_get_merges(const
             igraph_vector_clear(bridges);
         }
         if (modularity) {
-            igraph_vector_clear(modularity);
+            igraph_vector_resize(modularity, 1);
+            VECTOR(*modularity)[0] = IGRAPH_NAN;
         }
         if (membership) {
             igraph_vector_clear(membership);
@@ -481,7 +482,7 @@ int igraph_community_edge_betweenness(co
         }
 
         if (membership != 0) {
-            IGRAPH_WARNING("Membership vector will be selected based on the lowest "
+            IGRAPH_WARNING("Membership vector will be selected based on the highest "
                            "modularity score.");
         }
 
diff -pruN 0.9.6+ds-2/src/community/fast_modularity.c 0.9.9+ds-1/src/community/fast_modularity.c
--- 0.9.6+ds-2/src/community/fast_modularity.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/fast_modularity.c	2022-06-04 12:27:41.000000000 +0000
@@ -1050,6 +1050,14 @@ int igraph_community_fastgreedy(const ig
         igraph_vector_resize(modularity, no_of_joins + 1);
     }
 
+    /* Internally, the algorithm does not create NaN values.
+     * If the graph has no edges, the final modularity will be zero.
+     * We change this to NaN for consistency. */
+    if (modularity && no_of_edges == 0) {
+        IGRAPH_ASSERT(no_of_joins == 0);
+        VECTOR(*modularity)[0] = IGRAPH_NAN;
+    }
+
     debug("Freeing memory\n");
     IGRAPH_FREE(pairs);
     IGRAPH_FREE(dq);
diff -pruN 0.9.6+ds-2/src/community/fluid.c 0.9.9+ds-1/src/community/fluid.c
--- 0.9.6+ds-2/src/community/fluid.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/fluid.c	2022-06-04 12:27:41.000000000 +0000
@@ -59,8 +59,6 @@
  * \return Error code.
  *
  * Time complexity: O(|E|)
- *
- * \example examples/simple/igraph_community_fluid_communities.c
  */
 int igraph_community_fluid_communities(const igraph_t *graph,
                                        igraph_integer_t no_of_communities,
diff -pruN 0.9.6+ds-2/src/community/label_propagation.c 0.9.9+ds-1/src/community/label_propagation.c
--- 0.9.6+ds-2/src/community/label_propagation.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/label_propagation.c	2022-06-04 12:27:41.000000000 +0000
@@ -75,9 +75,12 @@
  *   in the future; please do not rely on it.
  * \param fixed Boolean vector denoting which labels are fixed. Of course
  *   this makes sense only if you provided an initial state, otherwise
- *   this element will be ignored. Also note that vertices without labels
- *   cannot be fixed. If they are, this vector will be modified to
- *   make it consistent with \p initial.
+ *   this element will be ignored. Note that vertices without labels
+ *   cannot be fixed. The fixed status will be ignord for these with a
+ *   warning. Also note that label numbers by themselves have no meaning,
+ *   and igraph may renumber labels. However, co-membership constraints
+ *   will be respected: two vertices can be fixed to be in the same or in
+ *   different communities.
  * \param modularity If not a null pointer, then it must be a pointer
  *   to a real number. The modularity score of the detected community
  *   structure is stored here.
@@ -99,7 +102,7 @@ int igraph_community_label_propagation(c
     long int i, j, k;
     igraph_adjlist_t al;
     igraph_inclist_t il;
-    igraph_bool_t running;
+    igraph_bool_t running, control_iteration;
     igraph_bool_t unlabelled_left;
 
     igraph_vector_t label_counters, dominant_labels, nonzero_labels, node_order;
@@ -107,7 +110,7 @@ int igraph_community_label_propagation(c
     /* We make a copy of 'fixed' as a pointer into 'fixed_copy' after casting
      * away the constness, and promise ourselves that we will make a proper
      * copy of 'fixed' into 'fixed_copy' as soon as we start mutating it */
-    igraph_vector_bool_t* fixed_copy = (igraph_vector_bool_t*) fixed;
+    igraph_vector_bool_t *fixed_copy = (igraph_vector_bool_t *) fixed;
 
     /* The implementation uses a trick to avoid negative array indexing:
      * elements of the membership vector are increased by 1 at the start
@@ -221,6 +224,11 @@ int igraph_community_label_propagation(c
         IGRAPH_FINALLY(igraph_vector_destroy, &node_order);
     }
 
+    /* There are two alternating types of iterations, one for changing labels and
+    the other one for checking the end condition - every vertex in the graph has
+    a label to which the maximum number of its neighbors belongs. If control_iteration
+    is true, we are just checking the end condition and not relabeling nodes. */
+    control_iteration = 1;
     running = 1;
     while (running) {
         long int v1, num_neis;
@@ -229,10 +237,15 @@ int igraph_community_label_propagation(c
         igraph_vector_int_t *ineis;
         igraph_bool_t was_zero;
 
-        running = 0;
-
-        /* Shuffle the node ordering vector */
-        IGRAPH_CHECK(igraph_vector_shuffle(&node_order));
+        if (control_iteration) {
+            /* If we are in the control iteration, we expect in the begining of
+            the iterationthat all vertices meet the end condition, so running is false.
+            If some of them does not, running is set to true later in the code. */
+            running = 0;
+        } else {
+            /* Shuffle the node ordering vector if we are in the label updating iteration */
+            IGRAPH_CHECK(igraph_vector_shuffle(&node_order));
+        }
 
         RNG_BEGIN();
         /* In the prescribed order, loop over the vertices and reassign labels */
@@ -290,15 +303,18 @@ int igraph_community_label_propagation(c
             }
 
             if (igraph_vector_size(&dominant_labels) > 0) {
-                /* Select randomly from the dominant labels */
-                k = RNG_INTEGER(0, igraph_vector_size(&dominant_labels) - 1);
-                k = (long int) VECTOR(dominant_labels)[k];
-                /* Check if the _current_ label of the node is also dominant */
-                if (VECTOR(label_counters)[(long)VECTOR(*membership)[v1]] != max_count) {
-                    /* Nope, we need at least one more iteration */
-                    running = 1;
+                if (control_iteration) {
+                    /* Check if the _current_ label of the node is also dominant */
+                    if (VECTOR(label_counters)[(long)VECTOR(*membership)[v1]] != max_count) {
+                        /* Nope, we need at least one more iteration */
+                        running = 1;
+                    }
+                }
+                else {
+                    /* Select randomly from the dominant labels */
+                    k = RNG_INTEGER(0, igraph_vector_size(&dominant_labels) - 1);
+                    VECTOR(*membership)[v1] = VECTOR(dominant_labels)[(long int)k];
                 }
-                VECTOR(*membership)[v1] = k;
             }
 
             /* Clear the nonzero elements in label_counters */
@@ -308,6 +324,9 @@ int igraph_community_label_propagation(c
             }
         }
         RNG_END();
+
+        /* Alternating between control iterations and label updating iterations */
+        control_iteration = !control_iteration;
     }
 
     if (weights) {
diff -pruN 0.9.6+ds-2/src/community/leading_eigenvector.c 0.9.9+ds-1/src/community/leading_eigenvector.c
--- 0.9.6+ds-2/src/community/leading_eigenvector.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/leading_eigenvector.c	2022-06-04 12:27:41.000000000 +0000
@@ -398,7 +398,7 @@ static void igraph_i_error_handler_none(
  *    community <quote>p</quote>, the merge in the second line forms
  *    community <quote>p+1</quote>, etc. The matrix should be
  *    initialized before calling and will be resized as needed.
- *    This argument is ignored of it is \c NULL.
+ *    This argument is ignored if it is \c NULL.
  * \param membership The membership of the vertices after all the
  *    splits were performed will be stored here. The vector must be
  *    initialized  before calling and will be resized as needed.
@@ -694,14 +694,17 @@ int igraph_community_leading_eigenvector
            the first call. */
         {
             int i;
+            int retval;
             igraph_error_handler_t *errh =
                 igraph_set_error_handler(igraph_i_error_handler_none);
             igraph_warning_handler_t *warnh =
                 igraph_set_warning_handler(igraph_warning_handler_ignore);
-            igraph_arpack_rssolve(arpcb2, &extra, options, &storage,
-                                  /*values=*/ 0, /*vectors=*/ 0);
+            retval = igraph_arpack_rssolve(arpcb2, &extra, options, &storage, /*values=*/ 0, /*vectors=*/ 0);
             igraph_set_error_handler(errh);
             igraph_set_warning_handler(warnh);
+            if (retval != IGRAPH_SUCCESS && retval != IGRAPH_ARPACK_MAXIT && retval != IGRAPH_ARPACK_NOSHIFT) {
+                IGRAPH_ERROR("ARPACK call failed", retval);
+            }
             if (options->nconv < 1) {
                 /* Call again from a fixed starting point. Note that we cannot use a
                  * fixed all-1 starting vector as sometimes ARPACK would return a
@@ -741,11 +744,14 @@ int igraph_community_leading_eigenvector
 
         {
             int i;
+            int retval;
             igraph_error_handler_t *errh =
                 igraph_set_error_handler(igraph_i_error_handler_none);
-            igraph_arpack_rssolve(arpcb1, &extra, options, &storage,
-                                  /*values=*/ 0, /*vectors=*/ 0);
+            retval = igraph_arpack_rssolve(arpcb1, &extra, options, &storage, /*values=*/ 0, /*vectors=*/ 0);
             igraph_set_error_handler(errh);
+            if (retval != IGRAPH_SUCCESS && retval != IGRAPH_ARPACK_MAXIT && retval != IGRAPH_ARPACK_NOSHIFT) {
+                IGRAPH_ERROR("ARPACK call failed", retval);
+            }
             if (options->nconv < 1) {
                 /* Call again from a fixed starting point. See the comment a few lines
                  * above about the exact choice of this starting vector */
diff -pruN 0.9.6+ds-2/src/community/leiden.c 0.9.9+ds-1/src/community/leiden.c
--- 0.9.6+ds-2/src/community/leiden.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/leiden.c	2022-06-04 12:27:41.000000000 +0000
@@ -958,7 +958,8 @@ static int igraph_i_community_leiden(
  * \param graph The input graph. It must be an undirected graph.
  * \param edge_weights Numeric vector containing edge weights. If \c NULL, every edge
  *    has equal weight of 1. The weights need not be non-negative.
- * \param node_weights Numeric vector containing node weights.
+ * \param node_weights Numeric vector containing node weights. If \c NULL, every node
+ *    has equal weight of 1.
  * \param resolution_parameter The resolution parameter used, which is
  *    represented by gamma in the objective function mentioned in the
  *    documentation.
@@ -972,8 +973,8 @@ static int igraph_i_community_leiden(
  *    must hence be properly initialized. When finding clusters from scratch it
  *    is typically started using a singleton clustering. This can be achieved
  *    using \c igraph_vector_init_seq.
- * \param nb_clusters The number of clusters contained in \c membership. Must
- *    not be a \c NULL pointer.
+ * \param nb_clusters The number of clusters contained in \c membership.
+ *    If \c NULL, the number of clusters will not be returned.
  * \param quality The quality of the partition, in terms of the objective
  *    function as included in the documentation. If \c NULL the quality will
  *    not be calculated.
@@ -988,8 +989,13 @@ int igraph_community_leiden(const igraph
                             const igraph_real_t resolution_parameter, const igraph_real_t beta, const igraph_bool_t start,
                             igraph_vector_t *membership, igraph_integer_t *nb_clusters, igraph_real_t *quality) {
     igraph_vector_t *i_edge_weights, *i_node_weights;
+    igraph_integer_t i_nb_clusters;
     igraph_integer_t n = igraph_vcount(graph);
 
+    if (!nb_clusters) {
+        nb_clusters = &i_nb_clusters;
+    }
+
     if (start) {
         if (!membership) {
             IGRAPH_ERROR("Cannot start optimization if membership is missing", IGRAPH_EINVAL);
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap_communities.cpp 0.9.9+ds-1/src/community/walktrap/walktrap_communities.cpp
--- 0.9.6+ds-2/src/community/walktrap/walktrap_communities.cpp	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap_communities.cpp	2022-06-04 12:27:41.000000000 +0000
@@ -66,8 +66,8 @@ namespace walktrap {
 
 IGRAPH_THREAD_LOCAL int Probabilities::length = 0;
 IGRAPH_THREAD_LOCAL Communities* Probabilities::C = 0;
-IGRAPH_THREAD_LOCAL float* Probabilities::tmp_vector1 = 0;
-IGRAPH_THREAD_LOCAL float* Probabilities::tmp_vector2 = 0;
+IGRAPH_THREAD_LOCAL double* Probabilities::tmp_vector1 = 0;
+IGRAPH_THREAD_LOCAL double* Probabilities::tmp_vector2 = 0;
 IGRAPH_THREAD_LOCAL int* Probabilities::id = 0;
 IGRAPH_THREAD_LOCAL int* Probabilities::vertices1 = 0;
 IGRAPH_THREAD_LOCAL int* Probabilities::vertices2 = 0;
@@ -97,7 +97,7 @@ Probabilities::Probabilities(int communi
     int nb_vertices1 = 0;
     int nb_vertices2 = 0;
 
-    float initial_proba = 1. / float(C->communities[community].size);
+    double initial_proba = 1. / double(C->communities[community].size);
     int last =  C->members[C->communities[community].last_member];
     for (int m = C->communities[community].first_member; m != last; m = C->members[m]) {
         tmp_vector1[m] = initial_proba;
@@ -113,7 +113,7 @@ Probabilities::Probabilities(int communi
             }
             if (nb_vertices1 == G->nb_vertices) {
                 for (int i = 0; i < G->nb_vertices; i++) {
-                    float proba = tmp_vector1[i] / G->vertices[i].total_weight;
+                    double proba = tmp_vector1[i] / G->vertices[i].total_weight;
                     for (int j = 0; j < G->vertices[i].degree; j++) {
                         tmp_vector2[G->vertices[i].edges[j].neighbor] += proba * G->vertices[i].edges[j].weight;
                     }
@@ -121,7 +121,7 @@ Probabilities::Probabilities(int communi
             } else {
                 for (int i = 0; i < nb_vertices1; i++) {
                     int v1 = vertices1[i];
-                    float proba = tmp_vector1[v1] / G->vertices[v1].total_weight;
+                    double proba = tmp_vector1[v1] / G->vertices[v1].total_weight;
                     for (int j = 0; j < G->vertices[v1].degree; j++) {
                         tmp_vector2[G->vertices[v1].edges[j].neighbor] += proba * G->vertices[v1].edges[j].weight;
                     }
@@ -131,7 +131,7 @@ Probabilities::Probabilities(int communi
             nb_vertices2 = 0;
             for (int i = 0; i < nb_vertices1; i++) {
                 int v1 = vertices1[i];
-                float proba = tmp_vector1[v1] / G->vertices[v1].total_weight;
+                double proba = tmp_vector1[v1] / G->vertices[v1].total_weight;
                 for (int j = 0; j < G->vertices[v1].degree; j++) {
                     int v2 = G->vertices[v1].edges[j].neighbor;
                     if (id[v2] == current_id) {
@@ -144,7 +144,7 @@ Probabilities::Probabilities(int communi
                 }
             }
         }
-        float* tmp = tmp_vector2;
+        double* tmp = tmp_vector2;
         tmp_vector2 = tmp_vector1;
         tmp_vector1 = tmp;
 
@@ -156,7 +156,7 @@ Probabilities::Probabilities(int communi
     }
 
     if (nb_vertices1 > (G->nb_vertices / 2)) {
-        P = new float[G->nb_vertices];
+        P = new double[G->nb_vertices];
         size = G->nb_vertices;
         vertices = 0;
         if (nb_vertices1 == G->nb_vertices) {
@@ -172,7 +172,7 @@ Probabilities::Probabilities(int communi
             }
         }
     } else {
-        P = new float[nb_vertices1];
+        P = new double[nb_vertices1];
         size = nb_vertices1;
         vertices = new int[nb_vertices1];
         int j = 0;
@@ -193,12 +193,12 @@ Probabilities::Probabilities(int communi
     Probabilities* P1 = C->communities[community1].P;
     Probabilities* P2 = C->communities[community2].P;
 
-    float w1 = float(C->communities[community1].size) / float(C->communities[community1].size + C->communities[community2].size);
-    float w2 = float(C->communities[community2].size) / float(C->communities[community1].size + C->communities[community2].size);
+    double w1 = double(C->communities[community1].size) / double(C->communities[community1].size + C->communities[community2].size);
+    double w2 = double(C->communities[community2].size) / double(C->communities[community1].size + C->communities[community2].size);
 
 
     if (P1->size == C->G->nb_vertices) {
-        P = new float[C->G->nb_vertices];
+        P = new double[C->G->nb_vertices];
         size = C->G->nb_vertices;
         vertices = 0;
 
@@ -221,7 +221,7 @@ Probabilities::Probabilities(int communi
         }
     } else {
         if (P2->size == C->G->nb_vertices) { // P1 partial vector, P2 full vector
-            P = new float[C->G->nb_vertices];
+            P = new double[C->G->nb_vertices];
             size = C->G->nb_vertices;
             vertices = 0;
 
@@ -271,7 +271,7 @@ Probabilities::Probabilities(int communi
             }
 
             if (nb_vertices1 > (C->G->nb_vertices / 2)) {
-                P = new float[C->G->nb_vertices];
+                P = new double[C->G->nb_vertices];
                 size = C->G->nb_vertices;
                 vertices = 0;
                 for (int i = 0; i < C->G->nb_vertices; i++) {
@@ -281,7 +281,7 @@ Probabilities::Probabilities(int communi
                     P[vertices1[i]] = tmp_vector1[vertices1[i]];
                 }
             } else {
-                P = new float[nb_vertices1];
+                P = new double[nb_vertices1];
                 size = nb_vertices1;
                 vertices = new int[nb_vertices1];
                 for (int i = 0; i < nb_vertices1; i++) {
@@ -363,9 +363,9 @@ double Probabilities::compute_distance(c
 
 long Probabilities::memory() {
     if (vertices) {
-        return (sizeof(Probabilities) + long(size) * (sizeof(float) + sizeof(int)));
+        return (sizeof(Probabilities) + long(size) * (sizeof(double) + sizeof(int)));
     } else {
-        return (sizeof(Probabilities) + long(size) * sizeof(float));
+        return (sizeof(Probabilities) + long(size) * sizeof(double));
     }
 }
 
@@ -400,8 +400,8 @@ Communities::Communities(Graph* graph, i
 
     Probabilities::C = this;
     Probabilities::length = random_walks_length;
-    Probabilities::tmp_vector1 = new float[G->nb_vertices];
-    Probabilities::tmp_vector2 = new float[G->nb_vertices];
+    Probabilities::tmp_vector1 = new double[G->nb_vertices];
+    Probabilities::tmp_vector2 = new double[G->nb_vertices];
     Probabilities::id = new int[G->nb_vertices];
     for (int i = 0; i < G->nb_vertices; i++) {
         Probabilities::id[i] = 0;
@@ -455,7 +455,7 @@ Communities::Communities(Graph* graph, i
     if (max_memory != -1) {
         memory_used += min_delta_sigma->memory();
         memory_used += 2 * long(G->nb_vertices) * sizeof(Community);
-        memory_used += long(G->nb_vertices) * (2 * sizeof(float) + 3 * sizeof(int)); // the static data of Probabilities class
+        memory_used += long(G->nb_vertices) * (2 * sizeof(double) + 3 * sizeof(int)); // the static data of Probabilities class
         memory_used += H->memory() + long(G->nb_edges) * sizeof(Neighbor);
         memory_used += G->memory();
     }
@@ -482,6 +482,16 @@ Communities::Communities(Graph* graph, i
         /*     } */
     }
 
+    if (modularity) {
+        double Q = 0.0;
+        for (int i = 0; i < nb_communities; i++) {
+            if (communities[i].sub_community_of == 0) {
+                Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight);
+            }
+        }
+        Q /= G->total_weight;
+        VECTOR(*modularity)[mergeidx] = Q;
+    }
 }
 
 Communities::~Communities() {
@@ -499,8 +509,8 @@ Communities::~Communities() {
     delete[] Probabilities::vertices2;
 }
 
-float Community::min_delta_sigma() {
-    float r = 1.;
+double Community::min_delta_sigma() {
+    double r = 1.;
     for (Neighbor* N = first_neighbor; N != 0;) {
         if (N->delta_sigma < r) {
             r = N->delta_sigma;
@@ -623,7 +633,7 @@ void Communities::add_neighbor(Neighbor*
     }
 }
 
-void Communities::update_neighbor(Neighbor* N, float new_delta_sigma) {
+void Communities::update_neighbor(Neighbor* N, double new_delta_sigma) {
     if (max_memory != -1) {
         if (new_delta_sigma < min_delta_sigma->delta_sigma[N->community1]) {
             min_delta_sigma->delta_sigma[N->community1] = new_delta_sigma;
@@ -639,7 +649,7 @@ void Communities::update_neighbor(Neighb
             }
         }
 
-        float old_delta_sigma = N->delta_sigma;
+        double old_delta_sigma = N->delta_sigma;
         N->delta_sigma = new_delta_sigma;
         H->update(N);
 
@@ -890,16 +900,18 @@ double Communities::merge_nearest_commun
     if (merges) {
         MATRIX(*merges, mergeidx, 0) = N->community1;
         MATRIX(*merges, mergeidx, 1) = N->community2;
-        mergeidx++;
     }
 
+    mergeidx++;
+
     if (modularity) {
-        float Q = 0.;
+        double Q = 0.0;
         for (int i = 0; i < nb_communities; i++) {
             if (communities[i].sub_community_of == 0) {
-                Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight) / G->total_weight;
+                Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight);
             }
         }
+        Q /= G->total_weight;
         VECTOR(*modularity)[mergeidx] = Q;
     }
 
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap_communities.h 0.9.9+ds-1/src/community/walktrap/walktrap_communities.h
--- 0.9.6+ds-2/src/community/walktrap/walktrap_communities.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap_communities.h	2022-06-04 12:27:41.000000000 +0000
@@ -69,8 +69,8 @@ namespace walktrap {
 class Communities;
 class Probabilities {
 public:
-    static IGRAPH_THREAD_LOCAL float* tmp_vector1;    //
-    static IGRAPH_THREAD_LOCAL float* tmp_vector2;    //
+    static IGRAPH_THREAD_LOCAL double* tmp_vector1;    //
+    static IGRAPH_THREAD_LOCAL double* tmp_vector2;    //
     static IGRAPH_THREAD_LOCAL int* id;       //
     static IGRAPH_THREAD_LOCAL int* vertices1;    //
     static IGRAPH_THREAD_LOCAL int* vertices2;    //
@@ -82,7 +82,7 @@ public:
 
     int size;                         // number of probabilities stored
     int* vertices;                        // the vertices corresponding to the stored probabilities, 0 if all the probabilities are stored
-    float* P;                         // the probabilities
+    double* P;                         // the probabilities
 
     long memory();                        // the memory (in Bytes) used by the object
     double compute_distance(const Probabilities* P2) const;   // compute the squared distance r^2 between this probability vector and P2
@@ -107,9 +107,9 @@ public:
     Probabilities* P;     // the probability vector, 0 if not stored.
 
 
-    float sigma;          // sigma(C) of the community
-    float internal_weight;    // sum of the weight of the internal edges
-    float total_weight;       // sum of the weight of all the edges of the community (an edge between two communities is a half-edge for each community)
+    double sigma;          // sigma(C) of the community
+    double internal_weight;    // sum of the weight of the internal edges
+    double total_weight;       // sum of the weight of all the edges of the community (an edge between two communities is a half-edge for each community)
 
     int sub_communities[2];   // the two sub sommunities, -1 if no sub communities;
     int sub_community_of;     // number of the community in which this community has been merged
@@ -119,7 +119,7 @@ public:
     void merge(Community &C1, Community &C2); // create a new community by merging C1 an C2
     void add_neighbor(Neighbor* N);
     void remove_neighbor(Neighbor* N);
-    float min_delta_sigma();          // compute the minimal delta sigma among all the neighbors of this community
+    double min_delta_sigma();          // compute the minimal delta sigma among all the neighbors of this community
 
     Community();          // create an empty community
     ~Community();         // destructor
@@ -163,7 +163,7 @@ public:
 
     void remove_neighbor(Neighbor* N);
     void add_neighbor(Neighbor* N);
-    void update_neighbor(Neighbor* N, float new_delta_sigma);
+    void update_neighbor(Neighbor* N, double new_delta_sigma);
 
     void manage_memory();
 
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap.cpp 0.9.9+ds-1/src/community/walktrap/walktrap.cpp
--- 0.9.6+ds-2/src/community/walktrap/walktrap.cpp	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap.cpp	2022-06-04 12:27:41.000000000 +0000
@@ -59,6 +59,8 @@
 #include "igraph_community.h"
 #include "igraph_components.h"
 #include "igraph_interface.h"
+
+#include "core/exceptions.h"
 #include "core/interruption.h"
 
 using namespace igraph::walktrap;
@@ -87,6 +89,8 @@ using namespace igraph::walktrap;
  *     If it is a NULL pointer then all edges will have equal
  *     weights. The weights are expected to be positive.
  * \param steps Integer constant, the length of the random walks.
+ *     Typically, good results are obtained with values between
+ *     3-8 with 4-5 being a reasonable default.
  * \param merges Pointer to a matrix, the merges performed by the
  *     algorithm will be stored here (if not NULL). Each merge is a
  *     row in a two-column matrix and contains the ids of the merged
@@ -123,45 +127,74 @@ int igraph_community_walktrap(const igra
                               igraph_vector_t *modularity,
                               igraph_vector_t *membership) {
 
-    long int no_of_nodes = (long int)igraph_vcount(graph);
-    int length = steps;
-    long max_memory = -1;
-
-    if (membership && !(modularity && merges)) {
-        IGRAPH_ERROR("Cannot calculate membership without modularity or merges",
-                     IGRAPH_EINVAL);
-    }
-
-    Graph G;
-    if (G.convert_from_igraph(graph, weights)) {
-        IGRAPH_ERROR("Cannot convert igraph graph into walktrap format", IGRAPH_EINVAL);
-    }
-
-    if (merges) {
-        igraph_integer_t no;
-        IGRAPH_CHECK(igraph_clusters(graph, /*membership=*/ 0, /*csize=*/ 0,
-                                     &no, IGRAPH_WEAK));
-        IGRAPH_CHECK(igraph_matrix_resize(merges, no_of_nodes - no, 2));
-    }
-    if (modularity) {
-        IGRAPH_CHECK(igraph_vector_resize(modularity, no_of_nodes));
-        igraph_vector_null(modularity);
-    }
-    Communities C(&G, length, max_memory, merges, modularity);
-
-    while (!C.H->is_empty()) {
-        IGRAPH_ALLOW_INTERRUPTION();
-        C.merge_nearest_communities();
-    }
-
-    if (membership) {
-        long int m;
-        m = no_of_nodes > 0 ? igraph_vector_which_max(modularity) : 0;
-        IGRAPH_CHECK(igraph_community_to_membership(merges, no_of_nodes,
-                     /*steps=*/ m,
-                     membership,
-                     /*csize=*/ NULL));
-    }
+    IGRAPH_HANDLE_EXCEPTIONS(
+        long int no_of_nodes = (long int) igraph_vcount(graph);
+        long int no_of_edges = (long int) igraph_ecount(graph);
+        int length = steps;
+        long max_memory = -1;
+        igraph_integer_t comp_count;
+
+        if (steps <= 0) {
+            IGRAPH_ERROR("Length of random walks must be positive for walktrap community detection.", IGRAPH_EINVAL);
+        }
+
+        if (weights) {
+            if (igraph_vector_size(weights) != no_of_edges) {
+                IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
+            }
+
+            if (no_of_edges > 0) {
+                igraph_real_t minweight = igraph_vector_min(weights);
+                if (minweight < 0) {
+                    IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL);
+                } else if (igraph_is_nan(minweight)) {
+                    IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL);
+                }
+            }
+        }
+
+        if (membership && !(modularity && merges)) {
+            IGRAPH_ERROR("Cannot calculate membership without modularity or merges",
+                         IGRAPH_EINVAL);
+        }
+
+        Graph G;
+        IGRAPH_CHECK(G.convert_from_igraph(graph, weights));
+
+        if (merges || modularity) {
+            IGRAPH_CHECK(igraph_clusters(graph, /*membership=*/ 0, /*csize=*/ 0,
+                                         &comp_count, IGRAPH_WEAK));
+        }
+        if (merges) {
+            IGRAPH_CHECK(igraph_matrix_resize(merges, no_of_nodes - comp_count, 2));
+        }
+        if (modularity) {
+            IGRAPH_CHECK(igraph_vector_resize(modularity, no_of_nodes - comp_count + 1));
+            igraph_vector_null(modularity);
+        }
+        Communities C(&G, length, max_memory, merges, modularity);
+
+        while (!C.H->is_empty()) {
+            IGRAPH_ALLOW_INTERRUPTION();
+            C.merge_nearest_communities();
+        }
+
+        if (membership) {
+            long int m;
+            m = no_of_nodes > 0 ? igraph_vector_which_max(modularity) : 0;
+            IGRAPH_CHECK(igraph_community_to_membership(merges, no_of_nodes,
+                         /*steps=*/ m,
+                         membership,
+                         /*csize=*/ NULL));
+        }
+
+        /* The walktrap implementation cannot work with NaN values internally,
+         * and produces 0 for the modularity of edgeless graphs. We correct
+         * this to NaN in the last step for consistency. */
+        if (modularity && no_of_edges == 0) {
+            VECTOR(*modularity)[0] = IGRAPH_NAN;
+        }
 
-    return IGRAPH_SUCCESS;
+        return IGRAPH_SUCCESS;
+    )
 }
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap_graph.cpp 0.9.9+ds-1/src/community/walktrap/walktrap_graph.cpp
--- 0.9.6+ds-2/src/community/walktrap/walktrap_graph.cpp	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap_graph.cpp	2022-06-04 12:27:41.000000000 +0000
@@ -99,18 +99,18 @@ class Edge_list {
 public:
     int* V1;
     int* V2;
-    float* W;
+    double* W;
 
     int size;
     int size_max;
 
-    void add(int v1, int v2, float w);
+    void add(int v1, int v2, double w);
     Edge_list() {
         size = 0;
         size_max = 1024;
         V1 = new int[1024];
         V2 = new int[1024];
-        W = new float[1024];
+        W = new double[1024];
     }
     ~Edge_list() {
         if (V1) {
@@ -125,11 +125,11 @@ public:
     }
 };
 
-void Edge_list::add(int v1, int v2, float w) {
+void Edge_list::add(int v1, int v2, double w) {
     if (size == size_max) {
         int* tmp1 = new int[2 * size_max];
         int* tmp2 = new int[2 * size_max];
-        float* tmp3 = new float[2 * size_max];
+        double* tmp3 = new double[2 * size_max];
         for (int i = 0; i < size_max; i++) {
             tmp1[i] = V1[i];
             tmp2[i] = V2[i];
@@ -204,6 +204,13 @@ int Graph::convert_from_igraph(const igr
     }
 
     for (int i = 0; i < G.nb_vertices; i++) {
+        /* Check for zero strength, as it may lead to crashed in walktrap algorithm.
+         * See https://github.com/igraph/igraph/pull/2043 */
+        if (G.vertices[i].total_weight == 0) {
+            /* G.vertices will be destroyed by Graph::~Graph() */
+            IGRAPH_ERROR("Vertex with zero strength found: all vertices must have positive strength for walktrap",
+                         IGRAPH_EINVAL);
+        }
         sort(G.vertices[i].edges, G.vertices[i].edges + G.vertices[i].degree);
     }
 
@@ -219,7 +226,7 @@ int Graph::convert_from_igraph(const igr
         G.vertices[i].degree = a + 1;
     }
 
-    return 0;
+    return IGRAPH_SUCCESS;
 }
 
 long Graph::memory() {
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap_graph.h 0.9.9+ds-1/src/community/walktrap/walktrap_graph.h
--- 0.9.6+ds-2/src/community/walktrap/walktrap_graph.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap_graph.h	2022-06-04 12:27:41.000000000 +0000
@@ -67,7 +67,7 @@ namespace walktrap {
 class Edge {            // code an edge of a given vertex
 public:
     int neighbor;         // the number of the neighbor vertex
-    float weight;         // the weight of the edge
+    double weight;         // the weight of the edge
 };
 bool operator<(const Edge& E1, const Edge& E2);
 
@@ -76,7 +76,7 @@ class Vertex {
 public:
     Edge* edges;          // the edges of the vertex
     int degree;           // number of neighbors
-    float total_weight;       // the total weight of the vertex
+    double total_weight;       // the total weight of the vertex
 
     Vertex();         // creates empty vertex
     ~Vertex();            // destructor
@@ -86,7 +86,7 @@ class Graph {
 public:
     int nb_vertices;      // number of vertices
     int nb_edges;         // number of edges
-    float total_weight;       // total weight of the edges
+    double total_weight;       // total weight of the edges
     Vertex* vertices;     // array of the vertices
 
     long memory();            // the total memory used in Bytes
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap_heap.cpp 0.9.9+ds-1/src/community/walktrap/walktrap_heap.cpp
--- 0.9.6+ds-2/src/community/walktrap/walktrap_heap.cpp	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap_heap.cpp	2022-06-04 12:27:41.000000000 +0000
@@ -215,7 +215,7 @@ void Min_delta_sigma_heap::update(int co
 }
 
 long Min_delta_sigma_heap::memory() {
-    return (sizeof(Min_delta_sigma_heap) + long(max_size) * (2 * sizeof(int) + sizeof(float)));
+    return (sizeof(Min_delta_sigma_heap) + long(max_size) * (2 * sizeof(int) + sizeof(double)));
 }
 
 Min_delta_sigma_heap::Min_delta_sigma_heap(int max_s) {
@@ -223,7 +223,7 @@ Min_delta_sigma_heap::Min_delta_sigma_he
     size = 0;
     H = new int[max_s];
     I = new int[max_s];
-    delta_sigma = new float[max_s];
+    delta_sigma = new double[max_s];
     for (int i = 0; i < max_size; i++) {
         I[i] = -1;
         delta_sigma[i] = 1.;
diff -pruN 0.9.6+ds-2/src/community/walktrap/walktrap_heap.h 0.9.9+ds-1/src/community/walktrap/walktrap_heap.h
--- 0.9.6+ds-2/src/community/walktrap/walktrap_heap.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/community/walktrap/walktrap_heap.h	2022-06-04 12:27:41.000000000 +0000
@@ -65,8 +65,8 @@ public:
     int community1;   // the two adjacent communities
     int community2;   // community1 < community2
 
-    float delta_sigma;    // the delta sigma between the two communities
-    float weight;     // the total weight of the edges between the two communities
+    double delta_sigma;    // the delta sigma between the two communities
+    double weight;     // the total weight of the edges between the two communities
     bool exact;       // true if delta_sigma is exact, false if it is only a lower bound
 
     Neighbor* next_community1;        // pointers of two double
@@ -121,7 +121,7 @@ public:
     long memory();                    // the memory used in Bytes.
     bool is_empty();
 
-    float* delta_sigma;                    // the delta_sigma of the stored communities
+    double* delta_sigma;                    // the delta_sigma of the stored communities
 
     Min_delta_sigma_heap(int max_size);
     ~Min_delta_sigma_heap();
diff -pruN 0.9.6+ds-2/src/connectivity/components.c 0.9.9+ds-1/src/connectivity/components.c
--- 0.9.6+ds-2/src/connectivity/components.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/connectivity/components.c	2022-06-04 12:27:41.000000000 +0000
@@ -345,7 +345,7 @@ static int igraph_is_connected_weak(cons
  * definition. This behaviour changed in igraph 0.9; earlier versions assumed
  * that the null graph is connected. See the following issue on Github for the
  * argument that led us to change the definition:
- * https://github.com/igraph/igraph/issues/1538
+ * https://github.com/igraph/igraph/issues/1539
  *
  * \param graph The graph object to analyze.
  * \param res Pointer to a logical variable, the result will be stored
@@ -369,7 +369,7 @@ int igraph_is_connected(const igraph_t *
     long int no_of_nodes = igraph_vcount(graph);
 
     if (no_of_nodes == 0) {
-        /* Changed in igraph 0.9; see https://github.com/igraph/igraph/issues/1538
+        /* Changed in igraph 0.9; see https://github.com/igraph/igraph/issues/1539
          * for the reasoning behind the change */
         *res = 0;
         return IGRAPH_SUCCESS;
diff -pruN 0.9.6+ds-2/src/constructors/full.c 0.9.9+ds-1/src/constructors/full.c
--- 0.9.6+ds-2/src/constructors/full.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/constructors/full.c	2022-06-04 12:27:41.000000000 +0000
@@ -73,7 +73,7 @@ int igraph_full(igraph_t *graph, igraph_
     IGRAPH_VECTOR_INIT_FINALLY(&edges, 0);
 
     if (directed && loops) {
-        IGRAPH_CHECK(igraph_vector_reserve(&edges, n * n));
+        IGRAPH_CHECK(igraph_vector_reserve(&edges, 2 * n * n));
         for (i = 0; i < n; i++) {
             for (j = 0; j < n; j++) {
                 igraph_vector_push_back(&edges, i); /* reserved */
@@ -81,7 +81,7 @@ int igraph_full(igraph_t *graph, igraph_
             }
         }
     } else if (directed && !loops) {
-        IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n - 1)));
+        IGRAPH_CHECK(igraph_vector_reserve(&edges, 2 * n * (n - 1)));
         for (i = 0; i < n; i++) {
             for (j = 0; j < i; j++) {
                 igraph_vector_push_back(&edges, i); /* reserved */
@@ -93,7 +93,7 @@ int igraph_full(igraph_t *graph, igraph_
             }
         }
     } else if (!directed && loops) {
-        IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n + 1) / 2));
+        IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n + 1)));
         for (i = 0; i < n; i++) {
             for (j = i; j < n; j++) {
                 igraph_vector_push_back(&edges, i); /* reserved */
@@ -101,7 +101,7 @@ int igraph_full(igraph_t *graph, igraph_
             }
         }
     } else {
-        IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n - 1) / 2));
+        IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n - 1)));
         for (i = 0; i < n; i++) {
             for (j = i + 1; j < n; j++) {
                 igraph_vector_push_back(&edges, i); /* reserved */
diff -pruN 0.9.6+ds-2/src/constructors/regular.c 0.9.9+ds-1/src/constructors/regular.c
--- 0.9.6+ds-2/src/constructors/regular.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/constructors/regular.c	2022-06-04 12:27:41.000000000 +0000
@@ -391,7 +391,7 @@ int igraph_tree(igraph_t *graph, igraph_
         IGRAPH_ERROR("Invalid tree orientation type.", IGRAPH_EINVMODE);
     }
 
-    IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * (n - 1));
+    IGRAPH_VECTOR_INIT_FINALLY(&edges, n > 0 ? 2 * (n - 1) : 0);
 
     i = 0;
     if (type == IGRAPH_TREE_OUT) {
diff -pruN 0.9.6+ds-2/src/core/hashtable.c 0.9.9+ds-1/src/core/hashtable.c
--- 0.9.6+ds-2/src/core/hashtable.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/core/hashtable.c	1970-01-01 00:00:00.000000000 +0000
@@ -1,129 +0,0 @@
-/* -*- mode: C -*-  */
-/*
-   IGraph library.
-   Copyright (C) 2006-2012  Gabor Csardi <csardi.gabor@gmail.com>
-   334 Harvard street, Cambridge, MA 02139 USA
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301 USA
-
-*/
-
-#include "igraph_types.h"
-#include "igraph_memory.h"
-#include "igraph_error.h"
-
-#include "core/hashtable.h"
-
-#include <string.h>
-
-int igraph_hashtable_init(igraph_hashtable_t *ht) {
-    IGRAPH_CHECK(igraph_trie_init(&ht->keys, 1));
-    IGRAPH_FINALLY(igraph_trie_destroy, &ht->keys);
-    IGRAPH_CHECK(igraph_strvector_init(&ht->elements, 0));
-    IGRAPH_FINALLY(igraph_strvector_destroy, &ht->elements);
-    IGRAPH_CHECK(igraph_strvector_init(&ht->defaults, 0));
-
-    IGRAPH_FINALLY_CLEAN(2);
-    return 0;
-}
-
-void igraph_hashtable_destroy(igraph_hashtable_t *ht) {
-    igraph_trie_destroy(&ht->keys);
-    igraph_strvector_destroy(&ht->elements);
-    igraph_strvector_destroy(&ht->defaults);
-}
-
-/* Note: may leave the hash table in an inconsistent state if a new
-   element is added, but this is not a big problem, since while the
-   defaults, or the defaults plus the elements may contain more elements
-   than the keys trie, but the data is always retrieved based on the trie
-*/
-
-int igraph_hashtable_addset(igraph_hashtable_t *ht,
-                            const char *key, const char *def,
-                            const char *elem) {
-    long int size = igraph_trie_size(&ht->keys);
-    long int newid;
-    IGRAPH_CHECK(igraph_trie_get(&ht->keys, key, &newid));
-
-    if (newid == size) {
-        /* this is a new element */
-        IGRAPH_CHECK(igraph_strvector_resize(&ht->defaults, newid + 1));
-        IGRAPH_CHECK(igraph_strvector_resize(&ht->elements, newid + 1));
-        IGRAPH_CHECK(igraph_strvector_set(&ht->defaults, newid, def));
-        IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, elem));
-    } else {
-        /* set an already existing element */
-        IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, elem));
-    }
-
-    return 0;
-}
-
-/* Previous comment also applies here */
-
-int igraph_hashtable_addset2(igraph_hashtable_t *ht,
-                             const char *key, const char *def,
-                             const char *elem, int elemlen) {
-    long int size = igraph_trie_size(&ht->keys);
-    long int newid;
-    char *tmp;
-
-    IGRAPH_CHECK(igraph_trie_get(&ht->keys, key, &newid));
-
-    tmp = IGRAPH_CALLOC(elemlen + 1, char);
-    if (tmp == 0) {
-        IGRAPH_ERROR("cannot add element to hash table", IGRAPH_ENOMEM);
-    }
-    IGRAPH_FINALLY(igraph_free, tmp);
-    strncpy(tmp, elem, elemlen);
-    tmp[elemlen] = '\0';
-
-    if (newid == size) {
-        IGRAPH_CHECK(igraph_strvector_resize(&ht->defaults, newid + 1));
-        IGRAPH_CHECK(igraph_strvector_resize(&ht->elements, newid + 1));
-        IGRAPH_CHECK(igraph_strvector_set(&ht->defaults, newid, def));
-        IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, tmp));
-    } else {
-        IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, tmp));
-    }
-
-    IGRAPH_FREE(tmp);
-    IGRAPH_FINALLY_CLEAN(1);
-
-    return 0;
-}
-
-int igraph_hashtable_get(igraph_hashtable_t *ht,
-                         const char *key, char **elem) {
-    long int newid;
-    IGRAPH_CHECK(igraph_trie_get(&ht->keys, key, &newid));
-
-    igraph_strvector_get(&ht->elements, newid, elem);
-
-    return 0;
-}
-
-int igraph_hashtable_reset(igraph_hashtable_t *ht) {
-    igraph_strvector_destroy(&ht->elements);
-    IGRAPH_CHECK(igraph_strvector_copy(&ht->elements, &ht->defaults));
-    return 0;
-}
-
-int igraph_hashtable_getkeys(igraph_hashtable_t *ht,
-                             const igraph_strvector_t **sv) {
-    return igraph_trie_getkeys(&ht->keys, sv);
-}
diff -pruN 0.9.6+ds-2/src/core/hashtable.h 0.9.9+ds-1/src/core/hashtable.h
--- 0.9.6+ds-2/src/core/hashtable.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/core/hashtable.h	1970-01-01 00:00:00.000000000 +0000
@@ -1,58 +0,0 @@
-/* -*- mode: C -*-  */
-/*
-   IGraph library.
-   Copyright (C) 2009-2020  The igraph development team
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301 USA
-
-*/
-
-#ifndef IGRAPH_CORE_HASHTABLE_H
-#define IGRAPH_CORE_HASHTABLE_H
-
-#include "igraph_decls.h"
-#include "igraph_types.h"
-#include "igraph_strvector.h"
-
-#include "core/trie.h"
-
-__BEGIN_DECLS
-
-/* string -> string hash table */
-
-typedef struct igraph_hashtable_t {
-    igraph_trie_t keys;
-    igraph_strvector_t elements;
-    igraph_strvector_t defaults;
-} igraph_hashtable_t;
-
-IGRAPH_PRIVATE_EXPORT int igraph_hashtable_init(igraph_hashtable_t *ht);
-IGRAPH_PRIVATE_EXPORT void igraph_hashtable_destroy(igraph_hashtable_t *ht);
-IGRAPH_PRIVATE_EXPORT int igraph_hashtable_addset(igraph_hashtable_t *ht,
-                                                  const char *key, const char *def,
-                                                  const char *elem);
-IGRAPH_PRIVATE_EXPORT int igraph_hashtable_addset2(igraph_hashtable_t *ht,
-                                                   const char *key, const char *def,
-                                                   const char *elem, int elemlen);
-IGRAPH_PRIVATE_EXPORT int igraph_hashtable_get(igraph_hashtable_t *ht,
-                                               const char *key, char **elem);
-IGRAPH_PRIVATE_EXPORT int igraph_hashtable_getkeys(igraph_hashtable_t *ht,
-                                                   const igraph_strvector_t **sv);
-IGRAPH_PRIVATE_EXPORT int igraph_hashtable_reset(igraph_hashtable_t *ht);
-
-__END_DECLS
-
-#endif
diff -pruN 0.9.6+ds-2/src/core/matrix.pmt 0.9.9+ds-1/src/core/matrix.pmt
--- 0.9.6+ds-2/src/core/matrix.pmt	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/core/matrix.pmt	2022-06-04 12:27:41.000000000 +0000
@@ -909,19 +909,15 @@ int FUNCTION(igraph_matrix, cbind)(TYPE(
  * \function igraph_matrix_swap
  * Swap two matrices.
  *
- * The contents of the two matrices will be swapped. They must have the
- * same dimensions.
+ * The contents of the two matrices will be swapped.
  * \param m1 The first matrix.
  * \param m2 The second matrix.
  * \return Error code.
  *
- * Time complexity: O(mn), the number of elements in the matrices.
+ * Time complexity: O(1).
  */
 
 int FUNCTION(igraph_matrix, swap)(TYPE(igraph_matrix) *m1, TYPE(igraph_matrix) *m2) {
-    if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) {
-        IGRAPH_ERROR("Cannot swap non-conformant matrices", IGRAPH_EINVAL);
-    }
     return FUNCTION(igraph_vector, swap)(&m1->data, &m2->data);
 }
 
diff -pruN 0.9.6+ds-2/src/core/printing.c 0.9.9+ds-1/src/core/printing.c
--- 0.9.6+ds-2/src/core/printing.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/core/printing.c	2022-06-04 12:27:41.000000000 +0000
@@ -35,8 +35,8 @@
     #define STRINGIFY(x) STRINGIFY_HELPER(x)
     #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%." STRINGIFY(DBL_DIG) "g"
 #else
-    /* Assume a precision of 10 digits for %g */
-    #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%.10g"
+    /* Assume a precision of 15 digits for %g */
+    #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%.15g"
 #endif
 
 #ifndef USING_R
diff -pruN 0.9.6+ds-2/src/core/psumtree.c 0.9.9+ds-1/src/core/psumtree.c
--- 0.9.6+ds-2/src/core/psumtree.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/core/psumtree.c	2022-06-04 12:27:41.000000000 +0000
@@ -162,7 +162,9 @@ igraph_real_t igraph_psumtree_get(const
  *
  * \param t The tree to query.
  * \param idx The index of the item is returned here.
- * \param search The value to use for the search.
+ * \param search The value to use for the search. Must be in the interval
+ *        <code>[0, sum)</code>, where \c sum is the sum of all elements
+ *        (leaves) in the tree.
  * \return Error code; currently the search always succeeds.
  *
  * Time complexity: O(log n), where n is the number of items in the tree.
@@ -173,8 +175,11 @@ int igraph_psumtree_search(const igraph_
     long int i = 1;
     long int size = igraph_vector_size(tree);
 
+    IGRAPH_ASSERT(search >= 0);
+    IGRAPH_ASSERT(search < igraph_psumtree_sum(t));
+
     while ( 2 * i + 1 <= size) {
-        if ( search <= VECTOR(*tree)[i * 2 - 1] ) {
+        if ( search < VECTOR(*tree)[i * 2 - 1] ) {
             i <<= 1;
         } else {
             search -= VECTOR(*tree)[i * 2 - 1];
diff -pruN 0.9.6+ds-2/src/core/vector.pmt 0.9.9+ds-1/src/core/vector.pmt
--- 0.9.6+ds-2/src/core/vector.pmt	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/core/vector.pmt	2022-06-04 12:27:41.000000000 +0000
@@ -123,9 +123,7 @@
 
 int FUNCTION(igraph_vector, init)      (TYPE(igraph_vector)* v, int long size) {
     long int alloc_size = size > 0 ? size : 1;
-    if (size < 0) {
-        size = 0;
-    }
+    IGRAPH_ASSERT(size >= 0);
     v->stor_begin = IGRAPH_CALLOC(alloc_size, BASE);
     if (v->stor_begin == 0) {
         IGRAPH_ERROR("cannot init vector", IGRAPH_ENOMEM);
@@ -2271,31 +2269,22 @@ int FUNCTION(igraph_vector, update)(TYPE
  * \function igraph_vector_swap
  * \brief Swap elements of two vectors.
  *
- * The two vectors must have the same length, otherwise an error
- * happens.
  * \param v1 The first vector.
  * \param v2 The second vector.
  * \return Error code.
  *
- * Time complexity: O(n), the length of the vectors.
+ * Time complexity: O(1).
  */
 
 int FUNCTION(igraph_vector, swap)(TYPE(igraph_vector) *v1, TYPE(igraph_vector) *v2) {
 
-    long int i, n1 = FUNCTION(igraph_vector, size)(v1);
-    long int n2 = FUNCTION(igraph_vector, size)(v2);
-    if (n1 != n2) {
-        IGRAPH_ERROR("Vectors must have the same number of elements for swapping",
-                     IGRAPH_EINVAL);
-    }
+    TYPE(igraph_vector) tmp;
 
-    for (i = 0; i < n1; i++) {
-        BASE tmp;
-        tmp = VECTOR(*v1)[i];
-        VECTOR(*v1)[i] = VECTOR(*v2)[i];
-        VECTOR(*v2)[i] = tmp;
-    }
-    return 0;
+    tmp = *v1;
+    *v1 = *v2;
+    *v2 = tmp;
+
+    return IGRAPH_SUCCESS;
 }
 
 /**
diff -pruN 0.9.6+ds-2/src/games/barabasi.c 0.9.9+ds-1/src/games/barabasi.c
--- 0.9.6+ds-2/src/games/barabasi.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/games/barabasi.c	2022-06-04 12:27:41.000000000 +0000
@@ -245,7 +245,13 @@ static int igraph_i_barabasi_game_psumtr
             no_of_neighbors = (long int) VECTOR(*outseq)[k];
         }
         for (j = 0; j < no_of_neighbors; j++) {
-            igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            if (sum == 0) {
+                /* If none of the so-far added nodes have positive weights,
+                 * we choose one uniformly to connect to. */
+                to = RNG_INTEGER(0, i-1);
+            } else {
+                igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            }
             VECTOR(degree)[to]++;
             VECTOR(edges)[edgeptr++] = i;
             VECTOR(edges)[edgeptr++] = to;
@@ -363,7 +369,13 @@ static int igraph_i_barabasi_game_psumtr
         } else {
             for (j = 0; j < no_of_neighbors; j++) {
                 sum = igraph_psumtree_sum(&sumtree);
-                igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+                if (sum == 0) {
+                    /* If none of the so-far added nodes have positive weights,
+                     * we choose one uniformly to connect to. */
+                    to = RNG_INTEGER(0, i-1);
+                } else {
+                    igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+                }
                 VECTOR(degree)[to]++;
                 IGRAPH_CHECK(igraph_vector_push_back(&edges, i));
                 IGRAPH_CHECK(igraph_vector_push_back(&edges, to));
@@ -636,8 +648,8 @@ int igraph_barabasi_aging_game(igraph_t
     }
     if (outseq != 0 && igraph_vector_size(outseq) != 0 && igraph_vector_size(outseq) != no_of_nodes) {
         IGRAPH_ERRORF("The length of the out-degree sequence (%ld) does not agree with the number of nodes (%ld).",
-                       IGRAPH_EINVAL,
-                       igraph_vector_size(outseq), no_of_nodes);
+                      IGRAPH_EINVAL,
+                      igraph_vector_size(outseq), no_of_nodes);
     }
     if ( (outseq == 0 || igraph_vector_size(outseq) == 0) && m < 0) {
         IGRAPH_ERRORF("The number of edges per time step must not be negative, got %" IGRAPH_PRId ".",
@@ -646,29 +658,29 @@ int igraph_barabasi_aging_game(igraph_t
     }
     if (aging_bins <= 0) {
         IGRAPH_ERRORF("Number of aging bins must be positive, got %" IGRAPH_PRId ".",
-                     IGRAPH_EINVAL,
-                     aging_bins);
+                      IGRAPH_EINVAL,
+                      aging_bins);
     }
     if (deg_coef < 0) {
         IGRAPH_ERRORF("Degree coefficient must be non-negative, got %g.",
-                     IGRAPH_EINVAL,
-                     deg_coef);
+                      IGRAPH_EINVAL,
+                      deg_coef);
     }
     if (age_coef < 0) {
         IGRAPH_ERRORF("Age coefficient must be non-negative, got %g.",
-                     IGRAPH_EINVAL,
-                     deg_coef);
+                      IGRAPH_EINVAL,
+                      deg_coef);
     }
 
     if (zero_deg_appeal < 0) {
         IGRAPH_ERRORF("Zero degree appeal must be non-negative, got %g.",
-                     IGRAPH_EINVAL,
-                     zero_deg_appeal);
+                      IGRAPH_EINVAL,
+                      zero_deg_appeal);
     }
     if (zero_age_appeal < 0) {
         IGRAPH_ERRORF("Zero age appeal must be non-negative, got %g.",
-                     IGRAPH_EINVAL,
-                     zero_age_appeal);
+                      IGRAPH_EINVAL,
+                      zero_age_appeal);
     }
 
     if (no_of_nodes == 0) {
@@ -709,7 +721,13 @@ int igraph_barabasi_aging_game(igraph_t
         }
         sum = igraph_psumtree_sum(&sumtree);
         for (j = 0; j < no_of_neighbors; j++) {
-            igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            if (sum == 0) {
+                /* If none of the so-far added nodes have positive weights,
+                 * we choose one uniformly to connect to. */
+                to = RNG_INTEGER(0, i-1);
+            } else {
+                igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            }
             VECTOR(degree)[to]++;
             VECTOR(edges)[edgeptr++] = i;
             VECTOR(edges)[edgeptr++] = to;
diff -pruN 0.9.6+ds-2/src/games/citations.c 0.9.9+ds-1/src/games/citations.c
--- 0.9.6+ds-2/src/games/citations.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/games/citations.c	2022-06-04 12:27:41.000000000 +0000
@@ -163,7 +163,13 @@ int igraph_lastcit_game(igraph_t *graph,
         for (j = 0; j < edges_per_node; j++) {
             long int to;
             igraph_real_t sum = igraph_psumtree_sum(&sumtree);
-            igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            if (sum == 0) {
+                /* If none of the so-far added nodes have positive weight,
+                 * we choose one uniformly to connect to. */
+                to = RNG_INTEGER(0, i-1);
+            } else {
+                igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            }
             igraph_vector_push_back(&edges, i);
             igraph_vector_push_back(&edges, to);
             lastcit[to] = i + 1;
@@ -452,7 +458,13 @@ int igraph_citing_cited_type_game(igraph
         igraph_real_t sum = VECTOR(sums)[type];
         for (j = 0; j < edges_per_step; j++) {
             long int to;
-            igraph_psumtree_search(&sumtrees[type], &to, RNG_UNIF(0, sum));
+            if (sum == 0) {
+                /* If none of the so-far added nodes have positive weight,
+                 * we choose one uniformly to connect to. */
+                to = RNG_INTEGER(0, i-1);
+            } else {
+                igraph_psumtree_search(&sumtrees[type], &to, RNG_UNIF(0, sum));
+            }
             igraph_vector_push_back(&edges, i);
             igraph_vector_push_back(&edges, to);
         }
diff -pruN 0.9.6+ds-2/src/games/correlated.c 0.9.9+ds-1/src/games/correlated.c
--- 0.9.6+ds-2/src/games/correlated.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/games/correlated.c	2022-06-04 12:27:41.000000000 +0000
@@ -26,22 +26,59 @@
 #include "igraph_conversion.h"
 #include "igraph_constructors.h"
 #include "igraph_interface.h"
+#include "igraph_qsort.h"
 #include "igraph_random.h"
+#include "igraph_structural.h"
+
+#include "core/interruption.h"
+
+/* The "code" of an edge is a single index representing its location in the adjacency matrix,
+ * More specifically, the relevant parts of the adjacency matrix (i.e. non-diagonal in directed,
+ * upper triangular in undirecred) are column-wise concatenated into an array. The "code" is
+ * the index in this array. We use floating point numbers for the code, as it can easily
+ * exceed integers representable on 32 bits.
+ */
+#define D_CODE(f,t) (((t)==no_of_nodes-1 ? (f) : (t)) * no_of_nodes + (f))
+#define U_CODE(f,t) ((t) * ((t)-1) / 2 + (f))
+#define CODE(f,t) (directed ? D_CODE((double)(f),(double)(t)) : U_CODE((double)(f),(double)(t)))
+
+/* TODO: Slight speedup may be possible if repeated vertex count queries are avoided. */
+static int code_cmp(void *graph, const void *va, const void *vb) {
+    const igraph_real_t *a = (const igraph_real_t *) va;
+    const igraph_real_t *b = (const igraph_real_t *) vb;
+    const long int no_of_nodes = igraph_vcount((igraph_t *) graph);
+    const igraph_bool_t directed = igraph_is_directed((igraph_t *) graph);
+    const igraph_real_t codea = CODE(a[0], a[1]);
+    const igraph_real_t codeb = CODE(b[0], b[1]);
+    if (codea < codeb) {
+        return -1;
+    } else if (codea > codeb) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+/* Sort an edge vector by edge codes. */
+static void sort_edges(igraph_vector_t *edges, const igraph_t *graph) {
+    igraph_qsort_r(VECTOR(*edges), igraph_vector_size(edges) / 2, 2*sizeof(igraph_real_t), (void *) graph, code_cmp);
+}
 
 /**
  * \function igraph_correlated_game
  * \brief Generates a random graph correlated to an existing graph.
  *
  * Sample a new graph by perturbing the adjacency matrix of a
- * given graph and shuffling its vertices.
+ * given simple graph and shuffling its vertices.
  *
- * \param old_graph The original graph.
+ * \param old_graph The original graph, it must be simple.
  * \param new_graph The new graph will be stored here.
- * \param corr A scalar in the unit interval, the target Pearson
- *        correlation between the adjacency matrices of the original the
+ * \param corr A scalar in the unit interval [0,1], the target Pearson
+ *        correlation between the adjacency matrices of the original and the
  *        generated graph (the adjacency matrix being used as a vector).
  * \param p A numeric scalar, the probability of an edge between two
- *        vertices, it must in the open (0,1) interval.
+ *        vertices, it must in the open (0,1) interval. Typically,
+ *        the density of \p old_graph.
  * \param permutation A permutation to apply to the vertices of the
  *        generated graph. It can also be a null pointer, in which case
  *        the vertices will not be permuted.
@@ -54,8 +91,8 @@ int igraph_correlated_game(const igraph_
                            igraph_real_t corr, igraph_real_t p,
                            const igraph_vector_t *permutation) {
 
-    int no_of_nodes = igraph_vcount(old_graph);
-    int no_of_edges = igraph_ecount(old_graph);
+    long int no_of_nodes = igraph_vcount(old_graph);
+    long int no_of_edges = igraph_ecount(old_graph);
     igraph_bool_t directed = igraph_is_directed(old_graph);
     igraph_real_t no_of_all = directed ? no_of_nodes * (no_of_nodes - 1) :
                               no_of_nodes * (no_of_nodes - 1) / 2;
@@ -65,25 +102,31 @@ int igraph_correlated_game(const igraph_
     igraph_real_t p_add = ((1 - q) * (p / (1 - p)));
     igraph_vector_t add, delete, edges, newedges;
     igraph_real_t last;
-    int p_e = 0, p_a = 0, p_d = 0, no_add, no_del;
+    long int p_e = 0, p_a = 0, p_d = 0, no_add, no_del;
     igraph_real_t inf = IGRAPH_INFINITY;
     igraph_real_t next_e, next_a, next_d;
-    int i;
+    long int i;
+    igraph_bool_t simple;
 
-    if (corr < -1 || corr > 1) {
-        IGRAPH_ERROR("Correlation must be in [-1,1] in correlated "
-                     "Erdos-Renyi game", IGRAPH_EINVAL);
+    if (corr < 0 || corr > 1) {
+        IGRAPH_ERRORF("Correlation must be in [0,1] in correlated Erdos-Renyi game, got %g.",
+                      IGRAPH_EINVAL, corr);
     }
     if (p <= 0 || p >= 1) {
-        IGRAPH_ERROR("Edge probability must be in (0,1) in correlated "
-                     "Erdos-Renyi game", IGRAPH_EINVAL);
+        IGRAPH_ERRORF("Edge probability must be in (0,1) in correlated Erdos-Renyi game, got %g.",
+                      IGRAPH_EINVAL, p);
     }
     if (permutation) {
         if (igraph_vector_size(permutation) != no_of_nodes) {
-            IGRAPH_ERROR("Invalid permutation length in correlated Erdos-Renyi game",
+            IGRAPH_ERROR("Invalid permutation length in correlated Erdos-Renyi game.",
                          IGRAPH_EINVAL);
         }
     }
+    IGRAPH_CHECK(igraph_is_simple(old_graph, &simple));
+    if (! simple) {
+        IGRAPH_ERROR("The original graph must be simple for correlated Erdos-Renyi game.",
+                     IGRAPH_EINVAL);
+    }
 
     /* Special cases */
 
@@ -115,6 +158,11 @@ int igraph_correlated_game(const igraph_
     IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2);
 
     IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ 0));
+    /* The samping method used is analogous to the one in igraph_erdos_renyi_game_gnp(),
+     * and assumes that the edge list of the old graph is in order of increasing "codes".
+     * Even IGRAPH_EDGEORDER_TO does not guarantee this, therefore we sort explicitly.
+     */
+    sort_edges(&edges, old_graph);
 
     RNG_BEGIN();
 
@@ -140,8 +188,6 @@ int igraph_correlated_game(const igraph_
 
     RNG_END();
 
-    IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ 0));
-
     /* Now we are merging the original edges, the edges that are removed,
        and the new edges. We have the following pointers:
        - p_a: the next edge to add
@@ -151,9 +197,6 @@ int igraph_correlated_game(const igraph_
        - next_a: the code of the next edge to add
        - next_d: the code of the next edge to delete */
 
-#define D_CODE(f,t) (((t)==no_of_nodes-1 ? f : t) * no_of_nodes + (f))
-#define U_CODE(f,t) ((t) * ((t)-1) / 2 + (f))
-#define CODE(f,t) (directed ? D_CODE(f,t) : U_CODE(f,t))
 #define CODEE() (CODE(VECTOR(edges)[2*p_e], VECTOR(edges)[2*p_e+1]))
 
     /* First we (re)code the edges to delete */
@@ -171,18 +214,19 @@ int igraph_correlated_game(const igraph_
     /* Now we can do the merge. Additional edges are tricky, because
        the code must be shifted by the edges in the original graph. */
 
-#define UPD_E()                             \
+#define UPD_E() \
     { if (p_e < no_of_edges) { next_e=CODEE(); } else { next_e = inf; } }
-#define UPD_A()                             \
-{ if (p_a < no_add) { \
+#define UPD_A() \
+    { if (p_a < no_add) { \
             next_a = VECTOR(add)[p_a] + p_e; } else { next_a = inf; } }
-#define UPD_D()                             \
-{ if (p_d < no_del) { \
+#define UPD_D() \
+    { if (p_d < no_del) { \
             next_d = VECTOR(delete)[p_d]; } else { next_d = inf; } }
 
     UPD_E(); UPD_A(); UPD_D();
 
     while (next_e != inf || next_a != inf || next_d != inf) {
+        IGRAPH_ALLOW_INTERRUPTION();
         if (next_e <= next_a && next_e < next_d) {
 
             /* keep an edge */
@@ -199,16 +243,17 @@ int igraph_correlated_game(const igraph_
         } else {
 
             /* add an edge */
-            int to, from;
+            long int to, from;
+            IGRAPH_ASSERT(igraph_finite(next_a));
             if (directed) {
-                to = (int) floor(next_a / no_of_nodes);
-                from = (int) (next_a - ((igraph_real_t)to) * no_of_nodes);
+                to = (long int) floor(next_a / no_of_nodes);
+                from = (long int) (next_a - ((igraph_real_t)to) * no_of_nodes);
                 if (from == to) {
                     to = no_of_nodes - 1;
                 }
             } else {
-                to = (int) floor((sqrt(8 * next_a + 1) + 1) / 2);
-                from = (int) (next_a - (((igraph_real_t)to) * (to - 1)) / 2);
+                to = (long int) floor((sqrt(8 * next_a + 1) + 1) / 2);
+                from = (long int) (next_a - (((igraph_real_t)to) * (to - 1)) / 2);
             }
             IGRAPH_CHECK(igraph_vector_push_back(&newedges, from));
             IGRAPH_CHECK(igraph_vector_push_back(&newedges, to));
@@ -223,9 +268,9 @@ int igraph_correlated_game(const igraph_
     IGRAPH_FINALLY_CLEAN(3);
 
     if (permutation) {
-        int newec = igraph_vector_size(&newedges);
+        long int newec = igraph_vector_size(&newedges);
         for (i = 0; i < newec; i++) {
-            int tmp = VECTOR(newedges)[i];
+            long int tmp = VECTOR(newedges)[i];
             VECTOR(newedges)[i] = VECTOR(*permutation)[tmp];
         }
     }
@@ -235,7 +280,7 @@ int igraph_correlated_game(const igraph_
     igraph_vector_destroy(&newedges);
     IGRAPH_FINALLY_CLEAN(1);
 
-    return 0;
+    return IGRAPH_SUCCESS;
 }
 
 #undef D_CODE
@@ -277,5 +322,5 @@ int igraph_correlated_pair_game(igraph_t
     IGRAPH_CHECK(igraph_erdos_renyi_game(graph1, IGRAPH_ERDOS_RENYI_GNP, n, p,
                                          directed, IGRAPH_NO_LOOPS));
     IGRAPH_CHECK(igraph_correlated_game(graph1, graph2, corr, p, permutation));
-    return 0;
+    return IGRAPH_SUCCESS;
 }
diff -pruN 0.9.6+ds-2/src/games/growing_random.c 0.9.9+ds-1/src/games/growing_random.c
--- 0.9.6+ds-2/src/games/growing_random.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/games/growing_random.c	2022-06-04 12:27:41.000000000 +0000
@@ -72,7 +72,7 @@ int igraph_growing_random_game(igraph_t
         IGRAPH_ERROR("Invalid number of edges per step (m)", IGRAPH_EINVAL);
     }
 
-    no_of_edges = (no_of_nodes - 1) * no_of_neighbors;
+    no_of_edges = no_of_nodes > 0 ? (no_of_nodes - 1) * no_of_neighbors : 0;
 
     IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2);
 
diff -pruN 0.9.6+ds-2/src/games/preference.c 0.9.9+ds-1/src/games/preference.c
--- 0.9.6+ds-2/src/games/preference.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/games/preference.c	2022-06-04 12:27:41.000000000 +0000
@@ -226,11 +226,17 @@ int igraph_preference_game(igraph_t *gra
                 }
             }
         } else {
-            long int fixno = (long int) ceil( (double)nodes / types);
+            igraph_integer_t size_of_one_group = (igraph_integer_t) floor( (double)nodes / types);
+            igraph_integer_t num_groups_with_one_extra_node = nodes - size_of_one_group * types;
             for (i = 0; i < types; i++) {
                 igraph_vector_t *v = VECTOR(vids_by_type)[i];
-                for (j = 0; j < fixno && an < nodes; j++) {
-                    VECTOR(*nodetypes)[an++] = i;
+                for (j = 0; j < size_of_one_group; j++) {
+                    VECTOR(*nodetypes)[an] = i;
+                    IGRAPH_CHECK(igraph_vector_push_back(v, an));
+                    an++;
+                }
+                if (i < num_groups_with_one_extra_node) {
+                    VECTOR(*nodetypes)[an] = i;
                     IGRAPH_CHECK(igraph_vector_push_back(v, an));
                     an++;
                 }
diff -pruN 0.9.6+ds-2/src/games/recent_degree.c 0.9.9+ds-1/src/games/recent_degree.c
--- 0.9.6+ds-2/src/games/recent_degree.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/games/recent_degree.c	2022-06-04 12:27:41.000000000 +0000
@@ -146,7 +146,13 @@ int igraph_recent_degree_game(igraph_t *
 
         sum = igraph_psumtree_sum(&sumtree);
         for (j = 0; j < no_of_neighbors; j++) {
-            igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            if (sum == 0) {
+                /* If none of the so-far added nodes have positive weight,
+                 * we choose one uniformly to connect to. */
+                to = RNG_INTEGER(0, i-1);
+            } else {
+                igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            }
             VECTOR(degree)[to]++;
             VECTOR(edges)[edgeptr++] = i;
             VECTOR(edges)[edgeptr++] = to;
@@ -318,7 +324,13 @@ int igraph_recent_degree_aging_game(igra
 
         sum = igraph_psumtree_sum(&sumtree);
         for (j = 0; j < no_of_neighbors; j++) {
-            igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            if (sum == 0) {
+                /* If none of the so-far added nodes have positive weight,
+                 * we choose one uniformly to connect to. */
+                to = RNG_INTEGER(0, i-1);
+            } else {
+                igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum));
+            }
             VECTOR(degree)[to]++;
             VECTOR(edges)[edgeptr++] = i;
             VECTOR(edges)[edgeptr++] = to;
diff -pruN 0.9.6+ds-2/src/graph/adjlist.c 0.9.9+ds-1/src/graph/adjlist.c
--- 0.9.6+ds-2/src/graph/adjlist.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/graph/adjlist.c	2022-06-04 12:27:41.000000000 +0000
@@ -297,9 +297,7 @@ int igraph_adjlist_init_complementer(con
 void igraph_adjlist_destroy(igraph_adjlist_t *al) {
     long int i;
     for (i = 0; i < al->length; i++) {
-        if (&al->adjs[i]) {
-            igraph_vector_int_destroy(&al->adjs[i]);
-        }
+        igraph_vector_int_destroy(&al->adjs[i]);
     }
     IGRAPH_FREE(al->adjs);
 }
diff -pruN 0.9.6+ds-2/src/graph/cattributes.c 0.9.9+ds-1/src/graph/cattributes.c
--- 0.9.6+ds-2/src/graph/cattributes.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/graph/cattributes.c	2022-06-04 12:27:41.000000000 +0000
@@ -2313,6 +2313,9 @@ static int igraph_i_cattribute_get_numer
     }
 
     rec = VECTOR(*gal)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) {
+        IGRAPH_ERROR("Numeric graph attribute expected.", IGRAPH_EINVAL);
+    }
     num = (igraph_vector_t*)rec->value;
     IGRAPH_CHECK(igraph_vector_resize(value, 1));
     VECTOR(*value)[0] = VECTOR(*num)[0];
@@ -2335,6 +2338,9 @@ static int igraph_i_cattribute_get_bool_
     }
 
     rec = VECTOR(*gal)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) {
+        IGRAPH_ERROR("Boolean graph attribute expected.", IGRAPH_EINVAL);
+    }
     log = (igraph_vector_bool_t*)rec->value;
     IGRAPH_CHECK(igraph_vector_bool_resize(value, 1));
     VECTOR(*value)[0] = VECTOR(*log)[0];
@@ -2357,6 +2363,9 @@ static int igraph_i_cattribute_get_strin
     }
 
     rec = VECTOR(*gal)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_STRING) {
+        IGRAPH_ERROR("String graph attribute expected.", IGRAPH_EINVAL);
+    }
     str = (igraph_strvector_t*)rec->value;
     IGRAPH_CHECK(igraph_strvector_resize(value, 1));
     IGRAPH_CHECK(igraph_strvector_set(value, 0, STR(*str, 0)));
@@ -2380,6 +2389,9 @@ static int igraph_i_cattribute_get_numer
     }
 
     rec = VECTOR(*val)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) {
+        IGRAPH_ERROR("Numeric vertex attribute expected.", IGRAPH_EINVAL);
+    }
     num = (igraph_vector_t*)rec->value;
     if (igraph_vs_is_all(&vs)) {
         igraph_vector_clear(value);
@@ -2418,6 +2430,9 @@ static int igraph_i_cattribute_get_bool_
     }
 
     rec = VECTOR(*val)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) {
+        IGRAPH_ERROR("Boolean vertex attribute expected.", IGRAPH_EINVAL);
+    }
     log = (igraph_vector_bool_t*)rec->value;
     if (igraph_vs_is_all(&vs)) {
         igraph_vector_bool_clear(value);
@@ -2453,6 +2468,9 @@ static int igraph_i_cattribute_get_strin
     }
 
     rec = VECTOR(*val)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_STRING) {
+        IGRAPH_ERROR("String vertex attribute expected.", IGRAPH_EINVAL);
+    }
     str = (igraph_strvector_t*)rec->value;
     if (igraph_vs_is_all(&vs)) {
         igraph_strvector_resize(value, 0);
@@ -2492,6 +2510,9 @@ static int igraph_i_cattribute_get_numer
     }
 
     rec = VECTOR(*eal)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) {
+        IGRAPH_ERROR("Numeric edge attribute expected.", IGRAPH_EINVAL);
+    }
     num = (igraph_vector_t*)rec->value;
     if (igraph_es_is_all(&es)) {
         igraph_vector_clear(value);
@@ -2529,6 +2550,9 @@ static int igraph_i_cattribute_get_strin
     }
 
     rec = VECTOR(*eal)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_STRING) {
+        IGRAPH_ERROR("String edge attribute expected.", IGRAPH_EINVAL);
+    }
     str = (igraph_strvector_t*)rec->value;
     if (igraph_es_is_all(&es)) {
         igraph_strvector_resize(value, 0);
@@ -2568,6 +2592,9 @@ static int igraph_i_cattribute_get_bool_
     }
 
     rec = VECTOR(*eal)[j];
+    if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) {
+        IGRAPH_ERROR("Boolean edge attribute expected.", IGRAPH_EINVAL);
+    }
     log = (igraph_vector_bool_t*)rec->value;
     if (igraph_es_is_all(&es)) {
         igraph_vector_bool_clear(value);
@@ -2632,19 +2659,23 @@ const igraph_attribute_table_t igraph_ca
  * attributes and an active attribute handler, which might cause
  * unexpected program behaviour. The rule is that you attach the
  * attribute handler in the beginning of your
- * <function>main()</function> and never touch it again. (Detaching
- * the attribute handler might lead to memory leaks.)</para>
+ * <function>main()</function> and never touch it again. Detaching
+ * the attribute handler might lead to memory leaks.</para>
  *
  * <para>It is not currently possible to have attribute handlers on a
  * per-graph basis. All graphs in an application must be managed with
- * the same attribute handler. (Including the default case when there
- * is no attribute handler at all.</para>
+ * the same attribute handler. This also applies to the default case
+ * when there is no attribute handler at all.</para>
+ *
+ * <para>The C attribute handler supports attaching real numbers, boolean
+ * values and character strings as attributes. No vector values are allowed.
+ * For example, vertices have a <code>name</code> attribute holding a single
+ * string value for each vertex, but it is not possible to have a <code>coords</code>
+ * attribute which is a vector of numbers per vertex.</para>
  *
- * <para>The C attribute handler supports attaching real numbers and
- * character strings as attributes. No vectors are allowed, i.e. every
- * vertex might have an attribute called <code>name</code>, but it is
- * not possible to have a <code>coords</code> graph (or other)
- * attribute which is a vector of numbers.</para>
+ * <para>The functions documented in this section are specific to the C
+ * attribute handler. Code using these functions will not function when
+ * a different attribute handler is attached.</para>
  *
  * \example examples/simple/cattributes.c
  * \example examples/simple/cattributes2.c
diff -pruN 0.9.6+ds-2/src/graph/visitors.c 0.9.9+ds-1/src/graph/visitors.c
--- 0.9.6+ds-2/src/graph/visitors.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/graph/visitors.c	2022-06-04 12:27:41.000000000 +0000
@@ -122,7 +122,7 @@ int igraph_bfs(const igraph_t *graph,
         IGRAPH_ERROR("Invalid root vertex in BFS", IGRAPH_EINVAL);
     }
 
-    if (roots) {
+    if (roots && noroots > 0) {
         igraph_real_t min, max;
         igraph_vector_minmax(roots, &min, &max);
         if (min < 0 || max >= no_of_nodes) {
@@ -130,7 +130,7 @@ int igraph_bfs(const igraph_t *graph,
         }
     }
 
-    if (restricted) {
+    if (restricted && igraph_vector_size(restricted) > 0) {
         igraph_real_t min, max;
         igraph_vector_minmax(restricted, &min, &max);
         if (min < 0 || max >= no_of_nodes) {
diff -pruN 0.9.6+ds-2/src/hrg/hrg.cc 0.9.9+ds-1/src/hrg/hrg.cc
--- 0.9.6+ds-2/src/hrg/hrg.cc	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/hrg/hrg.cc	2022-06-04 12:27:41.000000000 +0000
@@ -252,6 +252,18 @@ static int igraph_i_hrg_getsimplegraph(c
     return 0;
 }
 
+static void igraph_i_delete_dendrogram(dendro* d) {
+    delete d;
+}
+
+static void igraph_i_delete_simple_graph(simpleGraph* g) {
+    delete g;
+}
+
+static void igraph_i_clear_pblock_array(pblock* arr) {
+    delete [] arr;
+}
+
 /**
  * \function igraph_hrg_init
  * Allocate memory for a HRG.
@@ -378,16 +390,16 @@ int igraph_hrg_fit(const igraph_t *graph
     RNG_BEGIN();
 
     d = new dendro;
+    IGRAPH_FINALLY(igraph_i_delete_dendrogram, d);
 
     // If we want to start from HRG
     if (start) {
-        d->clearDendrograph();
         if (igraph_hrg_size(hrg) != no_of_nodes) {
-            delete d;
             IGRAPH_ERROR("Invalid HRG to start from", IGRAPH_EINVAL);
         }
         // Convert the igraph graph
         IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d));
+        d->clearDendrograph();
         d->importDendrogramStructure(hrg);
     } else {
         // Convert the igraph graph
@@ -403,6 +415,7 @@ int igraph_hrg_fit(const igraph_t *graph
     }
 
     delete d;
+    IGRAPH_FINALLY_CLEAN(1);
 
     RNG_END();
 
@@ -488,6 +501,7 @@ int igraph_hrg_sample(const igraph_t *in
     RNG_BEGIN();
 
     d = new dendro;
+    IGRAPH_FINALLY(igraph_i_delete_dendrogram, d);
 
     // Need to find equilibrium first?
     if (start) {
@@ -527,6 +541,7 @@ int igraph_hrg_sample(const igraph_t *in
     }
 
     delete d;
+    IGRAPH_FINALLY_CLEAN(1);
 
     RNG_END();
 
@@ -575,7 +590,7 @@ int igraph_hrg_dendrogram(igraph_t *grap
 
     int orig_nodes = igraph_hrg_size(hrg);
     int no_of_nodes = orig_nodes * 2 - 1;
-    int no_of_edges = no_of_nodes - 1;
+    int no_of_edges = no_of_nodes > 0 ? no_of_nodes - 1 : 0;
     igraph_vector_t edges;
     int i, idx = 0;
     igraph_vector_ptr_t vattrs;
@@ -666,15 +681,16 @@ int igraph_hrg_consensus(const igraph_t
     RNG_BEGIN();
 
     d = new dendro;
+    IGRAPH_FINALLY(igraph_i_delete_dendrogram, d);
 
     if (start) {
-        d->clearDendrograph();
         IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d));
+        d->clearDendrograph();
         d->importDendrogramStructure(hrg);
     } else {
         IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d));
         if (hrg) {
-            igraph_hrg_resize(hrg, igraph_vcount(graph));
+            IGRAPH_CHECK(igraph_hrg_resize(hrg, igraph_vcount(graph)));
         }
         IGRAPH_CHECK(MCMCEquilibrium_Find(d, hrg));
     }
@@ -684,6 +700,7 @@ int igraph_hrg_consensus(const igraph_t
     d->recordConsensusTree(parents, weights);
 
     delete d;
+    IGRAPH_FINALLY_CLEAN(1);
 
     RNG_END();
 
@@ -859,8 +876,10 @@ int igraph_hrg_predict(const igraph_t *g
     RNG_BEGIN();
 
     d = new dendro;
+    IGRAPH_FINALLY(igraph_i_delete_dendrogram, d);
 
     IGRAPH_CHECK(igraph_i_hrg_getsimplegraph(graph, d, &sg, num_bins));
+    IGRAPH_FINALLY(igraph_i_delete_simple_graph, sg);
 
     mk = sg->getNumNodes() * (sg->getNumNodes() - 1) / 2 - sg->getNumLinks() / 2;
     br_list = new pblock[mk];
@@ -869,15 +888,14 @@ int igraph_hrg_predict(const igraph_t *g
         br_list[i].i = -1;
         br_list[i].j = -1;
     }
+    IGRAPH_FINALLY(igraph_i_clear_pblock_array, br_list);
 
     if (start) {
         d->clearDendrograph();
-        // this has cleared the graph as well.... bug?
-        IGRAPH_CHECK(igraph_i_hrg_getsimplegraph(graph, d, &sg, num_bins));
         d->importDendrogramStructure(hrg);
     } else {
         if (hrg) {
-            igraph_hrg_resize(hrg, igraph_vcount(graph));
+            IGRAPH_CHECK(igraph_hrg_resize(hrg, igraph_vcount(graph)));
         }
         IGRAPH_CHECK(MCMCEquilibrium_Find(d, hrg));
     }
@@ -889,6 +907,7 @@ int igraph_hrg_predict(const igraph_t *g
     delete d;
     delete sg;
     delete [] br_list;
+    IGRAPH_FINALLY_CLEAN(3);
 
     RNG_END();
 
@@ -917,7 +936,7 @@ int igraph_hrg_create(igraph_hrg_t *hrg,
                       const igraph_vector_t *prob) {
 
     int no_of_nodes = igraph_vcount(graph);
-    int no_of_internal = (no_of_nodes - 1) / 2;
+    int no_of_internal = no_of_nodes > 0 ? (no_of_nodes - 1) / 2 : 0;
     igraph_vector_t deg, idx;
     int root = 0;
     int d0 = 0, d1 = 0, d2 = 0;
@@ -1021,7 +1040,7 @@ int igraph_hrg_create(igraph_hrg_t *hrg,
         }
     }
 
-    igraph_hrg_resize(hrg, no_of_internal + 1);
+    IGRAPH_CHECK(igraph_hrg_resize(hrg, no_of_internal + 1));
     IGRAPH_VECTOR_INIT_FINALLY(&neis, 0);
     for (int i = 0; i < no_of_nodes; i++) {
         int ri = VECTOR(idx)[i];
diff -pruN 0.9.6+ds-2/src/hrg/hrg_types.cc 0.9.9+ds-1/src/hrg/hrg_types.cc
--- 0.9.6+ds-2/src/hrg/hrg_types.cc	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/hrg/hrg_types.cc	2022-06-04 12:27:41.000000000 +0000
@@ -1124,10 +1124,6 @@ void dendro::clearDendrograph() {
     // importDendrogramStructure call so as to avoid memory leaks and
     // overwriting the references therein.
 
-    if (g        != NULL) {
-        delete    g;           // O(m)
-        g        = NULL;
-    }
     if (leaf     != NULL) {
         delete [] leaf;        // O(n)
         leaf     = NULL;
diff -pruN 0.9.6+ds-2/src/internal/glpk_support.c 0.9.9+ds-1/src/internal/glpk_support.c
--- 0.9.6+ds-2/src/internal/glpk_support.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/internal/glpk_support.c	2022-06-04 12:27:41.000000000 +0000
@@ -34,6 +34,16 @@
 
 IGRAPH_THREAD_LOCAL igraph_i_glpk_error_info_t igraph_i_glpk_error_info;
 
+/* glp_at_error() was added in GLPK 4.57. Due to the R interface, we need to
+ * support ancient GLPK versions like GLPK 4.38 so we need to guard the
+ * invocation of glp_at_error(). Note that this is a temporary workaround only
+ * for sake of supporting R 4.1, so it is enabled only if USING_R is defined */
+#ifdef USING_R
+#  define HAS_GLP_AT_ERROR (GLP_MAJOR_VERSION > 4 || (GLP_MAJOR_VERSION == 4 && GLP_MINOR_VERSION >= 57))
+#else
+#  define HAS_GLP_AT_ERROR 1
+#endif
+
 int igraph_i_glpk_terminal_hook(void *info, const char *s) {
     IGRAPH_UNUSED(info);
 
@@ -45,6 +55,7 @@ int igraph_i_glpk_terminal_hook(void *in
            and the error_hook. */
         igraph_i_glpk_error_info.is_interrupted = 1;
         glp_error("GLPK was interrupted."); /* This dummy message is never printed */
+#if HAS_GLP_AT_ERROR
     } else if (glp_at_error()) {
         /* Copy the error messages into a buffer for later reporting */
         /* We must use glp_at_error() instead of igraph_i_glpk_error_info.is_error
@@ -55,6 +66,7 @@ int igraph_i_glpk_terminal_hook(void *in
             *(igraph_i_glpk_error_info.msg_ptr++) = *(s++);
         }
         *igraph_i_glpk_error_info.msg_ptr = '\0';
+#endif
     }
 
     return 1; /* Non-zero return value signals to GLPK not to print to the terminal */
diff -pruN 0.9.6+ds-2/src/internal/glpk_support.h 0.9.9+ds-1/src/internal/glpk_support.h
--- 0.9.6+ds-2/src/internal/glpk_support.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/internal/glpk_support.h	2022-06-04 12:27:41.000000000 +0000
@@ -126,6 +126,10 @@ void igraph_i_glp_delete_prob(glp_prob *
                     } \
                     *igraph_i_glpk_error_info.msg_ptr = '\0'; \
                     igraph_error(igraph_i_glpk_error_info.msg, IGRAPH_FILE_BASENAME, __LINE__, IGRAPH_EGLP); \
+                } else if (igraph_i_glpk_error_info.is_error) { \
+                    /* This branch can never be reached unless compiled with USING_R and using */ \
+                    /* the hack to support pre-4.57 GLPK versions. See comments in glpk_support.c. */ \
+                    igraph_error("Error while running GLPK solver.", IGRAPH_FILE_BASENAME, __LINE__, IGRAPH_EGLP); \
                 } \
                 return IGRAPH_EGLP; \
             } \
diff -pruN 0.9.6+ds-2/src/io/dot.c 0.9.9+ds-1/src/io/dot.c
--- 0.9.6+ds-2/src/io/dot.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/dot.c	2022-06-04 12:27:41.000000000 +0000
@@ -41,10 +41,10 @@ static int igraph_i_dot_escape(const cha
     long int i, j, len = (long int) strlen(orig), newlen = 0;
     igraph_bool_t need_quote = 0, is_number = 1;
 
-    /* first, check whether the string is equal to some reserved word */
+    /* first, check whether the string is equal to some reserved word, or empty */
     if (!strcasecmp(orig, "graph") || !strcasecmp(orig, "digraph") ||
         !strcasecmp(orig, "node") || !strcasecmp(orig, "edge") ||
-        !strcasecmp(orig, "strict") || !strcasecmp(orig, "subgraph")) {
+        !strcasecmp(orig, "strict") || !strcasecmp(orig, "subgraph") || len == 0) {
         need_quote = 1;
         is_number = 0;
     }
@@ -73,7 +73,7 @@ static int igraph_i_dot_escape(const cha
             is_number = 0; need_quote = 1; newlen++;
         }
     }
-    if (is_number && orig[len - 1] == '.') {
+    if (is_number && len > 0 && orig[len - 1] == '.') {
         is_number = 0;
     }
     if (!is_number && isdigit(orig[0])) {
@@ -93,6 +93,11 @@ static int igraph_i_dot_escape(const cha
         (*result)[0] = '"';
         (*result)[newlen + 1] = '"';
         (*result)[newlen + 2] = '\0';
+        /* Escape quotes, backslashes and newlines.
+         * Even though the format spec at https://graphviz.org/doc/info/lang.html
+         * claims that only quotes need escaping, escaping backslashes appears to
+         * be necessary as well for GraphViz to render labels correctly.
+         * Tested with GraphViz 2.50. */
         for (i = 0, j = 1; i < len; i++) {
             if (orig[i] == '\n') {
                 (*result)[j++] = '\\';
@@ -117,9 +122,8 @@ static int igraph_i_dot_escape(const cha
  * http://www.graphviz.org for details. The grammar of the DOT format
  * can be found here: http://www.graphviz.org/doc/info/lang.html
  *
- * </para><para>This is only a preliminary implementation, only the vertices
- * and the edges are written but not the attributes or any visualization
- * information.
+ * </para><para>This is only a preliminary implementation, no visualization
+ * information is written.
  *
  * \param graph The graph to write to the stream.
  * \param outstream The stream to write the file to.
@@ -216,9 +220,9 @@ int igraph_write_graph_dot(const igraph_
                 IGRAPH_CHECK(igraph_i_dot_escape(name, &newname));
                 IGRAPH_FINALLY(igraph_free, newname);
                 if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) {
-                    IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, igraph_vss_1((igraph_integer_t) i), &numv));
-                    if (VECTOR(numv)[0] == (long)VECTOR(numv)[0]) {
-                        CHECK(fprintf(outstream, "    %s=%ld\n", newname, (long)VECTOR(numv)[0]));
+                    IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, igraph_vss_1(i), &numv));
+                    if (VECTOR(numv)[0] == floor(VECTOR(numv)[0])) {
+                        CHECK(fprintf(outstream, "    %s=%g\n", newname, VECTOR(numv)[0]));
                     } else {
                         CHECK(fprintf(outstream, "    %s=", newname));
                         CHECK(igraph_real_fprintf_precise(outstream,
@@ -264,9 +268,9 @@ int igraph_write_graph_dot(const igraph_
                 IGRAPH_FINALLY(igraph_free, newname);
                 if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) {
                     IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph,
-                                 name, igraph_ess_1((igraph_integer_t) i), &numv));
-                    if (VECTOR(numv)[0] == (long)VECTOR(numv)[0]) {
-                        CHECK(fprintf(outstream, "    %s=%ld\n", newname, (long)VECTOR(numv)[0]));
+                                 name, igraph_ess_1(i), &numv));
+                    if (VECTOR(numv)[0] == floor(VECTOR(numv)[0])) {
+                        CHECK(fprintf(outstream, "    %s=%g\n", newname, VECTOR(numv)[0]));
                     } else {
                         CHECK(fprintf(outstream, "    %s=", newname));
                         CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]));
diff -pruN 0.9.6+ds-2/src/io/edgelist.c 0.9.9+ds-1/src/io/edgelist.c
--- 0.9.6+ds-2/src/io/edgelist.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/edgelist.c	2022-06-04 12:27:41.000000000 +0000
@@ -36,6 +36,9 @@
  * <para>These functions can write a graph to a file, or read a graph
  * from a file.</para>
  *
+ * <para>They assume that the current locale uses a decimal point and not
+ * a decimal comma.</para>
+ *
  * <para>Note that as \a igraph uses the traditional C streams, it is
  * possible to read/write files from/to memory, at least on GNU
  * operating systems supporting \quote non-standard\endquote streams.</para>
diff -pruN 0.9.6+ds-2/src/io/graphml.c 0.9.9+ds-1/src/io/graphml.c
--- 0.9.6+ds-2/src/io/graphml.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/graphml.c	2022-06-04 12:27:41.000000000 +0000
@@ -33,7 +33,6 @@
 
 #include "config.h"
 
-#include <locale.h>
 #include <math.h>    /* isnan */
 #include <string.h>
 #include <stdarg.h>  /* va_start & co */
@@ -1344,6 +1343,8 @@ int igraph_read_graph_graphml(igraph_t *
 
 #if HAVE_LIBXML == 1
     xmlParserCtxtPtr ctxt;
+    xmlDocPtr doc;
+
     struct igraph_i_graphml_parser_state state;
     int res;
     char buffer[4096];
@@ -1359,7 +1360,10 @@ int igraph_read_graph_graphml(igraph_t *
     IGRAPH_FINALLY(igraph_i_graphml_parser_state_destroy, &state);
 
     /* Create a progressive parser context */
-    res = (int) fread(buffer, 1, 4096, instream);
+    res = (int) fread(buffer, 1, sizeof(buffer), instream);
+    if (res < sizeof(buffer) && !feof(instream)) {
+        IGRAPH_ERROR("IO error while reading GraphML data", IGRAPH_PARSEERROR);
+    }
     ctxt = xmlCreatePushParserCtxt(&igraph_i_graphml_sax_handler,
                                    &state,
                                    buffer,
@@ -1392,7 +1396,7 @@ int igraph_read_graph_graphml(igraph_t *
     IGRAPH_FINALLY_CLEAN(1);
 
     /* Do the parsing */
-    while ((res = (int) fread(buffer, 1, 4096, instream)) > 0) {
+    while ((res = (int) fread(buffer, 1, sizeof(buffer), instream)) > 0) {
         xmlParseChunk(ctxt, buffer, res, 0);
         if (!state.successful) {
             break;
@@ -1401,7 +1405,13 @@ int igraph_read_graph_graphml(igraph_t *
     xmlParseChunk(ctxt, buffer, res, 1);
 
     /* Free the context */
+    doc = ctxt->myDoc;
     xmlFreeParserCtxt(ctxt);
+    if (doc) {
+        /* In theory this should not be necessary, but it looks like certain malformed
+         * GraphML files leave a partially-parsed doc in memory */
+        xmlFreeDoc(doc);
+    }
 
     /* Extract the error message from the parser state (if any), and make a
      * copy so we can safely destroy the parser state before triggering the
@@ -1491,14 +1501,6 @@ int igraph_write_graph_graphml(const igr
     const char *vprefix = prefixattr ? "v_" : "";
     const char *eprefix = prefixattr ? "e_" : "";
 
-    /* set standard C locale lest we sometimes get commas instead of dots */
-    char *saved_locale = strdup(setlocale(LC_NUMERIC, NULL));
-    if (saved_locale == NULL) {
-        IGRAPH_ERROR("Not enough memory", IGRAPH_ENOMEM);
-    }
-    IGRAPH_FINALLY(igraph_free, saved_locale);
-    setlocale(LC_NUMERIC, "C");
-
     ret = fprintf(outstream, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
     if (ret < 0) {
         IGRAPH_ERROR("Write failed", IGRAPH_EFILE);
@@ -1831,10 +1833,6 @@ int igraph_write_graph_graphml(const igr
         IGRAPH_ERROR("Write failed", IGRAPH_EFILE);
     }
 
-    /* reset locale to whatever was before this function */
-    setlocale(LC_NUMERIC, saved_locale);
-
-    igraph_free(saved_locale);
     igraph_strvector_destroy(&gnames);
     igraph_strvector_destroy(&vnames);
     igraph_strvector_destroy(&enames);
@@ -1844,7 +1842,7 @@ int igraph_write_graph_graphml(const igr
     igraph_vector_destroy(&numv);
     igraph_strvector_destroy(&strv);
     igraph_vector_bool_destroy(&boolv);
-    IGRAPH_FINALLY_CLEAN(10);
+    IGRAPH_FINALLY_CLEAN(9);
 
     return 0;
 }
diff -pruN 0.9.6+ds-2/src/io/lgl.c 0.9.9+ds-1/src/io/lgl.c
--- 0.9.6+ds-2/src/io/lgl.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/lgl.c	2022-06-04 12:27:41.000000000 +0000
@@ -192,14 +192,16 @@ int igraph_read_graph_lgl(igraph_t *grap
  * Note that having multiple or loop edges in an
  * <code>.lgl</code> file breaks the  LGL software but \a igraph
  * does not check for this condition.
+ *
  * \param graph The graph to write.
  * \param outstream The stream object to write to, it should be
  *        writable.
- * \param names The name of the vertex attribute, if symbolic names
- *        are written to the file. If not supply 0 here.
- * \param weights The name of the edge attribute, if they are also
- *        written to the file. If you don't want weights supply 0
- *        here.
+ * \param names The name of a string vertex attribute, if symbolic names
+ *        are to be written to the file. Supply \c NULL to write vertex
+ *        ids instead.
+ * \param weights The name of a numerical edge attribute, which will be
+ *        written as weights to the file. Supply \c NULL to skip writing
+ *        edge weights.
  * \param isolates Logical, if TRUE isolated vertices are also written
  *        to the file. If FALSE they will be omitted.
  * \return Error code:
@@ -236,7 +238,7 @@ int igraph_write_graph_lgl(const igraph_
     if (names) {
         IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &nametype,
                                                 IGRAPH_ATTRIBUTE_VERTEX, names));
-        if (nametype != IGRAPH_ATTRIBUTE_NUMERIC && nametype != IGRAPH_ATTRIBUTE_STRING) {
+        if (nametype != IGRAPH_ATTRIBUTE_STRING) {
             IGRAPH_WARNING("ignoring names attribute, unknown attribute type");
             names = 0;
         }
@@ -251,7 +253,7 @@ int igraph_write_graph_lgl(const igraph_
     if (weights) {
         IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &weighttype,
                                                 IGRAPH_ATTRIBUTE_EDGE, weights));
-        if (weighttype != IGRAPH_ATTRIBUTE_NUMERIC && weighttype != IGRAPH_ATTRIBUTE_STRING) {
+        if (weighttype != IGRAPH_ATTRIBUTE_NUMERIC) {
             IGRAPH_WARNING("ignoring weights attribute, unknown attribute type");
             weights = 0;
         }
@@ -304,41 +306,40 @@ int igraph_write_graph_lgl(const igraph_
         }
         IGRAPH_FINALLY_CLEAN(1);
     } else if (names == 0) {
-        igraph_strvector_t wvec;
-        IGRAPH_CHECK(igraph_strvector_init(&wvec, igraph_ecount(graph)));
-        IGRAPH_FINALLY(igraph_strvector_destroy, &wvec);
-        IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, weights,
+        /* No names but weights */
+        igraph_vector_t wvec;
+        IGRAPH_VECTOR_INIT_FINALLY(&wvec, igraph_ecount(graph));
+        IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights,
                      igraph_ess_all(IGRAPH_EDGEORDER_ID),
                      &wvec));
-        /* No names but weights */
         while (!IGRAPH_EIT_END(it)) {
             igraph_integer_t edge = IGRAPH_EIT_GET(it);
             igraph_integer_t from, to;
-            int ret = 0;
-            char *str1;
+            int ret1, ret2, ret3;
             igraph_edge(graph, edge, &from, &to);
-            igraph_strvector_get(&wvec, edge, &str1);
             if (from == actvertex) {
-                ret = fprintf(outstream, "%li %s\n", (long)to, str1);
+                ret1 = fprintf(outstream, "%li ", (long)to);
             } else {
                 actvertex = from;
-                ret = fprintf(outstream, "# %li\n%li %s\n", (long)from, (long)to, str1);
+                ret1 = fprintf(outstream, "# %li\n%li ", (long)from, (long)to);
             }
-            if (ret < 0) {
+            ret2 = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[(long int)edge]);
+            ret3 = fputc('\n', outstream);
+            if (ret1 < 0 || ret2 < 0 || ret3 == EOF) {
                 IGRAPH_ERROR("Write failed", IGRAPH_EFILE);
             }
             IGRAPH_EIT_NEXT(it);
         }
-        igraph_strvector_destroy(&wvec);
+        igraph_vector_destroy(&wvec);
         IGRAPH_FINALLY_CLEAN(1);
     } else {
         /* Both names and weights */
-        igraph_strvector_t nvec, wvec;
-        IGRAPH_CHECK(igraph_strvector_init(&wvec, igraph_ecount(graph)));
-        IGRAPH_FINALLY(igraph_strvector_destroy, &wvec);
+        igraph_strvector_t nvec;
+        igraph_vector_t wvec;
+        IGRAPH_VECTOR_INIT_FINALLY(&wvec, igraph_ecount(graph));
         IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph)));
         IGRAPH_FINALLY(igraph_strvector_destroy, &nvec);
-        IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, weights,
+        IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights,
                      igraph_ess_all(IGRAPH_EDGEORDER_ID),
                      &wvec));
         IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names,
@@ -347,11 +348,10 @@ int igraph_write_graph_lgl(const igraph_
         while (!IGRAPH_EIT_END(it)) {
             igraph_integer_t edge = IGRAPH_EIT_GET(it);
             igraph_integer_t from, to;
-            int ret = 0;
-            char *str1, *str2, *str3;
+            int ret = 0, ret2;
+            char *str1, *str2;
             igraph_edge(graph, edge, &from, &to);
             igraph_strvector_get(&nvec, to, &str2);
-            igraph_strvector_get(&wvec, edge, &str3);
             if (from == actvertex) {
                 ret = fprintf(outstream, "%s ", str2);
             } else {
@@ -362,14 +362,15 @@ int igraph_write_graph_lgl(const igraph_
             if (ret < 0) {
                 IGRAPH_ERROR("Write failed", IGRAPH_EFILE);
             }
-            ret = fprintf(outstream, "%s\n", str3);
-            if (ret < 0) {
+            ret = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[(long int)edge]);
+            ret2 = fputc('\n', outstream);
+            if (ret < 0 || ret2 == EOF) {
                 IGRAPH_ERROR("Write failed", IGRAPH_EFILE);
             }
             IGRAPH_EIT_NEXT(it);
         }
         igraph_strvector_destroy(&nvec);
-        igraph_strvector_destroy(&wvec);
+        igraph_vector_destroy(&wvec);
         IGRAPH_FINALLY_CLEAN(2);
     }
 
diff -pruN 0.9.6+ds-2/src/io/ncol.c 0.9.9+ds-1/src/io/ncol.c
--- 0.9.6+ds-2/src/io/ncol.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/ncol.c	2022-06-04 12:27:41.000000000 +0000
@@ -38,7 +38,7 @@ void igraph_ncol_yyset_in  (FILE * in_st
 /**
  * \ingroup loadsave
  * \function igraph_read_graph_ncol
- * \brief Reads a <code>.ncol</code> file used by LGL.
+ * \brief Reads an <code>.ncol</code> file used by LGL.
  *
  * Also useful for creating graphs from \quote named\endquote (and
  * optionally weighted) edge lists.
@@ -48,8 +48,8 @@ void igraph_ncol_yyset_in  (FILE * in_st
  * (http://lgl.sourceforge.net), and it is simply a
  * symbolic weighted edge list. It is a simple text file with one edge
  * per line. An edge is defined by two symbolic vertex names separated
- * by whitespace. (The symbolic vertex names themselves cannot contain
- * whitespace. They might follow by an optional number, this will be
+ * by whitespace. The vertex names themselves cannot contain
+ * whitespace. They may be followed by an optional number,
  * the weight of the edge; the number can be negative and can be in
  * scientific notation. If there is no weight specified to an edge it
  * is assumed to be zero.
@@ -59,13 +59,14 @@ void igraph_ncol_yyset_in  (FILE * in_st
  * LGL cannot deal with files which contain multiple or loop edges,
  * this is however not checked here, as \a igraph is happy with
  * these.
+ *
  * \param graph Pointer to an uninitialized graph object.
  * \param instream Pointer to a stream, it should be readable.
  * \param predefnames Pointer to the symbolic names of the vertices in
  *        the file. If \c NULL is given here then vertex ids will be
  *        assigned to vertex names in the order of their appearance in
- *        the \c .ncol file. If it is not \c NULL and some unknown
- *        vertex names are found in the \c .ncol file then new vertex
+ *        the <code>.ncol</code> file. If it is not \c NULL and some unknown
+ *        vertex names are found in the <code>.ncol</code> file then new vertex
  *        ids will be assigned to them.
  * \param names Logical value, if TRUE the symbolic names of the
  *        vertices will be added to the graph as a vertex attribute
@@ -206,7 +207,7 @@ int igraph_read_graph_ncol(igraph_t *gra
 /**
  * \ingroup loadsave
  * \function igraph_write_graph_ncol
- * \brief Writes the graph to a file in <code>.ncol</code> format
+ * \brief Writes the graph to a file in <code>.ncol</code> format.
  *
  * </para><para>
  * <code>.ncol</code> is a format used by LGL, see \ref
@@ -216,14 +217,19 @@ int igraph_read_graph_ncol(igraph_t *gra
  * Note that having multiple or loop edges in an
  * <code>.ncol</code> file breaks the  LGL software but
  * \a igraph does not check for this condition.
+ *
+ * </para><para>
+ * This format cannot represent zero-degree vertices.
+ *
  * \param graph The graph to write.
  * \param outstream The stream object to write to, it should be
  *        writable.
- * \param names The name of the vertex attribute, if symbolic names
- *        are written to the file. If not, supply 0 here.
- * \param weights The name of the edge attribute, if they are also
- *        written to the file. If you don't want weights, supply 0
- *        here.
+ * \param names The name of a string vertex attribute, if symbolic names
+ *        are to be written to the file. Supply \c NULL to write vertex
+ *        ids instead.
+ * \param weights The name of a numerical edge attribute, which will be
+ *        written as weights to the file. Supply \c NULL to skip writing
+ *        edge weights.
  * \return Error code:
  *         \c IGRAPH_EFILE if there is an error writing the
  *         file.
@@ -252,7 +258,7 @@ int igraph_write_graph_ncol(const igraph
     if (names) {
         IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &nametype,
                                                 IGRAPH_ATTRIBUTE_VERTEX, names));
-        if (nametype != IGRAPH_ATTRIBUTE_NUMERIC && nametype != IGRAPH_ATTRIBUTE_STRING) {
+        if (nametype != IGRAPH_ATTRIBUTE_STRING) {
             IGRAPH_WARNING("ignoring names attribute, unknown attribute type");
             names = 0;
         }
diff -pruN 0.9.6+ds-2/src/io/parsers/dl-parser.c 0.9.9+ds-1/src/io/parsers/dl-parser.c
--- 0.9.6+ds-2/src/io/parsers/dl-parser.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/dl-parser.c	2022-06-04 12:27:41.000000000 +0000
@@ -74,6 +74,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      NUM = 258,
      NEWLINE = 259,
      DL = 260,
@@ -91,6 +92,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define NUM 258
 #define NEWLINE 259
 #define DL 260
@@ -526,13 +528,13 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint16 yyrline[] =
 {
-       0,   108,   108,   110,   110,   112,   112,   114,   115,   116,
-     119,   119,   121,   121,   123,   124,   125,   128,   129,   135,
-     135,   140,   140,   142,   152,   154,   156,   156,   158,   162,
-     166,   171,   175,   177,   178,   179,   180,   181,   184,   185,
-     188,   190,   194,   197,   198,   201,   203,   207,   210,   226,
-     228,   229,   230,   231,   232,   235,   236,   239,   241,   244,
-     244,   250,   251,   254,   256,   260,   260
+       0,   109,   109,   111,   111,   113,   113,   115,   116,   117,
+     120,   120,   122,   122,   124,   125,   126,   129,   130,   136,
+     136,   141,   141,   143,   153,   155,   157,   157,   159,   163,
+     167,   172,   176,   178,   179,   180,   181,   182,   185,   186,
+     189,   191,   195,   198,   199,   202,   204,   208,   211,   227,
+     229,   230,   231,   232,   233,   236,   237,   240,   242,   245,
+     245,   251,   252,   255,   257,   261,   261
 };
 #endif
 
@@ -541,11 +543,11 @@ static const yytype_uint16 yyrline[] =
    First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
 static const char *const yytname[] =
 {
-  "$end", "error", "$undefined", "NUM", "NEWLINE", "DL", "NEQ", "DATA",
-  "LABELS", "LABELSEMBEDDED", "FORMATFULLMATRIX", "FORMATEDGELIST1",
-  "FORMATNODELIST1", "DIGIT", "LABEL", "EOFF", "ERROR", "$accept", "input",
-  "trail", "eof", "rest", "formfullmatrix", "newline", "fullmatrix",
-  "labels", "fullmatrixdata", "zerooneseq", "zeroone",
+  "\"end of file\"", "error", "$undefined", "NUM", "NEWLINE", "DL", "NEQ",
+  "DATA", "LABELS", "LABELSEMBEDDED", "FORMATFULLMATRIX",
+  "FORMATEDGELIST1", "FORMATNODELIST1", "DIGIT", "LABEL", "EOFF", "ERROR",
+  "$accept", "input", "trail", "eof", "rest", "formfullmatrix", "newline",
+  "fullmatrix", "labels", "fullmatrixdata", "zerooneseq", "zeroone",
   "labeledfullmatrixdata", "reallabeledfullmatrixdata", "labelseq",
   "label", "labeledmatrixlines", "labeledmatrixline", "edgelist1",
   "edgelist1rest", "edgelist1data", "edgelist1dataline", "integer",
diff -pruN 0.9.6+ds-2/src/io/parsers/dl-parser.h 0.9.9+ds-1/src/io/parsers/dl-parser.h
--- 0.9.6+ds-2/src/io/parsers/dl-parser.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/dl-parser.h	2022-06-04 12:27:41.000000000 +0000
@@ -39,6 +39,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      NUM = 258,
      NEWLINE = 259,
      DL = 260,
@@ -56,6 +57,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define NUM 258
 #define NEWLINE 259
 #define DL 260
diff -pruN 0.9.6+ds-2/src/io/parsers/gml-parser.c 0.9.9+ds-1/src/io/parsers/gml-parser.c
--- 0.9.6+ds-2/src/io/parsers/gml-parser.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/gml-parser.c	2022-06-04 12:27:41.000000000 +0000
@@ -74,6 +74,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      STRING = 258,
      NUM = 259,
      KEYWORD = 260,
@@ -84,6 +85,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define STRING 258
 #define NUM 259
 #define KEYWORD 260
@@ -498,8 +500,8 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint8 yyrline[] =
 {
-       0,   119,   119,   120,   123,   124,   126,   128,   130,   132,
-     136,   139,   142
+       0,   120,   120,   121,   124,   125,   127,   129,   131,   133,
+     137,   140,   143
 };
 #endif
 
@@ -508,9 +510,9 @@ static const yytype_uint8 yyrline[] =
    First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
 static const char *const yytname[] =
 {
-  "$end", "error", "$undefined", "STRING", "NUM", "KEYWORD", "LISTOPEN",
-  "LISTCLOSE", "EOFF", "ERROR", "$accept", "input", "list", "keyvalue",
-  "key", "num", "string", 0
+  "\"end of file\"", "error", "$undefined", "STRING", "NUM", "KEYWORD",
+  "LISTOPEN", "LISTCLOSE", "EOFF", "ERROR", "$accept", "input", "list",
+  "keyvalue", "key", "num", "string", 0
 };
 #endif
 
diff -pruN 0.9.6+ds-2/src/io/parsers/gml-parser.h 0.9.9+ds-1/src/io/parsers/gml-parser.h
--- 0.9.6+ds-2/src/io/parsers/gml-parser.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/gml-parser.h	2022-06-04 12:27:41.000000000 +0000
@@ -39,6 +39,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      STRING = 258,
      NUM = 259,
      KEYWORD = 260,
@@ -49,6 +50,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define STRING 258
 #define NUM 259
 #define KEYWORD 260
diff -pruN 0.9.6+ds-2/src/io/parsers/lgl-parser.c 0.9.9+ds-1/src/io/parsers/lgl-parser.c
--- 0.9.6+ds-2/src/io/parsers/lgl-parser.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/lgl-parser.c	2022-06-04 12:27:41.000000000 +0000
@@ -74,6 +74,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      ALNUM = 258,
      NEWLINE = 259,
      HASH = 260,
@@ -81,6 +82,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define ALNUM 258
 #define NEWLINE 259
 #define HASH 260
@@ -475,8 +477,8 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint8 yyrline[] =
 {
-       0,    93,    93,    94,    95,    98,   100,   102,   102,   104,
-     109,   118,   123
+       0,    94,    94,    95,    96,    99,   101,   103,   103,   105,
+     110,   119,   124
 };
 #endif
 
@@ -485,9 +487,9 @@ static const yytype_uint8 yyrline[] =
    First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
 static const char *const yytname[] =
 {
-  "$end", "error", "$undefined", "ALNUM", "NEWLINE", "HASH", "ERROR",
-  "$accept", "input", "vertex", "vertexdef", "edges", "edge", "edgeid",
-  "weight", 0
+  "\"end of file\"", "error", "$undefined", "ALNUM", "NEWLINE", "\"#\"",
+  "ERROR", "$accept", "input", "vertex", "vertexdef", "edges", "edge",
+  "edgeid", "weight", 0
 };
 #endif
 
diff -pruN 0.9.6+ds-2/src/io/parsers/lgl-parser.h 0.9.9+ds-1/src/io/parsers/lgl-parser.h
--- 0.9.6+ds-2/src/io/parsers/lgl-parser.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/lgl-parser.h	2022-06-04 12:27:41.000000000 +0000
@@ -39,6 +39,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      ALNUM = 258,
      NEWLINE = 259,
      HASH = 260,
@@ -46,6 +47,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define ALNUM 258
 #define NEWLINE 259
 #define HASH 260
diff -pruN 0.9.6+ds-2/src/io/parsers/ncol-parser.c 0.9.9+ds-1/src/io/parsers/ncol-parser.c
--- 0.9.6+ds-2/src/io/parsers/ncol-parser.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/ncol-parser.c	2022-06-04 12:27:41.000000000 +0000
@@ -74,12 +74,14 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      ALNUM = 258,
      NEWLINE = 259,
      ERROR = 260
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define ALNUM 258
 #define NEWLINE 259
 #define ERROR 260
@@ -472,7 +474,7 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint8 yyrline[] =
 {
-       0,    93,    93,    94,    95,    98,   103,   111,   116
+       0,    94,    94,    95,    96,    99,   104,   112,   117
 };
 #endif
 
@@ -481,8 +483,8 @@ static const yytype_uint8 yyrline[] =
    First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
 static const char *const yytname[] =
 {
-  "$end", "error", "$undefined", "ALNUM", "NEWLINE", "ERROR", "$accept",
-  "input", "edge", "edgeid", "weight", 0
+  "\"end of file\"", "error", "$undefined", "ALNUM", "NEWLINE", "ERROR",
+  "$accept", "input", "edge", "edgeid", "weight", 0
 };
 #endif
 
diff -pruN 0.9.6+ds-2/src/io/parsers/ncol-parser.h 0.9.9+ds-1/src/io/parsers/ncol-parser.h
--- 0.9.6+ds-2/src/io/parsers/ncol-parser.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/ncol-parser.h	2022-06-04 12:27:41.000000000 +0000
@@ -39,12 +39,14 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      ALNUM = 258,
      NEWLINE = 259,
      ERROR = 260
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define ALNUM 258
 #define NEWLINE 259
 #define ERROR 260
diff -pruN 0.9.6+ds-2/src/io/parsers/pajek-parser.c 0.9.9+ds-1/src/io/parsers/pajek-parser.c
--- 0.9.6+ds-2/src/io/parsers/pajek-parser.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/pajek-parser.c	2022-06-04 12:27:41.000000000 +0000
@@ -74,6 +74,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      NEWLINE = 258,
      NUM = 259,
      ALNUM = 260,
@@ -126,6 +127,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define NEWLINE 258
 #define NUM 259
 #define ALNUM 260
@@ -655,20 +657,20 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint16 yyrline[] =
 {
-       0,   189,   189,   193,   193,   195,   197,   201,   207,   207,
-     209,   210,   211,   211,   214,   216,   221,   222,   226,   232,
-     232,   236,   236,   239,   240,   243,   246,   251,   256,   261,
-     264,   267,   270,   273,   276,   279,   282,   285,   290,   290,
-     294,   294,   298,   298,   302,   302,   307,   307,   314,   316,
-     316,   316,   316,   316,   316,   318,   319,   321,   321,   323,
-     324,   324,   330,   332,   334,   335,   337,   337,   339,   340,
-     340,   346,   348,   350,   350,   354,   354,   357,   358,   363,
-     366,   369,   372,   375,   378,   381,   384,   387,   390,   393,
-     396,   399,   402,   405,   410,   410,   414,   414,   418,   418,
-     422,   422,   426,   426,   432,   434,   436,   436,   438,   438,
-     440,   440,   442,   444,   449,   451,   451,   453,   453,   455,
-     455,   457,   459,   466,   468,   473,   473,   475,   477,   477,
-     479,   499,   502,   505,   505,   507,   509,   511
+       0,   190,   190,   194,   194,   196,   198,   202,   208,   208,
+     210,   211,   212,   212,   215,   217,   222,   223,   227,   233,
+     233,   237,   237,   240,   241,   244,   247,   252,   257,   262,
+     265,   268,   271,   274,   277,   280,   283,   286,   291,   291,
+     295,   295,   299,   299,   303,   303,   308,   308,   315,   317,
+     317,   317,   317,   317,   317,   319,   320,   322,   322,   324,
+     325,   325,   331,   333,   335,   336,   338,   338,   340,   341,
+     341,   347,   349,   351,   351,   355,   355,   358,   359,   364,
+     367,   370,   373,   376,   379,   382,   385,   388,   391,   394,
+     397,   400,   403,   406,   411,   411,   415,   415,   419,   419,
+     423,   423,   427,   427,   433,   435,   437,   437,   439,   439,
+     441,   441,   443,   445,   450,   452,   452,   454,   454,   456,
+     456,   458,   460,   467,   469,   474,   474,   476,   478,   478,
+     480,   500,   503,   506,   506,   508,   510,   512
 };
 #endif
 
@@ -677,8 +679,8 @@ static const yytype_uint16 yyrline[] =
    First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
 static const char *const yytname[] =
 {
-  "$end", "error", "$undefined", "NEWLINE", "NUM", "ALNUM", "QSTR",
-  "PSTR", "NETWORKLINE", "VERTICESLINE", "ARCSLINE", "EDGESLINE",
+  "\"end of file\"", "error", "$undefined", "NEWLINE", "NUM", "ALNUM",
+  "QSTR", "PSTR", "NETWORKLINE", "VERTICESLINE", "ARCSLINE", "EDGESLINE",
   "ARCSLISTLINE", "EDGESLISTLINE", "MATRIXLINE", "ERROR", "VP_X_FACT",
   "VP_Y_FACT", "VP_IC", "VP_BC", "VP_LC", "VP_LR", "VP_LPHI", "VP_BW",
   "VP_FOS", "VP_PHI", "VP_R", "VP_Q", "VP_LA", "VP_FONT", "VP_URL",
diff -pruN 0.9.6+ds-2/src/io/parsers/pajek-parser.h 0.9.9+ds-1/src/io/parsers/pajek-parser.h
--- 0.9.6+ds-2/src/io/parsers/pajek-parser.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/io/parsers/pajek-parser.h	2022-06-04 12:27:41.000000000 +0000
@@ -39,6 +39,7 @@
    /* Put the tokens into the symbol table, so that GDB and other debuggers
       know about them.  */
    enum yytokentype {
+     END = 0,
      NEWLINE = 258,
      NUM = 259,
      ALNUM = 260,
@@ -91,6 +92,7 @@
    };
 #endif
 /* Tokens.  */
+#define END 0
 #define NEWLINE 258
 #define NUM 259
 #define ALNUM 260
diff -pruN 0.9.6+ds-2/src/isomorphism/bliss/graph.cc 0.9.9+ds-1/src/isomorphism/bliss/graph.cc
--- 0.9.6+ds-2/src/isomorphism/bliss/graph.cc	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/isomorphism/bliss/graph.cc	2022-06-04 12:27:41.000000000 +0000
@@ -604,7 +604,7 @@ public:
   unsigned int cr_cep_index;
   unsigned int cr_level;
 
-  bool needs_long_prune;
+  bool needs_long_prune = false; /* igraph-specific patch: initialize to false to silence UBSan */
   unsigned int long_prune_begin;
   std::set<unsigned int, std::less<unsigned int> > long_prune_redundant;
 
@@ -1499,9 +1499,6 @@ AbstractGraph::search(const bool canonic
           child_node.cr_cep_stack_size = cr_cep_stack.size();
           child_node.cr_cep_index = cr_cep_index;
 
-          /* Initialize needs_long_prune to prevent a gcc-ubsan warning */
-          child_node.needs_long_prune = true;
-
           search_stack.push_back(child_node);
           continue;
         }
diff -pruN 0.9.6+ds-2/src/isomorphism/bliss/partition.cc 0.9.9+ds-1/src/isomorphism/bliss/partition.cc
--- 0.9.6+ds-2/src/isomorphism/bliss/partition.cc	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/isomorphism/bliss/partition.cc	2022-06-04 12:27:41.000000000 +0000
@@ -4,6 +4,8 @@
 #include "graph.hh"
 #include "partition.hh"
 
+#include "igraph_decls.h"
+
 /* Allow using 'and' instead of '&&' with MSVC */
 #if _MSC_VER
 #include <ciso646>
@@ -966,6 +968,7 @@ Partition::zplit_cell(Partition::Cell* c
           /* Generic sorting and splitting */
           const bool sorted = shellsort_cell(cell);
           assert(sorted);
+          IGRAPH_UNUSED(sorted);
           last_new_cell = split_cell(cell);
         }
     }
diff -pruN 0.9.6+ds-2/src/layout/fruchterman_reingold.c 0.9.9+ds-1/src/layout/fruchterman_reingold.c
--- 0.9.6+ds-2/src/layout/fruchterman_reingold.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/layout/fruchterman_reingold.c	2022-06-04 12:27:41.000000000 +0000
@@ -29,6 +29,7 @@
 
 #include "core/grid.h"
 #include "core/interruption.h"
+#include "layout/layout_internal.h"
 
 static int igraph_layout_i_fr(const igraph_t *graph,
                               igraph_matrix_t *res,
@@ -47,7 +48,6 @@ static int igraph_layout_i_fr(const igra
     igraph_vector_float_t dispx, dispy;
     igraph_real_t temp = start_temp;
     igraph_real_t difftemp = start_temp / niter;
-    float width = sqrtf(no_nodes), height = width;
     igraph_bool_t conn = 1;
     float C = 0;
 
@@ -59,27 +59,7 @@ static int igraph_layout_i_fr(const igra
     RNG_BEGIN();
 
     if (!use_seed) {
-        IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2));
-        for (i = 0; i < no_nodes; i++) {
-            igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2;
-            igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] :  width / 2;
-            igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2;
-            igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] :  height / 2;
-            if (!igraph_finite(x1)) {
-                x1 = -sqrt(no_nodes) / 2;
-            }
-            if (!igraph_finite(x2)) {
-                x2 =  sqrt(no_nodes) / 2;
-            }
-            if (!igraph_finite(y1)) {
-                y1 = -sqrt(no_nodes) / 2;
-            }
-            if (!igraph_finite(y2)) {
-                y2 =  sqrt(no_nodes) / 2;
-            }
-            MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
-            MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
-        }
+        igraph_i_layout_random_bounded(graph, res, minx, maxx, miny, maxy);
     }
 
     IGRAPH_CHECK(igraph_vector_float_init(&dispx, no_nodes));
@@ -103,9 +83,9 @@ static int igraph_layout_i_fr(const igra
                     float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1);
                     float dlen = dx * dx + dy * dy;
 
-                    if (dlen == 0) {
-                        dx = RNG_UNIF01() * 1e-9;
-                        dy = RNG_UNIF01() * 1e-9;
+                    while (dlen == 0) {
+                        dx = RNG_UNIF(-1e-9, 1e-9);
+                        dy = RNG_UNIF(-1e-9, 1e-9);
                         dlen = dx * dx + dy * dy;
                     }
 
@@ -123,9 +103,9 @@ static int igraph_layout_i_fr(const igra
                     float dlen, rdlen;
 
                     dlen = dx * dx + dy * dy;
-                    if (dlen == 0) {
-                        dx = RNG_UNIF(0, 1e-6);
-                        dy = RNG_UNIF(0, 1e-6);
+                    while (dlen == 0) {
+                        dx = RNG_UNIF(-1e-9, 1e-9);
+                        dy = RNG_UNIF(-1e-9, 1e-9);
                         dlen = dx * dx + dy * dy;
                     }
 
@@ -141,7 +121,7 @@ static int igraph_layout_i_fr(const igra
 
         /* calculate attractive forces */
         for (e = 0; e < no_edges; e++) {
-            /* each edges is an ordered pair of vertices v and u */
+            /* each edge is an ordered pair of vertices v and u */
             igraph_integer_t v = IGRAPH_FROM(graph, e);
             igraph_integer_t u = IGRAPH_TO(graph, e);
             igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0);
@@ -157,14 +137,18 @@ static int igraph_layout_i_fr(const igra
         /* limit max displacement to temperature t and prevent from
            displacement outside frame */
         for (v = 0; v < no_nodes; v++) {
-            igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF01() * 1e-9;
-            igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF01() * 1e-9;
+            igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF(-1e-9, 1e-9);
+            igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF(-1e-9, 1e-9);
             igraph_real_t displen = sqrt(dx * dx + dy * dy);
-            igraph_real_t mx = fabs(dx) < temp ? dx : temp;
-            igraph_real_t my = fabs(dy) < temp ? dy : temp;
+
+            if (displen > temp) {
+                dx *= temp/displen;
+                dy *= temp/displen;
+            }
+
             if (displen > 0) {
-                MATRIX(*res, v, 0) += (dx / displen) * mx;
-                MATRIX(*res, v, 1) += (dy / displen) * my;
+                MATRIX(*res, v, 0) += dx;
+                MATRIX(*res, v, 1) += dy;
             }
             if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) {
                 MATRIX(*res, v, 0) = VECTOR(*minx)[v];
@@ -214,27 +198,7 @@ static int igraph_layout_i_grid_fr(
     RNG_BEGIN();
 
     if (!use_seed) {
-        IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2));
-        for (i = 0; i < no_nodes; i++) {
-            igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2;
-            igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] :  width / 2;
-            igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2;
-            igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] :  height / 2;
-            if (!igraph_finite(x1)) {
-                x1 = -sqrt(no_nodes) / 2;
-            }
-            if (!igraph_finite(x2)) {
-                x2 =  sqrt(no_nodes) / 2;
-            }
-            if (!igraph_finite(y1)) {
-                y1 = -sqrt(no_nodes) / 2;
-            }
-            if (!igraph_finite(y2)) {
-                y2 =  sqrt(no_nodes) / 2;
-            }
-            MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
-            MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
-        }
+        igraph_i_layout_random_bounded(graph, res, minx, maxx, miny, maxy);
     }
 
     /* make grid */
@@ -267,6 +231,11 @@ static int igraph_layout_i_grid_fr(
                 float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0);
                 float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1);
                 float dlen = dx * dx + dy * dy;
+                while (dlen == 0) {
+                    dx = RNG_UNIF(-1e-9, 1e-9);
+                    dy = RNG_UNIF(-1e-9, 1e-9);
+                    dlen = dx * dx + dy * dy;
+                }
                 if (dlen < cellsize * cellsize) {
                     VECTOR(dispx)[v] += dx / dlen;
                     VECTOR(dispy)[v] += dy / dlen;
@@ -292,14 +261,18 @@ static int igraph_layout_i_grid_fr(
 
         /* update */
         for (v = 0; v < no_nodes; v++) {
-            igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF01() * 1e-9;
-            igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF01() * 1e-9;
+            igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF(-1e-9, 1e-9);
+            igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF(-1e-9, 1e-9);
             igraph_real_t displen = sqrt(dx * dx + dy * dy);
-            igraph_real_t mx = fabs(dx) < temp ? dx : temp;
-            igraph_real_t my = fabs(dy) < temp ? dy : temp;
+
+            if (displen > temp) {
+                dx *= temp/displen;
+                dy *= temp/displen;
+            }
+
             if (displen > 0) {
-                MATRIX(*res, v, 0) += (dx / displen) * mx;
-                MATRIX(*res, v, 1) += (dy / displen) * my;
+                MATRIX(*res, v, 0) += dx;
+                MATRIX(*res, v, 1) += dy;
             }
             if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) {
                 MATRIX(*res, v, 0) = VECTOR(*minx)[v];
@@ -331,10 +304,31 @@ static int igraph_layout_i_grid_fr(
  * \brief Places the vertices on a plane according to the Fruchterman-Reingold algorithm.
  *
  * </para><para>
- * This is a force-directed layout, see Fruchterman, T.M.J. and
- * Reingold, E.M.: Graph Drawing by Force-directed Placement.
+ * This is a force-directed layout that simulates an attractive force \c f_a between
+ * connected vertex pairs and a repulsive force \c f_r between all vertex pairs.
+ * The forces are computed as a function of the distance \c d between the two vertices as
+ *
+ * </para><para>
+ * <code>f_a(d) = -w * d^2</code> and <code>f_r(d) = 1/d</code>,
+ *
+ * </para><para>
+ * where \c w represents the edge weight. The equilibrium distance of two connected
+ * vertices is thus <code>1/w^3</code>, assuming no other forces acting on them.
+ *
+ * </para><para>
+ * In disconnected graphs, igraph effectively inserts a weak connection of weight
+ * <code>n^(-3/2)</code> between all pairs of vertices, where \c n is the vertex count.
+ * This ensures that components are kept near each other.
+ *
+ * </para><para>
+ * Reference:
+ *
+ * </para><para>
+ * Fruchterman, T.M.J. and Reingold, E.M.:
+ * Graph Drawing by Force-directed Placement.
  * Software -- Practice and Experience, 21/11, 1129--1164,
- * 1991.
+ * 1991. https://doi.org/10.1002/spe.4380211102
+ *
  * \param graph Pointer to an initialized graph object.
  * \param res Pointer to an initialized matrix object. This will
  *        contain the result and will be resized as needed.
@@ -442,9 +436,8 @@ int igraph_layout_fruchterman_reingold(c
  * \function igraph_layout_fruchterman_reingold_3d
  * \brief 3D Fruchterman-Reingold algorithm.
  *
- * This is the 3D version of the force based
- * Fruchterman-Reingold layout (see \ref
- * igraph_layout_fruchterman_reingold for the 2D version
+ * This is the 3D version of the force based Fruchterman-Reingold layout.
+ * See \ref igraph_layout_fruchterman_reingold() for the 2D version.
  *
  * \param graph Pointer to an initialized graph object.
  * \param res Pointer to an initialized matrix object. This will
@@ -499,13 +492,12 @@ int igraph_layout_fruchterman_reingold_3
         const igraph_vector_t *minz,
         const igraph_vector_t *maxz) {
 
-    igraph_integer_t no_nodes = igraph_vcount(graph);
-    igraph_integer_t no_edges = igraph_ecount(graph);
+    const igraph_integer_t no_nodes = igraph_vcount(graph);
+    const igraph_integer_t no_edges = igraph_ecount(graph);
     igraph_integer_t i;
     igraph_vector_float_t dispx, dispy, dispz;
     igraph_real_t temp = start_temp;
     igraph_real_t difftemp = start_temp / niter;
-    float width = sqrtf(no_nodes), height = width, depth = width;
     igraph_bool_t conn = 1;
     float C = 0;
 
@@ -560,18 +552,7 @@ int igraph_layout_fruchterman_reingold_3
     RNG_BEGIN();
 
     if (!use_seed) {
-        IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 3));
-        for (i = 0; i < no_nodes; i++) {
-            igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2;
-            igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] :  width / 2;
-            igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2;
-            igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] :  height / 2;
-            igraph_real_t z1 = minz ? VECTOR(*minz)[i] : -depth / 2;
-            igraph_real_t z2 = maxz ? VECTOR(*maxz)[i] :  depth / 2;
-            MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
-            MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
-            MATRIX(*res, i, 2) = RNG_UNIF(z1, z2);
-        }
+        igraph_i_layout_random_bounded_3d(graph, res, minx, maxx, miny, maxy, minz, maxz);
     }
 
     IGRAPH_CHECK(igraph_vector_float_init(&dispx, no_nodes));
@@ -599,10 +580,10 @@ int igraph_layout_fruchterman_reingold_3
                     float dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2);
                     float dlen = dx * dx + dy * dy + dz * dz;
 
-                    if (dlen == 0) {
-                        dx = RNG_UNIF01() * 1e-9;
-                        dy = RNG_UNIF01() * 1e-9;
-                        dz = RNG_UNIF01() * 1e-9;
+                    while (dlen == 0) {
+                        dx = RNG_UNIF(-1e-9, 1e-9);
+                        dy = RNG_UNIF(-1e-9, 1e-9);
+                        dz = RNG_UNIF(-1e-9, 1e-9);
                         dlen = dx * dx + dy * dy + dz * dz;
                     }
 
@@ -623,10 +604,10 @@ int igraph_layout_fruchterman_reingold_3
                     float dlen, rdlen;
 
                     dlen = dx * dx + dy * dy + dz * dz;
-                    if (dlen == 0) {
-                        dx = RNG_UNIF01() * 1e-9;
-                        dy = RNG_UNIF01() * 1e-9;
-                        dz = RNG_UNIF01() * 1e-9;
+                    while (dlen == 0) {
+                        dx = RNG_UNIF(-1e-9, 1e-9);
+                        dy = RNG_UNIF(-1e-9, 1e-9);
+                        dz = RNG_UNIF(-1e-9, 1e-9);
                         dlen = dx * dx + dy * dy + dz * dz;
                     }
 
@@ -663,17 +644,21 @@ int igraph_layout_fruchterman_reingold_3
         /* limit max displacement to temperature t and prevent from
            displacement outside frame */
         for (v = 0; v < no_nodes; v++) {
-            igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF01() * 1e-9;
-            igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF01() * 1e-9;
-            igraph_real_t dz = VECTOR(dispz)[v] + RNG_UNIF01() * 1e-9;
+            igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF(-1e-9, 1e-9);
+            igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF(-1e-9, 1e-9);
+            igraph_real_t dz = VECTOR(dispz)[v] + RNG_UNIF(-1e-9, 1e-9);
             igraph_real_t displen = sqrt(dx * dx + dy * dy + dz * dz);
-            igraph_real_t mx = fabs(dx) < temp ? dx : temp;
-            igraph_real_t my = fabs(dy) < temp ? dy : temp;
-            igraph_real_t mz = fabs(dz) < temp ? dz : temp;
+
+            if (displen > temp) {
+                dx *= temp/displen;
+                dy *= temp/displen;
+                dz *= temp/displen;
+            }
+
             if (displen > 0) {
-                MATRIX(*res, v, 0) += (dx / displen) * mx;
-                MATRIX(*res, v, 1) += (dy / displen) * my;
-                MATRIX(*res, v, 2) += (dz / displen) * mz;
+                MATRIX(*res, v, 0) += dx;
+                MATRIX(*res, v, 1) += dy;
+                MATRIX(*res, v, 2) += dz;
             }
             if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) {
                 MATRIX(*res, v, 0) = VECTOR(*minx)[v];
diff -pruN 0.9.6+ds-2/src/layout/kamada_kawai.c 0.9.9+ds-1/src/layout/kamada_kawai.c
--- 0.9.6+ds-2/src/layout/kamada_kawai.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/layout/kamada_kawai.c	2022-06-04 12:27:41.000000000 +0000
@@ -28,6 +28,10 @@
 #include "igraph_random.h"
 
 #include "core/interruption.h"
+#include "layout/layout_internal.h"
+
+/* Energy gradient values below this threshold are considered to be zero. */
+#define KK_EPS 1e-13
 
 /**
  * \ingroup layout
@@ -43,6 +47,10 @@
  * far-apart veritces will have a smaller effect on the layout.
  *
  * </para><para>
+ * This layout works particularly well for locally connected spatial networks
+ * such as lattices.
+ *
+ * </para><para>
  * This layout algorithm is not suitable for large graphs. The memory
  * requirements are of the order O(|V|^2).
  *
@@ -144,32 +152,14 @@ int igraph_layout_kamada_kawai(const igr
 
     if (!use_seed) {
         if (minx || maxx || miny || maxy) {
-            const igraph_real_t width = sqrt(no_nodes), height = width;
-            IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2));
-            RNG_BEGIN();
-            for (i = 0; i < no_nodes; i++) {
-                igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2;
-                igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] :  width / 2;
-                igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2;
-                igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] :  height / 2;
-                if (!igraph_finite(x1)) {
-                    x1 = -width / 2;
-                }
-                if (!igraph_finite(x2)) {
-                    x2 =  width / 2;
-                }
-                if (!igraph_finite(y1)) {
-                    y1 = -height / 2;
-                }
-                if (!igraph_finite(y2)) {
-                    y2 =  height / 2;
-                }
-                MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
-                MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
-            }
-            RNG_END();
+            igraph_i_layout_random_bounded(graph, res, minx, maxx, miny, maxy);
         } else {
             igraph_layout_circle(graph, res, /* order= */ igraph_vss_all());
+            /* The original paper recommends using a radius of 0.5*L0 here.
+             * The coefficient of 0.36 was chosen empirically so that this initial
+             * layout would be as close as possible to the equilibrium layout
+             * when the graph is a cycle graph. */
+            igraph_matrix_scale(res, 0.36 * L0);
         }
     }
 
@@ -284,9 +274,25 @@ int igraph_layout_kamada_kawai(const igr
         myD1 = VECTOR(D1)[m];
         myD2 = VECTOR(D2)[m];
 
-        /* Need to solve some linear equations */
-        delta_y = (B * myD1 - myD2 * A) / (C * A - B * B);
-        delta_x = - (myD1 + B * delta_y) / A;
+        /* We need to solve the following linear equations, corresponding to
+         * eqs. (11) and (12) in the paper.
+         *
+         * A * delta_x + B * delta_y == myD1
+         * B * delta_x + C * delta_y == myD2
+         *
+         * We special-case the equilibrium case, i.e. when the energy gradient
+         * is zero and no displacement is necessary. This is important for the
+         * case of path graphs, where the determinant of the LHS will be
+         * zero in equilibrium, causing numerical problems.
+         */
+        if (myD1*myD1 + myD2*myD2 < KK_EPS*KK_EPS) {
+            delta_x = 0;
+            delta_y = 0;
+        } else {
+            igraph_real_t det = C * A - B * B;
+            delta_y = (B * myD1 - A * myD2) / det;
+            delta_x = (B * myD2 - C * myD1) / det;
+        }
 
         new_x = old_x + delta_x;
         new_y = old_y + delta_y;
@@ -354,7 +360,7 @@ int igraph_layout_kamada_kawai(const igr
  * \function igraph_layout_kamada_kawai_3d
  * \brief 3D version of the Kamada-Kawai layout generator.
  *
- * This is the 3D version of igraph_layout_kamada_kawai().
+ * This is the 3D version of \ref igraph_layout_kamada_kawai().
  * See the documentation of that function for more information.
  *
  * </para><para>
@@ -409,8 +415,8 @@ int igraph_layout_kamada_kawai_3d(const
                                   const igraph_vector_t *miny, const igraph_vector_t *maxy,
                                   const igraph_vector_t *minz, const igraph_vector_t *maxz) {
 
-    igraph_integer_t no_nodes = igraph_vcount(graph);
-    igraph_integer_t no_edges = igraph_ecount(graph);
+    const igraph_integer_t no_nodes = igraph_vcount(graph);
+    const igraph_integer_t no_edges = igraph_ecount(graph);
     igraph_real_t L, L0 = sqrt(no_nodes);
     igraph_matrix_t dij, lij, kij;
     igraph_real_t max_dij;
@@ -465,41 +471,13 @@ int igraph_layout_kamada_kawai_3d(const
 
     if (!use_seed) {
         if (minx || maxx || miny || maxy || minz || maxz) {
-            const igraph_real_t width = sqrt(no_nodes), height = width, depth = width;
-            IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 3));
-            RNG_BEGIN();
-            for (i = 0; i < no_nodes; i++) {
-                igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2;
-                igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] :  width / 2;
-                igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2;
-                igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] :  height / 2;
-                igraph_real_t z1 = minz ? VECTOR(*minz)[i] : -depth / 2;
-                igraph_real_t z2 = maxz ? VECTOR(*maxz)[i] :  depth / 2;
-                if (!igraph_finite(x1)) {
-                    x1 = -width / 2;
-                }
-                if (!igraph_finite(x2)) {
-                    x2 =  width / 2;
-                }
-                if (!igraph_finite(y1)) {
-                    y1 = -height / 2;
-                }
-                if (!igraph_finite(y2)) {
-                    y2 =  height / 2;
-                }
-                if (!igraph_finite(z1)) {
-                    z1 = -depth / 2;
-                }
-                if (!igraph_finite(z2)) {
-                    z2 =  depth / 2;
-                }
-                MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
-                MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
-                MATRIX(*res, i, 2) = RNG_UNIF(z1, z2);
-            }
-            RNG_END();
+            igraph_i_layout_random_bounded_3d(graph, res, minx, maxx, miny, maxy, minz, maxz);
         } else {
             igraph_layout_sphere(graph, res);
+            /* The coefficient of 0.36 was chosen empirically so that this initial layout
+             * would be as close as possible to the equilibrium layout when the graph is
+             * a Goldberg polyhedron, i.e. having a naturally spherical layout. */
+            igraph_matrix_scale(res, 0.36*L0);
         }
     }
 
@@ -575,7 +553,6 @@ int igraph_layout_kamada_kawai_3d(const
         igraph_real_t Axx = 0.0, Axy = 0.0, Axz = 0.0, Ayy = 0.0, Ayz = 0.0, Azz = 0.0;
         igraph_real_t max_delta, delta_x, delta_y, delta_z;
         igraph_real_t old_x, old_y, old_z, new_x, new_y, new_z;
-        igraph_real_t detnum;
 
         IGRAPH_ALLOW_INTERRUPTION();
 
@@ -624,16 +601,15 @@ int igraph_layout_kamada_kawai_3d(const
         /* Need to solve some linear equations, we just use Cramer's rule */
 #define DET(a,b,c,d,e,f,g,h,i) ((a*e*i+b*f*g+c*d*h)-(c*e*g+b*d*i+a*f*h))
 
-        detnum  = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Axz, Ayz, Azz);
-        if (detnum != 0) {
+        /* See comments in 2D version for the reason for this check */
+        if (Ax*Ax + Ay*Ay + Az*Az < KK_EPS*KK_EPS) {
+            delta_x = delta_y = delta_z = 0;
+        } else {
+            igraph_real_t detnum;
+            detnum  = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Axz, Ayz, Azz);
             delta_x = DET(Ax, Ay, Az, Axy, Ayy, Ayz, Axz, Ayz, Azz) / detnum;
             delta_y = DET(Axx, Axy, Axz, Ax, Ay, Az, Axz, Ayz, Azz) / detnum;
             delta_z = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Ax, Ay, Az ) / detnum;
-        } else {
-            /* No new stable position for node m; this can happen in rare
-             * cases, e.g., if the graph has two nodes only. It's best to leave
-             * the node where it is. */
-            delta_x = delta_y = delta_z = 0;
         }
 
         new_x = old_x + delta_x;
diff -pruN 0.9.6+ds-2/src/layout/layout_internal.h 0.9.9+ds-1/src/layout/layout_internal.h
--- 0.9.6+ds-2/src/layout/layout_internal.h	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/layout/layout_internal.h	2022-06-04 12:27:41.000000000 +0000
@@ -23,6 +23,8 @@
 #ifndef IGRAPH_LAYOUT_INTERNAL_H
 #define IGRAPH_LAYOUT_INTERNAL_H
 
+#include "igraph_datatype.h"
+#include "igraph_decls.h"
 #include "igraph_types.h"
 
 #include "layout/merge_grid.h"
@@ -50,6 +52,17 @@ IGRAPH_PRIVATE_EXPORT igraph_bool_t igra
                                                                        float p2_x, float p2_y,
                                                                        float p3_x, float p3_y);
 
+int igraph_i_layout_random_bounded(
+        const igraph_t *graph, igraph_matrix_t *res,
+        const igraph_vector_t *minx, const igraph_vector_t *maxx,
+        const igraph_vector_t *miny, const igraph_vector_t *maxy);
+
+int igraph_i_layout_random_bounded_3d(
+        const igraph_t *graph, igraph_matrix_t *res,
+        const igraph_vector_t *minx, const igraph_vector_t *maxx,
+        const igraph_vector_t *miny, const igraph_vector_t *maxy,
+        const igraph_vector_t *minz, const igraph_vector_t *maxz);
+
 __END_DECLS
 
 #endif
diff -pruN 0.9.6+ds-2/src/layout/layout_random.c 0.9.9+ds-1/src/layout/layout_random.c
--- 0.9.6+ds-2/src/layout/layout_random.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/layout/layout_random.c	2022-06-04 12:27:41.000000000 +0000
@@ -26,6 +26,8 @@
 #include "igraph_interface.h"
 #include "igraph_random.h"
 
+#include "layout/layout_internal.h"
+
 /**
  * \ingroup layout
  * \function igraph_layout_random
@@ -96,3 +98,187 @@ int igraph_layout_random_3d(const igraph
 
     return IGRAPH_SUCCESS;
 }
+
+
+/* The following functions generate suitable initial random layouts for
+ * the Fruchterman-Reingold and Kamada-Kawai algorithms. */
+
+int igraph_i_layout_random_bounded(
+        const igraph_t *graph,
+        igraph_matrix_t *res,
+        const igraph_vector_t *minx, const igraph_vector_t *maxx,
+        const igraph_vector_t *miny, const igraph_vector_t *maxy) {
+
+    const igraph_integer_t no_nodes = igraph_vcount(graph);
+    const igraph_real_t width = sqrt(no_nodes), height = width;
+
+    igraph_real_t dminx = -width/2,  dmaxx = width/2,
+                  dminy = -height/2, dmaxy = height/2; /* default values */
+
+    /* Caller should ensure that minx, etc. do not contain NaN. */
+
+    if (minx && !igraph_vector_empty(minx)) {
+        igraph_real_t m = igraph_vector_max(minx);
+        if (m == IGRAPH_POSINFINITY) {
+            IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m > dmaxx) {
+            dmaxx += m;
+        }
+    }
+    if (maxx && !igraph_vector_empty(maxx)) {
+        igraph_real_t m = igraph_vector_min(maxx);
+        if (m == IGRAPH_NEGINFINITY) {
+            IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m < dminx) {
+            dminx -= m;
+        }
+    }
+    if (miny && !igraph_vector_empty(miny)) {
+        igraph_real_t m = igraph_vector_max(miny);
+        if (m == IGRAPH_POSINFINITY) {
+            IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m > dmaxy) {
+            dmaxy += m;
+        }
+    }
+    if (maxy && !igraph_vector_empty(maxy)) {
+        igraph_real_t m = igraph_vector_min(maxy);
+        if (m == IGRAPH_NEGINFINITY) {
+            IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m < dminy) {
+            dminy -= m;
+        }
+    }
+
+    IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2));
+    for (igraph_integer_t i = 0; i < no_nodes; i++) {
+        igraph_real_t x1 = minx ? VECTOR(*minx)[i] : dminx;
+        igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : dmaxx;
+        igraph_real_t y1 = miny ? VECTOR(*miny)[i] : dminy;
+        igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : dmaxy;
+        if (!igraph_finite(x1)) {
+            x1 = -width / 2;
+        }
+        if (!igraph_finite(x2)) {
+            x2 =  width / 2;
+        }
+        if (!igraph_finite(y1)) {
+            y1 = -height / 2;
+        }
+        if (!igraph_finite(y2)) {
+            y2 =  height / 2;
+        }
+        MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
+        MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
+    }
+
+    return IGRAPH_SUCCESS;
+}
+
+int igraph_i_layout_random_bounded_3d(
+        const igraph_t *graph, igraph_matrix_t *res,
+        const igraph_vector_t *minx, const igraph_vector_t *maxx,
+        const igraph_vector_t *miny, const igraph_vector_t *maxy,
+        const igraph_vector_t *minz, const igraph_vector_t *maxz) {
+
+    const igraph_integer_t no_nodes = igraph_vcount(graph);
+    const igraph_real_t width = sqrt(no_nodes), height = width, depth = width;
+
+    igraph_real_t dminx = -width/2,  dmaxx = width/2,
+                  dminy = -height/2, dmaxy = height/2,
+                  dminz = -depth/2,  dmaxz = depth/2; /* default values */
+
+    /* Caller should ensure that minx, etc. do not contain NaN. */
+
+    if (minx && !igraph_vector_empty(minx)) {
+        igraph_real_t m = igraph_vector_max(minx);
+        if (m == IGRAPH_POSINFINITY) {
+            IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m > dmaxx) {
+            dmaxx += m;
+        }
+    }
+    if (maxx && !igraph_vector_empty(maxx)) {
+        igraph_real_t m = igraph_vector_min(maxx);
+        if (m == IGRAPH_NEGINFINITY) {
+            IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m < dminx) {
+            dminx -= m;
+        }
+    }
+    if (miny && !igraph_vector_empty(miny)) {
+        igraph_real_t m = igraph_vector_max(miny);
+        if (m == IGRAPH_POSINFINITY) {
+            IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m > dmaxy) {
+            dmaxy += m;
+        }
+    }
+    if (maxy && !igraph_vector_empty(maxy)) {
+        igraph_real_t m = igraph_vector_min(maxy);
+        if (m == IGRAPH_NEGINFINITY) {
+            IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m < dminy) {
+            dminy -= m;
+        }
+    }
+    if (minz && !igraph_vector_empty(minz)) {
+        igraph_real_t m = igraph_vector_max(minz);
+        if (m == IGRAPH_POSINFINITY) {
+            IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m > dmaxz) {
+            dmaxz += m;
+        }
+    }
+    if (maxz && !igraph_vector_empty(maxz)) {
+        igraph_real_t m = igraph_vector_min(maxz);
+        if (m == IGRAPH_NEGINFINITY) {
+            IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL);
+        }
+        if (m < dminz) {
+            dminz -= m;
+        }
+    }
+
+    IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 3));
+    for (igraph_integer_t i = 0; i < no_nodes; i++) {
+        igraph_real_t x1 = minx ? VECTOR(*minx)[i] : dminx;
+        igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : dmaxx;
+        igraph_real_t y1 = miny ? VECTOR(*miny)[i] : dminy;
+        igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : dmaxy;
+        igraph_real_t z1 = minz ? VECTOR(*minz)[i] : dminz;
+        igraph_real_t z2 = maxz ? VECTOR(*maxz)[i] : dmaxz;
+        if (!igraph_finite(x1)) {
+            x1 = -width / 2;
+        }
+        if (!igraph_finite(x2)) {
+            x2 =  width / 2;
+        }
+        if (!igraph_finite(y1)) {
+            y1 = -height / 2;
+        }
+        if (!igraph_finite(y2)) {
+            y2 =  height / 2;
+        }
+        if (!igraph_finite(z1)) {
+            z1 = -depth / 2;
+        }
+        if (!igraph_finite(z2)) {
+            z2 =  depth / 2;
+        }
+        MATRIX(*res, i, 0) = RNG_UNIF(x1, x2);
+        MATRIX(*res, i, 1) = RNG_UNIF(y1, y2);
+        MATRIX(*res, i, 2) = RNG_UNIF(z1, z2);
+    }
+
+    return IGRAPH_SUCCESS;
+}
diff -pruN 0.9.6+ds-2/src/linalg/blas.c 0.9.9+ds-1/src/linalg/blas.c
--- 0.9.6+ds-2/src/linalg/blas.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/linalg/blas.c	2022-06-04 12:27:41.000000000 +0000
@@ -113,6 +113,15 @@ void igraph_blas_dgemv_array(igraph_bool
 #endif
 }
 
+/**
+ * \function igraph_blas_dnrm2
+ * \brief Euclidean norm of a vector.
+ *
+ * \param v The vector.
+ * \return Real value, the norm of \p v.
+ *
+ * Time complexity: O(n) where n is the length of the vector.
+ */
 igraph_real_t igraph_blas_dnrm2(const igraph_vector_t *v) {
     int n = igraph_vector_size(v);
     int one = 1;
diff -pruN 0.9.6+ds-2/src/misc/bipartite.c 0.9.9+ds-1/src/misc/bipartite.c
--- 0.9.6+ds-2/src/misc/bipartite.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/misc/bipartite.c	2022-06-04 12:27:41.000000000 +0000
@@ -524,7 +524,7 @@ int igraph_create_bipartite(igraph_t *gr
         igraph_vector_minmax(edges, &min_edge, &max_edge);
     }
     if (min_edge < 0 || max_edge >= no_of_nodes) {
-        IGRAPH_ERROR("Invalid (negative) vertex id", IGRAPH_EINVVID);
+        IGRAPH_ERROR("Invalid (negative or too large) vertex id", IGRAPH_EINVVID);
     }
 
     /* Check bipartiteness */
diff -pruN 0.9.6+ds-2/src/misc/feedback_arc_set.c 0.9.9+ds-1/src/misc/feedback_arc_set.c
--- 0.9.6+ds-2/src/misc/feedback_arc_set.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/misc/feedback_arc_set.c	2022-06-04 12:27:41.000000000 +0000
@@ -112,7 +112,7 @@ int igraph_i_feedback_arc_set_undirected
     igraph_vector_t edges;
     long int i, j, n, no_of_nodes = igraph_vcount(graph);
 
-    IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_nodes - 1);
+    IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_nodes > 0 ? no_of_nodes - 1 : 0);
     if (weights) {
         /* Find a maximum weight spanning tree. igraph has a routine for minimum
          * spanning trees, so we negate the weights */
diff -pruN 0.9.6+ds-2/src/misc/mixing.c 0.9.9+ds-1/src/misc/mixing.c
--- 0.9.6+ds-2/src/misc/mixing.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/misc/mixing.c	2022-06-04 12:27:41.000000000 +0000
@@ -75,6 +75,12 @@ int igraph_assortativity_nominal(const i
         IGRAPH_ERROR("Invalid `types' vector length", IGRAPH_EINVAL);
     }
 
+    if (no_of_nodes == 0) {
+        *res = IGRAPH_NAN;
+        return IGRAPH_SUCCESS;
+    }
+
+    /* 'types' length > 0 here, safe to call vector_min() */
     if (igraph_vector_min(types) < 0) {
         IGRAPH_ERROR("Invalid `types' vector", IGRAPH_EINVAL);
     }
diff -pruN 0.9.6+ds-2/src/operators/rewire.c 0.9.9+ds-1/src/operators/rewire.c
--- 0.9.6+ds-2/src/operators/rewire.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/operators/rewire.c	2022-06-04 12:27:41.000000000 +0000
@@ -226,14 +226,14 @@ int igraph_i_rewire(igraph_t *graph, igr
 /**
  * \ingroup structural
  * \function igraph_rewire
- * \brief Randomly rewires a graph while preserving the degree distribution.
+ * \brief Randomly rewires a graph while preserving its degree sequence.
  *
  * </para><para>
  * This function generates a new graph based on the original one by randomly
- * rewiring edges while preserving the original graph's degree distribution.
- * Please note that the rewiring is done "in place", so no new graph will
+ * rewiring edges while preserving the original graph's degree sequence.
+ * The rewiring is done "in place", so no new graph will
  * be allocated. If you would like to keep the original graph intact, use
- * \ref igraph_copy() beforehand.
+ * \ref igraph_copy() beforehand. All graph attributes will be lost.
  *
  * \param graph The graph object to be rewired.
  * \param n Number of rewiring trials to perform.
diff -pruN 0.9.6+ds-2/src/paths/dijkstra.c 0.9.9+ds-1/src/paths/dijkstra.c
--- 0.9.6+ds-2/src/paths/dijkstra.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/paths/dijkstra.c	2022-06-04 12:27:41.000000000 +0000
@@ -31,6 +31,7 @@
 
 #include "core/indheap.h"
 #include "core/interruption.h"
+#include "core/math.h"
 
 #include <string.h>   /* memset */
 
@@ -701,6 +702,8 @@ int igraph_get_all_shortest_paths_dijkst
     igraph_finally_func_t *res_item_destructor;
     unsigned char *is_target;
     long int i, n, to_reach;
+    int cmp_result;
+    const double eps = IGRAPH_SHORTEST_PATH_EPSILON;
 
     if (!weights) {
         return igraph_get_all_shortest_paths(graph, res, nrgeo, from, to, mode);
@@ -808,13 +811,14 @@ int igraph_get_all_shortest_paths_dijkst
             igraph_real_t curdist = VECTOR(dists)[tto];
             igraph_vector_t *parent_vec;
 
+            cmp_result = igraph_cmp_epsilon(curdist, altdist, eps);
             if (curdist < 0) {
                 /* This is the first non-infinite distance */
                 VECTOR(dists)[tto] = altdist;
                 parent_vec = (igraph_vector_t*)VECTOR(parents)[tto];
                 IGRAPH_CHECK(igraph_vector_push_back(parent_vec, minnei));
                 IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist));
-            } else if (altdist == curdist && VECTOR(*weights)[edge] > 0) {
+            } else if (cmp_result == 0 /* altdist == curdist */ && VECTOR(*weights)[edge] > 0) {
                 /* This is an alternative path with exactly the same length.
                      * Note that we consider this case only if the edge via which we
                      * reached the node has a nonzero weight; otherwise we could create
@@ -822,7 +826,7 @@ int igraph_get_all_shortest_paths_dijkst
                      * back-and-forth */
                 parent_vec = (igraph_vector_t*)VECTOR(parents)[tto];
                 IGRAPH_CHECK(igraph_vector_push_back(parent_vec, minnei));
-            } else if (altdist < curdist) {
+            } else if (cmp_result > 0 /* altdist < curdist */) {
                 /* This is a shorter path */
                 VECTOR(dists)[tto] = altdist;
                 parent_vec = (igraph_vector_t*)VECTOR(parents)[tto];
diff -pruN 0.9.6+ds-2/src/properties/basic_properties.c 0.9.9+ds-1/src/properties/basic_properties.c
--- 0.9.6+ds-2/src/properties/basic_properties.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/properties/basic_properties.c	2022-06-04 12:27:41.000000000 +0000
@@ -35,17 +35,23 @@
 
 /**
  * \function igraph_density
- * Calculate the density of a graph.
+ * \brief Calculate the density of a graph.
+ *
+ * The density of a graph is simply the ratio of the actual number of its
+ * edges and the largest possible number of edges it could have.
+ * The maximum number of edges depends on interpretation: are vertices
+ * allowed to have a connected to themselves? This is controlled by the
+ * \p loops parameter.
+ *
+ * </para><para>
+ * Note that density is ill-defined for graphs which have multiple edges
+ * between some pairs of vertices. Consider calling \ref igraph_simplify()
+ * on such graphs.
  *
- * </para><para>The density of a graph is simply the ratio number of
- * edges and the number of possible edges. Note that density is
- * ill-defined for graphs with multiple and/or loop edges, so consider
- * calling \ref igraph_simplify() on the graph if you know that it
- * contains multiple or loop edges.
  * \param graph The input graph object.
  * \param res Pointer to a real number, the result will be stored
  *   here.
- * \param loops Logical constant, whether to include loops in the
+ * \param loops Logical constant, whether to include self-loops in the
  *   calculation. If this constant is TRUE then
  *   loop edges are thought to be possible in the graph (this does not
  *   necessarily mean that the graph really contains any loops). If
@@ -101,7 +107,8 @@ int igraph_density(const igraph_t *graph
  * where p[i,j]=w[i,j]/sum(w[i,l], l=1..k[i]),  k[i] is the (total)
  * degree of vertex i, and w[i,j] is the weight of the edge(s) between
  * vertex i and j. The diversity of isolated vertices will be NaN
- * (not-a-number).
+ * (not-a-number), while that of vertices with a single connection
+ * will be zero.
  *
  * </para><para>
  * The measure works only if the graph is undirected and has no multiple edges.
@@ -111,7 +118,7 @@ int igraph_density(const igraph_t *graph
  *
  * \param graph The undirected input graph.
  * \param weights The edge weights, in the order of the edge ids, must
- *    have appropriate length.
+ *    have appropriate length. Weights must be non-negative.
  * \param res An initialized vector, the results are stored here.
  * \param vids Vertex selector that specifies the vertices which to calculate
  *    the measure.
@@ -123,13 +130,11 @@ int igraph_density(const igraph_t *graph
 int igraph_diversity(const igraph_t *graph, const igraph_vector_t *weights,
                      igraph_vector_t *res, const igraph_vs_t vids) {
 
-    int no_of_nodes = igraph_vcount(graph);
-    int no_of_edges = igraph_ecount(graph);
+    long int no_of_edges = igraph_ecount(graph);
+    long int k, i;
     igraph_vector_t incident;
-    igraph_vit_t vit;
-    igraph_real_t s, ent, w;
-    int i, j, k;
     igraph_bool_t has_multiple;
+    igraph_vit_t vit;
 
     if (igraph_is_directed(graph)) {
         IGRAPH_ERROR("Diversity measure works with undirected graphs only.", IGRAPH_EINVAL);
@@ -148,48 +153,65 @@ int igraph_diversity(const igraph_t *gra
         IGRAPH_ERROR("Diversity measure works only if the graph has no multiple edges.", IGRAPH_EINVAL);
     }
 
+    if (no_of_edges > 0) {
+        igraph_real_t minweight = igraph_vector_min(weights);
+        if (minweight < 0) {
+            IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL);
+        } else if (igraph_is_nan(minweight)) {
+            IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL);
+        }
+    }
+
     IGRAPH_VECTOR_INIT_FINALLY(&incident, 10);
 
-    if (igraph_vs_is_all(&vids)) {
-        IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes));
-        for (i = 0; i < no_of_nodes; i++) {
-            s = ent = 0.0;
-            IGRAPH_CHECK(igraph_incident(graph, &incident, i, /*mode=*/ IGRAPH_ALL));
-            for (j = 0, k = (int) igraph_vector_size(&incident); j < k; j++) {
-                w = VECTOR(*weights)[(long int)VECTOR(incident)[j]];
-                s += w;
-                ent += (w * log(w));
-            }
-            VECTOR(*res)[i] = (log(s) - ent / s) / log(k);
-        }
-    } else {
-        IGRAPH_CHECK(igraph_vector_resize(res, 0));
-        IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit));
-        IGRAPH_FINALLY(igraph_vit_destroy, &vit);
-
-        for (IGRAPH_VIT_RESET(vit), i = 0;
-             !IGRAPH_VIT_END(vit);
-             IGRAPH_VIT_NEXT(vit), i++) {
-            long int v = IGRAPH_VIT_GET(vit);
-            s = ent = 0.0;
-            IGRAPH_CHECK(igraph_incident(graph, &incident, (igraph_integer_t) v,
-                                         /*mode=*/ IGRAPH_ALL));
-            for (j = 0, k = (int) igraph_vector_size(&incident); j < k; j++) {
-                w = VECTOR(*weights)[(long int)VECTOR(incident)[j]];
+    IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit));
+    IGRAPH_FINALLY(igraph_vit_destroy, &vit);
+
+    igraph_vector_clear(res);
+    IGRAPH_CHECK(igraph_vector_reserve(res, IGRAPH_VIT_SIZE(vit)));
+
+    for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) {
+        igraph_real_t d;
+        long int v = IGRAPH_VIT_GET(vit);
+
+        IGRAPH_CHECK(igraph_incident(graph, &incident, v, /*mode=*/ IGRAPH_ALL));
+        k = igraph_vector_size(&incident); /* degree */
+
+        /*
+         * Non-normalized diversity is defined as
+         * d = -sum_i w_i/s log (w_i/s)
+         * where s = sum_i w_i. In order to avoid two passes through the w vector,
+         * we use the equivalent formulation of
+         * d = log s - (sum_i w_i log w_i) / s
+         * However, this formulation may not give an exact 0.0 for some w when k=1,
+         * due to roundoff errors (examples: w=3 or w=7). For this reason, we
+         * special-case the computation for k=1 even for the unnormalized diversity
+         * insted of just setting the normalization factor to 1 for this case.
+         */
+        if (k == 0) {
+            d = IGRAPH_NAN;
+        } else if (k == 1) {
+            if (VECTOR(*weights)[0] > 0) d = 0.0; /* s > 0 */
+            else d = IGRAPH_NAN; /* s == 0 */
+        } else {
+            igraph_real_t s = 0.0, ent = 0.0;
+            for (i = 0; i < k; i++) {
+                igraph_real_t w = VECTOR(*weights)[(long int)VECTOR(incident)[i]];
+                if (w == 0) continue;
                 s += w;
                 ent += (w * log(w));
             }
-            IGRAPH_CHECK(igraph_vector_push_back(res, (log(s) - ent / s) / log(k)));
+            d = (log(s) - ent / s) / log(k);
         }
 
-        igraph_vit_destroy(&vit);
-        IGRAPH_FINALLY_CLEAN(1);
+        igraph_vector_push_back(res, d); /* reserved */
     }
 
+    igraph_vit_destroy(&vit);
     igraph_vector_destroy(&incident);
-    IGRAPH_FINALLY_CLEAN(1);
+    IGRAPH_FINALLY_CLEAN(2);
 
-    return 0;
+    return IGRAPH_SUCCESS;
 }
 
 /**
diff -pruN 0.9.6+ds-2/src/properties/dag.c 0.9.9+ds-1/src/properties/dag.c
--- 0.9.6+ds-2/src/properties/dag.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/properties/dag.c	2022-06-04 12:27:41.000000000 +0000
@@ -37,7 +37,7 @@
  * of its vertices where each vertex comes before all nodes to which it has
  * edges. Every DAG has at least one topological sort, and may have many.
  * This function returns one possible topological sort among them. If the
- * graph is not acyclic (it has at least one cycle), an error is raised.
+ * graph contains any cycles that are not self-loops, an error is raised.
  *
  * \param graph The input graph.
  * \param res Pointer to a vector, the result will be stored here.
@@ -124,7 +124,7 @@ int igraph_topological_sorting(const igr
 
 /**
  * \function igraph_is_dag
- * Checks whether a graph is a directed acyclic graph (DAG) or not.
+ * \brief Checks whether a graph is a directed acyclic graph (DAG).
  *
  * </para><para>
  * A directed acyclic graph (DAG) is a directed graph with no cycles.
diff -pruN 0.9.6+ds-2/src/random/random.c 0.9.9+ds-1/src/random/random.c
--- 0.9.6+ds-2/src/random/random.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/src/random/random.c	2022-06-04 12:27:41.000000000 +0000
@@ -489,8 +489,6 @@ const igraph_rng_type_t igraph_rngtype_m
 
 /* ------------------------------------ */
 
-#ifndef USING_R
-
 igraph_i_rng_mt19937_state_t igraph_i_rng_default_state;
 
 #define addr(a) (&a)
@@ -533,102 +531,12 @@ void igraph_rng_set_default(igraph_rng_t
     igraph_i_rng_default = (*rng);
 }
 
-#endif
-
-
-/* ------------------------------------ */
-
-#ifdef USING_R
-
-double  unif_rand(void);
-double  norm_rand(void);
-double  exp_rand(void);
-double  Rf_rgeom(double);
-double  Rf_rbinom(double, double);
-double  Rf_rgamma(double, double);
-
-int igraph_rng_R_init(void **state) {
-    IGRAPH_ERROR("R RNG error, unsupported function called",
-                 IGRAPH_EINTERNAL);
-    return IGRAPH_SUCCESS;
-}
-
-void igraph_rng_R_destroy(void *state) {
-    igraph_error("R RNG error, unsupported function called",
-                 __FILE__, __LINE__, IGRAPH_EINTERNAL);
-}
-
-int igraph_rng_R_seed(void *state, unsigned long int seed) {
-    IGRAPH_ERROR("R RNG error, unsupported function called",
-                 IGRAPH_EINTERNAL);
-    return IGRAPH_SUCCESS;
-}
-
-unsigned long int igraph_rng_R_get(void *state) {
-    return (unsigned long) (unif_rand() * 0x7FFFFFFFUL);
-}
-
-igraph_real_t igraph_rng_R_get_real(void *state) {
-    return unif_rand();
-}
-
-igraph_real_t igraph_rng_R_get_norm(void *state) {
-    return norm_rand();
-}
-
-igraph_real_t igraph_rng_R_get_geom(void *state, igraph_real_t p) {
-    return Rf_rgeom(p);
-}
-
-igraph_real_t igraph_rng_R_get_binom(void *state, long int n,
-                                     igraph_real_t p) {
-    return Rf_rbinom(n, p);
-}
-
-igraph_real_t igraph_rng_R_get_gamma(void *state, igraph_real_t shape,
-                                     igraph_real_t scale) {
-    return Rf_rgamma(shape, scale);
-}
-
-igraph_real_t igraph_rng_R_get_exp(void *state, igraph_real_t rate) {
-    igraph_real_t scale = 1.0 / rate;
-    if (!IGRAPH_FINITE(scale) || scale <= 0.0) {
-        if (scale == 0.0) {
-            return 0.0;
-        }
-        return IGRAPH_NAN;
-    }
-    return scale * exp_rand();
-}
-
-igraph_rng_type_t igraph_rngtype_R = {
-    /* name= */      "GNU R",
-    /* min=  */      0,
-    /* max=  */      0x7FFFFFFFUL,
-    /* init= */      igraph_rng_R_init,
-    /* destroy= */   igraph_rng_R_destroy,
-    /* seed= */      igraph_rng_R_seed,
-    /* get= */       igraph_rng_R_get,
-    /* get_real= */  igraph_rng_R_get_real,
-    /* get_norm= */  igraph_rng_R_get_norm,
-    /* get_geom= */  igraph_rng_R_get_geom,
-    /* get_binom= */ igraph_rng_R_get_binom,
-    /* get_exp= */   igraph_rng_R_get_exp
-};
-
-IGRAPH_THREAD_LOCAL igraph_rng_t igraph_i_rng_default = {
-    &igraph_rngtype_R,
-    0,
-    /* def= */ 1
-};
-
-#endif
 
 /* ------------------------------------ */
 
 /**
  * \function igraph_rng_default
- * Query the default random number generator.
+ * \brief Query the default random number generator.
  *
  * \return A pointer to the default random number generator.
  *
@@ -721,15 +629,21 @@ unsigned long int igraph_rng_max(igraph_
  * \function igraph_rng_min
  * \brief Query the minimum possible integer for a random number generator.
  *
+ * <emphasis>This function will be removed in a future version. Assume zero
+ * as the retun value.</emphasis>
+ *
  * \param rng The RNG.
  * \return The smallest possible integer that can be generated by
  *         calling \ref igraph_rng_get_integer() on the RNG.
  *
  * Time complexity: O(1).
+ *
+ * \deprecated 0.9.3
  */
 
 unsigned long int igraph_rng_min(igraph_rng_t *rng) {
     const igraph_rng_type_t *type = rng->type;
+    IGRAPH_WARNING("igraph_rng_min() is deprecated; assume 0 as the return value.");
     return type->min;
 }
 
@@ -767,18 +681,31 @@ const char *igraph_rng_name(igraph_rng_t
 long int igraph_rng_get_integer(igraph_rng_t *rng,
                                 long int l, long int h) {
     const igraph_rng_type_t *type = rng->type;
+    /* We require the random integer to be in the range [l, h]. We do so by
+     * first casting (truncate toward zero) to the range [0, h - l] and then add
+     * l to arrive at the range [l, h]. That is, we calculate
+     * (long)( r * (h - l + 1) ) + l
+     * instead of
+     * (long)( r * (h - l + 1) + l),
+     * please note the difference in the parentheses.
+     *
+     * In the latter formulation, if l is negative, this would incorrectly lead
+     * to the range [l + 1, h] instead of the desired [l, h] because negative
+     * numbers are truncated towards zero when cast. For example, if l = -5, any
+     * real in the range (-5, -4] would get cast to -4, not to -5.
+     */
     if (type->get_real) {
-        return (long int)(type->get_real(rng->state) * (h - l + 1) + l);
+        return (long int)(type->get_real(rng->state) * (h - l + 1)) + l;
     } else if (type->get) {
         unsigned long int max = type->max;
-        return (long int)(type->get(rng->state) / ((double)max + 1) * (h - l + 1) + l);
+        return (long int)(type->get(rng->state) / ((double)max + 1) * (h - l + 1)) + l;
     }
     IGRAPH_FATAL("Internal random generator error");
 }
 
 /**
  * \function igraph_rng_get_normal
- * Normally distributed random numbers
+ * \brief Normally distributed random numbers.
  *
  * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default()
  *        here to use the default igraph RNG.
@@ -801,7 +728,7 @@ igraph_real_t igraph_rng_get_normal(igra
 
 /**
  * \function igraph_rng_get_unif
- * Generate real, uniform random numbers from an interval
+ * \brief Generate real, uniform random numbers from an interval.
  *
  * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default()
  *        here to use the default igraph RNG.
@@ -827,7 +754,7 @@ igraph_real_t igraph_rng_get_unif(igraph
 
 /**
  * \function igraph_rng_get_unif01
- * Generate real, uniform random number from the unit interval
+ * \brief Generate real, uniform random number from the unit interval.
  *
  * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default()
  *        here to use the default igraph RNG.
@@ -849,7 +776,7 @@ igraph_real_t igraph_rng_get_unif01(igra
 
 /**
  * \function igraph_rng_get_geom
- * Generate geometrically distributed random numbers
+ * \brief Generate geometrically distributed random numbers.
  *
  * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default()
  *        here to use the default igraph RNG.
@@ -871,7 +798,7 @@ igraph_real_t igraph_rng_get_geom(igraph
 
 /**
  * \function igraph_rng_get_binom
- * Generate binomially distributed random numbers
+ * \brief Generate binomially distributed random numbers.
  *
  * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default()
  *        here to use the default igraph RNG.
@@ -894,7 +821,7 @@ igraph_real_t igraph_rng_get_binom(igrap
 
 /**
  * \function igraph_rng_get_gamma
- * Generate sample from a Gamma distribution
+ * \brief Generate sample from a Gamma distribution.
  *
  * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default()
  *        here to use the default igraph RNG.
@@ -937,45 +864,6 @@ igraph_real_t igraph_rng_get_exp(igraph_
 }
 
 
-#ifndef HAVE_EXPM1
-#ifndef USING_R         /* R provides a replacement */
-/* expm1 replacement */
-double expm1 (double x) {
-    if (fabs(x) < M_LN2) {
-        /* Compute the Taylor series S = x + (1/2!) x^2 + (1/3!) x^3 + ... */
-
-        double i = 1.0;
-        double sum = x;
-        double term = x / 1.0;
-
-        do {
-            term *= x / ++i;
-            sum += term;
-        } while (fabs(term) > fabs(sum) * 2.22e-16);
-
-        return sum;
-    }
-
-    return expl(x) - 1.0L;
-}
-#endif
-#endif
-
-#ifndef HAVE_RINT
-#ifndef USING_R         /* R provides a replacement */
-/* rint replacement */
-double rint (double x) {
-    return ( (x < 0.) ? -floor(-x + .5) : floor(x + .5) );
-}
-#endif
-#endif
-
-#ifndef HAVE_RINTF
-float rintf (float x) {
-    return ( (x < (float)0.) ? -(float)floor(-x + .5) : (float)floor(x + .5) );
-}
-#endif
-
 /*
  * \ingroup internal
  *
@@ -1187,39 +1075,6 @@ int igraph_random_sample(igraph_vector_t
     return retval;
 }
 
-#ifdef USING_R
-
-/* These are never called. But they are correct, nevertheless */
-
-double igraph_norm_rand(igraph_rng_t *rng) {
-    return norm_rand();
-}
-
-double igraph_rgeom(igraph_rng_t *rng, double p) {
-    return Rf_rgeom(p);
-}
-
-double igraph_rbinom(igraph_rng_t *rng, double nin, double pp) {
-    return Rf_rbinom(nin, pp);
-}
-
-double igraph_rexp(igraph_rng_t *rng, double rate) {
-    igraph_real_t scale = 1.0 / rate;
-    if (!IGRAPH_FINITE(scale) || scale <= 0.0) {
-        if (scale == 0.0) {
-            return 0.0;
-        }
-        return IGRAPH_NAN;
-    }
-    return scale * exp_rand();
-}
-
-double igraph_rgamma(igraph_rng_t *rng, double shape, double scale) {
-    return Rf_rgamma(shape, scale);
-}
-
-#else
-
 /*
  *  Mathlib : A C Library of Special Functions
  *  Copyright (C) 1998 Ross Ihaka
@@ -1288,6 +1143,12 @@ double igraph_rgamma(igraph_rng_t *rng,
  *
  */
 
+/* The ISNAN macro is used in some of the code borrowed from R below. */
+#define ISNAN isnan
+
+/* Indicates that we use systems which support NaN values. */
+#define IEEE_754 1
+
 /* Private header file for use during compilation of Mathlib */
 #ifndef MATHLIB_PRIVATE_H
 #define MATHLIB_PRIVATE_H
@@ -2247,11 +2108,11 @@ static double igraph_rgamma(igraph_rng_t
     static const double a6 = -0.1367177;
     static const double a7 = 0.1233795;
 
-    /* State variables [FIXME for threading!] :*/
-    static double aa = 0.;
-    static double aaa = 0.;
-    static double s, s2, d;    /* no. 1 (step 1) */
-    static double q0, b, si, c;/* no. 2 (step 4) */
+    /* State variables: */
+    static IGRAPH_THREAD_LOCAL double aa = 0.;
+    static IGRAPH_THREAD_LOCAL double aaa = 0.;
+    static IGRAPH_THREAD_LOCAL double s, s2, d;    /* no. 1 (step 1) */
+    static IGRAPH_THREAD_LOCAL double q0, b, si, c;/* no. 2 (step 4) */
 
     double e, p, q, r, t, u, v, w, x, ret_val;
 
@@ -2394,8 +2255,6 @@ static double igraph_rgamma(igraph_rng_t
     return scale * x * x;
 }
 
-#endif
-
 int igraph_rng_get_dirichlet(igraph_rng_t *rng,
                              const igraph_vector_t *alpha,
                              igraph_vector_t *result) {
@@ -2429,33 +2288,3 @@ int igraph_rng_get_dirichlet(igraph_rng_
 
     return IGRAPH_SUCCESS;
 }
-
-/**********************************************************
- * Testing purposes                                       *
- *********************************************************/
-
-/* int main() { */
-
-/*   int i; */
-
-/*   RNG_BEGIN(); */
-
-/*   for (i=0; i<1000; i++) { */
-/*     printf("%li ", RNG_INTEGER(1,10)); */
-/*   } */
-/*   printf("\n"); */
-
-/*   for (i=0; i<1000; i++) { */
-/*     printf("%f ", RNG_UNIF(0,1)); */
-/*   } */
-/*   printf("\n"); */
-
-/*   for (i=0; i<1000; i++) { */
-/*     printf("%f ", RNG_NORMAL(0,5)); */
-/*   } */
-/*   printf("\n"); */
-
-/*   RNG_END(); */
-
-/*   return 0; */
-/* } */
diff -pruN 0.9.6+ds-2/tests/CMakeLists.txt 0.9.9+ds-1/tests/CMakeLists.txt
--- 0.9.6+ds-2/tests/CMakeLists.txt	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/CMakeLists.txt	2022-06-04 12:27:41.000000000 +0000
@@ -113,7 +113,6 @@ add_legacy_tests(
   2wheap
   cutheap
   d_indheap
-  hashtable
   marked_queue
   set
   trie
@@ -516,7 +515,6 @@ add_examples(
   FOLDER examples/simple NAMES
   igraph_community_edge_betweenness
   igraph_community_fastgreedy
-  igraph_community_fluid_communities
   igraph_community_label_propagation
   igraph_community_leading_eigenvector
   igraph_community_leiden
@@ -531,6 +529,8 @@ add_legacy_tests(
   community_label_propagation
   community_label_propagation2
   community_label_propagation3
+  community_walktrap
+  igraph_community_fluid_communities
   igraph_community_infomap
   igraph_community_leading_eigenvector2
   igraph_compare_communities
@@ -539,6 +539,7 @@ add_legacy_tests(
   igraph_modularity_matrix
   igraph_split_join_distance
   levc-stress
+  null_communities
   spinglass
 )
 add_legacy_tests(
diff -pruN 0.9.6+ds-2/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out 0.9.9+ds-1/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out
--- 0.9.6+ds-2/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out	2022-06-04 12:27:41.000000000 +0000
@@ -1,2 +1,2 @@
-[ 0.00 0.00 -0.41
-  0.00 0.00 1.00 ]
+[ 0.00 0.00 -0.91
+  0.00 0.00 0.51 ]
diff -pruN 0.9.6+ds-2/tests/regression/igraph_read_graph_graphml_invalid_inputs.c 0.9.9+ds-1/tests/regression/igraph_read_graph_graphml_invalid_inputs.c
--- 0.9.6+ds-2/tests/regression/igraph_read_graph_graphml_invalid_inputs.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/regression/igraph_read_graph_graphml_invalid_inputs.c	2022-06-04 12:27:41.000000000 +0000
@@ -71,6 +71,7 @@ int main(int argc, char* argv[]) {
     RUN_TEST("invalid1.graphml", /* should_parse = */ 0);
     RUN_TEST("invalid2.graphml", /* should_parse = */ 1);
     RUN_TEST("invalid3.graphml", /* should_parse = */ 0);
+    RUN_TEST("invalid4.graphml", /* should_parse = */ 0);
 
     return 0;
 }
diff -pruN 0.9.6+ds-2/tests/regression/invalid4.graphml 0.9.9+ds-1/tests/regression/invalid4.graphml
--- 0.9.6+ds-2/tests/regression/invalid4.graphml	1970-01-01 00:00:00.000000000 +0000
+++ 0.9.9+ds-1/tests/regression/invalid4.graphml	2022-06-04 12:27:41.000000000 +0000
@@ -0,0 +1 @@
+<!DOCTYPEy[<!ENTITYa]>
\ No newline at end of file
diff -pruN 0.9.6+ds-2/tests/unit/all_shortest_paths.c 0.9.9+ds-1/tests/unit/all_shortest_paths.c
--- 0.9.6+ds-2/tests/unit/all_shortest_paths.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/all_shortest_paths.c	2022-06-04 12:27:41.000000000 +0000
@@ -128,9 +128,38 @@ int main() {
     }
     IGRAPH_ASSERT(igraph_vector_ptr_size(&paths) == VECTOR(nrgeo)[to]);
 
+    igraph_vector_ptr_free_all(&paths);
+    igraph_vector_destroy(&weights);
+    igraph_destroy(&graph);
+
+    /* Graph is from https://github.com/igraph/rigraph/issues/314 */
+    printf("\nWeighted, multiple weighted shortest paths, testing tolerances\n");
+    igraph_small(&graph, 0, IGRAPH_UNDIRECTED,
+                 0,3, 1,2, 2,5, 2,3, 2,4, 3,5, 5,6, 6,7, 1,8, 8,9, 4,10, 6,11, 8,12, 8,13, 4,14, 7,15,
+                 -1);
+
+    igraph_real_t weights_raw[] = { 1.9617537, 0.9060834, 2.2165446, 1.6251956,
+                                    2.4473929, 0.5913490, 8.7093236, 2.8387330,
+                                    6.1225042, 20.7217776, 6.8027218, 16.3147479,
+                                    5.2605598, 6.6816853, 4.9482123, 1.8989790 };
+
+    /* Choose carefully: If not using tolerances, the result would be incorrect
+     * for starting vertices 5 and 6, but not for all other starting vertices. */
+    from = 6;
+    printf("From: %" IGRAPH_PRId ", to: all.\n", from);
+
+    igraph_vector_view(&weights, weights_raw, sizeof(weights_raw) / sizeof(igraph_real_t));
+    igraph_get_all_shortest_paths_dijkstra(&graph, &paths, &nrgeo, from, igraph_vss_all(), &weights, IGRAPH_ALL);
+
+    for (i=0; i < igraph_vector_ptr_size(&paths); ++i) {
+        print_vector(VECTOR(paths)[i]);
+    }
+
+    printf("nrgeo: ");
+    print_vector(&nrgeo);
+
     igraph_vector_ptr_destroy_all(&paths);
 
-    igraph_vector_destroy(&weights);
     igraph_vector_destroy(&nrgeo);
     igraph_destroy(&graph);
 
diff -pruN 0.9.6+ds-2/tests/unit/all_shortest_paths.out 0.9.9+ds-1/tests/unit/all_shortest_paths.out
--- 0.9.6+ds-2/tests/unit/all_shortest_paths.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/all_shortest_paths.out	2022-06-04 12:27:41.000000000 +0000
@@ -22,3 +22,32 @@ Weighted, multiple weighted shortest pat
 ( 0 1 2 5 4 )
 ( 0 1 6 7 3 4 )
 ( 0 1 2 3 4 )
+
+Weighted, multiple weighted shortest paths, testing tolerances
+From: 6, to: all.
+( 6 5 3 0 )
+( 6 5 3 2 1 )
+( 6 5 2 1 )
+( 6 5 3 2 )
+( 6 5 2 )
+( 6 5 3 )
+( 6 5 2 4 )
+( 6 5 3 2 4 )
+( 6 5 )
+( 6 )
+( 6 7 )
+( 6 5 3 2 1 8 )
+( 6 5 2 1 8 )
+( 6 5 3 2 1 8 9 )
+( 6 5 2 1 8 9 )
+( 6 5 3 2 4 10 )
+( 6 5 2 4 10 )
+( 6 11 )
+( 6 5 3 2 1 8 12 )
+( 6 5 2 1 8 12 )
+( 6 5 2 1 8 13 )
+( 6 5 3 2 1 8 13 )
+( 6 5 2 4 14 )
+( 6 5 3 2 4 14 )
+( 6 7 15 )
+nrgeo: ( 1 2 2 1 2 1 1 1 2 2 2 1 2 2 2 1 )
diff -pruN 0.9.6+ds-2/tests/unit/bfs.c 0.9.9+ds-1/tests/unit/bfs.c
--- 0.9.6+ds-2/tests/unit/bfs.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/bfs.c	2022-06-04 12:27:41.000000000 +0000
@@ -127,6 +127,15 @@ int main() {
                0, 0, 0, 0, 0, 0, &bfs_callback, 0);
     printf(" )\n");
 
+    /* Empty root vertex vector */
+
+    igraph_vector_clear(&roots);
+    printf("(");
+    igraph_bfs(&graph, /*root=*/ -1, &roots, /*neimode=*/ IGRAPH_OUT,
+               /*unreachable=*/ 0, &restricted,
+               0, 0, 0, 0, 0, 0, &bfs_callback, 0);
+    printf(" )\n");
+
     igraph_vector_destroy(&roots);
     igraph_vector_destroy(&restricted);
     igraph_destroy(&graph);
diff -pruN 0.9.6+ds-2/tests/unit/bfs.out 0.9.9+ds-1/tests/unit/bfs.out
--- 0.9.6+ds-2/tests/unit/bfs.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/bfs.out	2022-06-04 12:27:41.000000000 +0000
@@ -10,3 +10,4 @@
 ( 5 6 7 8 9 10 11 19 12 18 13 17 14 16 15 )
 ( )
 ( 6 5 7 8 9 )
+( )
diff -pruN 0.9.6+ds-2/tests/unit/community_label_propagation2.out 0.9.9+ds-1/tests/unit/community_label_propagation2.out
--- 0.9.6+ds-2/tests/unit/community_label_propagation2.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/community_label_propagation2.out	2022-06-04 12:27:41.000000000 +0000
@@ -1,3 +1,3 @@
-( 0 1 1 2 )
-( 1 1 0 2 0 0 0 3 )
+( 0 2 2 1 )
+( 2 1 0 1 0 0 0 3 )
 ( 0 0 0 0 0 )
diff -pruN 0.9.6+ds-2/tests/unit/community_label_propagation3.c 0.9.9+ds-1/tests/unit/community_label_propagation3.c
--- 0.9.6+ds-2/tests/unit/community_label_propagation3.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/community_label_propagation3.c	2022-06-04 12:27:41.000000000 +0000
@@ -55,19 +55,13 @@ int main() {
     for (i = 0; i < igraph_vcount(&g); i++) {
         /* Check that the "fixed" vector has not been changed */
         if (i == 7 || i == 13) {
-            if (!VECTOR(fixed)[i]) {
-                return 1;
-            }
+            IGRAPH_ASSERT(VECTOR(fixed)[i]);
         } else {
-            if (VECTOR(fixed)[i]) {
-                return 1;
-            }
+            IGRAPH_ASSERT(!VECTOR(fixed)[i]);
         }
 
         /* Check that no vertex remained unlabeled */
-        if (VECTOR(membership)[i] < 0) {
-            return 2;
-        }
+        IGRAPH_ASSERT(VECTOR(membership)[i] >= 0);
     }
 
     igraph_vector_bool_destroy(&fixed);
diff -pruN 0.9.6+ds-2/tests/unit/community_label_propagation.c 0.9.9+ds-1/tests/unit/community_label_propagation.c
--- 0.9.6+ds-2/tests/unit/community_label_propagation.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/community_label_propagation.c	2022-06-04 12:27:41.000000000 +0000
@@ -30,7 +30,23 @@ int main() {
     igraph_t g;
     igraph_vector_t membership, weights, initial;
     igraph_vector_bool_t fixed;
-    long int i;
+    igraph_integer_t i, j;
+
+    /* Simple triangle graph, the output should be always one community */
+    igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,  1,  0,  2,  1,  2, -1);
+    igraph_vector_init(&membership, 0);
+
+    for (j = 0; j < 100; j++) {
+        /* label propagation is a stochastic method */
+        igraph_rng_seed(igraph_rng_default(), j);
+
+        igraph_community_label_propagation(&g, &membership, 0, 0, 0, 0);
+
+        for (i = 0; i < 3; i++)
+            IGRAPH_ASSERT(VECTOR(membership)[i] == VECTOR(membership)[0]);
+    }
+
+    igraph_destroy(&g);
 
     /* label propagation is a stochastic method */
     igraph_rng_seed(igraph_rng_default(), 765);
@@ -55,9 +71,7 @@ int main() {
                  31, 32, 31, 33, 32, 33,
                  -1);
 
-    igraph_vector_init(&membership, 0);
-    igraph_community_label_propagation(&g, &membership, 0, 0, 0,
-                                       /*modularity=*/ 0);
+    igraph_community_label_propagation(&g, &membership, 0, 0, 0, 0);
 
     igraph_destroy(&g);
 
@@ -74,15 +88,13 @@ int main() {
     igraph_community_label_propagation(&g, &membership, &weights,
                                        &initial, &fixed, /*modularity=*/ 0);
     for (i = 0; i < igraph_vcount(&g); i++)
-        if (VECTOR(membership)[i] != (i < 2 ? 0 : 1)) {
-            return 3;
-        }
+        IGRAPH_ASSERT(VECTOR(membership)[i] == (i < 2 ? 0 : 1));
+
     igraph_community_label_propagation(&g, &membership, 0,
-                                       &initial, &fixed, /*modularity=*/ 0);
+                                       &initial, &fixed, 0);
+
     for (i = 0; i < igraph_vcount(&g); i++)
-        if (VECTOR(membership)[i] != 0) {
-            return 4;
-        }
+        IGRAPH_ASSERT(VECTOR(membership)[i] == 0);
 
     /* Check whether it works with no fixed vertices at all
      * while an initial configuration is given -- see bug
diff -pruN 0.9.6+ds-2/tests/unit/community_walktrap.c 0.9.9+ds-1/tests/unit/community_walktrap.c
--- 0.9.6+ds-2/tests/unit/community_walktrap.c	1970-01-01 00:00:00.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/community_walktrap.c	2022-06-04 12:27:41.000000000 +0000
@@ -0,0 +1,204 @@
+/* -*- mode: C -*-  */
+/*
+   IGraph library.
+   Copyright (C) 2006-2012  Gabor Csardi <csardi.gabor@gmail.com>
+   334 Harvard street, Cambridge, MA 02139 USA
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA
+
+*/
+
+#include <igraph.h>
+
+#include <math.h>
+
+#include "test_utilities.inc"
+
+/* Replace modularity values which are very close to zero
+ * by exact zeros, so that we can have consistent test outputs
+ * across platforms. */
+void fixup_modularity(igraph_vector_t *modularity) {
+    igraph_integer_t i;
+    igraph_integer_t len = igraph_vector_size(modularity);
+
+    for (i=0; i < len; ++i) {
+        if (fabs(VECTOR(*modularity)[i]) < 1e-15) {
+            VECTOR(*modularity)[i] = 0.0;
+        }
+    }
+}
+
+int main() {
+  igraph_t graph;
+
+  igraph_matrix_t merges;
+  igraph_vector_t modularity;
+  igraph_vector_t membership;
+  igraph_vector_t weights;
+
+  /* Set default seed to get reproducible results */
+  igraph_rng_seed(igraph_rng_default(), 42);
+
+  printf("Basic test\n\n");
+
+  /* Simple unweighted graph */
+  igraph_small(&graph, 3, IGRAPH_UNDIRECTED,
+      0,1, 1,2, 2,0, -1);
+
+  igraph_matrix_init(&merges, 0, 0);
+  igraph_vector_init(&modularity, 0);
+  igraph_vector_init(&membership, 0);
+
+  igraph_community_walktrap(&graph, NULL, 4, &merges, &modularity, &membership);
+  printf("Merges:\n");
+  print_matrix(&merges);
+
+  printf("Modularity: ");
+  fixup_modularity(&modularity);
+  print_vector(&modularity);
+
+  printf("Membership: ");
+  print_vector(&membership);
+
+  igraph_vector_destroy(&membership);
+  igraph_vector_destroy(&modularity);
+  igraph_matrix_destroy(&merges);
+
+  /* Test the case when modularity=NULL and membership=NULL as this caused a crash in
+   * the R interface, see https://github.com/igraph/rigraph/issues/289 */
+
+  printf("\nWithout modularity and membership calculation\n\n");
+
+  igraph_matrix_init(&merges, 0, 0);
+
+  igraph_community_walktrap(&graph, NULL, 4, &merges, NULL, NULL);
+  printf("Merges:\n");
+  print_matrix(&merges);
+  igraph_matrix_destroy(&merges);
+
+  /* Test the case when merges=NULL but modularity is requested,
+   * as the result was previously invalid due to not incrementing
+   * the "merge index" during computation. */
+
+  printf("\nWithout merges matrix calculation\n\n");
+
+  igraph_vector_init(&modularity, 0);
+  igraph_community_walktrap(&graph, NULL, 4, NULL, &modularity, NULL);
+  printf("Modularity:\n");
+  fixup_modularity(&modularity);
+  print_vector(&modularity);
+  igraph_vector_destroy(&modularity);
+
+  igraph_destroy(&graph);
+
+  /* Test for bug https://github.com/igraph/igraph/issues */
+
+  printf("\nBug 2042\n\n");
+
+  igraph_small(&graph, 3, IGRAPH_UNDIRECTED,
+               0,1, -1);
+
+  igraph_matrix_init(&merges, 0, 0);
+  igraph_vector_init(&modularity, 0);
+  igraph_vector_init(&membership, 0);
+  igraph_vector_init_real(&weights, 1, 0.2);
+
+  igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership);
+  printf("Merges:\n");
+  print_matrix(&merges);
+
+  printf("Modularity: ");
+  fixup_modularity(&modularity);
+  print_vector(&modularity);
+
+  printf("Membership: ");
+  print_vector(&membership);
+
+  igraph_vector_destroy(&weights);
+  igraph_vector_destroy(&membership);
+  igraph_vector_destroy(&modularity);
+  igraph_matrix_destroy(&merges);
+
+  igraph_destroy(&graph);
+
+  printf("\nSmall weighted graph\n\n");
+
+  igraph_ring(&graph, 6, IGRAPH_UNDIRECTED, 0, 1);
+
+  igraph_matrix_init(&merges, 0, 0);
+  igraph_vector_init(&modularity, 0);
+  igraph_vector_init(&membership, 0);
+  igraph_vector_init_real(&weights, 6,
+                          1.0, 0.5, 0.25, 0.75, 1.25, 1.5);
+
+  igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership);
+  printf("Merges:\n");
+  print_matrix(&merges);
+
+  printf("Modularity: ");
+  fixup_modularity(&modularity);
+  print_vector(&modularity);
+
+  printf("Membership: ");
+  print_vector(&membership);
+
+  /* Negative weights are not allowed */
+  VECTOR(weights)[0] = -0.1;
+  CHECK_ERROR(igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership), IGRAPH_EINVAL);
+
+  /* Invalid weight vector size */
+  VECTOR(weights)[0] = 0.1;
+  igraph_vector_pop_back(&weights);
+  CHECK_ERROR(igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership), IGRAPH_EINVAL);
+
+  igraph_vector_destroy(&weights);
+  igraph_vector_destroy(&membership);
+  igraph_vector_destroy(&modularity);
+  igraph_matrix_destroy(&merges);
+
+  igraph_destroy(&graph);
+
+  printf("\nIsolated vertices\n\n");
+
+  igraph_empty(&graph, 5, IGRAPH_UNDIRECTED);
+
+  igraph_matrix_init(&merges, 0, 0);
+  igraph_vector_init(&modularity, 0);
+  igraph_vector_init(&membership, 0);
+
+  igraph_community_walktrap(&graph, NULL, 4, &merges, &modularity, &membership);
+  printf("Merges:\n");
+  print_matrix(&merges);
+
+  printf("Modularity: ");
+  fixup_modularity(&modularity);
+  print_vector(&modularity);
+
+  printf("Membership: ");
+  print_vector(&membership);
+
+  igraph_vector_destroy(&weights);
+  igraph_vector_destroy(&membership);
+  igraph_vector_destroy(&modularity);
+  igraph_matrix_destroy(&merges);
+
+  igraph_destroy(&graph);
+
+
+  VERIFY_FINALLY_STACK();
+
+  return 0;
+}
diff -pruN 0.9.6+ds-2/tests/unit/community_walktrap.out 0.9.9+ds-1/tests/unit/community_walktrap.out
--- 0.9.6+ds-2/tests/unit/community_walktrap.out	1970-01-01 00:00:00.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/community_walktrap.out	2022-06-04 12:27:41.000000000 +0000
@@ -0,0 +1,42 @@
+Basic test
+
+Merges:
+[        1        2
+         0        3 ]
+Modularity: ( -0.333333 -0.222222 0 )
+Membership: ( 0 0 0 )
+
+Without modularity and membership calculation
+
+Merges:
+[        1        2
+         0        3 ]
+
+Without merges matrix calculation
+
+Modularity:
+( -0.333333 -0.222222 0 )
+
+Bug 2042
+
+Merges:
+[        0        1 ]
+Modularity: ( -0.5 0 )
+Membership: ( 0 0 1 )
+
+Small weighted graph
+
+Merges:
+[        3        4
+         1        2
+         0        5
+         7        8
+         6        9 ]
+Modularity: ( -0.196145 -0.0895692 -0.0147392 0.146259 0.122449 0 )
+Membership: ( 0 1 1 2 2 0 )
+
+Isolated vertices
+
+Merges:
+Modularity: ( NaN )
+Membership: ( 0 1 2 3 4 )
diff -pruN 0.9.6+ds-2/tests/unit/dgemv.out 0.9.9+ds-1/tests/unit/dgemv.out
--- 0.9.6+ds-2/tests/unit/dgemv.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/dgemv.out	2022-06-04 12:27:41.000000000 +0000
@@ -1,23 +1,23 @@
 Input matrix A:
-[        2       -6        0       -7        9       -5        7        1
-        -7       10        2        8        3        0       -7       -9
-         8        0        6       -4       -4       -1        3        3
-        -8        7        4        8       -7       -4        9       -6
-        -6       10       -1       10       -3        2       -7        5 ]
+[        2       -7       -1       -8        9       -6        7        1
+        -8       10        2        8        3        0       -8      -10
+         8       -1        6       -5       -5       -2        3        3
+        -9        7        4        8       -8       -5        9       -7
+        -7       10       -2       10       -4        2       -8        5 ]
 
 Input vector x:
-( -7 3 2 6 7 -5 1 -6 )
+( -8 3 2 6 7 -6 1 -7 )
 
 Result vector DGEMV:
-( 15 199 -106 149 62 )
+( 12 229 -126 181 59 )
 
 Result vector naive:
-( 15 199 -106 149 62 )
+( 12 229 -126 181 59 )
 
 Adding to previous result with beta=2:
 
 Result vector DGEMV:
-( 45 597 -318 447 186 )
+( 36 687 -378 543 177 )
 
 Result vector naive:
-( 45 597 -318 447 186 )
+( 36 687 -378 543 177 )
diff -pruN 0.9.6+ds-2/tests/unit/hashtable.c 0.9.9+ds-1/tests/unit/hashtable.c
--- 0.9.6+ds-2/tests/unit/hashtable.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/hashtable.c	1970-01-01 00:00:00.000000000 +0000
@@ -1,133 +0,0 @@
-/* -*- mode: C -*-  */
-/*
-   IGraph library.
-   Copyright (C) 2006-2012  Gabor Csardi <csardi.gabor@gmail.com>
-   334 Harvard st, Cambridge MA, 02139 USA
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301 USA
-
-*/
-
-#include <igraph.h>
-#include "core/hashtable.h"
-
-#include "test_utilities.inc"
-
-int main() {
-
-    igraph_hashtable_t ht;
-    char *str;
-    const igraph_strvector_t *keys;
-    long int i;
-
-    /* init and destroy */
-    igraph_hashtable_init(&ht);
-    igraph_hashtable_destroy(&ht);
-
-    /* init, add some elements and destroy */
-    igraph_hashtable_init(&ht);
-    igraph_hashtable_addset(&ht, "color", "green", "red");
-    igraph_hashtable_addset(&ht, "size", "", "4");
-    igraph_hashtable_addset(&ht, "color", "", "grey");
-    igraph_hashtable_addset(&ht, "shape", "", "circle");
-    igraph_hashtable_addset(&ht, "shape", "", "diamond");
-    igraph_hashtable_destroy(&ht);
-
-    /* reset */
-    igraph_hashtable_init(&ht);
-    igraph_hashtable_addset(&ht, "color", "green", "red");
-    igraph_hashtable_addset(&ht, "size", "", "4");
-    igraph_hashtable_addset(&ht, "color", "", "grey");
-    igraph_hashtable_addset(&ht, "shape", "", "circle");
-    igraph_hashtable_addset(&ht, "shape", "", "diamond");
-    igraph_hashtable_reset(&ht);
-    igraph_hashtable_addset(&ht, "color", "green", "red");
-    igraph_hashtable_addset(&ht, "size", "", "4");
-    igraph_hashtable_addset(&ht, "color", "", "grey");
-    igraph_hashtable_addset(&ht, "shape", "", "circle");
-    igraph_hashtable_addset(&ht, "shape", "", "diamond");
-    igraph_hashtable_destroy(&ht);
-
-    /* Check semantics */
-    igraph_hashtable_init(&ht);
-    igraph_hashtable_addset(&ht, "color", "green", "red");
-    igraph_hashtable_addset(&ht, "size", "", "4");
-    igraph_hashtable_addset(&ht, "color", "", "grey");
-    igraph_hashtable_addset(&ht, "shape", "", "circle");
-    igraph_hashtable_addset(&ht, "shape", "", "diamond");
-
-    igraph_hashtable_get(&ht, "color", &str);
-    printf("color: %s\n", str);
-    igraph_hashtable_get(&ht, "size", &str);
-    printf("size: %s\n", str);
-    igraph_hashtable_get(&ht, "shape", &str);
-    printf("shape: %s\n", str);
-
-    igraph_hashtable_reset(&ht);
-
-    igraph_hashtable_get(&ht, "color", &str);
-    printf("color: %s\n", str);
-    igraph_hashtable_get(&ht, "size", &str);
-    printf("size: %s\n", str);
-    igraph_hashtable_get(&ht, "shape", &str);
-    printf("shape: %s\n", str);
-
-    igraph_hashtable_getkeys(&ht, &keys);
-    for (i = 0; i < igraph_strvector_size(keys); i++) {
-        igraph_strvector_get(keys, i, &str);
-        printf("%s ", str);
-    }
-    printf("\n");
-
-    igraph_hashtable_destroy(&ht);
-
-    /* addset2 */
-    igraph_hashtable_init(&ht);
-    igraph_hashtable_addset2(&ht, "color", "green", "redddd", 3);
-    igraph_hashtable_addset2(&ht, "size", "", "4111", 1);
-    igraph_hashtable_addset2(&ht, "color", "", "greysdsdf", 4);
-    igraph_hashtable_addset2(&ht, "shape", "", "circle", 6);
-    igraph_hashtable_addset(&ht, "shape", "", "diamond");
-
-    igraph_hashtable_get(&ht, "color", &str);
-    printf("color: %s\n", str);
-    igraph_hashtable_get(&ht, "size", &str);
-    printf("size: %s\n", str);
-    igraph_hashtable_get(&ht, "shape", &str);
-    printf("shape: %s\n", str);
-
-    igraph_hashtable_reset(&ht);
-
-    igraph_hashtable_get(&ht, "color", &str);
-    printf("color: %s\n", str);
-    igraph_hashtable_get(&ht, "size", &str);
-    printf("size: %s\n", str);
-    igraph_hashtable_get(&ht, "shape", &str);
-    printf("shape: %s\n", str);
-
-    igraph_hashtable_getkeys(&ht, &keys);
-    for (i = 0; i < igraph_strvector_size(keys); i++) {
-        igraph_strvector_get(keys, i, &str);
-        printf("%s ", str);
-    }
-    printf("\n");
-
-    igraph_hashtable_destroy(&ht);
-
-    VERIFY_FINALLY_STACK();
-
-    return 0;
-}
diff -pruN 0.9.6+ds-2/tests/unit/hashtable.out 0.9.9+ds-1/tests/unit/hashtable.out
--- 0.9.6+ds-2/tests/unit/hashtable.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/hashtable.out	1970-01-01 00:00:00.000000000 +0000
@@ -1,14 +0,0 @@
-color: grey
-size: 4
-shape: diamond
-color: green
-size: 
-shape: 
-color size shape 
-color: grey
-size: 4
-shape: diamond
-color: green
-size: 
-shape: 
-color size shape 
diff -pruN 0.9.6+ds-2/tests/unit/igraph_arpack_rnsolve.c 0.9.9+ds-1/tests/unit/igraph_arpack_rnsolve.c
--- 0.9.6+ds-2/tests/unit/igraph_arpack_rnsolve.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_arpack_rnsolve.c	2022-06-04 12:27:41.000000000 +0000
@@ -137,7 +137,7 @@ int main() {
 
     for (i = 0; i < DIM; i++) {
         for (j = 0; j < DIM; j++) {
-            MATRIX(A, i, j) = igraph_rng_get_integer(igraph_rng_default(), -10, 10);
+            MATRIX(A, i, j) = igraph_rng_get_integer(igraph_rng_default(), -12, 12);
         }
     }
 
diff -pruN 0.9.6+ds-2/tests/unit/igraph_arpack_rnsolve.out 0.9.9+ds-1/tests/unit/igraph_arpack_rnsolve.out
--- 0.9.6+ds-2/tests/unit/igraph_arpack_rnsolve.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_arpack_rnsolve.out	2022-06-04 12:27:41.000000000 +0000
@@ -1,41 +1,41 @@
 Input matrix:
--6 0 10 3 8 1 -4 10 -8 0
--6 1 0 8 -4 4 -7 1 1 6
-7 -7 8 6 -4 -8 -1 -7 -3 -7
-6 8 -4 -1 10 3 7 7 -3 -8
-1 -7 -4 9 0 5 5 6 -8 10
--9 10 -5 -9 5 3 -5 7 -7 10
--3 0 8 -6 -2 -7 1 -3 -8 1
-2 0 9 -3 0 -9 -4 0 10 0
--9 1 -6 -1 7 10 9 9 8 -2
--7 1 9 -7 10 -1 -2 -5 7 6
+-8 -1 12 4 10 2 -6 12 -10 -1
+-9 2 0 10 -6 5 -9 1 1 7
+9 -9 10 7 -6 -11 -2 -9 -4 -9
+7 10 -6 -2 12 4 9 8 -4 -11
+1 -9 -6 11 -1 7 5 7 -11 11
+-12 11 -7 -12 6 4 -7 8 -9 12
+-5 0 10 -8 -4 -10 1 -4 -11 1
+3 0 11 -4 0 -12 -6 1 12 -1
+-12 1 -8 -2 9 12 11 11 9 -3
+-9 1 11 -10 12 -2 -4 -8 9 7
 
 
 4 largest eigenvalues by magnitude:
 
 Eigenvalues:
-22.0483 0
--21.3281 0
--3.00735 19.2957
--3.00735 -19.2957
+27.9905 0
+-27.0754 0
+-3.58152 24.7518
+-3.58152 -24.7518
 
 3 largest eigenvalues by magnitude:
 
 Eigenvalues:
-22.0483 0
--21.3281 0
--3.00735 19.2957
+27.9905 0
+-27.0754 0
+-3.58152 24.7518
 
 3 smallest eigenvalues by real part:
 
 Eigenvalues:
--21.3281 0
--12.4527 0
--3.00735 19.2957
+-27.0754 0
+-15.6779 0
+-5.08829 0
 
 3 smallest eigenvalues by imaginary part:
 
 Eigenvalues:
--3.00735 19.2957
--3.00735 -19.2957
-12.1099 6.27293
+-3.58152 24.7518
+-3.58152 -24.7518
+14.7011 6.87043
diff -pruN 0.9.6+ds-2/tests/unit/igraph_community_fluid_communities.c 0.9.9+ds-1/tests/unit/igraph_community_fluid_communities.c
--- 0.9.6+ds-2/tests/unit/igraph_community_fluid_communities.c	1970-01-01 00:00:00.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_community_fluid_communities.c	2022-06-04 12:27:41.000000000 +0000
@@ -0,0 +1,94 @@
+/* -*- mode: C -*-  */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+   IGraph library.
+   Copyright (C) 2007-2012  Gabor Csardi <csardi.gabor@gmail.com>
+   334 Harvard street, Cambridge, MA 02139 USA
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA
+
+*/
+
+#include <igraph.h>
+
+#include "test_utilities.inc"
+
+int main() {
+    igraph_t g;
+    igraph_integer_t k;
+    igraph_vector_t membership;
+    igraph_real_t modularity;
+
+    igraph_rng_seed(igraph_rng_default(), 247);
+
+    /* Empty graph */
+    igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1);
+    igraph_vector_init(&membership, 0);
+    igraph_vector_push_back(&membership, 1);
+    igraph_community_fluid_communities(&g, 2, &membership, &modularity);
+    if (!igraph_is_nan(modularity) || igraph_vector_size(&membership) != 0) {
+        return 2;
+    }
+    igraph_vector_destroy(&membership);
+    igraph_destroy(&g);
+
+    /* Graph with one vertex only */
+    igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1);
+    igraph_vector_init(&membership, 0);
+    igraph_community_fluid_communities(&g, 2, &membership, &modularity);
+    if (!igraph_is_nan(modularity) || igraph_vector_size(&membership) != 1 || VECTOR(membership)[0] != 0) {
+        return 3;
+    }
+    igraph_vector_destroy(&membership);
+    igraph_destroy(&g);
+
+    /* Zachary Karate club -- this is just a quick smoke test */
+    igraph_small(&g, 0, IGRAPH_UNDIRECTED,
+                 0,  1,  0,  2,  0,  3,  0,  4,  0,  5,
+                 0,  6,  0,  7,  0,  8,  0, 10,  0, 11,
+                 0, 12,  0, 13,  0, 17,  0, 19,  0, 21,
+                 0, 31,  1,  2,  1,  3,  1,  7,  1, 13,
+                 1, 17,  1, 19,  1, 21,  1, 30,  2,  3,
+                 2,  7,  2,  8,  2,  9,  2, 13,  2, 27,
+                 2, 28,  2, 32,  3,  7,  3, 12,  3, 13,
+                 4,  6,  4, 10,  5,  6,  5, 10,  5, 16,
+                 6, 16,  8, 30,  8, 32,  8, 33,  9, 33,
+                 13, 33, 14, 32, 14, 33, 15, 32, 15, 33,
+                 18, 32, 18, 33, 19, 33, 20, 32, 20, 33,
+                 22, 32, 22, 33, 23, 25, 23, 27, 23, 29,
+                 23, 32, 23, 33, 24, 25, 24, 27, 24, 31,
+                 25, 31, 26, 29, 26, 33, 27, 33, 28, 31,
+                 28, 33, 29, 32, 29, 33, 30, 32, 30, 33,
+                 31, 32, 31, 33, 32, 33,
+                 -1);
+
+    igraph_vector_init(&membership, 0);
+    k = 2;
+    igraph_community_fluid_communities(&g, k, &membership,
+                                       /*modularity=*/ 0);
+    if (!igraph_vector_contains(&membership, 0) || !igraph_vector_contains(&membership, 1)) {
+        printf("Resulting graph does not have exactly 2 communities as expected.\n");
+        igraph_vector_print(&membership);
+        return 1;
+    }
+
+    igraph_destroy(&g);
+    igraph_vector_destroy(&membership);
+
+    VERIFY_FINALLY_STACK();
+
+    return 0;
+}
diff -pruN 0.9.6+ds-2/tests/unit/igraph_diversity.c 0.9.9+ds-1/tests/unit/igraph_diversity.c
--- 0.9.6+ds-2/tests/unit/igraph_diversity.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_diversity.c	2022-06-04 12:27:41.000000000 +0000
@@ -44,21 +44,38 @@ int main() {
     igraph_vector_destroy(&weights);
     igraph_destroy(&g);
 
+    /* degree-one vertices */
+    igraph_tree(&g, 10, 2, IGRAPH_TREE_UNDIRECTED);
+    igraph_vector_init_seq(&weights, 1, igraph_ecount(&g));
+
+    printf("Tree (having degree-one vertices):\n");
+    igraph_diversity(&g, &weights, &result, igraph_vss_all());
+    print_vector(&result);
+
+    igraph_vector_destroy(&weights);
+    igraph_destroy(&g);
+
     /* error conditions are tested from now on */
     VERIFY_FINALLY_STACK();
-    igraph_set_error_handler(igraph_error_handler_ignore);
 
     /* graph with multiple edges */
     igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 2,0, -1);
     igraph_vector_init_int_end(&weights, -1, 3, 2, 8, -1);
-    IGRAPH_ASSERT(igraph_diversity(&g, &weights, &result, igraph_vss_all()) == IGRAPH_EINVAL);
+    CHECK_ERROR(igraph_diversity(&g, &weights, &result, igraph_vss_all()), IGRAPH_EINVAL);
+    igraph_vector_destroy(&weights);
+    igraph_destroy(&g);
+
+    /* negative weights */
+    igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 2,1, -1);
+    igraph_vector_init_int_end(&weights, -1, 3, -2, 8, -1);
+    CHECK_ERROR(igraph_diversity(&g, &weights, &result, igraph_vss_all()), IGRAPH_EINVAL);
     igraph_vector_destroy(&weights);
     igraph_destroy(&g);
 
     /* directed graph */
     igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 0,2, -1);
     igraph_vector_init_int_end(&weights, -1, 3, 2, -1);
-    IGRAPH_ASSERT(igraph_diversity(&g, &weights, &result, igraph_vss_all()) == IGRAPH_EINVAL);
+    CHECK_ERROR(igraph_diversity(&g, &weights, &result, igraph_vss_all()), IGRAPH_EINVAL);
     igraph_vector_destroy(&weights);
     igraph_destroy(&g);
 
diff -pruN 0.9.6+ds-2/tests/unit/igraph_diversity.out 0.9.9+ds-1/tests/unit/igraph_diversity.out
--- 0.9.6+ds-2/tests/unit/igraph_diversity.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_diversity.out	2022-06-04 12:27:41.000000000 +0000
@@ -4,3 +4,5 @@ Empty graph:
 ( NaN NaN NaN NaN NaN )
 Graph with 4 nodes and 5 edges:
 ( 0.970951 0.75 0.69137 1 )
+Tree (having degree-one vertices):
+( 0.918296 0.88686 0.921463 0.934206 0.890492 0 0 0 0 0 )
diff -pruN 0.9.6+ds-2/tests/unit/igraph_hrg3.c 0.9.9+ds-1/tests/unit/igraph_hrg3.c
--- 0.9.6+ds-2/tests/unit/igraph_hrg3.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_hrg3.c	2022-06-04 12:27:41.000000000 +0000
@@ -68,7 +68,7 @@ int main() {
     igraph_hrg_predict(&karate, &edges, &prob, /* hrg= */ 0, /* start= */ 0,
                        /* num_samples= */ 100, /* num_bins= */ 25);
 
-    /* We do some simple validity tests on the resolts only; the exact results
+    /* We do some simple validity tests on the results only; the exact results
      * are different on i386 vs other platforms due to numerical inaccuracies */
     n = igraph_vector_size(&edges);
     for (i = 0; i < n; i++) {
diff -pruN 0.9.6+ds-2/tests/unit/igraph_preference_game.c 0.9.9+ds-1/tests/unit/igraph_preference_game.c
--- 0.9.6+ds-2/tests/unit/igraph_preference_game.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_preference_game.c	2022-06-04 12:27:41.000000000 +0000
@@ -44,7 +44,7 @@ int main() {
     igraph_vector_t types, in_types, out_types;
     igraph_bool_t connected, has_loop, has_multi;
     igraph_vector_bool_t is_loop;
-    long i;
+    igraph_integer_t i, j, count;
 
     igraph_vector_init(&types, 0);
 
@@ -143,6 +143,37 @@ int main() {
 
     igraph_destroy(&g);
 
+    /* fixed sizes, divide evenly */
+    IGRAPH_CHECK(igraph_matrix_resize(&pref_mat, 9, 9));
+    for (i = 0; i < 9; i++) {
+        for (j = 0; j < 9; j++) {
+            MATRIX(pref_mat, i, j) = (j == i + 1 || j == i - 1) ? 0.1 : 0;
+        }
+    }
+    IGRAPH_CHECK(igraph_preference_game(&g, 50, 9, /*type_dist=*/ 0, /*fixed_sizes=*/ 1,
+                                        &pref_mat, &types, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS));
+
+    IGRAPH_ASSERT(igraph_vcount(&g) == 50);
+    IGRAPH_ASSERT(!igraph_is_directed(&g));
+
+    igraph_has_loop(&g, &has_loop);
+    IGRAPH_ASSERT(! has_loop);
+
+    igraph_has_multiple(&g, &has_multi);
+    IGRAPH_ASSERT(! has_multi);
+
+    for (i = 0; i < 9; i++) {
+        count = 0;
+        for (j = 0; j < 50; j++) {
+            if (VECTOR(types)[j] == i) {
+                count++;
+            }
+        }
+        IGRAPH_ASSERT(count == 5 || count == 6);
+    }
+
+    igraph_destroy(&g);
+
     igraph_vector_destroy(&types);
 
     /* Asymmetric preference game */
diff -pruN 0.9.6+ds-2/tests/unit/igraph_psumtree.c 0.9.9+ds-1/tests/unit/igraph_psumtree.c
--- 0.9.6+ds-2/tests/unit/igraph_psumtree.c	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_psumtree.c	2022-06-04 12:27:41.000000000 +0000
@@ -23,7 +23,6 @@
 */
 
 #include <igraph.h>
-#include <stdlib.h>
 
 #include "test_utilities.inc"
 
@@ -33,6 +32,10 @@ int main() {
     long int i;
     igraph_real_t sum;
 
+    igraph_rng_seed(igraph_rng_default(), 42);
+
+    RNG_BEGIN();
+
     /* Uniform random numbers */
     igraph_vector_init(&vec, 16);
     igraph_psumtree_init(&tree, 16);
@@ -51,9 +54,8 @@ int main() {
     }
 
     for (i = 0; i < 16000; i++) {
-        igraph_real_t r = ((double)rand()) / RAND_MAX * sum;
         long int idx;
-        igraph_psumtree_search(&tree, &idx, r);
+        igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum));
         VECTOR(vec)[idx] += 1;
     }
     for (i = 0; i < 16; i++) {
@@ -73,9 +75,8 @@ int main() {
 
     igraph_vector_null(&vec);
     for (i = 0; i < 24000; i++) {
-        igraph_real_t r = ((double)rand()) / RAND_MAX * sum;
         long int idx;
-        igraph_psumtree_search(&tree, &idx, r);
+        igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum));
         VECTOR(vec)[idx] += 1;
     }
     for (i = 0; i < 16; i++) {
@@ -95,9 +96,8 @@ int main() {
 
     igraph_vector_null(&vec);
     for (i = 0; i < 20000; i++) {
-        igraph_real_t r = ((double)rand()) / RAND_MAX * sum;
         long int idx;
-        igraph_psumtree_search(&tree, &idx, r);
+        igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum));
         VECTOR(vec)[idx] += 1;
     }
     if (VECTOR(vec)[0] != 0 || VECTOR(vec)[5] != 0 || VECTOR(vec)[15] != 0) {
@@ -107,6 +107,25 @@ int main() {
     igraph_vector_destroy(&vec);
     igraph_psumtree_destroy(&tree);
 
+    /* Check that search considers intervals closed on the left,
+     * see https://github.com/igraph/igraph/issues/2080 */
+
+    igraph_psumtree_init(&tree, 6);
+    igraph_psumtree_update(&tree, 2, 1.0);
+    igraph_psumtree_update(&tree, 4, 1.0);
+    {
+        long int idx;
+        igraph_psumtree_search(&tree, &idx, 0.0);
+        IGRAPH_ASSERT(idx  == 2);
+        igraph_psumtree_search(&tree, &idx, 0.5);
+        IGRAPH_ASSERT(idx  == 2);
+        igraph_psumtree_search(&tree, &idx, 1.0);
+        IGRAPH_ASSERT(idx  == 4);
+        igraph_psumtree_search(&tree, &idx, 1.2);
+        IGRAPH_ASSERT(idx  == 4);
+    }
+    igraph_psumtree_destroy(&tree);
+
     /****************************************************/
     /* Non power-of-two vector size                     */
     /****************************************************/
@@ -120,9 +139,8 @@ int main() {
     sum = igraph_psumtree_sum(&tree);
 
     for (i = 0; i < 9000; i++) {
-        igraph_real_t r = ((double)rand()) / RAND_MAX * sum;
         long int idx;
-        igraph_psumtree_search(&tree, &idx, r);
+        igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum));
         VECTOR(vec)[idx] += 1;
     }
     for (i = 0; i < 9; i++) {
@@ -139,9 +157,8 @@ int main() {
 
     igraph_vector_null(&vec);
     for (i = 0; i < 14000; i++) {
-        igraph_real_t r = ((double)rand()) / RAND_MAX * sum;
         long int idx;
-        igraph_psumtree_search(&tree, &idx, r);
+        igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum));
         VECTOR(vec)[idx] += 1;
     }
     for (i = 0; i < 9; i++) {
@@ -170,10 +187,9 @@ int main() {
     sum = igraph_psumtree_sum(&tree);
 
     igraph_vector_null(&vec);
-    for (i = 0; i < 9000; i++) {
-        igraph_real_t r = ((double)rand()) / RAND_MAX * sum;
+    for (i = 0; i < 9000; i++) {;
         long int idx;
-        igraph_psumtree_search(&tree, &idx, r);
+        igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum));
         VECTOR(vec)[idx] += 1;
     }
     if (VECTOR(vec)[0] != 0 || VECTOR(vec)[5] != 0 || VECTOR(vec)[8] != 0) {
@@ -199,6 +215,8 @@ int main() {
     }
     igraph_psumtree_destroy(&tree);
 
+    RNG_END();
+
     VERIFY_FINALLY_STACK();
 
     return 0;
diff -pruN 0.9.6+ds-2/tests/unit/igraph_sparsemat_minmax.out 0.9.9+ds-1/tests/unit/igraph_sparsemat_minmax.out
--- 0.9.6+ds-2/tests/unit/igraph_sparsemat_minmax.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_sparsemat_minmax.out	2022-06-04 12:27:41.000000000 +0000
@@ -1,15 +1,15 @@
--7 -9 Inf -7 Inf -6 7 -5 6 7 -9 -6 -6 -9 -4 -3 -1 9 -5 -4
-5 -9 -4 -7 -9 -6 -9 -9 -5 -5
--7 -9 Inf -7 Inf -6 7 -5 6 7 -13 -12 -6 -9 -4 -3 1 9 -5 -4
-7 -9 -4 -7 -7 -12 -13 -9 -5 -5
+-8 -10 Inf -8 Inf -7 7 -6 6 7 -10 -7 -7 -10 -5 -4 -2 9 -6 -5
+5 -10 -5 -8 -10 -7 -10 -10 -6 -6
+-8 -10 Inf -8 Inf -7 7 -6 6 7 -15 -14 -7 -10 -5 -4 0 9 -6 -5
+7 -10 -5 -8 -8 -14 -15 -10 -6 -6
 Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf
 Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf
 Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf
 Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf
-7 9 -Inf 7 -Inf 6 -7 5 -6 -7 9 6 6 9 4 3 1 -9 5 4
--5 9 4 7 9 6 9 9 5 5
-7 9 -Inf 7 -Inf 6 -7 5 -6 -7 13 12 6 9 4 3 -1 -9 5 4
--7 9 4 7 7 12 13 9 5 5
+8 10 -Inf 8 -Inf 7 -7 6 -6 -7 10 7 7 10 5 4 2 -9 6 5
+-5 10 5 8 10 7 10 10 6 6
+8 10 -Inf 8 -Inf 7 -7 6 -6 -7 15 14 7 10 5 4 0 -9 6 5
+-7 10 5 8 8 14 15 10 6 6
 -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf
 -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf
 -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf
diff -pruN 0.9.6+ds-2/tests/unit/igraph_sparsemat_which_minmax.out 0.9.9+ds-1/tests/unit/igraph_sparsemat_which_minmax.out
--- 0.9.6+ds-2/tests/unit/igraph_sparsemat_which_minmax.out	2022-01-06 07:19:38.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/igraph_sparsemat_which_minmax.out	2022-06-04 12:27:41.000000000 +0000
@@ -1,10 +1,10 @@
--7 -9 Inf -7 Inf -6 7 -5 6 7 -9 -6 -6 -9 -4 -3 -1 9 -5 -4
+-8 -10 Inf -8 Inf -7 7 -6 6 7 -10 -7 -7 -10 -5 -4 -2 9 -6 -5
 3 4 0 4 0 3 9 9 9 6 6 5 3 7 3 6 5 3 8 3
-5 -9 -4 -7 -9 -6 -9 -9 -5 -5
+5 -10 -5 -8 -10 -7 -10 -10 -6 -6
 19 10 13 0 1 11 10 13 18 7
--7 -9 Inf -7 Inf -6 7 -5 6 7 -13 -12 -6 -9 -4 -3 1 9 -5 -4
+-8 -10 Inf -8 Inf -7 7 -6 6 7 -15 -14 -7 -10 -5 -4 0 9 -6 -5
 3 1 0 4 0 3 9 9 9 6 6 5 3 7 3 6 5 3 8 3
-7 -9 -4 -7 -7 -12 -13 -9 -5 -5
+7 -10 -5 -8 -8 -14 -15 -10 -6 -6
 0 1 13 0 3 11 10 13 18 7
 Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff -pruN 0.9.6+ds-2/tests/unit/null_communities.c 0.9.9+ds-1/tests/unit/null_communities.c
--- 0.9.6+ds-2/tests/unit/null_communities.c	1970-01-01 00:00:00.000000000 +0000
+++ 0.9.9+ds-1/tests/unit/null_communities.c	2022-06-04 12:27:41.000000000 +0000
@@ -0,0 +1,166 @@
+
+#include <igraph.h>
+
+#include "test_utilities.inc"
+
+/* Testing community detection on null graph:
+ *
+ *  - The modularity should be NaN.
+ *  - In hierarchical methods, the modularity vector should have size 1.
+ */
+
+int main() {
+    igraph_t g;
+    igraph_vector_t modularity, membership;
+    igraph_matrix_t merges;
+    igraph_real_t m;
+    igraph_integer_t nb_communities;
+    igraph_arpack_options_t ao;
+
+    igraph_empty(&g, 0, IGRAPH_UNDIRECTED);
+
+    igraph_vector_init(&modularity, 2);
+    igraph_vector_init(&membership, 1);
+    igraph_matrix_init(&merges, 1, 1);
+
+    /* Edge betweenness */
+
+    igraph_community_edge_betweenness(&g, NULL, NULL, &merges, NULL, &modularity, &membership, 0, NULL);
+
+    IGRAPH_ASSERT(igraph_matrix_nrow(&merges) == 0);
+    IGRAPH_ASSERT(igraph_matrix_ncol(&merges) == 2);
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1);
+    IGRAPH_ASSERT(igraph_is_nan(VECTOR(modularity)[0]));
+
+    /* Fast greedy */
+
+    igraph_vector_resize(&modularity, 2);
+    igraph_vector_resize(&membership, 1);
+    igraph_matrix_resize(&merges, 1, 1);
+
+    igraph_community_fastgreedy(&g, NULL, &merges, &modularity, &membership);
+
+    IGRAPH_ASSERT(igraph_matrix_nrow(&merges) == 0);
+    IGRAPH_ASSERT(igraph_matrix_ncol(&merges) == 2);
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1);
+    IGRAPH_ASSERT(igraph_is_nan(VECTOR(modularity)[0]));
+
+    /* Fluid communities */
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_fluid_communities(&g, 0, &membership, &m);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    /* InfoMAP */
+
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_infomap(&g, NULL, NULL, 3, &membership, &m);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+
+    /* Label propagation */
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_label_propagation(&g, &membership, NULL, NULL, NULL, &m);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    /* Leading eigenvector */
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+    igraph_matrix_resize(&merges, 1, 1);
+
+    igraph_arpack_options_init(&ao);
+    igraph_community_leading_eigenvector(&g, NULL, &merges, &membership, 1, &ao, &m, 0, NULL, NULL, NULL, NULL, NULL);
+
+    IGRAPH_ASSERT(igraph_matrix_nrow(&merges) == 0);
+    IGRAPH_ASSERT(igraph_matrix_ncol(&merges) == 2);
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    /* Leiden */
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_leiden(&g, NULL, NULL, 1, 0.01, 0, &membership, &nb_communities, &m);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    /* Multilevel */
+
+    igraph_vector_resize(&membership, 1);
+    igraph_vector_resize(&modularity, 2);
+
+    igraph_community_multilevel(&g, NULL, 1, &membership, NULL, &modularity);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1);
+    IGRAPH_ASSERT(igraph_is_nan(VECTOR(modularity)[0]));
+
+    /* Optimal modularity */
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_optimal_modularity(&g, &m, &membership, NULL);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    /* Spinglass */
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_spinglass(&g, NULL, &m, NULL, &membership, NULL, 5, 0, 1, 0.01, 0.99, IGRAPH_SPINCOMM_UPDATE_SIMPLE, 1, IGRAPH_SPINCOMM_IMP_ORIG, 1);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    m = 2;
+    igraph_vector_resize(&membership, 1);
+
+    igraph_community_spinglass(&g, NULL, &m, NULL, &membership, NULL, 5, 0, 1, 0.01, 0.99, IGRAPH_SPINCOMM_UPDATE_SIMPLE, 1, IGRAPH_SPINCOMM_IMP_NEG, 1);
+
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_is_nan(m));
+
+    /* Walktrap */
+
+    igraph_vector_resize(&modularity, 2);
+    igraph_vector_resize(&membership, 1);
+    igraph_matrix_resize(&merges, 1, 1);
+
+    igraph_community_walktrap(&g, NULL, 4, &merges, &modularity, &membership);
+
+    IGRAPH_ASSERT(igraph_matrix_nrow(&merges) == 0);
+    IGRAPH_ASSERT(igraph_matrix_ncol(&merges) == 2);
+    IGRAPH_ASSERT(igraph_vector_size(&membership) == 0);
+    IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1);
+    IGRAPH_ASSERT(igraph_is_nan(VECTOR(modularity)[0]));
+
+    /* Cleanup */
+
+    igraph_matrix_destroy(&merges);
+    igraph_vector_destroy(&membership);
+    igraph_vector_destroy(&modularity);
+
+    igraph_destroy(&g);
+
+    VERIFY_FINALLY_STACK();
+
+    return 0;
+}
