├── csharp ├── test │ ├── Usings.cs │ ├── test.csproj │ ├── SubspanTest.cs │ └── MiniballTest.cs ├── miniball │ ├── miniball.csproj │ ├── highdim │ │ ├── Logging.cs │ │ ├── Quality.cs │ │ └── Miniball.cs │ └── model │ │ ├── PointSet.cs │ │ ├── ArrayPointSet.cs │ │ └── PointSetUtils.cs ├── example │ ├── example.csproj │ └── Program.cs └── src.sln ├── cpp ├── test │ ├── .gitignore │ └── example.C └── main │ ├── Seb_point.h │ ├── Seb_debug.h │ ├── Seb_debug.C │ ├── Seb_configure.h │ ├── Seb.h │ ├── Subspan.h │ ├── Subspan-inl.h │ └── Seb-inl.h ├── material ├── seb.pdf └── old │ ├── rankplot.gnp │ ├── TODO │ ├── gensuit.pl │ ├── run_cplex.C │ └── pts2mps.C ├── python ├── .gitignore ├── setup.py ├── miniball │ └── __init__.py ├── pyproject.toml ├── test │ └── test_miniball.py ├── miniball_python.cpp └── CMakeLists.txt ├── .gitignore ├── java ├── src │ ├── test │ │ ├── resources │ │ │ └── com │ │ │ │ └── dreizak │ │ │ │ └── miniball │ │ │ │ └── highdim │ │ │ │ └── data │ │ │ │ ├── simplex_10.data │ │ │ │ └── simplex_15.data │ │ └── java │ │ │ └── com │ │ │ └── dreizak │ │ │ └── miniball │ │ │ └── highdim │ │ │ ├── SubspanTest.java │ │ │ └── MiniballTest.java │ └── main │ │ └── java │ │ └── com │ │ └── dreizak │ │ └── miniball │ │ ├── highdim │ │ ├── Logging.java │ │ ├── Quality.java │ │ ├── Miniball.java │ │ └── Subspan.java │ │ └── model │ │ ├── PointSet.java │ │ ├── ArrayPointSet.java │ │ └── PointSetUtils.java ├── Readme.text └── pom.xml ├── .pre-commit-config.yaml └── README.md /csharp/test/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /cpp/test/.gitignore: -------------------------------------------------------------------------------- 1 | example 2 | times.png 3 | boost* 4 | -------------------------------------------------------------------------------- /material/seb.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hbf/miniball/HEAD/material/seb.pdf -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | miniball_python.so 2 | test.cpp 3 | test.py 4 | __pycache__ 5 | dist/ 6 | build/ 7 | *.egg-info 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | .settings 4 | .project 5 | .classpath 6 | java/target 7 | benchmark/data/*.data 8 | bin 9 | obj 10 | .vscode -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | print("Setuptools installation is deprecated - please use `pip install .`") 4 | setup() 5 | -------------------------------------------------------------------------------- /java/src/test/resources/com/dreizak/miniball/highdim/data/simplex_10.data: -------------------------------------------------------------------------------- 1 | 10 10 2 | 1 0 0 0 0 0 0 0 0 0 3 | 0 1 0 0 0 0 0 0 0 0 4 | 0 0 1 0 0 0 0 0 0 0 5 | 0 0 0 1 0 0 0 0 0 0 6 | 0 0 0 0 1 0 0 0 0 0 7 | 0 0 0 0 0 1 0 0 0 0 8 | 0 0 0 0 0 0 1 0 0 0 9 | 0 0 0 0 0 0 0 1 0 0 10 | 0 0 0 0 0 0 0 0 1 0 11 | 0 0 0 0 0 0 0 0 0 1 12 | -------------------------------------------------------------------------------- /csharp/miniball/miniball.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | -------------------------------------------------------------------------------- /material/old/rankplot.gnp: -------------------------------------------------------------------------------- 1 | # rankplot.gnp 2 | # 2003, by Martin Kutz 3 | # plot rank data from data file "rank.log into "ranks.ps" 4 | 5 | set title "Rank Statistics" 6 | set xlabel "iteration" 7 | set ylabel "rank" 8 | set autoscale 9 | # set border 3 10 | set terminal postscript 11 | set output "ranks.ps" 12 | plot "ranks.log" notitle with lines 13 | -------------------------------------------------------------------------------- /csharp/example/example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | net7.0 10 | enable 11 | enable 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.1.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: https://github.com/astral-sh/ruff-pre-commit 9 | rev: v0.12.9 10 | hooks: 11 | - id: ruff 12 | types_or: [ python ] 13 | args: [ --fix ] 14 | - id: ruff-format 15 | types_or: [ python] 16 | -------------------------------------------------------------------------------- /material/old/TODO: -------------------------------------------------------------------------------- 1 | Questions: 2 | - Do we need size in Affine_sub_span? 3 | - Should Affine_sub_span::clear() intialize to Q={p} instead of Q={}? 4 | - Is it a good idea to update the radius after walking by computing the 5 | distance from the new center to the stopping point? (We do this now 6 | because we don't "know" the index of a ball in Q right now...) 7 | 8 | Possible optimization: 9 | - Implement class Point with C-arrays instead of vectors -- I guess 10 | this doesn't help anything at all. 11 | -------------------------------------------------------------------------------- /python/miniball/__init__.py: -------------------------------------------------------------------------------- 1 | from ._miniball import _compute_miniball 2 | 3 | import numpy as np 4 | 5 | __all__ = ["miniball"] 6 | 7 | 8 | def miniball(points: np.typing.ArrayLike): 9 | """Compute the smallest enclosing ball for a set of points.""" 10 | 11 | points = np.ascontiguousarray(points, dtype=np.float64) 12 | 13 | if len(points.shape) != 2: 14 | msg = f"Input array must be 2-dimensional! Got shape `{points.shape}`." 15 | raise TypeError(msg) 16 | return _compute_miniball(points) 17 | -------------------------------------------------------------------------------- /java/src/test/resources/com/dreizak/miniball/highdim/data/simplex_15.data: -------------------------------------------------------------------------------- 1 | 15 15 2 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 | 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 5 | 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 6 | 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 7 | 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 8 | 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 9 | 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 10 | 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 11 | 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 12 | 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 13 | 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 14 | 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 15 | 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 16 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 17 | -------------------------------------------------------------------------------- /java/src/main/java/com/dreizak/miniball/highdim/Logging.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.highdim; 2 | 3 | /** 4 | * A very simple logging class used for debugging. 5 | */ 6 | class Logging 7 | { 8 | final static boolean log = false; 9 | 10 | public final static void warn(String msg) 11 | { 12 | if (log) System.err.println("[warn] " + msg); 13 | } 14 | 15 | public final static void info(String msg) 16 | { 17 | if (log) System.err.println("[info] " + msg); 18 | } 19 | 20 | public final static void debug(String msg) 21 | { 22 | if (log) System.err.println("[debug] " + msg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /csharp/miniball/highdim/Logging.cs: -------------------------------------------------------------------------------- 1 | namespace SEB; 2 | 3 | /// 4 | /// A very simple logging class used for debugging. 5 | /// 6 | static class Logging 7 | { 8 | 9 | public static bool log = false; 10 | 11 | public static void Warn(string msg) 12 | { 13 | if (log) System.Console.WriteLine($"[warn] {msg}"); 14 | } 15 | 16 | public static void Info(string msg) 17 | { 18 | if (log) System.Console.WriteLine($"[info] {msg}"); 19 | } 20 | 21 | public static void Debug(string msg) 22 | { 23 | if (log) System.Console.WriteLine($"[debug] {msg}"); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core>=0.10", "nanobind>=1.3.2", "numpy"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "miniball" 7 | version = "2.0.0" 8 | description = "Library to find the smallest enclosing ball of points." 9 | readme = "../README.md" 10 | requires-python = ">=3.8" 11 | authors = [ 12 | { name = "Kaspar Fischer" }, 13 | { name = "Bernd Gärtner" }, 14 | { name = "Martin Kutz" }, 15 | ] 16 | 17 | dependencies = [ 18 | "numpy", 19 | "nanobind", 20 | ] 21 | 22 | [project.optional-dependencies] 23 | test = ["pytest"] 24 | 25 | [project.urls] 26 | Source = "https://github.com/hbf/miniball" 27 | Issues = "https://github.com/hbf/miniball/issues" 28 | 29 | [tool.scikit-build] 30 | build.verbose = true 31 | minimum-version = "build-system.requires" 32 | build-dir = "build/{wheel_tag}" 33 | wheel.py-api = "cp312" 34 | -------------------------------------------------------------------------------- /csharp/test/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /java/Readme.text: -------------------------------------------------------------------------------- 1 | Deployment instructions 2 | ======================= 3 | 4 | The Java project has been setup following the instructions from 5 | 6 | http://stackoverflow.com/questions/14013644/hosting-a-maven-repository-on-github/14013645#14013645 7 | 8 | In order to use it, ensure that your ~/.m2/settings.xml contains your Github login credentials: 9 | 10 | 11 | github 12 | hbf 13 | PASSWORD 14 | 15 | 16 | Then use 17 | 18 | mvn clean deploy 19 | 20 | to create the Maven site, store it in the gh-pages branch of the Github project (accessible at http://hbf.github.com/miniball) and store the JAR file in 21 | 22 | https://raw.github.com/hbf/miniball/mvn-repo/ 23 | 24 | After this, any Maven project that uses the repository 25 | 26 | 27 | miniball 28 | https://raw.github.com/hbf/miniball/mvn-repo/ 29 | 30 | true 31 | always 32 | 33 | 34 | 35 | will be able to download the JAR. -------------------------------------------------------------------------------- /python/test/test_miniball.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from miniball import miniball 3 | import numpy as np 4 | 5 | 6 | def test_package(): 7 | from miniball import miniball 8 | 9 | assert miniball.__doc__ is not None and ( 10 | "Compute the smallest enclosing ball for a set of points." in miniball.__doc__ 11 | ) 12 | 13 | 14 | def test_empty_vector(): 15 | with pytest.raises(TypeError, match="Input array must be 2-dimensional"): 16 | miniball(None) 17 | 18 | 19 | def test_identical_points(): 20 | point = [3.0, 1.0, 0.0] 21 | test_vector = np.array([point, point], dtype=np.double) 22 | res = miniball(test_vector) 23 | np.testing.assert_array_equal(res["center"], point) 24 | assert res["radius"] == 0 25 | assert res["radius_squared"] == 0 26 | 27 | 28 | def test_two_points(): 29 | test_vector = np.array([[3.0, 1.0], [3.0, 1.0], [1.0, 0.0]], dtype=np.double) 30 | res = miniball(test_vector) 31 | np.testing.assert_allclose(res["center"], [2.0, 0.5]) 32 | assert res["radius_squared"] == 1.25 33 | 34 | 35 | def test_three_points(): 36 | test_vector = [[0, 1], [1, 0], [1, 1]] 37 | res = miniball(test_vector) 38 | np.testing.assert_allclose(res["center"], [0.5, 0.5]) 39 | assert res["radius_squared"] == 0.5 40 | -------------------------------------------------------------------------------- /material/old/gensuit.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | 3 | # gensuit.pl 4 | # 5 | # Martin Kutz 6 | # Kaspar Fischer 7 | # 8 | # Mar. 2003 9 | 10 | 11 | print "========================================\n"; 12 | print "Test Suit Generator\n"; 13 | print "========================================\n"; 14 | 15 | 16 | # parse command line 17 | ($directory,$method,$nlist,$dlist) = @ARGV; 18 | 19 | die "usage: $0 directory method numbers dimensions\n" 20 | unless $directory and $method and $nlist and $dlist; 21 | 22 | # mkdir 23 | $directory .= "/" unless $directory =~ /\/$/; 24 | die "Error: cannot create directory $directory\n" 25 | unless -d $directory or mkdir $directory, oct 777; 26 | 27 | @N = split /\,/, $nlist; 28 | @D = split /\,/, $dlist; 29 | 30 | $" = ", "; 31 | print "n = @N\n"; 32 | print "d = @D\n"; 33 | print "method: $method\n"; 34 | print "directory: $directory\n"; 35 | 36 | # underscore method 37 | $_method = $method; 38 | $_method =~ s/\s+/_/g; 39 | 40 | for $d (@D) { 41 | for $n (@N) { 42 | $file = $directory.$_method."_".$n."_".$d.".pts"; 43 | print "creating $file.gz\n"; 44 | open (GEN,"./gen_random $n $d $method |") 45 | or die "Failed.\n"; 46 | open (OUT,">$file") 47 | or die "Failed.\n"; 48 | while () { 49 | die $_ if /error/i; 50 | print OUT; 51 | } 52 | close GEN; 53 | close OUT; 54 | 55 | system (gzip,"-f",$file) 56 | and die "Error: zipping failed\n"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /csharp/miniball/model/PointSet.cs: -------------------------------------------------------------------------------- 1 | namespace SEB; 2 | 3 | /// 4 | /// Abstraction to access the points and their Euclidean coordinates of a set of n points. 5 | /// Classes like {@link Miniball} do not take their input point set as a Java {@link List} are 6 | /// similar data structure, as this forces the user to provide the points in a certain format. 7 | /// Instead, the algorithms require an {@link PointSet} that allows the necessary characteristics of 8 | /// the points to be queried. 9 | ///

10 | /// Notice that most algorithms that use {@link PointSet}s will assume that the underlying point set 11 | /// is immutable. 12 | ///

13 | /// For optimal performance, you may want to copy your points to a Java array and use a ArrayPointSet. 14 | ///
15 | public interface PointSet 16 | { 17 | /// 18 | /// Number of points. 19 | /// 20 | /// the number of points in the point set. 21 | int Size { get; } 22 | 23 | /// 24 | /// The dimension of the ambient space of the points. 25 | ///
26 | /// Each point has dimension() many Euclidean coordinates. 27 | ///
28 | /// the dimension of the ambient space 29 | int Dimension { get; } 30 | 31 | /// 32 | /// The jth Euclidean coordinate of the ith point. 33 | /// 34 | /// the number of the point, 0 ≤ i < size() 35 | /// the dimension of the coordinate of interest, 0 ≤ j ≤ dimension() 36 | /// the jth Euclidean coordinate of the ith point 37 | double Coord(int i, int j); 38 | } 39 | -------------------------------------------------------------------------------- /java/src/main/java/com/dreizak/miniball/model/PointSet.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.model; 2 | 3 | import java.util.List; 4 | 5 | import com.dreizak.miniball.highdim.Miniball; 6 | 7 | /** 8 | * Abstraction to access the points and their Euclidean coordinates of a set of n points. 9 | *

10 | * Classes like {@link Miniball} do not take their input point set as a Java {@link List} are 11 | * similar data structure, as this forces the user to provide the points in a certain format. 12 | * Instead, the algorithms require an {@link PointSet} that allows the necessary characteristics of 13 | * the points to be queried. 14 | *

15 | * Notice that most algorithms that use {@link PointSet}s will assume that the underlying point set 16 | * is immutable. 17 | *

18 | * For optimal performance, you may want to copy your points to a Java array and use a 19 | * {@link ArrayPointSet}. 20 | * 21 | * @see ArrayPointSet 22 | */ 23 | public interface PointSet 24 | { 25 | /** 26 | * Number of points. 27 | * 28 | * @return the number of points in the point set. 29 | */ 30 | int size(); 31 | 32 | /** 33 | * The dimension of the ambient space of the points. 34 | *

35 | * Each point has {@code dimension()} many Euclidean coordinates. 36 | * 37 | * @return the dimension of the ambient space 38 | */ 39 | int dimension(); 40 | 41 | /** 42 | * The jth Euclidean coordinate of the ith point. 43 | * 44 | * @param i 45 | * the number of the point, 0 ≤ i < {@code size()} 46 | * @param j 47 | * the dimension of the coordinate of interest, 0 ≤ j ≤ {@code dimension()} 48 | * @return the jth Euclidean coordinate of the ith point 49 | */ 50 | double coord(int i, int j); 51 | } 52 | -------------------------------------------------------------------------------- /cpp/main/Seb_point.h: -------------------------------------------------------------------------------- 1 | // Synopsis: Simple point class 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_POINT_H 7 | #define SEB_POINT_H 8 | 9 | #include 10 | #include "Seb_configure.h" 11 | 12 | namespace SEB_NAMESPACE { 13 | 14 | template 15 | class Point 16 | // A simple class representing a d-dimensional point. 17 | { 18 | public: // types: 19 | typedef typename std::vector::const_iterator Const_iterator; 20 | typedef typename std::vector::iterator Iterator; 21 | 22 | public: // construction and destruction: 23 | 24 | Point(int d) 25 | // Constructs a d-dimensional point with undefined coordinates. 26 | : c(d) 27 | { 28 | } 29 | 30 | template 31 | Point(int d,InputIterator first) 32 | // Constructs a d-dimensional point with Cartesian center 33 | // coordinates [first,first+d). 34 | : c(first,first+d) 35 | { 36 | } 37 | 38 | public: // access: 39 | 40 | const Float& operator[](unsigned int i) const 41 | // Returns a const-reference to the i-th coordinate. 42 | { 43 | SEB_ASSERT(0 <= i && i < c.size()); 44 | return c[i]; 45 | } 46 | 47 | Float& operator[](unsigned int i) 48 | // Returns a reference to the i-th coordinate. 49 | { 50 | SEB_ASSERT(0 <= i && i < c.size()); 51 | return c[i]; 52 | } 53 | 54 | Const_iterator begin() const 55 | // Returns a const-iterator to the first of the d Cartesian coordinates. 56 | { 57 | return c.begin(); 58 | } 59 | 60 | Const_iterator end() const 61 | // Returns the past-the-end iterator corresponding to begin(). 62 | { 63 | return c.end(); 64 | } 65 | 66 | private: // member fields: 67 | std::vector c; // Cartesian center coordinates 68 | }; 69 | 70 | } // namespace SEB_NAMESPACE 71 | 72 | #endif // SEB_POINT_H 73 | -------------------------------------------------------------------------------- /csharp/src.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "miniball", "miniball\miniball.csproj", "{CB239AF9-E1CE-4A42-BF81-1363AC9D270A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{74EFD69E-315B-41F0-ACCC-AA604FE70977}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example", "example\example.csproj", "{0C74CFB6-21B8-4A7E-B644-B14BEDBD169B}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {CB239AF9-E1CE-4A42-BF81-1363AC9D270A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {CB239AF9-E1CE-4A42-BF81-1363AC9D270A}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {CB239AF9-E1CE-4A42-BF81-1363AC9D270A}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {CB239AF9-E1CE-4A42-BF81-1363AC9D270A}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {74EFD69E-315B-41F0-ACCC-AA604FE70977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {74EFD69E-315B-41F0-ACCC-AA604FE70977}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {74EFD69E-315B-41F0-ACCC-AA604FE70977}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {74EFD69E-315B-41F0-ACCC-AA604FE70977}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {0C74CFB6-21B8-4A7E-B644-B14BEDBD169B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {0C74CFB6-21B8-4A7E-B644-B14BEDBD169B}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {0C74CFB6-21B8-4A7E-B644-B14BEDBD169B}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {0C74CFB6-21B8-4A7E-B644-B14BEDBD169B}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /java/src/main/java/com/dreizak/miniball/model/ArrayPointSet.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.model; 2 | 3 | /** 4 | * A {@link PointSet} that stores n d-dimensional points in a Java array of 5 | * {@code double}s. 6 | */ 7 | public final class ArrayPointSet implements PointSet 8 | { 9 | private int d, n; 10 | private double[] c; 11 | 12 | /** 13 | * Creates an array-based point set to store n d-dimensional points. 14 | * 15 | * @param d 16 | * the dimensions of the ambient space 17 | * @param n 18 | * the number of points 19 | */ 20 | public ArrayPointSet(int d, int n) 21 | { 22 | this.d = d; 23 | this.n = n; 24 | this.c = new double[n * d]; 25 | } 26 | 27 | @Override 28 | public int size() 29 | { 30 | return n; 31 | } 32 | 33 | @Override 34 | public int dimension() 35 | { 36 | return d; 37 | } 38 | 39 | @Override 40 | public double coord(int i, int j) 41 | { 42 | assert 0 <= i && i < n; 43 | assert 0 <= j && j < d; 44 | return c[i * d + j]; 45 | } 46 | 47 | /** 48 | * Sets the jth Euclidean coordinate of the ith point to the given value. 49 | * 50 | * @param i 51 | * the number of the point, 0 ≤ i < {@code size()} 52 | * @param j 53 | * the dimension of the coordinate of interest, 0 ≤ j ≤ {@code dimension()} 54 | * @param v 55 | * the value to set as the jth Euclidean coordinate of the ith point 56 | */ 57 | public void set(int i, int j, double v) 58 | { 59 | assert 0 <= i && i < n; 60 | assert 0 <= j && j < d; 61 | c[i * d + j] = v; 62 | } 63 | 64 | public String toString() 65 | { 66 | StringBuffer s = new StringBuffer("{"); 67 | for (int i = 0; i < n; ++i) 68 | { 69 | s.append('['); 70 | for (int j = 0; j < d; ++j) 71 | { 72 | s.append(coord(i, j)); 73 | if (j < d - 1) s.append(","); 74 | } 75 | s.append(']'); 76 | if (i < n - 1) s.append(", "); 77 | } 78 | s.append('}'); 79 | return s.toString(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /python/miniball_python.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Synopsis: A binder for enabling this package using numpy arrays. 3 | 4 | Original author: Filip Cornell 5 | Modified by: Adam Heins 6 | Modified by: Jenna Bradley 7 | 8 | */ 9 | #include "../cpp/main/Seb.h" 10 | #include 11 | #include 12 | 13 | namespace nb = nanobind; 14 | 15 | using Point = Seb::Point; 16 | using Miniball = Seb::Smallest_enclosing_ball; 17 | 18 | /** 19 | * @brief Computes the smallest enclosing ball for a set of points. 20 | * 21 | * This function takes a 2D NumPy array of points and returns a dictionary 22 | * containing the center, radius, and squared radius of the enclosing ball. 23 | * 24 | * @param points_arr A 2D, C-contiguous NumPy array of dtype float64, 25 | * where each row represents a point. 26 | * @return A dictionary with keys "center" (np.ndarray), "radius" (float), 27 | * and "radius_squared" (float). 28 | */ 29 | nb::dict compute_miniball( 30 | nb::ndarray, nb::c_contig> points_arr) { 31 | size_t n_points = points_arr.shape(0); 32 | size_t dim = points_arr.shape(1); 33 | 34 | const double *data = points_arr.data(); 35 | 36 | // Create a vector of Point objects 37 | std::vector points; 38 | points.reserve(n_points); 39 | for (size_t i = 0; i < n_points; ++i) { 40 | // Pass a pointer to the beginning of the i-th row. 41 | points.emplace_back(dim, data + i * dim); 42 | } 43 | 44 | // Compute the smallest enclosing ball. 45 | Miniball mb(dim, points); 46 | 47 | nb::dict result; 48 | result["center"] = 49 | nb::ndarray(mb.center_begin(), {dim}).cast(); 50 | result["radius"] = mb.radius(); 51 | result["radius_squared"] = mb.squared_radius(); 52 | 53 | return result; 54 | } 55 | 56 | // Define the Python module using the NB_MODULE macro. 57 | // This replaces all the PyMethodDef, PyModuleDef, and PyInit boilerplate. 58 | NB_MODULE(_miniball, m) { 59 | m.def("_compute_miniball", &compute_miniball, nb::arg("points"), 60 | "Compute the smallest enclosing ball for a set of points."); 61 | } 62 | -------------------------------------------------------------------------------- /csharp/miniball/model/ArrayPointSet.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | 4 | namespace SEB; 5 | 6 | ///

7 | /// A PointSet that stores n d-dimensional points in a Java array of s 8 | /// 9 | public class ArrayPointSet : PointSet 10 | { 11 | 12 | int d, n; 13 | double[] c; 14 | 15 | /// 16 | /// Creates an array-based point set to store n d-dimensional points. 17 | /// 18 | /// the dimensions of the ambient space 19 | /// the number of points 20 | public ArrayPointSet(int d, int n) 21 | { 22 | this.d = d; 23 | this.n = n; 24 | this.c = new double[n * d]; 25 | } 26 | 27 | public int Size => n; 28 | 29 | public int Dimension => d; 30 | 31 | public double Coord(int i, int j) 32 | { 33 | Trace.Assert(0 <= i && i < n); 34 | Trace.Assert(0 <= j && j < d); 35 | return c[i * d + j]; 36 | } 37 | 38 | /// 39 | /// Sets the jth Euclidean coordinate of the ith point to the given value. 40 | /// 41 | /// the number of the point, 0 ≤ i < size() 42 | /// the dimension of the coordinate of interest, 0 ≤ j ≤ dimension() 43 | /// the value to set as the jth Euclidean coordinate of the ith point 44 | public void Set(int i, int j, double v) 45 | { 46 | Trace.Assert(0 <= i && i < n); 47 | Trace.Assert(0 <= j && j < d); 48 | c[i * d + j] = v; 49 | } 50 | 51 | public override string ToString() 52 | { 53 | var sb = new StringBuilder('{'); 54 | for (int i = 0; i < n; ++i) 55 | { 56 | sb.Append('['); 57 | for (int j = 0; j < d; ++j) 58 | { 59 | sb.Append(Coord(i, j)); 60 | if (j < d - 1) sb.Append(","); 61 | } 62 | sb.Append(']'); 63 | if (i < n - 1) sb.Append(", "); 64 | } 65 | sb.Append('}'); 66 | 67 | return sb.ToString(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /csharp/example/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using SEB; 3 | 4 | using static SEB.PointSetUtils; 5 | 6 | namespace example; 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | Console.WriteLine("===================================================="); 12 | Console.WriteLine("Seb example"); 13 | 14 | // Check for right number of arguments ... 15 | if (args.Length < 2) 16 | { 17 | Console.WriteLine($"Usage {AppDomain.CurrentDomain.FriendlyName} number-of-points dimension [boundary]"); 18 | Console.WriteLine("If 'boundary' is given, all points will be on the boundary of a sphere."); 19 | Console.WriteLine("===================================================="); 20 | Environment.Exit(1); 21 | } 22 | Console.WriteLine("===================================================="); 23 | 24 | // ... and parse command line arguments 25 | var n = int.Parse(args[0]); 26 | var d = int.Parse(args[1]); 27 | var on_boundary = args.Length > 2 && args[2] == "boundary"; 28 | 29 | // Construct n random points in dimension d 30 | var rnd = new Random(); 31 | var S = RandomPointSet(d, n, rnd, on_boundary); 32 | 33 | var sw = new Stopwatch(); 34 | 35 | Console.WriteLine("Starting computation..."); 36 | Console.WriteLine("===================================================="); 37 | 38 | sw.Start(); 39 | 40 | var mb = new Miniball(S); 41 | 42 | var rad = mb.Radius; 43 | var rad_squared = mb.SquaredRadius; 44 | var center = mb.Center; 45 | 46 | sw.Stop(); 47 | 48 | // Output 49 | Console.WriteLine($"Running time: {sw.Elapsed.TotalSeconds}s"); 50 | Console.WriteLine($"Radius = {rad} (squared: {rad_squared})"); 51 | Console.WriteLine("Center:"); 52 | for (int j = 0; j < center.Length; ++j) 53 | Console.WriteLine($" {center[j]}"); 54 | 55 | Console.WriteLine("===================================================="); 56 | Console.Write(mb.Verify().ConsoleFmt()); 57 | Console.WriteLine("===================================================="); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /java/src/test/java/com/dreizak/miniball/highdim/SubspanTest.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.highdim; 2 | 3 | import static junit.framework.Assert.assertEquals; 4 | import static junit.framework.Assert.assertFalse; 5 | import static junit.framework.Assert.assertTrue; 6 | 7 | import org.junit.Test; 8 | 9 | import com.dreizak.miniball.model.ArrayPointSet; 10 | 11 | public class SubspanTest 12 | { 13 | final static double Tolerance = 1.0e-15; // TODO 14 | 15 | @Test 16 | public void subspan2x2WithLast() 17 | { 18 | // S = [ (1, 2), (5, 2)] in the plane 19 | ArrayPointSet S = new ArrayPointSet(2, 2); 20 | S.set(0, 0, 1); 21 | S.set(0, 1, 2); 22 | S.set(1, 0, 5); 23 | S.set(1, 1, 2); 24 | 25 | // Sub-span containing point 1 (i.e., the last one) 26 | Subspan span = new Subspan(2, S, 1); 27 | assertFalse(span.isMember(0)); 28 | assertTrue(span.isMember(1)); 29 | assertEquals(1, span.globalIndex(0)); 30 | assertEquals(1, span.size()); 31 | assertEquals(0.0, span.representationError()); 32 | 33 | // Compute shortest vector to affine hull from a test point 34 | { 35 | double[] pt = { 36 | 0, 0 37 | }, expected = { 38 | 5, 2 39 | }; 40 | shortestVectorToHull(span, pt, expected); 41 | } 42 | 43 | // Add point 0 44 | span.add(0); 45 | assertTrue(span.isMember(0)); 46 | assertTrue(span.isMember(1)); 47 | assertEquals(0, span.globalIndex(0)); 48 | assertEquals(1, span.globalIndex(1)); 49 | assertEquals(2, span.size()); 50 | assertTrue(span.representationError() <= Tolerance); 51 | 52 | // Compute shortest vector to affine hull from a few test points 53 | { 54 | double[] pt = { 55 | 0, 0 56 | }, expected = { 57 | 0, 2 58 | }; 59 | shortestVectorToHull(span, pt, expected); 60 | } 61 | { 62 | double[] pt = { 63 | 4, 1 64 | }, expected = { 65 | 0, 1 66 | }; 67 | shortestVectorToHull(span, pt, expected); 68 | } 69 | { 70 | double[] pt = { 71 | 4, 2 72 | }, expected = { 73 | 0, 0 74 | }; 75 | shortestVectorToHull(span, pt, expected); 76 | } 77 | } 78 | 79 | static void shortestVectorToHull(Subspan span, double[] pt, double[] expected) 80 | { 81 | double[] sv = new double[span.dimension()]; 82 | span.shortestVectorToSpan(pt, sv); 83 | for (int i = 0; i < span.dimension(); ++i) 84 | assertTrue(Math.abs(expected[i] - sv[i]) <= Tolerance); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /csharp/test/SubspanTest.cs: -------------------------------------------------------------------------------- 1 | using SEB; 2 | using static System.Math; 3 | 4 | namespace test; 5 | 6 | public class SubspanTest 7 | { 8 | static double Tolerance = 1.0e-15; // TODO 9 | 10 | [Fact] 11 | public void subspan2x2WithLast() 12 | { 13 | // S = [ (1, 2), (5, 2)] in the plane 14 | var S = new ArrayPointSet(2, 2); 15 | S.Set(0, 0, 1); 16 | S.Set(0, 1, 2); 17 | S.Set(1, 0, 5); 18 | S.Set(1, 1, 2); 19 | 20 | // Sub-span containing point 1 (i.e., the last one) 21 | var span = new Subspan(2, S, 1); 22 | Assert.False(span.IsMember(0)); 23 | Assert.True(span.IsMember(1)); 24 | Assert.Equal(1, span.GlobalIndex(0)); 25 | Assert.Equal(1, span.Size); 26 | Assert.Equal(0.0, span.RepresentationError()); 27 | 28 | // Compute shortest vector to affine hull from a test point 29 | { 30 | double[] pt = { 31 | 0, 0 32 | }, expected = { 33 | 5, 2 34 | }; 35 | ShortestVectorToHull(span, pt, expected); 36 | } 37 | 38 | // Add point 0 39 | span.Add(0); 40 | Assert.True(span.IsMember(0)); 41 | Assert.True(span.IsMember(1)); 42 | Assert.Equal(0, span.GlobalIndex(0)); 43 | Assert.Equal(1, span.GlobalIndex(1)); 44 | Assert.Equal(2, span.Size); 45 | Assert.True(span.RepresentationError() <= Tolerance); 46 | 47 | // Compute shortest vector to affine hull from a few test points 48 | { 49 | double[] pt = { 50 | 0, 0 51 | }, expected = { 52 | 0, 2 53 | }; 54 | ShortestVectorToHull(span, pt, expected); 55 | } 56 | { 57 | double[] pt = { 58 | 4, 1 59 | }, expected = { 60 | 0, 1 61 | }; 62 | ShortestVectorToHull(span, pt, expected); 63 | } 64 | { 65 | double[] pt = { 66 | 4, 2 67 | }, expected = { 68 | 0, 0 69 | }; 70 | ShortestVectorToHull(span, pt, expected); 71 | } 72 | } 73 | 74 | static void ShortestVectorToHull(Subspan span, double[] pt, double[] expected) 75 | { 76 | var sv = new double[span.Dimension]; 77 | span.ShortestVectorToSpan(pt, sv); 78 | for (int i = 0; i < span.Dimension; ++i) 79 | Assert.True(Abs(expected[i] - sv[i]) <= Tolerance); 80 | } 81 | } -------------------------------------------------------------------------------- /python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(nanobind_example LANGUAGES CXX) 4 | 5 | if (NOT SKBUILD) 6 | message(WARNING "\ 7 | This CMake file is meant to be executed using 'scikit-build'. Running 8 | it directly will almost certainly not produce the desired result. If 9 | you are a user trying to install this package, please use the command 10 | below, which will install all necessary build dependencies, compile 11 | the package in an isolated environment, and then install it. 12 | ===================================================================== 13 | $ pip install . 14 | ===================================================================== 15 | If you are a software developer, and this is your own package, then 16 | it is usually much more efficient to install the build dependencies 17 | in your environment once and use the following command that avoids 18 | a costly creation of a new virtual environment at every compilation: 19 | ===================================================================== 20 | $ pip install nanobind scikit-build-core[pyproject] 21 | $ pip install --no-build-isolation -ve . 22 | ===================================================================== 23 | You may optionally add -Ceditable.rebuild=true to auto-rebuild when 24 | the package is imported. Otherwise, you need to re-run the above 25 | after editing C++ files.") 26 | endif() 27 | # Enable diagnostic colors for ninja 28 | if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0) 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") 30 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") 31 | endif() 32 | 33 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 34 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") 35 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") 36 | endif() 37 | 38 | # Try to import all Python components potentially needed by nanobind 39 | find_package(Python 3.8 40 | REQUIRED COMPONENTS Interpreter Development.Module 41 | OPTIONAL_COMPONENTS Development.SABIModule) 42 | 43 | # Import nanobind through CMake's find_package mechanism 44 | find_package(nanobind CONFIG REQUIRED) 45 | 46 | # We are now ready to compile the actual extension module 47 | nanobind_add_module( 48 | # Name of the extension. We wrap the code, so this does not matter 49 | _miniball 50 | 51 | # Target the stable ABI for Python 3.12+ 52 | STABLE_ABI 53 | 54 | # Build libnanobind statically and merge it into the 55 | # extension (which itself remains a shared library) 56 | NB_STATIC 57 | 58 | # Source code goes here 59 | miniball_python.cpp 60 | ) 61 | 62 | # Install directive for scikit-build-core 63 | install(TARGETS _miniball LIBRARY DESTINATION miniball) 64 | -------------------------------------------------------------------------------- /material/old/run_cplex.C: -------------------------------------------------------------------------------- 1 | // Synopsis: Runs the CPLEX 'baropt' solver on a given QP problem 2 | // instance which is given as a MPS file. 3 | // 4 | // Usage: run_cplex mps-file 5 | // 6 | // Authors: Martin Kutz , 7 | // Kaspar Fischer 8 | // 9 | // Revision: $Revision: 1094 $ ($Date: 2004-09-02 12:42:47 +0200 (Thu, 02 Sep 2004) $) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // We include Seb.h only because we use the timer in there: 17 | #include 18 | 19 | int main(int argn,char **argv) { 20 | using std::cout; 21 | using std::endl; 22 | using std::string; 23 | 24 | int status; // CPLEX status 25 | CPXENVptr env = NULL; 26 | CPXLPptr instance = NULL; 27 | 28 | try { 29 | // check paramaterss: 30 | if (argn < 2) 31 | throw string("no MPS file name specified as argument to program"); 32 | 33 | // initialize CPLEX environment: 34 | if ((env = CPXopenCPLEXdevelop(&status)) == NULL) 35 | throw string("couldn't open CPLEX environment"); 36 | 37 | // create problem instance: 38 | if ((instance = CPXcreateprob(env,&status,"Miniball instance")) == NULL) 39 | throw string("couldn't create CPLEX problem instance"); 40 | 41 | // read MPS file: 42 | if ((status = CPXreadcopyprob(env,instance,argv[1],NULL)) != 0) 43 | throw string("coudn't read problem from MPS file"); 44 | 45 | // start timer: 46 | Seb::Timer::instance().start("cplex"); 47 | 48 | // solve using Barrier solver: 49 | if ((status = CPXbaropt(env,instance)) != 0) 50 | throw string("coudn't solve problem instance"); 51 | 52 | // get objective value (i.e., the squared radius): 53 | double radius_square; 54 | if ((status = CPXgetobjval(env,instance,&radius_square)) != 0) 55 | throw string("couldn't get objective value"); 56 | 57 | // output: 58 | cout << "====================================================" << endl 59 | << "Input file: " << argv[1] << endl 60 | << "====================================================" << endl 61 | << "Running time: " << Seb::Timer::instance().lapse("cplex") 62 | << "s" << endl 63 | << "Squared radius: " << std::setprecision(17) 64 | << std::setiosflags(std::ios::scientific) 65 | << radius_square << endl 66 | << "====================================================" << endl; 67 | } 68 | 69 | // error handling: 70 | catch (std::string msg) { 71 | cout << "Error: " << msg << "." << endl; 72 | 73 | // fetch CPLEX error string from status: 74 | if (env != NULL) { 75 | char errormsg[1024]; 76 | CPXgeterrorstring(env,status,errormsg); 77 | cout << "CPLEX message: " << errormsg << endl; 78 | } 79 | } 80 | 81 | // deallocate resources: 82 | if (instance != NULL) 83 | CPXfreeprob(env,&instance); 84 | if (env != NULL) 85 | CPXcloseCPLEX(&env); 86 | } 87 | -------------------------------------------------------------------------------- /cpp/test/example.C: -------------------------------------------------------------------------------- 1 | // Synopsis: Example program illustrating how to use the Seb library 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #include 7 | #include 8 | 9 | #include "Seb.h" 10 | #include "Seb_debug.h" 11 | #include "Seb_debug.C" // ... only needed because we use Seb::Timer below 12 | 13 | int main(int argn,char **argv) { 14 | typedef double FT; 15 | typedef Seb::Point Point; 16 | typedef std::vector PointVector; 17 | typedef Seb::Smallest_enclosing_ball Miniball; 18 | 19 | using std::cout; 20 | using std::endl; 21 | using std::vector; 22 | 23 | cout << "====================================================" << endl 24 | << "Seb example" << endl; 25 | 26 | // Check for right number of arguments ... 27 | if (argn < 3) { 28 | cout << "Usage: " << argv[0] << " number-of-points dimension [boundary]" << endl 29 | << "If 'boundary' is given, all points will be on the boundary of a sphere." << endl 30 | << "====================================================" << endl; 31 | return 1; 32 | } 33 | cout << "====================================================" << endl; 34 | // ... and parse command line arguments 35 | const int n = std::atoi(argv[1]), d = std::atoi(argv[2]); 36 | const bool on_boundary = argn > 3 && std::string(argv[3]) == "boundary"; 37 | 38 | // Construct n random points in dimension d 39 | PointVector S; 40 | vector coords(d); 41 | srand(clock()); 42 | for (int i=0; i(2.0*rand()/RAND_MAX - 1.0); 48 | len += coords[j]*coords[j]; 49 | } 50 | 51 | // Normalize length to "almost" 1 (makes it harder for the algorithm) 52 | if (on_boundary) { 53 | const double Wiggle = 1e-2; 54 | len = 1/(std::sqrt(len)+Wiggle*rand()/RAND_MAX); 55 | for (int j=0; j 47 | * The stream is assumed to be encoded in UTF-8 and should contain integers and double values, all 48 | * separated by space or return. The first two numbers must be integers, specifying the number of 49 | * points in the point set and the dimension. The following numbers are all doubles and specify 50 | * the points by their Euclidean coordinates. 51 | *

52 | * For example, the three two-dimensional points {@code (0,0)}, {@code (2,3)}, {@code (4,5)} could 53 | * be stored as follows: 54 | * 55 | *

 56 |    *  3 2
 57 |    *  0 0
 58 |    *  2 3
 59 |    *  4 5
 60 |    * 
61 | * 62 | * @param s 63 | * the input stream to read from 64 | * @return 65 | */ 66 | public static final ArrayPointSet pointsFromStream(InputStream s) 67 | { 68 | try 69 | { 70 | Reader r = new InputStreamReader(new BufferedInputStream(s), "UTF-8"); 71 | Scanner in = new Scanner(r).useLocale(Locale.US); 72 | try 73 | { 74 | final int n = in.nextInt(); 75 | final int d = in.nextInt(); 76 | final ArrayPointSet pts = new ArrayPointSet(d, n); 77 | for (int i = 0; i < n; ++i) 78 | for (int j = 0; j < d; ++j) 79 | pts.set(i, j, in.nextDouble()); 80 | return pts; 81 | } 82 | finally 83 | { 84 | try 85 | { 86 | r.close(); 87 | } 88 | catch (IOException e) 89 | { 90 | throw new RuntimeException("Could not read points.", e); 91 | } 92 | } 93 | } 94 | catch (UnsupportedEncodingException e) 95 | { 96 | throw new RuntimeException("Unkown encoding.", e); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /cpp/main/Seb_debug.h: -------------------------------------------------------------------------------- 1 | // Synopsis: A simple class to save debugging comments to files 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_DEBUG_H 7 | #define SEB_DEBUG_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace SEB_NAMESPACE { 16 | 17 | class Logger 18 | // A singleton class which sends debugging comments to log files. 19 | // (A class is called a "singleton" if at most one instance of it 20 | // may ever exist.) By calling void log(channel,msg) you can send 21 | // the string msg to the file with name channel. 22 | { 23 | private: // (Construction and destruction are private to prevent 24 | // more than one instantiation.) 25 | 26 | Logger(); 27 | Logger(const Logger&); 28 | Logger& operator=(const Logger&); 29 | ~Logger(); 30 | 31 | public: // access and routines: 32 | 33 | static Logger& instance(); 34 | // Returns a reference to the only existing instance of this class: 35 | 36 | void log(const char *channel,const std::string& msg); 37 | // If this is the first call to log with string channel as the 38 | // first parameter, then the file with name channel.log is 39 | // opened (at the beginning) for writing and msg is written to 40 | // it. Otherwise, the string msg is opened to the already open 41 | // file channel.log. 42 | 43 | private: // private members: 44 | typedef std::map Streams; 45 | Streams channels; // a collection of pairs (k,v) where 46 | // k is the file-name and v is the 47 | // (open) stream associated with k 48 | }; 49 | 50 | class Timer 51 | // A singleton class which maintains a collection of named timers. 52 | // (A class is called a "singleton" if at most one instance of it 53 | // may ever exist.) The following routines are provided: 54 | // 55 | // - start(name): If this is the first time start() has been 56 | // called with name as the first parameter, then a new timer is 57 | // created and started. Otherwise, the timer with name name is 58 | // restarted. 59 | // 60 | // - lapse(name): Retuns the number of seconds which have elapsed 61 | // since start(name) was called last. 62 | // Precondition: start(name) has been called once. 63 | { 64 | private: // (Construction and destruction are private to prevent 65 | // more than one instantiation.) 66 | 67 | Timer(); 68 | Timer(const Timer&); 69 | Timer& operator=(const Timer&); 70 | 71 | public: // access and routines: 72 | 73 | static Timer& instance(); 74 | // Returns a reference to the only existing instance of this class: 75 | 76 | void start(const char *name); 77 | float lapse(const char *name); 78 | 79 | private: // private members: 80 | typedef std::map Timers; 81 | Timers timers; // a collection of pairs (k,v) where 82 | // k is the timer name and v is the 83 | // (started) timer associated with k 84 | }; 85 | 86 | } // namespace SEB_NAMESPACE 87 | 88 | #endif // SEB_DEBUG_H 89 | -------------------------------------------------------------------------------- /csharp/miniball/model/PointSetUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using static System.Math; 3 | 4 | namespace SEB; 5 | 6 | public static class PointSetUtils 7 | { 8 | 9 | /// 10 | /// Generates a random point set given a random number generator. 11 | /// 12 | /// dimension of the points in the point set 13 | /// number of points 14 | /// the random number generator 15 | /// Normalize length to "almost" 1 (makes it harder for the algorithm) 16 | /// a point set with {@code n} {@code d}-dimensional points 17 | public static ArrayPointSet RandomPointSet(int d, int n, Random r, bool on_boundary = false) 18 | { 19 | var pts = new ArrayPointSet(d, n); 20 | 21 | for (int i = 0; i < n; ++i) 22 | { 23 | var len = 0d; 24 | 25 | for (int j = 0; j < d; ++j) 26 | { 27 | // Generate coordindates in [-1,1] 28 | var v = 2d * r.NextDouble() - 1; 29 | pts.Set(i, j, v); 30 | len += v * v; 31 | } 32 | 33 | // Normalize length to "almost" 1 (makes it harder for the algorithm) 34 | if (on_boundary) 35 | { 36 | var Wiggle = 1e-2; 37 | len = 1d / Sqrt(len) + Wiggle * r.NextDouble(); 38 | for (int j = 0; j < d; ++j) pts.Set(i, j, pts.Coord(i, j) * len); 39 | } 40 | } 41 | 42 | return pts; 43 | } 44 | 45 | /// 46 | /// Reads a point set from an input stream. 47 | ///
48 | /// The stream is assumed to be encoded in UTF-8 and should contain integers and double values, all 49 | /// separated by space or return. The first two numbers must be integers, specifying the number of 50 | /// points in the point set and the dimension. The following numbers are all doubles and specify 51 | /// the points by their Euclidean coordinates. 52 | ///
53 | /// For example, the three two-dimensional points (0,0), (2,3), (4,5) could 54 | /// be stored as follows: 55 | /// 56 | ///
57 |     /// 3 2
58 |     /// 0 0
59 |     /// 2 3
60 |     /// 4 5
61 |     /// 
62 | ///
63 | /// the input stream to read from 64 | /// 65 | public static ArrayPointSet PointsFromStream(StreamReader s) 66 | { 67 | var line = s.ReadLine(); 68 | if (line is null) throw new Exception($"can't find header"); 69 | var ss = line.Trim().Split(' '); 70 | if (ss.Length != 2) throw new Exception($"invalid header"); 71 | 72 | var n = int.Parse(ss[0]); 73 | var d = int.Parse(ss[1]); 74 | var pts = new ArrayPointSet(d, n); 75 | 76 | for (int i = 0; i < n; ++i) 77 | { 78 | line = s.ReadLine(); 79 | if (line is null) throw new Exception($"can't find {i}-idx row"); 80 | ss = line.Trim().Split(' '); 81 | if (ss.Length != d) throw new Exception($"expecting {d} dimension instead of {ss.Length} at row {i}-idx"); 82 | 83 | for (int j = 0; j < d; ++j) 84 | pts.Set(i, j, double.Parse(ss[j], CultureInfo.InvariantCulture)); 85 | } 86 | 87 | return pts; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /cpp/main/Seb_debug.C: -------------------------------------------------------------------------------- 1 | // Synopsis: A simple class to save debugging comments to files 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Seb_configure.h" 11 | 12 | namespace SEB_NAMESPACE { 13 | 14 | // Implementation of class Logger: 15 | 16 | Logger::Logger() 17 | { 18 | } 19 | 20 | Logger::~Logger() 21 | { 22 | // we walk through the list of all opened files and close them: 23 | for (Streams::iterator it = channels.begin(); 24 | it != channels.end(); ++it) { 25 | (*it).second->close(); 26 | delete (*it).second; 27 | } 28 | } 29 | 30 | Logger& Logger::instance() 31 | { 32 | // Here's where we maintain the only instance: (Notice that it 33 | // gets constructed automatically the first time instance() is 34 | // called, and that it gets disposed of (if ever contructed) at 35 | // program termination.) 36 | static Logger instance; 37 | return instance; 38 | } 39 | 40 | void Logger::log(const char* ch,const std::string& msg) 41 | { 42 | const std::string name(ch); 43 | Streams::iterator it = channels.find(name); 44 | 45 | // have we already opened this file? 46 | if (it != channels.end()) { 47 | // If so, then just append the message: 48 | *(*it).second << msg; 49 | (*it).second->flush(); 50 | 51 | } else { 52 | // If we haven't seen 'name' before, we create a new file: 53 | using std::ofstream; 54 | ofstream *o = new ofstream((name+".log").c_str(), 55 | ofstream::out|ofstream::trunc); 56 | channels[name] = o; 57 | *o << msg; 58 | } 59 | } 60 | 61 | // Implementation of class Timer: 62 | 63 | // The following routine is taken from file mptimeval.h from 64 | // "Matpack Library Release 1.7.1" which is copyright (C) 1991-2002 65 | // by Berndt M. Gammel. It works on the timeval struct defined in 66 | // sys/time.h: 67 | // 68 | // struct timeval { 69 | // long tv_sec; /* seconds */ 70 | // long tv_usec; /* microseconds */ 71 | // }; 72 | // 73 | inline timeval& operator-=(timeval &t1,const timeval &t2) 74 | { 75 | t1.tv_sec -= t2.tv_sec; 76 | if ( (t1.tv_usec -= t2.tv_usec) < 0 ) { 77 | --t1.tv_sec; 78 | t1.tv_usec += 1000000; 79 | } 80 | return t1; 81 | } 82 | 83 | Timer::Timer() 84 | { 85 | } 86 | 87 | Timer& Timer::instance() 88 | { 89 | // Here's where we maintain the only instance: (Notice that it 90 | // gets constructed automatically the first time instance() is 91 | // called, and that it gets disposed of (if ever contructed) at 92 | // program termination.) 93 | static Timer instance; 94 | return instance; 95 | } 96 | 97 | void Timer::start(const char *timer_name) 98 | { 99 | // fetch current usage: 100 | rusage now; 101 | int status = getrusage(RUSAGE_SELF,&now); 102 | SEB_ASSERT(status == 0); 103 | 104 | // save it: 105 | timers[std::string(timer_name)] = now.ru_utime; 106 | } 107 | 108 | float Timer::lapse(const char *name) 109 | { 110 | // assert that start(name) has been called before: 111 | SEB_ASSERT(timers.find(std::string(name)) != timers.end()); 112 | 113 | // get current usage: 114 | rusage now; 115 | int status = getrusage(RUSAGE_SELF,&now); 116 | SEB_ASSERT(status == 0); 117 | 118 | // compute elapsed usage: 119 | now.ru_utime -= (*timers.find(std::string(name))).second; 120 | return now.ru_utime.tv_sec + now.ru_utime.tv_usec * 1e-6; 121 | } 122 | 123 | } // namespace SEB_NAMESPACE 124 | -------------------------------------------------------------------------------- /java/src/main/java/com/dreizak/miniball/highdim/Quality.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.highdim; 2 | 3 | /** 4 | * Information about the quality of the computed ball. 5 | */ 6 | public final class Quality 7 | { 8 | private final double qrInconsistency; 9 | private final double minConvexCoefficient; 10 | private final double maxOverlength; 11 | private final double maxUnderlength; 12 | private final int iterations; 13 | private final int supportSize; 14 | 15 | Quality(double qrInconsistency, double minConvexCoefficient, double maxOverlength, double maxUnderlength, 16 | int iterations, int supportSize) 17 | { 18 | super(); 19 | this.qrInconsistency = qrInconsistency; 20 | this.minConvexCoefficient = minConvexCoefficient; 21 | this.maxOverlength = maxOverlength; 22 | this.maxUnderlength = maxUnderlength; 23 | this.iterations = iterations; 24 | this.supportSize = supportSize; 25 | } 26 | 27 | /** 28 | * A measure for the quality of the internally used support points. 29 | *

30 | * The returned number should in theory be zero (but may be non-zero due to rounding errors). 31 | */ 32 | public final double getQrInconsistency() 33 | { 34 | return qrInconsistency; 35 | } 36 | 37 | /** 38 | * A measure for the minimality of the computed ball. 39 | * 40 | * The returned number should in theory be non-zero and positive. Due to rounding errors, it may 41 | * be negative. 42 | */ 43 | public final double getMinConvexCoefficient() 44 | { 45 | return minConvexCoefficient; 46 | } 47 | 48 | /** 49 | * The maximal over-length of a point from the input set, relative to the computed miniball's 50 | * radius. 51 | *

52 | * For each point p from the input point set, it is computed how far it is outside 53 | * the miniball ("over-length"). The returned number is the maximal such over-length, divided by 54 | * the radius of the computed miniball. 55 | *

56 | * Notice that {@code getMaxOverlength() == 0} if and only if all points are contained in the 57 | * miniball. 58 | * 59 | * @return the maximal over-length, a number ≥ 0 60 | */ 61 | public final double getMaxOverlength() 62 | { 63 | return maxOverlength; 64 | } 65 | 66 | /** 67 | * The maximal under-length of a point from the input set, relative to the computed miniball's 68 | * radius. 69 | *

70 | * For each point p from the input point set, it is computed how far one has to walk from 71 | * this point towards the boundary of the miniball ("under-length"). The returned number is the 72 | * maximal such under-length, divided by the radius of the computed miniball. 73 | *

74 | * Notice that in theory {@code getMaxUnderlength()} should be zero, otherwise the computed 75 | * miniball is enclosing but not minimal. 76 | * 77 | * @return the maximal under-length, a number ≥ 0 78 | */ 79 | public final double getMaxUnderlength() 80 | { 81 | return maxUnderlength; 82 | } 83 | 84 | /** 85 | * The number of iterations that the algorithm needed to compute the miniball. 86 | * 87 | * @return number of iterations 88 | */ 89 | public final int getIterations() 90 | { 91 | return iterations; 92 | } 93 | 94 | /** 95 | * The size of the support. 96 | *

97 | * Refer to the documentation of {@link Miniball#support()} for more information on the 98 | * support. 99 | * 100 | * @return size of the support 101 | */ 102 | public final int getSupportSize() 103 | { 104 | return supportSize; 105 | } 106 | 107 | @Override 108 | public String toString() 109 | { 110 | return "Quality [qrInconsistency=" 111 | + qrInconsistency 112 | + ", minConvexCoefficient=" 113 | + minConvexCoefficient 114 | + ", maxOverlength=" 115 | + maxOverlength 116 | + ", maxUnderlength=" 117 | + maxUnderlength 118 | + ", iterations=" 119 | + iterations 120 | + ", supportSize=" 121 | + supportSize 122 | + "]"; 123 | } 124 | } -------------------------------------------------------------------------------- /csharp/miniball/highdim/Quality.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace SEB; 4 | 5 | ///

6 | /// Information about the quality of the computed ball. 7 | /// 8 | public class Quality 9 | { 10 | 11 | /// 12 | /// A measure for the quality of the internally used support points. 13 | ///
14 | /// The returned number should in theory be zero (but may be non-zero due to rounding errors). 15 | ///
16 | public double qrInconsistency { get; private set; } 17 | 18 | /// 19 | /// A measure for the minimality of the computed ball. 20 | /// The returned number should in theory be non-zero and positive. Due to rounding errors, it may 21 | /// be negative. 22 | /// 23 | public double minConvexCoefficient { get; private set; } 24 | 25 | /// 26 | /// The maximal over-length of a point from the input set, relative to the computed miniball's radius. 27 | ///
28 | /// For each point p from the input point set, it is computed how far it is outside 29 | /// the miniball ("over-length"). The returned number is the maximal such over-length, divided by 30 | /// the radius of the computed miniball. 31 | ///
32 | /// Notice that getMaxOverlength() == 0 if and only if all points are contained in the miniball. 33 | ///
34 | /// the maximal over-length, a number ≥ 0 35 | public double maxOverlength { get; private set; } 36 | 37 | /// 38 | /// The maximal under-length of a point from the input set, relative to the computed miniball's radius. 39 | ///
40 | /// For each point p from the input point set, it is computed how far one has to walk from 41 | /// this point towards the boundary of the miniball ("under-length"). The returned number is the 42 | /// maximal such under-length, divided by the radius of the computed miniball. 43 | ///
44 | /// Notice that in theory getMaxUnderlength() should be zero, otherwise the computed 45 | /// miniball is enclosing but not minimal. 46 | ///
47 | /// the maximal under-length, a number ≥ 0 48 | public double maxUnderlength { get; private set; } 49 | 50 | /// 51 | /// The number of iterations that the algorithm needed to compute the miniball. 52 | /// 53 | /// number of iterations 54 | public int iterations { get; private set; } 55 | 56 | /// 57 | /// The size of the support. 58 | ///
59 | /// Refer to the documentation of {@link Miniball#support()} for more information on the 60 | /// support. 61 | ///
62 | /// size of the support 63 | public int supportSize { get; private set; } 64 | 65 | public Quality(double qrInconsistency, double minConvexCoefficient, double maxOverlength, double maxUnderlength, 66 | int iterations, int supportSize) 67 | { 68 | this.qrInconsistency = qrInconsistency; 69 | this.minConvexCoefficient = minConvexCoefficient; 70 | this.maxOverlength = maxOverlength; 71 | this.maxUnderlength = maxUnderlength; 72 | this.iterations = iterations; 73 | this.supportSize = supportSize; 74 | } 75 | 76 | public string ConsoleFmt() 77 | { 78 | var sb = new StringBuilder(); 79 | 80 | sb.AppendLine("Solution errors (relative to radius, nonsquared)"); 81 | sb.AppendLine($" final QR inconsistency : {qrInconsistency}"); 82 | sb.AppendLine($" minimal convex coefficient : {(minConvexCoefficient >= 0 ? "positive" : -minConvexCoefficient)}"); 83 | sb.AppendLine($" maximal overlength : {maxOverlength}"); 84 | sb.AppendLine($" maximal underlength : {maxUnderlength}"); 85 | sb.AppendLine($" iterations : {iterations}"); 86 | sb.AppendLine($" supportSize : {supportSize}"); 87 | 88 | return sb.ToString(); 89 | } 90 | 91 | public override string ToString() => "Quality [qrInconsistency=" 92 | + qrInconsistency 93 | + ", minConvexCoefficient=" 94 | + minConvexCoefficient 95 | + ", maxOverlength=" 96 | + maxOverlength 97 | + ", maxUnderlength=" 98 | + maxUnderlength 99 | + ", iterations=" 100 | + iterations 101 | + ", supportSize=" 102 | + supportSize 103 | + "]"; 104 | 105 | } -------------------------------------------------------------------------------- /material/old/pts2mps.C: -------------------------------------------------------------------------------- 1 | // Synopsis: Converts a point file to a MPS file (which can be passed 2 | // to CPLEX) The points file is assumed to start with two integers, 3 | // the number of points and the dimension. Then come the n points, 4 | // each in a line of d floating-point numbers. 5 | // 6 | // Authors: Martin Kutz , 7 | // Kaspar Fischer 8 | // 9 | // Revision: $Revision: 1094 $ ($Date: 2004-09-02 12:42:47 +0200 (Thu, 02 Sep 2004) $) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argn,char **argv) { 17 | typedef double FT; 18 | typedef Seb::Point Point; 19 | 20 | using std::cout; 21 | using std::endl; 22 | using std::vector; 23 | 24 | // check for right number of arguments: 25 | cout << "====================================================" << endl; 26 | if (argn < 2) { 27 | cout << "Usage: " << argv[0] << " point-file outfile" << endl 28 | << "====================================================" << endl; 29 | return 1; 30 | } 31 | 32 | // open input file: 33 | std::ifstream f(argv[1]); 34 | if (!f) { 35 | cout << "Could not open file '" << argv[1] << "'." << endl; 36 | return 1; 37 | } 38 | int n, d; 39 | f >> n; 40 | f >> d; 41 | 42 | // open output file: 43 | std::ofstream o(argv[2]); 44 | if (!o) { 45 | cout << "Could not open output file '" << argv[2] << "'." << endl; 46 | return 1; 47 | } 48 | 49 | // read in the points: 50 | vector S; 51 | vector coords(d); 52 | for (int i=0; i> coords[j]; 55 | S.push_back(Point(d,coords.begin())); 56 | } 57 | 58 | // We output the miniball program as in (MB'), eq. (18) in "An 59 | // efficient, exact, and generic quadratic programming solver for 60 | // geometric optimization" by B. Gaertner and S. Schoenherr: Notice 61 | // that the constraints x[i]>=0 need not be written down; they are 62 | // implicit. 63 | // 64 | // Set C to be the matrix holding the points S[i] in its columns. 65 | // We produce the following program: 66 | // 67 | // (obj) maximize -y^T y + sum_{i=0}^{n-1} S[i]^TS[i] x[i] 68 | // (s) s. t. x[0] + ... + x[n-1] = 1 69 | // (r[i]) -y[i] + sum_{j=0}^{n-1}C_{ij} x[j] = 0 70 | // 71 | // Here, the rows r[0] to r[d-1] encode the constraint y=Cx from (18). 72 | 73 | o << "NAME miniball.mps" << endl 74 | << "OBJSENSE" << endl 75 | << " max" << endl; 76 | 77 | // We have d+2 rows: obj, s, r[0] to r[d-1]. The first is the 78 | // objective row (N), the remaining ones are equality rows (E): 79 | o << "ROWS" << endl 80 | << " N obj" << endl 81 | << " E s" << endl; 82 | for (int i=0; i=0.) 121 | o << "BOUNDS" << endl; 122 | for (int j=0; j, 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_CONFIGURE_H 7 | #define SEB_CONFIGURE_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define SEB_NAMESPACE Seb // namespace of the library 14 | // #define SEB_ASSERTION_MODE // enables (cheap) assertions 15 | // #define SEB_DEBUG_MODE // enables debugging checks (cheap and 16 | // expensive ones) this enables 17 | // assertion and timer mode as well 18 | // #define SEB_TIMER_MODE // enables runtime measurements 19 | // #define SEB_STATS_MODE // enables statistics 20 | 21 | #include "Seb_debug.h" 22 | 23 | // Fixes for GCC series 2.95. (This fix is necessary for 2.95.2 at 24 | // least. But I guess it is needed for any earlier version of the 2.95 25 | // series, too.) Apparently, GCC doesn't come with a bitset and sstream 26 | // implementation, that's why we include them here. 27 | #if defined(__GNUC__) && __GNUC__==2 && \ 28 | __GNUC_MINOR__==95 && __GNUC_PATCHLEVEL__ <= 2 29 | #include 30 | #else 31 | #include 32 | #endif 33 | 34 | // We provide the following debugging routines: 35 | // 36 | // - SEB_DEBUG(expr): evaluates the expression expr when debugging mode 37 | // is on (see SEB_DEBUG_MODE above), or does nothing otherwise. 38 | // 39 | // - SEB_LOG(channel,expr): Writes to file channel.log the string 40 | // produced by the expression 's << expr' (where s it the stream for 41 | // file channel.log). This only happens when the debugging mode is 42 | // enabled; nothing is done otherwise. 43 | // Example: SEB_LOG("debug","Radius is " << radius_square << std::endl) 44 | // 45 | // - SEB_ASSERT(expr): Asserts that the Boolean expression expr evaluates 46 | // to true. This only happens when the assertion mode is enabled. This 47 | // macro should only be used for cheap assertions (which do not heavily 48 | // affect the running time). 49 | // 50 | // - SEB_ASSERT_EXPENSIVE(expr): Asserts that the Boolean expression expr 51 | // evaluates to true. This only happens when the debugging mode is on, 52 | // and not otherwise. (The assertion mode alone doesn't enable such 53 | // assertions.) 54 | // 55 | // - SEB_TIMER_START(timer): Restarts the timer with name timer. Nothing 56 | // happens when the timer mode is disabled (see SEB_TIMER_MODE above). 57 | // 58 | // - SEB_TIMER_PRINT(timer): Prints a string to std::cout telling how 59 | // much time has elapsed since the last SEB_TIMER_START(timer). 60 | // 61 | // - SEB_TIMER_STRING(timer): Returns a string with the number of seconds 62 | // elapsed since the last SEB_TIMER_START(timer). 63 | // 64 | // - SEB_STATS(expr): evaluates the expression when stats mode is 65 | // enabled (see SEB_STATS_MODE above), or does nothing otherwise. 66 | // 67 | 68 | // Debugging mode: 69 | #ifdef SEB_DEBUG_MODE 70 | // The debugging mode always enables the timer mode as well: 71 | #ifndef SEB_TIMER_MODE 72 | #define SEB_TIMER_MODE 73 | #endif 74 | #ifndef SEB_ASSERTION_MODE 75 | #define SEB_ASSERTION_MODE 76 | #endif 77 | 78 | #define SEB_ASSERT_EXPENSIVE(expr) assert(expr) 79 | #define SEB_DEBUG(expr) expr 80 | #define SEB_LOG(channel,expr) \ 81 | { \ 82 | std::stringstream s; \ 83 | s << expr; \ 84 | SEB_NAMESPACE::Logger::instance().log(channel,s.str()); \ 85 | } 86 | #else // SEB_DEBUG_MODE not defined 87 | #define SEB_ASSERT_EXPENSIVE(expr) 88 | #define SEB_DEBUG(expr) 89 | #define SEB_LOG(channel,expr) 90 | #endif // SEB_DEBUG_MODE 91 | 92 | // Assertion mode: 93 | #ifdef SEB_ASSERTION_MODE 94 | #define SEB_ASSERT(expr) assert(expr) 95 | #else // SEB_ASSERTION_MODE 96 | #define SEB_ASSERT(expr) 97 | #endif // SEB_ASSERTION_MODE 98 | 99 | // Timing mode: 100 | #ifdef SEB_TIMER_MODE 101 | #define SEB_TIMER_START(timer) \ 102 | { \ 103 | SEB_NAMESPACE::Timer::instance().start(timer); \ 104 | } 105 | #define SEB_TIMER_PRINT(timer) \ 106 | { \ 107 | std::cout << "Timer \'" << timer << "\': " \ 108 | << std::setprecision(5) \ 109 | << SEB_NAMESPACE::Timer::instance().lapse(timer) \ 110 | << 's' << std::endl; \ 111 | } 112 | #define SEB_TIMER_STRING(timer) \ 113 | SEB_NAMESPACE::Timer::instance().lapse(timer) 114 | #else // SEB_TIMER_MOD 115 | #define SEB_TIMER_START(timer) 116 | #define SEB_TIMER_PRINT(timer) 117 | #define SEB_TIMER_STRING(timer) 118 | #endif // SEB_TIMER_MODE 119 | 120 | // Stats mode: 121 | #ifdef SEB_STATS_MODE 122 | #define SEB_STATS(expr) { expr; } 123 | #else // SEB_STATS_MODE 124 | #define SEB_STATS(expr) 125 | #endif // SEB_STATS_MODE 126 | 127 | #endif // SEB_CONFIGURE_H 128 | -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.dreizak 4 | miniball 5 | 1.0.4-SNAPSHOT 6 | Miniball 7 | Smallest enclosing ball of a point set 8 | https://github.com/hbf/miniball 9 | 10 | org.sonatype.oss 11 | oss-parent 12 | 7 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | repo 19 | 20 | 21 | 22 | scm:git:git@github.com:hbf/miniball.git 23 | scm:git:git@github.com:hbf/miniball.git 24 | git@github.com:hbf/miniball.git 25 | miniball-1.0 26 | 27 | 28 | 29 | hbf 30 | Kaspar Fischer 31 | kaspar.fischer@dreizak.com 32 | http://www.linkedin.com/profile/view?id=90406280 33 | 34 | 35 | 36 | https://github.com/hbf/miniball/issues 37 | GitHub Issues 38 | 39 | 40 | 41 | 42 | com.github.github 43 | site-maven-plugin 44 | 0.8 45 | 46 | Building site for ${project.version} 47 | github 48 | true 49 | 50 | 51 | 52 | 53 | site 54 | 55 | site 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-site-plugin 62 | 3.0 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-project-info-reports-plugin 68 | 2.2 69 | 70 | true 71 | true 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-javadoc-plugin 77 | 2.7 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-surefire-report-plugin 82 | 2.6 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-checkstyle-plugin 87 | 2.6 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-compiler-plugin 95 | 3.1 96 | 97 | 1.6 98 | 1.6 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-jar-plugin 104 | 2.3.2 105 | 106 | 107 | 108 | true 109 | true 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-javadoc-plugin 117 | 2.9 118 | 119 | 120 | 121 | jar 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-source-plugin 129 | 2.2.1 130 | 131 | 132 | 133 | jar 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-release-plugin 141 | 2.4.1 142 | 143 | 144 | 145 | 146 | 147 | junit 148 | junit 149 | 4.13.1 150 | test 151 | 152 | 153 | com.google.guava 154 | guava 155 | 14.0.1 156 | test 157 | 158 | 159 | 160 | 161 | github 162 | miniball 163 | hbf 164 | UTF-8 165 | 166 | 167 | -------------------------------------------------------------------------------- /cpp/main/Seb.h: -------------------------------------------------------------------------------- 1 | // Synopsis: Library to find the smallest enclosing ball of points 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_SEB_H 7 | #define SEB_SEB_H 8 | 9 | #include 10 | #include "Seb_configure.h" 11 | #include "Seb_point.h" 12 | #include "Subspan.h" 13 | 14 | namespace SEB_NAMESPACE { 15 | 16 | // template arguments: 17 | // Float must be a floating point data type for which * / - + are defined 18 | // Pt[i] must return the i-th coordinate as a Float 19 | // PointAccessor[j] must return the j-th point in the data set as Pt and 20 | // size_t size() returns the size of the data set 21 | 22 | template, class PointAccessor = std::vector > 23 | class Smallest_enclosing_ball 24 | // An instance of class Smallest_enclosing_ball represents 25 | // the smallest enclosing ball of a set S of points. Initially, the 26 | // set S is empty; you can add points by calling insert(). 27 | { 28 | public: // iterator-type to iterate over the center coordinates of 29 | // the miniball (cf. center_begin() below): 30 | typedef Float *Coordinate_iterator; 31 | 32 | public: // construction and destruction: 33 | 34 | Smallest_enclosing_ball(unsigned int d, const PointAccessor &P) 35 | // Constructs an instance representing the miniball of points from 36 | // set S. The dimension of the ambient space is fixed to d for 37 | // lifetime of the instance. 38 | : dim(d), S(P), up_to_date(true), support(NULL) 39 | { 40 | allocate_resources(); 41 | SEB_ASSERT(!is_empty()); 42 | update(); 43 | } 44 | 45 | ~Smallest_enclosing_ball() 46 | { 47 | deallocate_resources(); 48 | } 49 | 50 | public: // modification: 51 | 52 | void invalidate() 53 | // Notifies the instance that the underlying point set S (passed to the constructor 54 | // of this instance as parameter P) has changed. This will cause the miniball to 55 | // be recomputed lazily (i.e., when you call for example radius(), the recalculation 56 | // will be triggered). 57 | { 58 | up_to_date = false; 59 | } 60 | 61 | public: // access: 62 | 63 | bool is_empty() 64 | // Returns whether the miniball is empty, i.e., if no point has 65 | // been inserted so far. 66 | { 67 | return S.size() == 0; 68 | } 69 | 70 | Float squared_radius() 71 | // Returns the squared radius of the miniball. 72 | // Precondition: !is_empty() 73 | { 74 | if (!up_to_date) 75 | update(); 76 | 77 | SEB_ASSERT(!is_empty()); 78 | return radius_square; 79 | } 80 | 81 | Float radius() 82 | // Returns the radius of the miniball. 83 | // This is equivalent to std:sqrt(squared_radius()) 84 | // Precondition: !is_empty() 85 | { 86 | if (!up_to_date) 87 | update(); 88 | 89 | SEB_ASSERT(!is_empty()); 90 | return radius_; 91 | } 92 | 93 | Coordinate_iterator center_begin() 94 | // Returns an iterator to the first Cartesian coordinate of the 95 | // center of the miniball. 96 | // Precondition: !is_empty() 97 | { 98 | if (!up_to_date) 99 | update(); 100 | 101 | SEB_ASSERT(!is_empty()); 102 | return center; 103 | } 104 | 105 | Coordinate_iterator center_end() 106 | // Returns the past-the-end iterator past the last Cartesian 107 | // coordinate of the center of the miniball. 108 | // Precondition: !is_empty 109 | { 110 | if (!up_to_date) 111 | update(); 112 | 113 | SEB_ASSERT(!is_empty()); 114 | return center+dim; 115 | } 116 | 117 | public: // testing: 118 | 119 | void verify(); 120 | // Verifies whether center lies really in affine hull, 121 | // determines the consistency of the QR decomposition, 122 | // and check whether all points of Q lie on the ball 123 | // and all others within; 124 | // prints the respective errors. 125 | 126 | void test_affine_stuff(); 127 | // Runs some testing routines on the affine hull layer, 128 | // using the points in S. 129 | // Creates a new Q, so better use before or after actual computations. 130 | // Prints the accumulated error. 131 | 132 | 133 | private: // internal routines concerning storage: 134 | void allocate_resources(); 135 | void deallocate_resources(); 136 | 137 | private: // internal helper routines for the actual algorithm: 138 | void init_ball(); 139 | Float find_stop_fraction(int& hinderer); 140 | bool successful_drop(); 141 | 142 | void update(); 143 | 144 | private: // we forbid copying (since we have dynamic storage): 145 | Smallest_enclosing_ball(const Smallest_enclosing_ball&); 146 | Smallest_enclosing_ball& operator=(const Smallest_enclosing_ball&); 147 | 148 | private: // member fields: 149 | unsigned int dim; // dimension of the amient space 150 | const PointAccessor &S; // set S of inserted points 151 | bool up_to_date; // whether the miniball has 152 | // already been computed 153 | Float *center; // center of the miniball 154 | Float radius_, radius_square; // squared radius of the miniball 155 | Subspan *support; // the points that lie on the current 156 | // boundary and "support" the ball; 157 | // the essential structure for update() 158 | 159 | private: // member fields for temporary use: 160 | Float *center_to_aff; 161 | Float *center_to_point; 162 | Float *lambdas; 163 | Float dist_to_aff, dist_to_aff_square; 164 | 165 | #ifdef SEB_STATS_MODE 166 | private: // memeber fields for statistics 167 | std::vector entry_count; // counts how often a point enters 168 | // the support; only available in 169 | // stats mode 170 | #endif // SEB_STATS_MODE 171 | 172 | private: // constants: 173 | static const Float Eps; 174 | }; 175 | 176 | } // namespace SEB_NAMESPACE 177 | 178 | #include "Seb-inl.h" 179 | 180 | #endif // SEB_SEB_H 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](http://hbf.github.com/miniball/miniball.png) 2 | 3 | # Miniball 4 | A C++ and Java library to compute the [miniball](http://en.wikipedia.org/wiki/Bounding_sphere) (a.k.a. _min-circle_, _min-sphere_, _smallest enclosing sphere_, etc.) of a point set. 5 | 6 | The code works for points in arbitrary dimension. It runs very fast in low dimensions and is practically efficient up to dimensions 10,000. The implementation is based on the algorithm from the paper _["Fast Smallest-Enclosing-Ball Computation in High Dimensions"](https://raw.githubusercontent.com/hbf/miniball/master/material/seb.pdf)_ by Kaspar Fischer, Bernd Gärtner, and Martin Kutz _([Proc. 11th European Symposium on Algorithms (ESA)](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.90.5783)_, p. 630-641, 2003). 7 | 8 | This project is dedicated to Martin Kutz†. 9 | 10 | # Speed 11 | On a 2.66 GHz Intel Core i7 MacBook Pro, the code performs as follows: 12 | 13 | ![](https://hbf.github.io/miniball/times.png) 14 | 15 | The chart shows the time in seconds (y-axis) needed for the computation of the miniball of *n* random points (x-axis) in dimensions *d* (3, 5, or 10 in this chart). 16 | 17 | # Stability 18 | 19 | The code is well-tested and its underlying algorithm should be numerically stable. By "numerically stable" we mean that even for points in _degenerate position_ – like all on a line, all on a circle, identical points in the input, etc. – the algorithm will (i) terminate, (ii) be fast, (iii) provide an accurate result. 20 | 21 | Please report any problems you may find as [tickets](https://github.com/hbf/miniball/issues). 22 | 23 | # Getting started (Java) 24 | 25 | You can either download the latest JAR file from TODO or use a build system like Maven or Graddle, or SBT (for Scala users). 26 | 27 | Maven dependency: 28 | 29 | ``` 30 | 31 | com.dreizak 32 | miniball 33 | 1.0.1 34 | 35 | ``` 36 | 37 | SBT dependency: 38 | 39 | libraryDependencies += "com.dreizak" % "miniball" % "1.0.3" 40 | 41 | Documentation: 42 | 43 | * [Project information](http://hbf.github.com/miniball/) 44 | * [JavaDoc](http://hbf.github.io/miniball/apidocs/com/dreizak/miniball/highdim/Miniball.html) 45 | * [`MiniballTest.java`](https://github.com/hbf/miniball/blob/wip-java/java/src/test/java/com/dreizak/miniball/highdim/MiniballTest.java): A few tests take will help you understand how to use the library. Take a look at method `TODO` for a simple example. 46 | 47 | # Getting started (C++) 48 | 49 | On Linux or MacOS, the following steps will get you going: (Notice that the Boost library that the instructions below download is only used in this example to generate some input points; the library as such does not depend on it.) 50 | 51 | ```bash 52 | # Get the source code 53 | git clone https://github.com/hbf/miniball.git 54 | cd miniball/cpp/test 55 | 56 | # Compile an example, which generates random points and computes their miniball 57 | g++ -I../main example.C -o example -O3 58 | 59 | # Run it on one million points in 3D 60 | ./example 1000000 3 61 | ``` 62 | 63 | (More documentation to come. Contact us in case you run into any problems.) 64 | 65 | # Getting started (Python) 66 | 67 | On MacOS, do the following (not yet made on other ). 68 | 69 | 1. Stand in the `python`-folder. 70 | 2. Run `pip install .[test]` 71 | 3. Run `pytest` 72 | 4. Does it work? Great! You are now ready to use the python function. 73 | 74 | # Getting started (C#) 75 | 76 | On Linux or MacOS, the following steps will get you going: 77 | 78 | ```bash 79 | # Get the source code 80 | git clone https://github.com/hbf/miniball.git 81 | cd miniball/csharp 82 | 83 | # Compile an example, which generates random points and computes their miniball 84 | dotnet build 85 | 86 | # Run it on one million points in 3D 87 | ./example/bin/Debug/net7.0/example 1000000 3 88 | ``` 89 | 90 | To run unit tests: 91 | 92 | ```bash 93 | dotnet test 94 | ``` 95 | 96 | ## Float or double? 97 | 98 | The C++ code is written using [templates](http://en.wikipedia.org/wiki/C%2B%2B#Templates) and allows the number type to be specified as a template argument. 99 | 100 | However, please only use `double` as the number type, as is illustrated in the example program [`example.C`](https://github.com/hbf/miniball/blob/master/cpp/test/example.C) (see line `typedef double FT`). 101 | 102 | The code will not run correctly if you use `float`. 103 | 104 | # License 105 | The code is available under the [Apache 2 License](http://www.apache.org/licenses/LICENSE-2.0.html), which is explained [here](http://www.tldrlegal.com/license/apache-license-2.0-(apache-2.0). 106 | 107 | If you use the code in your project/product, please drop us a note – we are always interested in learning about new applications! 108 | 109 | # Authors & acknowledgements 110 | 111 | Authors: 112 | 113 | * Martin Kutz, FU Berlin 114 | * [Kaspar Fischer](http://github.com/hbf), ETH Zurich 115 | * [Bernd Gärtner](http://www.inf.ethz.ch/personal/gaertner/), ETH Zurich 116 | 117 | Many thanks go to the following people who have – sometimes substantially – contributed to the code: 118 | 119 | 120 | * Thomas Otto (University of Hamburg) for [submitting several compiler fixes](https://github.com/hbf/miniball/issues/3) (g++ 4.7 and 4.5 on SUSE Linux 12.2 and 11.3) and for [introducing generic point and point coordinate accessors](https://github.com/hbf/miniball/pull/5) in the code. 121 | * [Adam Heins](https://github.com/adamheins) (University of Toronto) for [updating the Python bindings](https://github.com/hbf/miniball/pull/32) to be installable with `pip`. 122 | * [Lorenzo Delana](https://github.com/devel0) for [porting Java to C#](https://github.com/hbf/miniball/pull/34). 123 | * [Jen Bradley](https://github.com/janbridley) for [updating the example code](https://github.com/hbf/miniball/pull/38) to work with g++ 10 and newer. 124 | 125 | # Links 126 | * For small dimensions like 2D or 3D, [Bernd Gärtner's code](http://www.inf.ethz.ch/personal/gaertner/miniball.html), which is based on Welzl's algorithm, may be faster. 127 | * The [Computational Geometry Algorithms Library (CGAL)](http://www.cgal.org/) contains both floating-point and _arbitrary-precision arithmetic_ implementations of several bounding sphere algorithms. Among then, there is an algorithm to compute the minsphere of _spheres_ (not just points as input). See the [Chapter on Bounding Volumes](http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Bounding_volumes/Chapter_main.html). 128 | -------------------------------------------------------------------------------- /cpp/main/Subspan.h: -------------------------------------------------------------------------------- 1 | // Synopsis: Class representing the affine hull of a point set. 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_SUBSPAN_H 7 | #define SEB_SUBSPAN_H 8 | 9 | #include 10 | #include "Seb_configure.h" 11 | 12 | namespace SEB_NAMESPACE { 13 | 14 | template 15 | inline Float sqr(const Float x) 16 | { 17 | return x * x; 18 | } 19 | 20 | template 21 | class Subspan 22 | // An instance of this class represents the affine hull of a 23 | // non-empty set M of affinely independent points. The set M is not 24 | // represented explicity; when an instance of this class is 25 | // constructed, you pass a list S of points to it (which the 26 | // instance will never change and which is assumed to stay fixed for 27 | // the lifetime of this instance): The set M is then a subset of S, 28 | // and its members are identified by their (zero-based) indices in 29 | // S. The following routines are provided to query and change the 30 | // set M: 31 | // 32 | // - int size() returns the size of the instance's set M, a number 33 | // between 0 and dim+1. 34 | // Complexity: O(1). 35 | // 36 | // - bool is_member(int global_index) returns true iff S[global_index] 37 | // is a member of M. 38 | // Complexity: O(1) 39 | // 40 | // - global_index(int local_index) returns the index (into S) of the 41 | // local_index-th point in M. The points in M are internally 42 | // ordered (in an arbitrary way) and this order only changes when 43 | // add() or remove() (see below) is called. 44 | // Complexity: O(1) 45 | // 46 | // - void add_point(int global_index) adds the global_index-th point 47 | // of S to the instance's set M. 48 | // Precondition: !is_member(global_index) 49 | // Complexity: O(dim^2). 50 | // 51 | // - void remove_point(int local_index) removes the local_index-th 52 | // point in M. 53 | // Precondition: 0<=local_index<=size() and size()>1 54 | // Complexity: O(dim^2) 55 | // 56 | // - int any_member() returns the global index (into S) of an 57 | // arbitrary element of M. 58 | // Precondition: size()>0 59 | // Postcondition: is_member(any_member()) 60 | // 61 | // The following routines are provided to query the affine hull of M: 62 | // 63 | // - void shortest_vector_to_span(p,w): Computes the vector w 64 | // directed from point p to v, where v is the point in aff(M) that 65 | // lies nearest to p. Returned is the squared length of w. 66 | // Precondition: size()>0 67 | // Complexity: O(dim^2) 68 | // 69 | // - void find_affine_coefficients(c,coeffs): 70 | // Preconditions: c lies in the affine hull aff(M) and size() > 0. 71 | // Calculates the size()-many coefficients in the representation 72 | // of c as an affine combination of the points M. The i-th computed 73 | // coefficient coeffs[i] corresponds to the i-th point in M, or, 74 | // in other words, to the point in S with index global_index(i). 75 | // Complexity: O(dim^2) 76 | { 77 | public: // construction and deletion: 78 | 79 | Subspan(unsigned int dim, const PointAccessor& S, int i); 80 | // Constructs an instance representing the affine hull aff(M) of M={p}, 81 | // where p is the point S[i] from S. 82 | // 83 | // Notice that S must not changed as long as this instance of 84 | // Subspan is in use. 85 | 86 | ~Subspan(); 87 | 88 | public: // modification: 89 | 90 | void add_point(int global_index); 91 | void remove_point(unsigned int local_index); 92 | 93 | public: // access: 94 | 95 | unsigned int size() const 96 | { 97 | return r+1; 98 | } 99 | 100 | bool is_member(unsigned int i) const 101 | { 102 | SEB_ASSERT(i < S.size()); 103 | return membership[i]; 104 | } 105 | 106 | unsigned int global_index(unsigned int i) const 107 | { 108 | SEB_ASSERT(i < size()); 109 | return members[i]; 110 | } 111 | 112 | unsigned int any_member() const { 113 | SEB_ASSERT(size()>0); 114 | return members[r]; 115 | } 116 | 117 | template 119 | Float shortest_vector_to_span(RandomAccessIterator1 p, 120 | RandomAccessIterator2 w); 121 | 122 | template 124 | void find_affine_coefficients(RandomAccessIterator1 c, 125 | RandomAccessIterator2 coeffs); 126 | 127 | public: // debugging routines: 128 | 129 | Float representation_error(); 130 | // Computes the coefficient representations of all points in the 131 | // (internally used) system (Q) and returns the maximal deviation 132 | // from the theoretical values. 133 | // Warning: This routine has running time O(dim^3). 134 | 135 | private: // private helper routines: 136 | 137 | void append_column(); 138 | // Appends the new column u (which is a member of this instance) to 139 | // the right of "A = QR", updating Q and R. It assumes r to still 140 | // be the old value, i.e., the index of the column used now for 141 | // insertion; r is not altered by this routine and should be changed 142 | // by the caller afterwards. 143 | // Precondition: r membership; // S[i] in M iff membership[i] 157 | const unsigned int dim; // ambient dimension (not to be 158 | // confused with the rank r, 159 | // see below) 160 | 161 | // Entry i of members contains the index into S of the i-th point 162 | // in M. The point members[r] is called the "origin." 163 | std::vector members; 164 | 165 | private: // member fields for maintaining the QR-decomposition: 166 | Float **Q, **R; // (dim x dim)-matrices Q 167 | // (orthogonal) and R (upper 168 | // triangular); notice that 169 | // e.g. Q[j][i] is the element 170 | // in row i and column j 171 | Float *u,*w; // needed for rank-1 update 172 | unsigned int r; // the rank of R (i.e. #points - 1) 173 | }; 174 | 175 | } // namespace SEB_NAMESPACE 176 | 177 | #include "Subspan-inl.h" 178 | 179 | #endif // SEB_SUBSPAN_H 180 | -------------------------------------------------------------------------------- /cpp/main/Subspan-inl.h: -------------------------------------------------------------------------------- 1 | // Synopsis: Class representing the affine hull of a point set. 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_SUBSPAN_INL_H 7 | #define SEB_SUBSPAN_INL_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "Seb_configure.h" 13 | 14 | #include "Subspan.h" // Note: header included for better syntax highlighting in some IDEs. 15 | 16 | // The point members[r] is called the origin; we use the following macro 17 | // to increase the readibilty of the code: 18 | #define SEB_AFFINE_ORIGIN S[members[r]] 19 | 20 | namespace SEB_NAMESPACE { 21 | 22 | template 23 | inline void givens(Float& c, Float& s, const Float a, const Float b) 24 | // Determine the Givens coefficients (c,s) satisfying 25 | // 26 | // c * a + s * b = +/- (a^2 + b^2) 27 | // c * b - s * a = 0 28 | // 29 | // We don't care about the signs here for efficiency, 30 | // so make sure not to rely on them anywhere. 31 | // 32 | // Source: taken from "Matrix Computations" (2nd edition) by Gene 33 | // H. B. Golub & Charles F. B. Van Loan (Johns Hopkins University 34 | // Press, 1989), p. 216. 35 | { 36 | using std::abs; 37 | using std::sqrt; 38 | 39 | if (b == 0) { 40 | c = 1; 41 | s = 0; 42 | } else if (abs(b) > abs(a)) { 43 | const Float t = a / b; 44 | s = 1 / sqrt (1 + sqr(t)); 45 | c = s * t; 46 | } else { 47 | const Float t = b / a; 48 | c = 1 / sqrt (1 + sqr(t)); 49 | s = c * t; 50 | } 51 | } 52 | 53 | template 54 | Subspan::Subspan(unsigned int dim, const PointAccessor& S, int index) 55 | : S(S), membership(S.size()), dim(dim), members(dim+1) 56 | { 57 | // allocate storage for Q, R, u, and w: 58 | Q = new Float *[dim]; 59 | R = new Float *[dim]; 60 | for (unsigned int i=0; i 79 | Subspan::~Subspan() 80 | { 81 | for (unsigned int i=0; i 92 | void Subspan::add_point(int index) { 93 | SEB_ASSERT(!is_member(index)); 94 | 95 | // compute S[i] - origin into u: 96 | for (unsigned int i=0; i 113 | void Subspan::remove_point(const unsigned int local_index) { 114 | SEB_ASSERT(is_member(global_index(local_index)) && size() > 1); 115 | 116 | membership[global_index(local_index)] = false; 117 | 118 | if (local_index == r) { 119 | // origin must be deleted 120 | 121 | // We choose the right-most member of Q, i.e., column r-1 of R, 122 | // as the new origin. So all relative vectors (i.e., the 123 | // columns of "A = QR") have to be updated by u:= old origin - 124 | // S[global_index(r-1)]: 125 | for (unsigned int i=0; i 152 | template 154 | Float Subspan:: 155 | shortest_vector_to_span(RandomAccessIterator1 p, 156 | RandomAccessIterator2 w) 157 | { 158 | using std::inner_product; 159 | 160 | // compute vector from p to origin, i.e., w = origin - p: 161 | for (unsigned int i=0; i 175 | Float Subspan::representation_error() 176 | { 177 | using std::abs; 178 | 179 | std::vector lambdas(size()); 180 | Float max = 0; 181 | Float error; 182 | 183 | // cycle through all points in hull 184 | for (unsigned int j = 0; j < size(); ++j) { 185 | // compute the affine representation: 186 | find_affine_coefficients(S[global_index(j)],lambdas.begin()); 187 | 188 | // compare coefficient of point #j to 1.0 189 | error = abs(lambdas[j] - 1.0); 190 | if (error > max) max = error; 191 | 192 | // compare the other coefficients against 0.0 193 | for (unsigned int i = 0; i < j; ++i) { 194 | error = abs(lambdas[i] - 0.0); 195 | if (error > max) max = error; 196 | } 197 | for (unsigned int i = j+1; i < size(); ++i) { 198 | error = abs(lambdas[i] - 0.0); 199 | if (error > max) max = error; 200 | } 201 | } 202 | 203 | return max; 204 | } 205 | 206 | template 207 | template 209 | void Subspan:: 210 | find_affine_coefficients(RandomAccessIterator1 p, 211 | RandomAccessIterator2 lambdas) 212 | { 213 | // compute relative position of p, i.e., u = p - origin: 214 | for (unsigned int i=0; i=0; --j) { 233 | for (unsigned int k=j+1; k 242 | void Subspan::append_column() 243 | // Appends the new column u (which is a member of this instance) to 244 | // the right of "A = QR", updating Q and R. It assumes r to still 245 | // be the old value, i.e., the index of the column used now for 246 | // insertion; r is not altered by this routine and should be changed 247 | // by the caller afterwards. 248 | // Precondition: r r; --j) { 261 | // j is the index of the entry to be cleared 262 | // with the help of entry j-1 263 | 264 | // compute Givens coefficients c,s 265 | Float c, s; 266 | givens (c,s,R[r][j-1],R[r][j]); 267 | 268 | // rotate one R-entry (the other one is an implicit zero) 269 | R[r][j-1] = c * R[r][j-1] + s * R[r][j]; 270 | 271 | // rotate two Q-columns 272 | for (unsigned int i = 0; i < dim; ++i) { 273 | const Float a = Q[j-1][i]; 274 | const Float b = Q[j][i]; 275 | Q[j-1][i] = c * a + s * b; 276 | Q[j][i] = c * b - s * a; 277 | } 278 | } 279 | } 280 | 281 | template 282 | void Subspan::hessenberg_clear (unsigned int pos) 283 | // Given R in lower Hessenberg form with subdiagonal entries 0 to 284 | // pos-1 already all zero, clears the remaining subdiagonal entries 285 | // via Givens rotations. 286 | { 287 | // clear new subdiagonal entries 288 | for (; pos < r; ++pos) { 289 | // pos is the column index of the entry to be cleared 290 | 291 | // compute Givens coefficients c,s 292 | Float c, s; 293 | givens (c,s,R[pos][pos],R[pos][pos+1]); 294 | 295 | // rotate partial R-rows (of the first pair, only one entry is 296 | // needed, the other one is an implicit zero) 297 | R[pos][pos] = c * R[pos][pos] + s * R[pos][pos+1]; 298 | // (then begin at posumn pos+1) 299 | for (unsigned int j = pos+1; j < r; ++j) { 300 | const Float a = R[j][pos]; 301 | const Float b = R[j][pos+1]; 302 | R[j][pos] = c * a + s * b; 303 | R[j][pos+1] = c * b - s * a; 304 | } 305 | 306 | // rotate Q-columns 307 | for (unsigned int i = 0; i < dim; ++i) { 308 | const Float a = Q[pos][i]; 309 | const Float b = Q[pos+1][i]; 310 | Q[pos][i] = c * a + s * b; 311 | Q[pos+1][i] = c * b - s * a; 312 | } 313 | } 314 | } 315 | 316 | template 317 | void Subspan::special_rank_1_update () 318 | // Update current QR-decomposition "A = QR" to 319 | // A + u * [1,...,1] = Q' R'. 320 | { 321 | // compute w = Q^T * u 322 | for (unsigned int i = 0; i < dim; ++i) { 323 | w[i] = 0; 324 | for (unsigned int k = 0; k < dim; ++k) 325 | w[i] += Q[i][k] * u[k]; 326 | } 327 | 328 | // rotate w down to a multiple of the first unit vector; 329 | // the operations have to be recorded in R and Q 330 | for (unsigned int k = dim-1; k > 0; --k) { 331 | // k is the index of the entry to be cleared 332 | // with the help of entry k-1 333 | 334 | // compute Givens coefficients c,s 335 | Float c, s; 336 | givens (c,s,w[k-1],w[k]); 337 | 338 | // rotate w-entry 339 | w[k-1] = c * w[k-1] + s * w[k]; 340 | 341 | // rotate two R-rows; 342 | // the first column has to be treated separately 343 | // in order to account for the implicit zero in R[k-1][k] 344 | R[k-1][k] = -s * R[k-1][k-1]; 345 | R[k-1][k-1] *= c; 346 | for (unsigned int j = k; j < r; ++j) { 347 | const Float a = R[j][k-1]; 348 | const Float b = R[j][k]; 349 | R[j][k-1] = c * a + s * b; 350 | R[j][k] = c * b - s * a; 351 | } 352 | 353 | // rotate two Q-columns 354 | for (unsigned int i = 0; i < dim; ++i) { 355 | const Float a = Q[k-1][i]; 356 | const Float b = Q[k][i]; 357 | Q[k-1][i] = c * a + s * b; 358 | Q[k][i] = c * b - s * a; 359 | } 360 | } 361 | 362 | // add w * (1,...,1)^T to new R 363 | // which means simply to add u[0] to each column 364 | // since the other entries of u have just been eliminated 365 | for (unsigned int j = 0; j < r; ++j) 366 | R[j][0] += w[0]; 367 | 368 | // clear subdiagonal entries 369 | hessenberg_clear(0); 370 | } 371 | 372 | } // namespace SEB_NAMESPACE 373 | 374 | #endif // SEB_SUBSPAN_INL_H 375 | -------------------------------------------------------------------------------- /java/src/main/java/com/dreizak/miniball/highdim/Miniball.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.highdim; 2 | 3 | import static com.dreizak.miniball.highdim.Logging.debug; 4 | import static com.dreizak.miniball.highdim.Logging.info; 5 | import static com.dreizak.miniball.highdim.Logging.log; 6 | import static java.lang.Math.sqrt; 7 | 8 | import java.util.List; 9 | 10 | import com.dreizak.miniball.model.ArrayPointSet; 11 | import com.dreizak.miniball.model.PointSet; 12 | 13 | /** 14 | * The smallest enclosing ball (a.k.a. miniball) of a set of points. 15 | *

16 | * The miniball MB(P) of a non-empty set P of points in d-dimensional Euclidean 17 | * space R^d is defined to be the smallest ball that contains all points from P. 18 | *

19 | * In order to compute the miniball of a given point set, you create a {@link PointSet} instance, 20 | * {@code pts}, say, that provides the algorithm access to the Euclidean coordinates of the points. 21 | * You then pass this instance to the constructor {@link #Miniball(PointSet)}. The point set may be 22 | * empty (i.e., {@code pts.size() == 0}), in which case {@link #isEmpty()} will return true. 23 | *

24 | * Instances of class {@link Miniball} are immutable. That is, when you create a {@link Miniball}, 25 | * passing your point set to it, it computes the center and radius of the miniball and allows you to 26 | * access this information via {@link #radius()}, {@link #squaredRadius()}, and {@link #center()}. 27 | * However the {@link Miniball} instance will not reflect any subsequent changes you make to the 28 | * underlying point set. 29 | * 30 | * @see ArrayPointSet 31 | */ 32 | public class Miniball 33 | { 34 | private final static double Eps = 1e-14; 35 | 36 | private final PointSet S; 37 | private final int size; 38 | private final int dim; 39 | private int iteration; 40 | private final double[] center, centerToAff, centerToPoint, lambdas; 41 | private double distToAff, distToAffSquare; 42 | private double squaredRadius, radius; 43 | private final Subspan support; 44 | private int stopper; 45 | 46 | /** 47 | * Computes the miniball of the given point set. 48 | * 49 | * Notice that the point set {@code pts} is assumed to be immutable during the computation. That 50 | * is, if you add, remove, or change points in the point set, you have to create a new instance of 51 | * {@link Miniball}. 52 | * 53 | * @param pts 54 | * the point set 55 | */ 56 | public Miniball(PointSet pts) 57 | { 58 | S = pts; 59 | size = S.size(); 60 | dim = S.dimension(); 61 | center = new double[dim]; 62 | centerToAff = new double[dim]; 63 | centerToPoint = new double[dim]; 64 | lambdas = new double[dim + 1]; 65 | support = initBall(); 66 | compute(); 67 | } 68 | 69 | /** 70 | * Whether or not the miniball is the empty set, equivalently, whether {@code points.size() == 0} 71 | * was true when this miniball instance was constructed. 72 | * 73 | * Notice that the miniball of a point set S is empty if and only if S={}. 74 | * 75 | * @return true iff 76 | */ 77 | public boolean isEmpty() 78 | { 79 | return size == 0; 80 | } 81 | 82 | /** 83 | * The radius of the miniball. 84 | *

85 | * Precondition: {@code !isEmpty()} 86 | * 87 | * @return the radius of the miniball, a number ≥ 0 88 | */ 89 | public double radius() 90 | { 91 | return radius; 92 | } 93 | 94 | /** 95 | * The squared radius of the miniball. 96 | *

97 | * This is equivalent to {@code radius() * radius()}. 98 | *

99 | * Precondition: {@code !isEmpty()} 100 | * 101 | * @return the squared radius of the miniball 102 | */ 103 | public double squaredRadius() 104 | { 105 | return squaredRadius; 106 | } 107 | 108 | /** 109 | * The Euclidean coordinates of the center of the miniball. 110 | *

111 | * Precondition: {@code !isEmpty()} 112 | * 113 | * @return an array holding the coordinates of the center of the miniball 114 | */ 115 | public double[] center() 116 | { 117 | return center; 118 | } 119 | 120 | /** 121 | * The number of input points. 122 | * 123 | * @return the number of points in the original point set, i.e., {@code pts.size()} where 124 | * {@code pts} was the {@link PointSet} instance passed to the constructor of this 125 | * instance 126 | */ 127 | public int size() 128 | { 129 | return size; 130 | } 131 | 132 | /** 133 | * TODO 134 | * 135 | * @return 136 | */ 137 | public List support() 138 | { 139 | throw new RuntimeException("Not implemented yet."); 140 | } 141 | 142 | private static double sqr(double x) 143 | { 144 | return x * x; 145 | } 146 | 147 | /** 148 | * Sets up the search ball with an arbitrary point of S as center and with exactly one of 149 | * the points farthest from center in the support. So the current ball contains all points of 150 | * S and has radius at most twice as large as the minball. 151 | *

152 | * Precondition: {@code size > 0} 153 | */ 154 | private Subspan initBall() 155 | { 156 | assert size > 0; 157 | 158 | // Set center to the first point in S 159 | for (int i = 0; i < dim; ++i) 160 | center[i] = S.coord(0, i); 161 | 162 | // Find farthest point 163 | squaredRadius = 0; 164 | int farthest = 0; 165 | for (int j = 1; j < S.size(); ++j) 166 | { 167 | // Compute squared distance from center to S[j] 168 | double dist = 0; 169 | for (int i = 0; i < dim; ++i) 170 | dist += sqr(S.coord(j, i) - center[i]); 171 | 172 | // enlarge radius if needed: 173 | if (dist >= squaredRadius) 174 | { 175 | squaredRadius = dist; 176 | farthest = j; 177 | } 178 | } 179 | radius = Math.sqrt(squaredRadius); 180 | 181 | // Initialize support to the farthest point: 182 | return new Subspan(dim, S, farthest); 183 | } 184 | 185 | private void computeDistToAff() 186 | { 187 | distToAffSquare = support.shortestVectorToSpan(center, centerToAff); 188 | distToAff = Math.sqrt(distToAffSquare); 189 | } 190 | 191 | private void updateRadius() 192 | { 193 | final int any = support.anyMember(); 194 | squaredRadius = 0; 195 | for (int i = 0; i < dim; ++i) 196 | squaredRadius += sqr(S.coord(any, i) - center[i]); 197 | radius = sqrt(squaredRadius); 198 | if (log) debug("current radius = " + radius); 199 | } 200 | 201 | /** 202 | * The main function containing the main loop. 203 | *

204 | * Iteratively, we compute the point in support that is closest to the current center and then 205 | * walk towards this target as far as we can, i.e., we move until some new point touches the 206 | * boundary of the ball and must thus be inserted into support. In each of these two alternating 207 | * phases, we always have to check whether some point must be dropped from support, which is the 208 | * case when the center lies in aff(support). If such an attempt to drop fails, we are 209 | * done; because then the center lies even conv(support). 210 | */ 211 | private void compute() 212 | { 213 | // Invariant: The ball B(center,radius) always contains the whole 214 | // point set S and has the points in support on its boundary. 215 | while (true) 216 | { 217 | ++iteration; 218 | 219 | if (log) 220 | { 221 | debug("Iteration " + iteration); 222 | debug(support.size() + " points on the boundary"); 223 | } 224 | 225 | // Compute a walking direction and walking vector, 226 | // and check if the former is perhaps too small: 227 | computeDistToAff(); 228 | while (distToAff <= Eps * radius || 229 | /* 230 | * Note: the following line is currently needed because of point sets like schnartz, see 231 | * MiniballTest. 232 | */ 233 | support.size() == dim + 1) 234 | { 235 | // We are closer than Eps * radius_square, so we try a drop 236 | if (!successfulDrop()) 237 | { 238 | // If that is not possible, the center lies in the convex hull 239 | // and we are done. 240 | if (log) info("Done"); 241 | return; 242 | } 243 | computeDistToAff(); 244 | } 245 | // if (log) debug("distance to affine hull = " + distToAff); 246 | 247 | // Determine how far we can walk in direction centerToAff 248 | // without losing any point ('stopper', say) in S: 249 | final double scale = findStopFraction(); 250 | 251 | // Stopping point exists 252 | if (stopper >= 0) 253 | { 254 | // Walk as far as we can 255 | for (int i = 0; i < dim; ++i) 256 | center[i] += scale * centerToAff[i]; 257 | 258 | updateRadius(); 259 | 260 | // and add stopper to support 261 | support.add(stopper); 262 | // if (log) debug("adding global point #" + stopper); 263 | 264 | // No obstacle on our way into the affine hull 265 | } 266 | else 267 | { 268 | for (int i = 0; i < dim; ++i) 269 | center[i] += centerToAff[i]; 270 | 271 | updateRadius(); 272 | 273 | // Theoretically, the distance to the affine hull is now zero 274 | // and we would thus drop a point in the next iteration. 275 | // For numerical stability, we don't rely on that to happen but 276 | // try to drop a point right now: 277 | if (!successfulDrop()) 278 | { 279 | // Drop failed, so the center lies in conv(support) and is thus optimal. 280 | return; 281 | } 282 | } 283 | } 284 | } 285 | 286 | /** 287 | * If center doesn't already lie in conv(support) and is thus not optimal yet, 288 | * {@link #successfulDrop()} elects a suitable point k to be removed from the support and 289 | * returns true. If the center lies in the convex hull, however, false is returned (and the 290 | * support remains unaltered). 291 | *

292 | * Precondition: center lies in aff(support). 293 | */ 294 | boolean successfulDrop() 295 | { 296 | // Find coefficients of the affine combination of center 297 | support.findAffineCoefficients(center, lambdas); 298 | 299 | // find a non-positive coefficient 300 | int smallest = 0; 301 | double minimum = 1; 302 | for (int i = 0; i < support.size(); ++i) 303 | if (lambdas[i] < minimum) 304 | { 305 | minimum = lambdas[i]; 306 | smallest = i; 307 | } 308 | 309 | // Drop a point with non-positive coefficient, if any 310 | if (minimum <= 0) 311 | { 312 | // if (log) debug("removing local point #" + smallest); 313 | support.remove(smallest); 314 | return true; 315 | } 316 | return false; 317 | } 318 | 319 | /** 320 | * Given the center of the current enclosing ball and the walking direction {@code centerToAff}, 321 | * determine how much we can walk into this direction without losing a point from S. The 322 | * (positive) factor by which we can walk along {@code centerToAff} is returned. Further, 323 | * {@code stopper} is set to the index of the most restricting point and to -1 if no such point 324 | * was found. 325 | */ 326 | private double findStopFraction() 327 | { 328 | // We would like to walk the full length of centerToAff ... 329 | double scale = 1; 330 | stopper = -1; 331 | 332 | // ... but one of the points in S might hinder us 333 | for (int j = 0; j < size; ++j) 334 | if (!support.isMember(j)) 335 | { 336 | // Compute vector centerToPoint from center to the point S[j]: 337 | for (int i = 0; i < dim; ++i) 338 | centerToPoint[i] = S.coord(j, i) - center[i]; 339 | 340 | double dirPointProd = 0; 341 | for (int i = 0; i < dim; ++i) 342 | dirPointProd += centerToAff[i] * centerToPoint[i]; 343 | 344 | // We can ignore points beyond support since they stay enclosed anyway 345 | if (distToAffSquare - dirPointProd < Eps * radius * distToAff) continue; 346 | 347 | // Compute the fraction we can walk along centerToAff until 348 | // we hit point S[i] on the boundary. 349 | // (Better don't try to understand this calculus from the code, 350 | // it needs some pencil-and-paper work.) 351 | double bound = 0; 352 | for (int i = 0; i < dim; ++i) 353 | bound += centerToPoint[i] * centerToPoint[i]; 354 | bound = (squaredRadius - bound) / 2 / (distToAffSquare - dirPointProd); 355 | 356 | // Take the smallest fraction 357 | if (bound > 0 && bound < scale) 358 | { 359 | if (log) debug("found stopper " + j + " bound=" + bound + " scale=" + scale); 360 | scale = bound; 361 | stopper = j; 362 | } 363 | } 364 | 365 | return scale; 366 | } 367 | 368 | /** 369 | * Verifies that the computed ball is indeed the miniball. 370 | *

371 | * This method should be called for testing purposes only; it may not be very efficient. 372 | */ 373 | public Quality verify() 374 | { 375 | double min_lambda = 1; // for center-in-convex-hull check 376 | double max_overlength = 0; // for all-points-in-ball check 377 | double min_underlength = 0; // for all-boundary-points-on-boundary 378 | double ball_error; 379 | double qr_error = support.representationError(); 380 | 381 | // Center really in convex hull? 382 | support.findAffineCoefficients(center, lambdas); 383 | for (int k = 0; k < support.size(); ++k) 384 | if (lambdas[k] <= min_lambda) min_lambda = lambdas[k]; 385 | 386 | // All points in ball, all support points really on boundary? 387 | for (int k = 0; k < S.size(); ++k) 388 | { 389 | 390 | // Compare center-to-point distance with radius 391 | for (int i = 0; i < dim; ++i) 392 | centerToPoint[i] = S.coord(k, i) - center[i]; 393 | double sqDist = 0; 394 | for (int i = 0; i < dim; ++i) 395 | sqDist += sqr(centerToPoint[i]); 396 | ball_error = Math.sqrt(sqDist) - radius; 397 | 398 | // Check for sphere violations 399 | if (ball_error > max_overlength) max_overlength = ball_error; 400 | 401 | // check for boundary violations 402 | if (support.isMember(k)) if (ball_error < min_underlength) min_underlength = ball_error; 403 | } 404 | 405 | return new Quality(qr_error, min_lambda, max_overlength / radius, Math.abs(min_underlength / radius), iteration, 406 | support.size()); 407 | } 408 | 409 | /** 410 | * Outputs information about the miniball; this includes the quality information provided by 411 | * {@link #verify()} (and as a consequence, {@link #toString()} is expensive to call). 412 | */ 413 | @Override 414 | public String toString() 415 | { 416 | StringBuilder s = new StringBuilder("Miniball ["); 417 | if (isEmpty()) 418 | { 419 | s.append("isEmpty=true"); 420 | } 421 | else 422 | { 423 | s.append("center=("); 424 | for (int i = 0; i < dim; ++i) 425 | { 426 | s.append(center[i]); 427 | if (i < dim - 1) s.append(", "); 428 | } 429 | s.append("), radius=") 430 | .append(radius) 431 | .append(", squaredRadius=") 432 | .append(squaredRadius) 433 | .append(", quality=") 434 | .append(verify()); 435 | } 436 | return s.append("]").toString(); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /java/src/test/java/com/dreizak/miniball/highdim/MiniballTest.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.highdim; 2 | 3 | import static junit.framework.Assert.assertEquals; 4 | import static junit.framework.Assert.assertTrue; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | 12 | import com.dreizak.miniball.model.PointSet; 13 | import com.dreizak.miniball.model.PointSetUtils; 14 | 15 | public class MiniballTest 16 | { 17 | @Test 18 | public void test_almost_cospherical_points_3() throws IOException 19 | { 20 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/almost_cospherical_points_3.data")); 21 | double[] expectedCenter = { 22 | -1.56087201342490318e-12, -4.71446026502953592e-12, 3.67580472387244665e-12 23 | }; 24 | assertEquals(10000, mb.size()); 25 | assertAlmostEquals(expectedCenter, mb.center()); 26 | assertAlmostEquals(1.00000000015068569e+00, mb.squaredRadius()); 27 | } 28 | 29 | @Test 30 | public void test_almost_cospherical_points_10() throws IOException 31 | { 32 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/almost_cospherical_points_10.data")); 33 | double[] expectedCenter = { 34 | 5.87465071980114019e-12, 35 | -8.50168966753148976e-12, 36 | 5.16296955479470709e-12, 37 | 5.76827248197897234e-12, 38 | 4.79834156807867167e-12, 39 | 3.60221381957526638e-13, 40 | -5.39518968945866875e-12, 41 | 1.10109862800149464e-11, 42 | 5.92724627952881232e-12, 43 | 3.08121800360276428e-12 44 | }; 45 | assertEquals(10000, mb.size()); 46 | assertAlmostEquals(expectedCenter, mb.center()); 47 | assertAlmostEquals(1.00000000016537482e+00, mb.squaredRadius()); 48 | } 49 | 50 | @Test 51 | public void test_cocircular_points_large_radius_2() throws IOException 52 | { 53 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/cocircular_points_large_radius_2.data")); 54 | double[] expectedCenter = { 55 | 2.25917732813639420e-08, 2.66532983586183870e-08 56 | }; 57 | assertEquals(13824, mb.size()); 58 | assertAlmostEquals(expectedCenter, mb.center()); 59 | assertAlmostEquals(1.12830550249511283e+18, mb.squaredRadius()); 60 | } 61 | 62 | @Test 63 | public void test_cocircular_points_small_radius_2() throws IOException 64 | { 65 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/cocircular_points_small_radius_2.data")); 66 | double[] expectedCenter = { 67 | 0, 0 68 | }; 69 | assertEquals(6144, mb.size()); 70 | assertAlmostEquals(expectedCenter, mb.center()); 71 | assertAlmostEquals(3.72870291637512500e+15, mb.squaredRadius()); 72 | } 73 | 74 | @Test 75 | public void test_cube_10() throws IOException 76 | { 77 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/cube_10.data")); 78 | double[] expectedCenter = { 79 | 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 80 | }; 81 | assertEquals(1024, mb.size()); 82 | assertAlmostEquals(expectedCenter, mb.center()); 83 | assertAlmostEquals(2.5, mb.squaredRadius()); 84 | } 85 | 86 | @Test 87 | public void test_cube_12() throws IOException 88 | { 89 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/cube_12.data")); 90 | double[] expectedCenter = { 91 | 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 92 | }; 93 | assertEquals(4096, mb.size()); 94 | assertAlmostEquals(expectedCenter, mb.center()); 95 | assertAlmostEquals(3, mb.squaredRadius()); 96 | } 97 | 98 | @Test 99 | public void test_hurz() throws IOException 100 | { 101 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/hurz.data")); 102 | double[] expectedCenter = { 103 | -9.32385490743424666e-02, 104 | -1.50055661837044246e-01, 105 | 2.69364248349470529e-02, 106 | -2.40723461491440050e-03, 107 | -6.73931277729599293e-02, 108 | 5.48757211321871177e-02, 109 | -3.77507874171383526e-02, 110 | 1.52069101200107918e-02, 111 | 3.17372740475474271e-02, 112 | 1.19705682509647776e-01, 113 | 2.30776967046545170e-02, 114 | 1.34715212599093803e-01, 115 | -6.77742873939496671e-02, 116 | 8.15765590285277495e-03, 117 | 9.10084642845849501e-02, 118 | 7.59318418646900695e-02, 119 | 6.67014412781230803e-02, 120 | 2.43008063975082983e-02, 121 | 5.75994499483733213e-02, 122 | 2.71948026809261494e-02, 123 | 8.93012380869349226e-03, 124 | 6.78549081409481059e-03, 125 | -3.91898096095426723e-02, 126 | 2.61203895241791072e-02, 127 | 1.30183917339636385e-01, 128 | -9.05159956449119174e-02, 129 | 8.29177344908292452e-02, 130 | -1.01263907022741025e-02, 131 | 7.26859883818094521e-02, 132 | 1.50591656331595884e-02, 133 | -6.68358467554956426e-02, 134 | 1.32664103589673517e-02, 135 | 5.26161351518988074e-02, 136 | -5.95612639904603055e-03, 137 | 1.83611423209012921e-02, 138 | 5.87399305108671430e-02, 139 | 2.88703317032385394e-02, 140 | -7.63799904284965109e-02, 141 | -3.91152746123283185e-02, 142 | -3.89767810320450492e-02, 143 | 2.35807392210171836e-02, 144 | -2.03958450688055126e-02, 145 | -5.39645593710812513e-02, 146 | -1.59803577488685754e-02, 147 | 6.80661505439016240e-02, 148 | -2.98838443491668999e-02, 149 | 6.29793667302477600e-02, 150 | -5.06384997867233358e-02, 151 | -1.12101972723559248e-01, 152 | 2.63368521873624216e-02, 153 | -3.07658266670295631e-02, 154 | -2.64456299786865051e-02, 155 | 1.48886115569620697e-01, 156 | 6.39413771405114711e-02, 157 | 3.46874273101637105e-03, 158 | 5.04544870734000214e-02, 159 | -7.23303966655623881e-02, 160 | 6.69173418830567995e-02, 161 | -2.05141517773424237e-01, 162 | 1.53060995099086744e-01, 163 | 1.09764268432820569e-01, 164 | -4.15657204407630654e-02, 165 | 5.59111099323817001e-02, 166 | -1.08878576328361426e-01, 167 | 6.72514700094211659e-02, 168 | 5.67031662004510370e-02, 169 | 2.98503270757259527e-02, 170 | -2.22211414794732769e-02, 171 | 1.17699103704017344e-01, 172 | -7.74243833358304601e-02, 173 | 1.14144909884642876e-01, 174 | 1.18244885236012495e-01, 175 | -1.16498953956752041e-01, 176 | -1.54335227921318263e-01, 177 | -1.27061966479000893e-01, 178 | 4.93839729837722702e-02, 179 | 9.65276608923077567e-02, 180 | 6.87789555146762766e-02, 181 | 8.02775373675647941e-02, 182 | -2.95398290193190638e-02, 183 | 1.00148883621633628e-01, 184 | 1.41660341680982599e-01, 185 | -1.13077032939844724e-01, 186 | -9.92412686510760061e-02, 187 | -7.34660260569389156e-02, 188 | 2.44119317172067987e-02, 189 | -8.60823590551833689e-02, 190 | 4.81621197168055418e-02, 191 | 2.13685862730959644e-02, 192 | -2.10485109618934463e-02, 193 | 1.25159956599585509e-01, 194 | -9.81387077643431638e-02, 195 | 7.39057728784511953e-02, 196 | -5.74760989576298745e-02, 197 | 9.02697615359867450e-02, 198 | -7.13332356872704767e-03, 199 | -4.58852458824499237e-02, 200 | -4.63562081793597661e-02, 201 | 5.72641924237869832e-02, 202 | 5.81975472026072596e-02, 203 | 9.24491451791044916e-02, 204 | -9.11941779278649667e-02, 205 | 1.30119072412531483e-01, 206 | 1.36620780424713567e-01, 207 | 5.49437946994311352e-02, 208 | -8.13593869366321526e-02, 209 | 8.82168141230831315e-02, 210 | 1.63874559179065585e-02, 211 | 6.33525944638172467e-02, 212 | -7.78800446589289624e-02, 213 | 1.05095502046718692e-01, 214 | 6.49385016232090495e-02, 215 | -8.05548020284199695e-02, 216 | -7.51567403689807900e-02, 217 | 5.64638210880798116e-02, 218 | 3.20522130857825993e-02, 219 | -9.09777076137542745e-02, 220 | -9.76163942088352082e-02, 221 | -6.18280846431228312e-02, 222 | -1.31709582117138319e-01, 223 | -1.97527865667240527e-03, 224 | 7.21931497585995285e-03, 225 | -4.20086728084883079e-02, 226 | -1.97727359026356797e-01, 227 | 9.60228739542908459e-03, 228 | 1.16114754875734708e-01, 229 | 1.34450809189754477e-02, 230 | -4.53777260307672362e-02, 231 | -3.16343163102534713e-02, 232 | 1.28342389158762504e-01, 233 | 3.22817704875962702e-02, 234 | 1.54647738406440208e-02, 235 | 1.47101218437264564e-01, 236 | 2.15748587532079462e-02, 237 | 1.14410676952691956e-01, 238 | -5.61923008472568625e-02, 239 | 5.33700224338453030e-02, 240 | 2.68966630713247251e-02, 241 | -3.85872036409353192e-02, 242 | 3.38504979473726250e-02, 243 | -6.20998780759002694e-02, 244 | 2.05930310272340843e-02, 245 | 2.62773470028755222e-02, 246 | -1.54454477761890429e-02, 247 | 7.57992611852749548e-02, 248 | 2.73988468770113916e-02, 249 | 8.65941702756104076e-02, 250 | 5.07588096831466246e-02, 251 | -6.93803682141353462e-02, 252 | 4.81234642042299016e-02, 253 | 1.30443064335880442e-02, 254 | 8.27268777941885247e-02, 255 | -9.16112442109258546e-02, 256 | -4.45938341297191718e-02, 257 | 8.33022514572618299e-03, 258 | -5.50105572034664814e-03, 259 | 3.42063053108011871e-03, 260 | 2.62405615322419770e-03, 261 | 6.01664324087401378e-02, 262 | -2.07679618603799840e-02, 263 | 2.85058169191099720e-02, 264 | -1.09744548510173290e-01, 265 | -7.77693036824233380e-02, 266 | 1.02404605592325412e-03, 267 | -1.42314434178243930e-02, 268 | -5.62917711091049031e-02, 269 | 1.26842625168207629e-01, 270 | -3.14995069731299759e-02, 271 | -2.32973697443584604e-03, 272 | -1.04374526387181921e-01, 273 | -8.16693564187557230e-02, 274 | 4.21643856974714057e-02, 275 | 3.82603433374553228e-02, 276 | 7.30422312859391326e-02, 277 | 1.21749543666098747e-01, 278 | 6.11472483977238065e-02, 279 | 3.84288077038831277e-02, 280 | -7.17878918962588375e-02, 281 | -5.64552109618581682e-02, 282 | 1.60817706579412106e-01, 283 | 2.99709054001969129e-02, 284 | 3.41409563710960914e-02, 285 | -1.75688728854233277e-02, 286 | -2.63404757837025147e-02, 287 | 1.69281593990837642e-02, 288 | 7.60971983283898479e-02, 289 | -1.81655270703341247e-02, 290 | 3.49304205730180256e-02, 291 | 1.92995515142081568e-03, 292 | 2.16056157812245936e-02, 293 | -5.76236634233693035e-02, 294 | -7.61103361807527121e-03, 295 | -1.64553293154055154e-02, 296 | -4.08614772137683158e-02, 297 | 2.29673439914067565e-02, 298 | -7.45104306720340281e-02, 299 | 2.41470698178044355e-02, 300 | -8.83633789907483091e-02, 301 | -5.45070973771131506e-02, 302 | 4.86914400373976639e-02 303 | }; 304 | assertEquals(800, mb.size()); 305 | assertAlmostEquals(expectedCenter, mb.center()); 306 | assertAlmostEquals(7.38649825408094216e+01, mb.squaredRadius()); 307 | } 308 | 309 | @Test 310 | public void test_longitude_latitude_model_3() throws IOException 311 | { 312 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/longitude_latitude_model_3.data")); 313 | double[] expectedCenter = { 314 | 1.09529643705128193e-12, 7.76487134128295763e-12, 2.09782485152182732e-16 315 | }; 316 | assertEquals(10000, mb.size()); 317 | assertAlmostEquals(expectedCenter, mb.center()); 318 | assertAlmostEquals(1.00000000015833201e+00, mb.squaredRadius()); 319 | } 320 | 321 | @Test 322 | public void test_random_points_3() throws IOException 323 | { 324 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/random_points_3.data")); 325 | double[] expectedCenter = { 326 | 1.61093103034421342e-02, -3.39403799912667802e-03, -3.31378412407934593e-03 327 | }; 328 | assertEquals(10000, mb.size()); 329 | assertAlmostEquals(expectedCenter, mb.center()); 330 | assertAlmostEquals(6.86700124388323507e-01, mb.squaredRadius()); 331 | } 332 | 333 | @Test 334 | public void test_random_points_5() throws IOException 335 | { 336 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/random_points_5.data")); 337 | double[] expectedCenter = { 338 | 1.68879606901127695e-02, 339 | -5.59476980486128209e-03, 340 | -3.50733482301275185e-02, 341 | 4.97024582833288132e-05, 342 | -1.64470438482790457e-02 343 | }; 344 | assertEquals(10000, mb.size()); 345 | assertAlmostEquals(expectedCenter, mb.center()); 346 | assertAlmostEquals(9.86950425115668661e-01, mb.squaredRadius()); 347 | } 348 | 349 | @Test 350 | public void test_random_points_10() throws IOException 351 | { 352 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/random_points_10.data")); 353 | double[] expectedCenter = { 354 | -3.68108604382322915e-02, 355 | -2.56379744386364197e-02, 356 | 2.63525258190180009e-02, 357 | -4.39111259663507118e-03, 358 | 5.07655020786604433e-02, 359 | 2.46652284563754105e-02, 360 | 6.32648262334569861e-02, 361 | 1.72315689301736075e-02, 362 | -1.20835421442481560e-02, 363 | 6.39141412880165488e-03 364 | }; 365 | assertEquals(10000, mb.size()); 366 | assertAlmostEquals(expectedCenter, mb.center()); 367 | assertAlmostEquals(1.62004498782482909e+00, mb.squaredRadius()); 368 | } 369 | 370 | @Ignore 371 | @Test 372 | public void test_schnarz() throws IOException // TODO: see #6 373 | { 374 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/schnarz.data")); 375 | double[] expectedCenter = { 376 | 0, 0 377 | }; 378 | System.err.println("schnarz " + mb); 379 | assertEquals(10000, mb.size()); 380 | assertAlmostEquals(expectedCenter, mb.center()); 381 | assertAlmostEquals(0, mb.squaredRadius()); 382 | } 383 | 384 | @Test 385 | public void test_simplex_10() throws IOException 386 | { 387 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/simplex_10.data")); 388 | double[] expectedCenter = { 389 | 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 390 | }; 391 | assertEquals(10, mb.size()); 392 | assertAlmostEquals(expectedCenter, mb.center()); 393 | assertAlmostEquals(1 - 1 / 10.0, mb.squaredRadius()); 394 | } 395 | 396 | @Test 397 | public void test_simplex_15() throws IOException 398 | { 399 | Miniball mb = computeFromFile(getClass().getResourceAsStream("data/simplex_15.data")); 400 | double[] expectedCenter = { 401 | 1 / 15.0, 402 | 1 / 15.0, 403 | 1 / 15.0, 404 | 1 / 15.0, 405 | 1 / 15.0, 406 | 1 / 15.0, 407 | 1 / 15.0, 408 | 1 / 15.0, 409 | 1 / 15.0, 410 | 1 / 15.0, 411 | 1 / 15.0, 412 | 1 / 15.0, 413 | 1 / 15.0, 414 | 1 / 15.0, 415 | 1 / 15.0, 416 | }; 417 | assertEquals(15, mb.size()); 418 | assertAlmostEquals(expectedCenter, mb.center()); 419 | assertAlmostEquals(1 - 1 / 15.0, mb.squaredRadius()); 420 | } 421 | 422 | Miniball computeFromFile(InputStream s) throws IOException 423 | { 424 | PointSet pts = PointSetUtils.pointsFromStream(s); 425 | Miniball mb = new Miniball(pts); 426 | System.out.println(mb); 427 | return mb; 428 | } 429 | 430 | public final static void assertAlmostEquals(double[] expected, double[] actual) 431 | { 432 | for (int i = 0; i < expected.length; ++i) 433 | assertAlmostEquals(expected[i], actual[i]); 434 | } 435 | 436 | public final static void assertAlmostEquals(double expected, double actual) 437 | { 438 | assertTrue(Math.abs(expected - actual) < 1e-15); 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /csharp/miniball/highdim/Miniball.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | using static System.Math; 4 | using static SEB.Logging; 5 | 6 | namespace SEB; 7 | 8 | ///

9 | /// The smallest enclosing ball (a.k.a. miniball) of a set of points. 10 | ///
11 | /// The miniball MB(P) of a non-empty set P of points in d-dimensional Euclidean 12 | /// space R^d is defined to be the smallest ball that contains all points from P. 13 | ///
14 | /// In order to compute the miniball of a given point set, you create a PointSet instance, 15 | /// pts, say, that provides the algorithm access to the Euclidean coordinates of the points. 16 | /// You then pass this instance to the constructor #Miniball(PointSet). The point set may be 17 | /// empty (i.e., pts.size() == 0}, in which case #isEmpty() will return true. 18 | ///
19 | /// Instances of class Miniball are immutable. That is, when you create a Miniball, 20 | /// passing your point set to it, it computes the center and radius of the miniball and allows you to 21 | /// access this information via #radius(), #squaredRadius(), and #center(). 22 | /// However the Miniball instance will not reflect any subsequent changes you make to the 23 | /// underlying point set. 24 | ///
25 | public class Miniball 26 | { 27 | 28 | static double Eps = 1e-14; 29 | 30 | PointSet S; 31 | int size; 32 | int dim; 33 | int iteration; 34 | double[] center, centerToAff, centerToPoint, lambdas; 35 | double distToAff, distToAffSquare; 36 | double squaredRadius, radius; 37 | Subspan support; 38 | int stopper; 39 | 40 | /// 41 | /// Computes the miniball of the given point set. 42 | /// 43 | /// Notice that the point set {@code pts} is assumed to be immutable during the computation. That 44 | /// is, if you add, remove, or change points in the point set, you have to create a new instance of Miniball. 45 | /// 46 | /// the point set 47 | public Miniball(PointSet pts) 48 | { 49 | S = pts; 50 | size = S.Size; 51 | dim = S.Dimension; 52 | center = new double[dim]; 53 | centerToAff = new double[dim]; 54 | centerToPoint = new double[dim]; 55 | lambdas = new double[dim + 1]; 56 | support = InitBall(); 57 | Compute(); 58 | } 59 | 60 | /// 61 | /// Whether or not the miniball is the empty set, equivalently, whether points.size() == 0 62 | /// was true when this miniball instance was constructed. 63 | /// 64 | /// Notice that the miniball of a point set S is empty if and only if S={}. 65 | /// 66 | public bool IsEmpty => size == 0; 67 | 68 | /// 69 | /// The radius of the miniball. 70 | ///
71 | /// Precondition: !isEmpty() 72 | ///
73 | /// the radius of the miniball, a number ≥ 0 74 | public double Radius => radius; 75 | 76 | /// 77 | /// The squared radius of the miniball. 78 | ///
79 | /// This is equivalent to {@code radius() * radius()}. 80 | ///
81 | /// Precondition: !isEmpty() 82 | ///
83 | /// the squared radius of the miniball 84 | public double SquaredRadius => squaredRadius; 85 | 86 | /// 87 | /// The Euclidean coordinates of the center of the miniball. 88 | ///
89 | /// Precondition: !isEmpty() 90 | ///
91 | /// an array holding the coordinates of the center of the miniball 92 | public double[] Center => center; 93 | 94 | /// 95 | /// The number of input points. 96 | /// 97 | /// the number of points in the original point set, i.e., pts.size() where 98 | /// pts was the PointSet instance passed to the constructor of this instance 99 | public int Size => size; 100 | 101 | /// 102 | /// TODO 103 | /// 104 | public List Support() 105 | { 106 | throw new NotImplementedException(); 107 | } 108 | 109 | static double sqr(double x) => x * x; 110 | 111 | /// 112 | /// Sets up the search ball with an arbitrary point of S as center and with exactly one of 113 | /// the points farthest from center in the support. So the current ball contains all points of 114 | /// S and has radius at most twice as large as the minball. 115 | ///
116 | /// Precondition: size > 0 117 | ///
118 | Subspan InitBall() 119 | { 120 | Trace.Assert(Size > 0); 121 | 122 | // Set center to the first point in S 123 | for (int i = 0; i < dim; ++i) 124 | center[i] = S.Coord(0, i); 125 | 126 | // Find farthest point 127 | squaredRadius = 0; 128 | var farthest = 0; 129 | for (int j = 1; j < S.Size; ++j) 130 | { 131 | // Compute squared distance from center to S[j] 132 | var dist = 0d; 133 | for (int i = 0; i < dim; ++i) 134 | dist += sqr(S.Coord(j, i) - center[i]); 135 | 136 | // enlarge radius if needed: 137 | if (dist >= squaredRadius) 138 | { 139 | squaredRadius = dist; 140 | farthest = j; 141 | } 142 | } 143 | radius = Sqrt(squaredRadius); 144 | 145 | // Initialize support to the farthest point: 146 | return new Subspan(dim, S, farthest); 147 | } 148 | 149 | void ComputeDistToAff() 150 | { 151 | distToAffSquare = support.ShortestVectorToSpan(center, centerToAff); 152 | distToAff = Sqrt(distToAffSquare); 153 | } 154 | 155 | void UpdateRadius() 156 | { 157 | var any = support.AnyMember(); 158 | squaredRadius = 0; 159 | for (int i = 0; i < dim; ++i) 160 | squaredRadius += sqr(S.Coord(any, i) - center[i]); 161 | radius = Sqrt(squaredRadius); 162 | if (log) Debug("current radius = " + radius); 163 | } 164 | 165 | /// 166 | /// The main function containing the main loop. 167 | ///
168 | /// Iteratively, we compute the point in support that is closest to the current center and then 169 | /// walk towards this target as far as we can, i.e., we move until some new point touches the 170 | /// boundary of the ball and must thus be inserted into support. In each of these two alternating 171 | /// phases, we always have to check whether some point must be dropped from support, which is the 172 | /// case when the center lies in aff(support). If such an attempt to drop fails, we are 173 | /// done; because then the center lies even conv(support). 174 | ///
175 | void Compute() 176 | { 177 | // Invariant: The ball B(center,radius) always contains the whole 178 | // point set S and has the points in support on its boundary. 179 | while (true) 180 | { 181 | ++iteration; 182 | 183 | if (log) 184 | { 185 | Debug("Iteration " + iteration); 186 | Debug(support.Size + " points on the boundary"); 187 | } 188 | 189 | // Compute a walking direction and walking vector, 190 | // and check if the former is perhaps too small: 191 | ComputeDistToAff(); 192 | while (distToAff <= Eps * radius || 193 | /* 194 | * Note: the following line is currently needed because of point sets like schnartz, see 195 | * MiniballTest. 196 | */ 197 | support.Size == dim + 1) 198 | { 199 | // We are closer than Eps * radius_square, so we try a drop 200 | if (!SuccessfulDrop()) 201 | { 202 | // If that is not possible, the center lies in the convex hull 203 | // and we are done. 204 | if (log) Info("Done"); 205 | return; 206 | } 207 | ComputeDistToAff(); 208 | } 209 | // if (log) debug("distance to affine hull = " + distToAff); 210 | 211 | // Determine how far we can walk in direction centerToAff 212 | // without losing any point ('stopper', say) in S: 213 | var scale = FindStopFraction(); 214 | 215 | // Stopping point exists 216 | if (stopper >= 0) 217 | { 218 | // Walk as far as we can 219 | for (int i = 0; i < dim; ++i) 220 | center[i] += scale * centerToAff[i]; 221 | 222 | UpdateRadius(); 223 | 224 | // and add stopper to support 225 | support.Add(stopper); 226 | // if (log) debug("adding global point #" + stopper); 227 | 228 | // No obstacle on our way into the affine hull 229 | } 230 | else 231 | { 232 | for (int i = 0; i < dim; ++i) 233 | center[i] += centerToAff[i]; 234 | 235 | UpdateRadius(); 236 | 237 | // Theoretically, the distance to the affine hull is now zero 238 | // and we would thus drop a point in the next iteration. 239 | // For numerical stability, we don't rely on that to happen but 240 | // try to drop a point right now: 241 | if (!SuccessfulDrop()) 242 | { 243 | // Drop failed, so the center lies in conv(support) and is thus optimal. 244 | return; 245 | } 246 | } 247 | } 248 | } 249 | 250 | /// 251 | /// If center doesn't already lie in conv(support) and is thus not optimal yet, 252 | /// #successfulDrop() elects a suitable point k to be removed from the support and 253 | /// returns true. If the center lies in the convex hull, however, false is returned (and the 254 | /// support remains unaltered). 255 | ///
256 | /// Precondition: center lies in aff(support). 257 | ///
258 | bool SuccessfulDrop() 259 | { 260 | // Find coefficients of the affine combination of center 261 | support.FindAffineCoefficients(center, lambdas); 262 | 263 | // find a non-positive coefficient 264 | int smallest = 0; 265 | double minimum = 1; 266 | for (int i = 0; i < support.Size; ++i) 267 | if (lambdas[i] < minimum) 268 | { 269 | minimum = lambdas[i]; 270 | smallest = i; 271 | } 272 | 273 | // Drop a point with non-positive coefficient, if any 274 | if (minimum <= 0) 275 | { 276 | // if (log) debug("removing local point #" + smallest); 277 | support.Remove(smallest); 278 | return true; 279 | } 280 | return false; 281 | } 282 | 283 | /// 284 | /// Given the center of the current enclosing ball and the walking direction centerToAff, 285 | /// determine how much we can walk into this direction without losing a point from S. The 286 | /// (positive) factor by which we can walk along centerToAff is returned. Further, 287 | /// stopper is set to the index of the most restricting point and to -1 if no such point was found. 288 | /// 289 | double FindStopFraction() 290 | { 291 | // We would like to walk the full length of centerToAff ... 292 | var scale = 1d; 293 | stopper = -1; 294 | 295 | // ... but one of the points in S might hinder us 296 | for (int j = 0; j < size; ++j) 297 | if (!support.IsMember(j)) 298 | { 299 | // Compute vector centerToPoint from center to the point S[j]: 300 | for (int i = 0; i < dim; ++i) 301 | centerToPoint[i] = S.Coord(j, i) - center[i]; 302 | 303 | var dirPointProd = 0d; 304 | for (int i = 0; i < dim; ++i) 305 | dirPointProd += centerToAff[i] * centerToPoint[i]; 306 | 307 | // We can ignore points beyond support since they stay enclosed anyway 308 | if (distToAffSquare - dirPointProd < Eps * radius * distToAff) continue; 309 | 310 | // Compute the fraction we can walk along centerToAff until 311 | // we hit point S[i] on the boundary. 312 | // (Better don't try to understand this calculus from the code, 313 | // it needs some pencil-and-paper work.) 314 | var bound = 0d; 315 | for (int i = 0; i < dim; ++i) 316 | bound += centerToPoint[i] * centerToPoint[i]; 317 | bound = (squaredRadius - bound) / 2 / (distToAffSquare - dirPointProd); 318 | 319 | // Take the smallest fraction 320 | if (bound > 0 && bound < scale) 321 | { 322 | if (log) Debug("found stopper " + j + " bound=" + bound + " scale=" + scale); 323 | scale = bound; 324 | stopper = j; 325 | } 326 | } 327 | 328 | return scale; 329 | } 330 | 331 | /// 332 | /// Verifies that the computed ball is indeed the miniball. 333 | ///
334 | /// This method should be called for testing purposes only; it may not be very efficient. 335 | ///
336 | public Quality Verify() 337 | { 338 | var min_lambda = 1d; // for center-in-convex-hull check 339 | var max_overlength = 0d; // for all-points-in-ball check 340 | var min_underlength = 0d; // for all-boundary-points-on-boundary 341 | double ball_error; 342 | var qr_error = support.RepresentationError(); 343 | 344 | // Center really in convex hull? 345 | support.FindAffineCoefficients(center, lambdas); 346 | for (int k = 0; k < support.Size; ++k) 347 | if (lambdas[k] <= min_lambda) min_lambda = lambdas[k]; 348 | 349 | // All points in ball, all support points really on boundary? 350 | for (int k = 0; k < S.Size; ++k) 351 | { 352 | 353 | // Compare center-to-point distance with radius 354 | for (int i = 0; i < dim; ++i) 355 | centerToPoint[i] = S.Coord(k, i) - center[i]; 356 | double sqDist = 0; 357 | for (int i = 0; i < dim; ++i) 358 | sqDist += sqr(centerToPoint[i]); 359 | ball_error = Sqrt(sqDist) - radius; 360 | 361 | // Check for sphere violations 362 | if (ball_error > max_overlength) max_overlength = ball_error; 363 | 364 | // check for boundary violations 365 | if (support.IsMember(k)) if (ball_error < min_underlength) min_underlength = ball_error; 366 | } 367 | 368 | return new Quality(qr_error, min_lambda, max_overlength / radius, Abs(min_underlength / radius), iteration, 369 | support.Size); 370 | } 371 | 372 | /// 373 | /// Outputs information about the miniball; this includes the quality information provided by 374 | /// #verify() (and as a consequence, #toString() is expensive to call). 375 | /// 376 | public override string ToString() 377 | { 378 | var sb = new StringBuilder("Miniball ["); 379 | 380 | if (IsEmpty) 381 | { 382 | sb.Append("isEmpty=true"); 383 | } 384 | else 385 | { 386 | sb.Append("center=("); 387 | for (int i = 0; i < dim; ++i) 388 | { 389 | sb.Append(center[i]); 390 | if (i < dim - 1) sb.Append(", "); 391 | } 392 | sb 393 | .Append("), radius=") 394 | .Append(radius) 395 | .Append(", squaredRadius=") 396 | .Append(squaredRadius) 397 | .Append(", quality=") 398 | .Append(Verify()); 399 | } 400 | sb.Append("]"); 401 | 402 | return sb.ToString(); 403 | } 404 | 405 | } -------------------------------------------------------------------------------- /cpp/main/Seb-inl.h: -------------------------------------------------------------------------------- 1 | // Synopsis: Library to find the smallest enclosing ball of points 2 | // 3 | // Authors: Martin Kutz , 4 | // Kaspar Fischer 5 | 6 | #ifndef SEB_SEB_INL_H 7 | #define SEB_SEB_INL_H 8 | 9 | #include 10 | #include 11 | 12 | #include "Seb.h" // Note: header included for better syntax highlighting in some IDEs. 13 | 14 | namespace SEB_NAMESPACE { 15 | 16 | template 17 | void Smallest_enclosing_ball::allocate_resources() 18 | { 19 | center = new Float[dim]; 20 | center_to_aff = new Float[dim]; 21 | center_to_point = new Float[dim]; 22 | lambdas = new Float[dim+1]; 23 | } 24 | 25 | template 26 | void Smallest_enclosing_ball::deallocate_resources() 27 | { 28 | delete[] center; 29 | delete[] center_to_aff; 30 | delete[] center_to_point; 31 | delete[] lambdas; 32 | 33 | if (support != NULL) 34 | delete support; 35 | } 36 | 37 | template 38 | void Smallest_enclosing_ball::init_ball() 39 | // Precondition: |S| > 0 40 | // Sets up the search ball with an arbitrary point of S as center 41 | // and with with exactly one of the points farthest from center in 42 | // support, which is instantiated here. 43 | // So the current ball contains all points of S and has radius at 44 | // most twice as large as the minball. 45 | { 46 | SEB_ASSERT(S.size() > 0); 47 | 48 | // set center to the first point in S: 49 | for (unsigned int i = 0; i < dim; ++i) 50 | center[i] = S[0][i]; 51 | 52 | // find farthest point: 53 | radius_square = 0; 54 | unsigned int farthest = 0; // Note: assignment prevents compiler warnings. 55 | for (unsigned int j = 1; j < S.size(); ++j) { 56 | // compute squared distance from center to S[j]: 57 | Float dist = 0; 58 | for (unsigned int i = 0; i < dim; ++i) 59 | dist += sqr(S[j][i] - center[i]); 60 | 61 | // enlarge radius if needed: 62 | if (dist >= radius_square) { 63 | radius_square = dist; 64 | farthest = j; 65 | } 66 | radius_ = sqrt(radius_square); 67 | } 68 | 69 | // initialize support to the farthest point: 70 | if (support != NULL) 71 | delete support; 72 | support = new Subspan(dim,S,farthest); 73 | 74 | // statistics: 75 | // initialize entry-counters to zero: 76 | SEB_STATS(entry_count = std::vector(S.size(),0)); 77 | } 78 | 79 | template 80 | bool Smallest_enclosing_ball::successful_drop() 81 | // Precondition: center lies in aff(support). 82 | // If center doesn't already lie in conv(support) and is thus 83 | // not optimal yet, successful_drop() elects a suitable point k to 84 | // be removed from support -- and returns true. 85 | // If center lies in the convex hull, however, false is returned 86 | // (and support remains unaltered). 87 | { 88 | // find coefficients of the affine combination of center: 89 | support->find_affine_coefficients(center,lambdas); 90 | 91 | // find a non-positive coefficient: 92 | unsigned int smallest = 0; // Note: assignment prevents compiler warnings. 93 | Float minimum(1); 94 | for (unsigned int i=0; isize(); ++i) 95 | if (lambdas[i] < minimum) { 96 | minimum = lambdas[i]; 97 | smallest = i; 98 | } 99 | 100 | // drop a point with non-positive coefficient, if any: 101 | if (minimum <= 0) { 102 | SEB_LOG ("debug"," removing local point #" << smallest << std::endl); 103 | support->remove_point(smallest); 104 | return true; 105 | } 106 | return false; 107 | } 108 | 109 | template 110 | Float Smallest_enclosing_ball::find_stop_fraction(int& stopper) 111 | // Given the center of the current enclosing ball and the 112 | // walking direction center_to_aff, determine how much we can walk 113 | // into this direction without losing a point from S. The (positive) 114 | // factor by which we can walk along center_to_aff is returned. 115 | // Further, stopper is set to the index of the most restricting point 116 | // and to -1 if no such point was found. 117 | { 118 | using std::inner_product; 119 | 120 | // We would like to walk the full length of center_to_aff ... 121 | Float scale = 1; 122 | stopper = -1; 123 | 124 | SEB_DEBUG (Float margin = 0;) 125 | 126 | // ... but one of the points in S might hinder us: 127 | for (unsigned int j = 0; j < S.size(); ++j) 128 | if (!support->is_member(j)) { 129 | 130 | // compute vector center_to_point from center to the point S[i]: 131 | for (unsigned int i = 0; i < dim; ++i) 132 | center_to_point[i] = S[j][i] - center[i]; 133 | 134 | const Float dir_point_prod 135 | = inner_product(center_to_aff,center_to_aff+dim, 136 | center_to_point,Float(0)); 137 | 138 | // we can ignore points beyond support since they stay 139 | // enclosed anyway: 140 | if (dist_to_aff_square - dir_point_prod 141 | // make new variable 'radius_times_dist_to_aff'? ! 142 | < Eps * radius_ * dist_to_aff) 143 | continue; 144 | 145 | // compute the fraction we can walk along center_to_aff until 146 | // we hit point S[i] on the boundary: 147 | // (Better don't try to understand this calculus from the code, 148 | // it needs some pencil-and-paper work.) 149 | Float bound = radius_square; 150 | bound -= inner_product(center_to_point,center_to_point+dim, 151 | center_to_point,Float(0)); 152 | bound /= 2 * (dist_to_aff_square - dir_point_prod); 153 | 154 | // watch for numerical instability - if bound=0 then we are 155 | // going to walk by zero units, and thus hit an infinite loop. 156 | // 157 | // If we are walking < 0, then we are going the wrong way, 158 | // which can happen if we were to disable the test just above 159 | // (the "dist_to_aff_square-dir_point_prod" test) 160 | 161 | // take the smallest fraction: 162 | if (bound > 0 && bound < scale) { 163 | scale = bound; 164 | stopper = j; 165 | SEB_DEBUG (margin = dist_to_aff - dir_point_prod / dist_to_aff;) 166 | } 167 | } 168 | 169 | SEB_LOG ("debug"," margin = " << margin << std::endl); 170 | 171 | return scale; 172 | } 173 | 174 | 175 | template 176 | void Smallest_enclosing_ball::update() 177 | // The main function containing the main loop. 178 | // Iteratively, we compute the point in support that is closest 179 | // to the current center and then walk towards this target as far 180 | // as we can, i.e., we move until some new point touches the 181 | // boundary of the ball and must thus be inserted into support. 182 | // In each of these two alternating phases, we always have to check 183 | // whether some point must be dropped from support, which is the 184 | // case when the center lies in aff(support). 185 | // If such an attempt to drop fails, we are done; because then 186 | // the center lies even conv(support). 187 | { 188 | SEB_DEBUG (int iteration = 0;) 189 | 190 | SEB_TIMER_START("computation"); 191 | 192 | // optimistically, we set this flag now; 193 | // on return from this function it will be true: 194 | up_to_date = true; 195 | 196 | init_ball(); 197 | 198 | // Invariant: The ball B(center,radius_) always contains the whole 199 | // point set S and has the points in support on its boundary. 200 | 201 | while (true) { 202 | 203 | SEB_LOG ("debug"," iteration " << ++iteration << std::endl); 204 | 205 | SEB_LOG ("debug"," " << support->size() 206 | << " points on boundary" << std::endl); 207 | 208 | // Compute a walking direction and walking vector, 209 | // and check if the former is perhaps too small: 210 | while ((dist_to_aff 211 | = sqrt(dist_to_aff_square 212 | = support->shortest_vector_to_span(center, 213 | center_to_aff))) 214 | <= Eps * radius_) 215 | // We are closer than Eps * radius_square, so we try a drop: 216 | if (!successful_drop()) { 217 | // If that is not possible, the center lies in the convex hull 218 | // and we are done. 219 | SEB_TIMER_PRINT("computation"); 220 | return; 221 | } 222 | 223 | SEB_LOG ("debug"," distance to affine hull = " 224 | << dist_to_aff << std::endl); 225 | 226 | // determine how far we can walk in direction center_to_aff 227 | // without losing any point ('stopper', say) in S: 228 | int stopper; 229 | Float scale = find_stop_fraction(stopper); 230 | SEB_LOG ("debug"," stop fraction = " << scale << std::endl); 231 | 232 | // Note: In theory, the following if-statement should simply read 233 | // 234 | // if (stopper >= 0) { 235 | // // ... 236 | // 237 | // However, due to rounding errors, it may happen in practice that 238 | // stopper is nonnegative and the support is already full (see #14); 239 | // in this casev we cannot add yet another point to the support. 240 | // 241 | // Therefore, the condition reads: 242 | if (stopper >= 0 && support->size() <= dim) { 243 | // stopping point exists 244 | 245 | // walk as far as we can 246 | for (unsigned int i = 0; i < dim; ++i) 247 | center[i] += scale * center_to_aff[i]; 248 | 249 | // update the radius 250 | const Pt& stop_point = S[support->any_member()]; 251 | radius_square = 0; 252 | for (unsigned int i = 0; i < dim; ++i) 253 | radius_square += sqr(stop_point[i] - center[i]); 254 | radius_ = sqrt(radius_square); 255 | SEB_LOG ("debug"," current radius = " 256 | << std::setiosflags(std::ios::scientific) 257 | << std::setprecision(17) << radius_ 258 | << std::endl << std::endl); 259 | 260 | // and add stopper to support 261 | support->add_point(stopper); 262 | SEB_STATS (++entry_count[stopper]); 263 | SEB_LOG ("debug"," adding global point #" << stopper << std::endl); 264 | } 265 | else { 266 | // we can run unhindered into the affine hull 267 | SEB_LOG ("debug"," moving into affine hull" << std::endl); 268 | for (unsigned int i=0; iany_member()]; 273 | radius_square = 0; 274 | for (unsigned int i = 0; i < dim; ++i) 275 | radius_square += sqr(stop_point[i] - center[i]); 276 | radius_ = sqrt(radius_square); 277 | SEB_LOG ("debug"," current radius = " 278 | << std::setiosflags(std::ios::scientific) 279 | << std::setprecision(17) << radius_ 280 | << std::endl << std::endl); 281 | 282 | // Theoretically, the distance to the affine hull is now zero 283 | // and we would thus drop a point in the next iteration. 284 | // For numerical stability, we don't rely on that to happen but 285 | // try to drop a point right now: 286 | if (!successful_drop()) { 287 | // Drop failed, so the center lies in conv(support) and is thus 288 | // optimal. 289 | SEB_TIMER_PRINT("computation"); 290 | return; 291 | } 292 | } 293 | } 294 | } 295 | 296 | template 297 | void Smallest_enclosing_ball::verify() 298 | { 299 | using std::inner_product; 300 | using std::abs; 301 | using std::cout; 302 | using std::endl; 303 | 304 | Float min_lambda = 1; // for center-in-convex-hull check 305 | Float max_overlength = 0; // for all-points-in-ball check 306 | Float min_underlength = 0; // for all-boundary-points-on-boundary 307 | Float ball_error; 308 | Float qr_error = support->representation_error(); 309 | 310 | // center really in convex hull? 311 | support->find_affine_coefficients(center,lambdas); 312 | for (unsigned int k = 0; k < support->size(); ++k) 313 | if (lambdas[k] <= min_lambda) 314 | min_lambda = lambdas[k]; 315 | 316 | // all points in ball, all support points really on boundary? 317 | for (unsigned int k = 0; k < S.size(); ++k) { 318 | 319 | // compare center-to-point distance with radius 320 | for (unsigned int i = 0; i < dim; ++i) 321 | center_to_point[i] = S[k][i] - center[i]; 322 | ball_error = sqrt(inner_product(center_to_point,center_to_point+dim, 323 | center_to_point,Float(0))) 324 | - radius_; 325 | 326 | // check for sphere violations 327 | if (ball_error > max_overlength) max_overlength = ball_error; 328 | 329 | // check for boundary violations 330 | if (support->is_member(k)) 331 | if (ball_error < min_underlength) min_underlength = ball_error; 332 | } 333 | 334 | cout << "Solution errors (relative to radius, nonsquared)" << endl 335 | << " final QR inconsistency : " << qr_error << endl 336 | << " minimal convex coefficient : "; 337 | if (min_lambda >= 0) cout << "positive"; 338 | else cout << (-min_lambda); 339 | cout << endl 340 | << " maximal overlength : " 341 | << (max_overlength / radius_) << endl 342 | << " maximal underlength : " 343 | << (abs(min_underlength / radius_)) 344 | << endl; 345 | 346 | 347 | #ifdef SEB_STATS_MODE 348 | // And let's print some statistics about the rank changes 349 | cout << "=====================================================" << endl 350 | << "Statistics" << endl; 351 | 352 | // determine how often a single point entered support at most 353 | int max_enter = 0; 354 | for (int i = 0; i < S.size(); ++i) 355 | if (entry_count[i] > max_enter) 356 | max_enter = entry_count[i]; 357 | ++max_enter; 358 | 359 | // compute a histogram from the entry data ... 360 | std::vector histogram(max_enter+1); 361 | for (int j = 0; j <= max_enter; ++j) 362 | histogram[j] = 0; 363 | for (int i = 0; i < S.size(); ++i) 364 | histogram[entry_count[i]]++; 365 | // ... and print it 366 | for (int j = 0; j <= max_enter; j++) 367 | if (histogram[j]) 368 | cout << histogram[j] << " points entered " << j << " times" << endl; 369 | #endif // SEB_STATS MODE 370 | } 371 | 372 | 373 | template 374 | void Smallest_enclosing_ball::test_affine_stuff() 375 | { 376 | using std::cout; 377 | using std::endl; 378 | 379 | Float *direction; 380 | Float error; 381 | Float max_representation_error = 0; 382 | 383 | cout << S.size() << " points in " << dim << " dimensions" << endl; 384 | 385 | if (!is_empty()) { 386 | support = new Subspan(dim,S,0); 387 | cout << "initializing affine subspace with point S[0]" << endl; 388 | 389 | direction = new Float[dim]; 390 | } 391 | else return; 392 | 393 | for (int loop = 0; loop < 5; ++loop) { 394 | 395 | // Try to fill each point of S into aff 396 | for (int i = 0; i < S.size(); ++i) { 397 | 398 | cout << endl << "Trying new point #" << i << endl; 399 | 400 | Float dist = sqrt(support->shortest_vector_to_span(S[i],direction)); 401 | cout << "dist(S[" << i << "],affine_hull) = " 402 | << dist << endl; 403 | 404 | if (dist > 1.0E-8) { 405 | cout << "inserting point S["<add_point(i); 408 | 409 | cout << "representation error: " 410 | << (error = support->representation_error()) << endl; 411 | if (error > max_representation_error) 412 | max_representation_error = error; 413 | } 414 | } 415 | 416 | // Throw out half of the points 417 | while (support->size() > dim/2) { 418 | 419 | // throw away the origin or point at position r/2, 420 | // depending on whether size is odd or even: 421 | int k = support->size()/2; 422 | if (2 * k == support->size()) { 423 | // size even 424 | cout << endl << "Throwing out local point #" << k << endl; 425 | support->remove_point(k); 426 | } else { 427 | // size odd 428 | cout << endl << "Throughing out the origin" << endl; 429 | support->remove(support->size()-1); 430 | } 431 | 432 | cout << "representation error: " 433 | << (error = support->representation_error()) << endl; 434 | if (error > max_representation_error) 435 | max_representation_error = error; 436 | } 437 | } 438 | 439 | cout << "maximal representation error: " 440 | << max_representation_error << endl 441 | << "End of test." << endl; 442 | 443 | delete support; 444 | delete direction; 445 | } 446 | 447 | 448 | template 449 | const Float Smallest_enclosing_ball::Eps = Float(1e-14); 450 | 451 | } // namespace SEB_NAMESPACE 452 | 453 | #endif // SEB_SEB_INL_H 454 | -------------------------------------------------------------------------------- /java/src/main/java/com/dreizak/miniball/highdim/Subspan.java: -------------------------------------------------------------------------------- 1 | package com.dreizak.miniball.highdim; 2 | 3 | import static com.dreizak.miniball.highdim.Logging.info; 4 | import static com.dreizak.miniball.highdim.Logging.log; 5 | import static java.lang.Math.abs; 6 | import static java.lang.Math.sqrt; 7 | 8 | import java.util.BitSet; 9 | 10 | import com.dreizak.miniball.model.PointSet; 11 | 12 | /** 13 | * Affine hull of a non-empty set of affinely independent points. 14 | *

15 | * An instance of this class represents the affine hull of a non-empty set M of affinely 16 | * independent points. The set M is not represented explicity; instead, when an instance of 17 | * this class is constructed, you pass a list S of points to it (which the instance will 18 | * never change and which is assumed to stay fixed for the lifetime of this instance): The set 19 | * M is then a subset of S, and its members are identified by their (zero-based) 20 | * indices in S. 21 | */ 22 | final class Subspan 23 | { 24 | private final PointSet S; 25 | 26 | // S[i] in M iff memberhsip[i] 27 | private final BitSet membership; 28 | 29 | // Ambient dimension (not to be confused with the rank r from below) 30 | private final int dim; 31 | 32 | // members[i] contains the index into S of the i-th point of M. 33 | // The point members[r] is called the "origin". 34 | private final int members[]; 35 | 36 | // (dim x dim)-matrices Q (orthogonal) and R (upper triangular); 37 | // notice that Q[j][i] is the element in row i and column j 38 | private final double[][] Q, R; 39 | 40 | // dim-vectors needed for rank-1 update 41 | private final double[] u, w; 42 | 43 | // Rank of R (i.e. #points - 1) 44 | private int r; 45 | 46 | // Used in givens() below, see documentation of the latter. 47 | private double c, s; 48 | 49 | /** 50 | * Constructs an instance representing the affine hull aff(M) of M={p}, where 51 | * p is the point S[k] from S. 52 | *

53 | * Notice that S must not change during the lifetime of this instance. 54 | * 55 | * @param dim 56 | * the ambient space dimension 57 | * @param S 58 | * the point set 59 | * @param k 60 | * index of the point p in the point set S 61 | */ 62 | Subspan(int dim, PointSet S, int k) 63 | { 64 | this.S = S; 65 | this.dim = dim; 66 | this.membership = new BitSet(S.size()); 67 | this.members = new int[dim + 1]; 68 | this.r = 0; 69 | 70 | // Allocate storage for Q, R, u, and w 71 | Q = new double[dim][]; 72 | R = new double[dim][]; 73 | for (int i = 0; i < dim; ++i) 74 | { 75 | Q[i] = new double[dim]; 76 | R[i] = new double[dim]; 77 | } 78 | u = new double[dim]; 79 | w = new double[dim]; 80 | 81 | // Initialize Q to the identity matrix: 82 | for (int i = 0; i < dim; ++i) 83 | for (int j = 0; j < dim; ++j) 84 | Q[j][i] = (i == j) ? 1.0 : 0.0; 85 | 86 | members[r] = k; 87 | membership.set(k); 88 | 89 | if (log) info("rank: " + r); 90 | } 91 | 92 | public int dimension() 93 | { 94 | return dim; 95 | } 96 | 97 | /** 98 | * The size of the instance's set M, a number between 0 and {@code dim+1}. 99 | *

100 | * Complexity: O(1). 101 | * 102 | * @return |M| 103 | */ 104 | public int size() 105 | { 106 | return r + 1; 107 | } 108 | 109 | /** 110 | * Whether S[i] is a member of M. 111 | *

112 | * Complexity: O(1) 113 | * 114 | * @param i 115 | * the "global" index into S 116 | * @return true iff S[i] is a member of M 117 | */ 118 | public boolean isMember(int i) 119 | { 120 | assert 0 <= i && i < S.size(); 121 | return membership.get(i); 122 | } 123 | 124 | /** 125 | * The global index (into S) of an arbitrary element of M. 126 | *

127 | * Precondition: {@code size()>0} 128 | *

129 | * Postcondition: {@code isMember(anyMember())} 130 | */ 131 | public int anyMember() 132 | { 133 | assert size() > 0; 134 | return members[r]; 135 | } 136 | 137 | /** 138 | * The index (into S) of the ith point in M. The points in M are 139 | * internally ordered (in an arbitrary way) and this order only changes when {@link add()} or 140 | * {@link remove()} is called. 141 | *

142 | * Complexity: O(1) 143 | * 144 | * @param i 145 | * the "local" index, 0 ≤ i < {@code size()} 146 | * @return j such that S[j] equals the ith point of M 147 | */ 148 | public int globalIndex(int i) 149 | { 150 | assert 0 <= i && i < size(); 151 | return members[i]; 152 | } 153 | 154 | /** 155 | * Short-hand for code readability to access element (i,j) of a matrix that is stored in a 156 | * one-dimensional array. 157 | * 158 | * @param i 159 | * zero-based row number 160 | * @param j 161 | * zero-based column number 162 | * @return the index into the one-dimensional array to get the element at position (i,j) in 163 | * the matrix 164 | */ 165 | private final int ind(int i, int j) 166 | { 167 | return i * dim + j; 168 | } 169 | 170 | /** 171 | * The point {@code members[r]} is called the origin. 172 | * 173 | * @return index into S of the origin. 174 | */ 175 | private final int origin() 176 | { 177 | return members[r]; 178 | } 179 | 180 | /** 181 | * Determine the Givens coefficients (c,s) satisfying 182 | * 183 | *

184 |    * c * a + s * b = +/- (a^2 + b^2) c * b - s * a = 0
185 |    * 
186 | * 187 | * We don't care about the signs here, for efficiency, so make sure not to rely on them anywhere. 188 | *

189 | * Source: "Matrix Computations" (2nd edition) by Gene H. B. Golub & Charles F. B. Van Loan 190 | * (Johns Hopkins University Press, 1989), p. 216. 191 | *

192 | * Note that the code of this class sometimes does not call this method but only mentions it in a 193 | * comment. The reason for this is performance; Java does not allow an efficient way of returning 194 | * a pair of doubles, so we sometimes manually "inline" {@code givens()} for the sake of 195 | * performance. 196 | */ 197 | private final void givens(final double a, final double b) 198 | { 199 | if (b == 0.0) 200 | { 201 | c = 1.0; 202 | s = 0.0; 203 | } 204 | else if (abs(b) > abs(a)) 205 | { 206 | final double t = a / b; 207 | s = 1 / sqrt(1 + t * t); 208 | c = s * t; 209 | } 210 | else 211 | { 212 | final double t = b / a; 213 | c = 1 / sqrt(1 + t * t); 214 | s = c * t; 215 | } 216 | } 217 | 218 | /** 219 | * Appends the new column u (which is a member field of this instance) to the right of A 220 | * = QR, updating Q and R. It assumes r to still be the old value, i.e., 221 | * the index of the column used now for insertion; r is not altered by this routine and 222 | * should be changed by the caller afterwards. 223 | *

224 | * Precondition: {@code r r; --j) 240 | { 241 | // Note: j is the index of the entry to be cleared with the help of entry j-1. 242 | 243 | // Compute Givens coefficients c,s 244 | givens(R[r][j - 1], R[r][j]); // PERF: inline 245 | 246 | // Rotate one R-entry (the other one is an implicit zero) 247 | R[r][j - 1] = c * R[r][j - 1] + s * R[r][j]; 248 | 249 | // Rotate two Q-columns 250 | for (int i = 0; i < dim; ++i) 251 | { 252 | final double a = Q[j - 1][i]; 253 | final double b = Q[j][i]; 254 | Q[j - 1][i] = c * a + s * b; 255 | Q[j][i] = c * b - s * a; 256 | } 257 | } 258 | } 259 | 260 | /** 261 | * Adds the point S[index] to the instance's set M. 262 | *

263 | * Precondition: {@code !isMember(index)} 264 | *

265 | * Complexity: O(dim^2). 266 | * 267 | * @param index 268 | * index into S of the point to add 269 | */ 270 | public void add(int index) 271 | { 272 | assert !isMember(index); 273 | 274 | // Compute S[i] - origin into u 275 | final int o = origin(); 276 | for (int i = 0; i < dim; ++i) 277 | u[i] = S.coord(index, i) - S.coord(o, i); 278 | 279 | // Appends new column u to R and updates QR-decomposition (note: routine works with old r) 280 | appendColumn(); 281 | 282 | // move origin index and insert new index: 283 | membership.set(index); 284 | members[r + 1] = members[r]; 285 | members[r] = index; 286 | ++r; 287 | 288 | info("rank: " + r); 289 | } 290 | 291 | /** 292 | * Computes the vector w directed from point p to v, where v is the 293 | * point in aff(M) that lies nearest to p. 294 | *

295 | * Precondition: {@code size()}>0 296 | *

297 | * Complexity: O(dim^2) 298 | * 299 | * @param p 300 | * Euclidean coordinates of point p 301 | * @param w 302 | * the squared length of w 303 | * @return 304 | */ 305 | public double shortestVectorToSpan(double[] p, double[] w) 306 | { 307 | // Compute vector from p to origin, i.e., w = origin - p 308 | final int o = origin(); 309 | for (int i = 0; i < dim; ++i) 310 | w[i] = S.coord(o, i) - p[i]; 311 | 312 | // Remove projections of w onto the affine hull 313 | for (int j = 0; j < r; ++j) 314 | { 315 | double scale = 0; 316 | for (int i = 0; i < dim; ++i) 317 | scale += w[i] * Q[j][i]; 318 | for (int i = 0; i < dim; ++i) 319 | w[i] -= scale * Q[j][i]; 320 | } 321 | 322 | double sl = 0; 323 | for (int i = 0; i < dim; ++i) 324 | sl += w[i] * w[i]; 325 | return sl; 326 | } 327 | 328 | /** 329 | * Use this for testing only; the method allocates additional storage and copies point 330 | * coordinates. 331 | */ 332 | public double representationError() 333 | { 334 | final double[] lambdas = new double[size()]; 335 | final double[] pt = new double[dim]; 336 | double max = 0, error; 337 | 338 | // Cycle through all points in hull 339 | for (int j = 0; j < size(); ++j) 340 | { 341 | // Get point 342 | for (int i = 0; i < dim; ++i) 343 | pt[i] = S.coord(globalIndex(j), i); 344 | 345 | // Compute the affine representation: 346 | findAffineCoefficients(pt, lambdas); 347 | 348 | // compare coefficient of point j to 1.0 349 | error = abs(lambdas[j] - 1.0); 350 | if (error > max) max = error; 351 | 352 | // compare the other coefficients against 0.0 353 | for (int i = 0; i < j; ++i) 354 | { 355 | error = abs(lambdas[i] - 0.0); 356 | if (error > max) max = error; 357 | } 358 | for (int i = j + 1; i < size(); ++i) 359 | { 360 | error = abs(lambdas[i] - 0.0); 361 | if (error > max) max = error; 362 | } 363 | } 364 | 365 | return max; 366 | } 367 | 368 | /** 369 | * Calculates the {@code size()}-many coefficients in the representation of p as an affine 370 | * combination of the points M. 371 | *

372 | * The ith computed coefficient {@code lambdas[i]} corresponds to the ith point in 373 | * M, or, in other words, to the point in S with index {@code globalIndex(i)}. 374 | *

375 | * Complexity: O(dim^2) 376 | *

377 | * Preconditions: c lies in the affine hull aff(M) and size() > 0. 378 | */ 379 | void findAffineCoefficients(double[] p, double[] lambdas) 380 | { 381 | // Compute relative position of p, i.e., u = p - origin 382 | final int o = origin(); 383 | for (int i = 0; i < dim; ++i) 384 | u[i] = p[i] - S.coord(o, i); 385 | 386 | // Calculate Q^T u into w 387 | for (int i = 0; i < dim; ++i) 388 | { 389 | w[i] = 0; 390 | for (int k = 0; k < dim; ++k) 391 | w[i] += Q[i][k] * u[k]; 392 | } 393 | 394 | // We compute the coefficients by backsubstitution. Notice that 395 | // 396 | // c = \sum_{i\in M} \lambda_i (S[i] - origin) 397 | // = \sum_{i\in M} \lambda_i S[i] + (1-s) origin 398 | // 399 | // where s = \sum_{i\in M} \lambda_i.-- We compute the coefficient 400 | // (1-s) of the origin in the variable origin_lambda: 401 | double origin_lambda = 1; 402 | for (int j = r - 1; j >= 0; --j) 403 | { 404 | for (int k = j + 1; k < r; ++k) 405 | w[j] -= lambdas[k] * R[k][j]; 406 | final double lj = w[j] / R[j][j]; 407 | lambdas[j] = lj; 408 | origin_lambda -= lj; 409 | } 410 | // The r-th coefficient corresponds to the origin (see remove()): 411 | lambdas[r] = origin_lambda; 412 | } 413 | 414 | /** 415 | * Given R in lower Hessenberg form with subdiagonal entries 0 to {@code pos-1} already all 416 | * zero, clears the remaining subdiagonal entries via Givens rotations. 417 | */ 418 | private void hessenberg_clear(int pos) 419 | { 420 | // Clear new subdiagonal entries 421 | for (; pos < r; ++pos) 422 | { 423 | // Note: pos is the column index of the entry to be cleared 424 | 425 | // Compute Givens coefficients c,s 426 | givens(R[pos][pos], R[pos][pos + 1]); // PERF: inline 427 | 428 | // Rotate partial R-rows (of the first pair, only one entry is 429 | // needed, the other one is an implicit zero) 430 | R[pos][pos] = c * R[pos][pos] + s * R[pos][pos + 1]; 431 | // Then begin at position pos+1 432 | for (int j = pos + 1; j < r; ++j) 433 | { 434 | final double a = R[j][pos]; 435 | final double b = R[j][pos + 1]; 436 | R[j][pos] = c * a + s * b; 437 | R[j][pos + 1] = c * b - s * a; 438 | } 439 | 440 | // Rotate Q-columns 441 | for (int i = 0; i < dim; ++i) 442 | { 443 | final double a = Q[pos][i]; 444 | final double b = Q[pos + 1][i]; 445 | Q[pos][i] = c * a + s * b; 446 | Q[pos + 1][i] = c * b - s * a; 447 | } 448 | } 449 | } 450 | 451 | /** 452 | * Update current QR-decomposition A = QR to 453 | * 454 | *

455 |    *   A + u * [1,...,1] = Q' R'.
456 |    * 
457 | */ 458 | private void special_rank_1_update() 459 | { 460 | // Compute w = Q^T * u 461 | for (int i = 0; i < dim; ++i) 462 | { 463 | w[i] = 0; 464 | for (int k = 0; k < dim; ++k) 465 | w[i] += Q[i][k] * u[k]; 466 | } 467 | 468 | // Rotate w down to a multiple of the first unit vector; 469 | // the operations have to be recorded in R and Q 470 | for (int k = dim - 1; k > 0; --k) 471 | { 472 | // Note: k is the index of the entry to be cleared with the help of entry k-1. 473 | 474 | // Compute Givens coefficients c,s 475 | givens(w[k - 1], w[k]); 476 | 477 | // rotate w-entry 478 | w[k - 1] = c * w[k - 1] + s * w[k]; 479 | 480 | // Rotate two R-rows; 481 | // the first column has to be treated separately 482 | // in order to account for the implicit zero in R[k-1][k] 483 | R[k - 1][k] = -s * R[k - 1][k - 1]; 484 | R[k - 1][k - 1] *= c; 485 | for (int j = k; j < r; ++j) 486 | { 487 | final double a = R[j][k - 1]; 488 | final double b = R[j][k]; 489 | R[j][k - 1] = c * a + s * b; 490 | R[j][k] = c * b - s * a; 491 | } 492 | 493 | // Rotate two Q-columns 494 | for (int i = 0; i < dim; ++i) 495 | { 496 | final double a = Q[k - 1][i]; 497 | final double b = Q[k][i]; 498 | Q[k - 1][i] = c * a + s * b; 499 | Q[k][i] = c * b - s * a; 500 | } 501 | } 502 | 503 | // Add w * (1,...,1)^T to new R, which means simply to add u[0] to each column 504 | // since the other entries of u have just been eliminated 505 | for (int j = 0; j < r; ++j) 506 | R[j][0] += w[0]; 507 | 508 | // Clear subdiagonal entries 509 | hessenberg_clear(0); 510 | } 511 | 512 | public void remove(int index) 513 | { 514 | assert isMember(globalIndex(index)) && size() > 1; 515 | 516 | membership.clear(globalIndex(index)); 517 | 518 | if (index == r) 519 | { 520 | // Origin must be deleted. 521 | final int o = origin(); 522 | 523 | // We choose the right-most member of Q, i.e., column r-1 of R, 524 | // as the new origin. So all relative vectors (i.e., the 525 | // columns of "A = QR") have to be updated by u:= old origin - 526 | // S[global_index(r-1)]: 527 | final int gi = globalIndex(r - 1); 528 | for (int i = 0; i < dim; ++i) 529 | u[i] = S.coord(o, i) - S.coord(gi, i); 530 | 531 | --r; 532 | 533 | if (log) info("rank: " + r); 534 | 535 | special_rank_1_update(); 536 | 537 | } 538 | else 539 | { 540 | // General case: delete column from R 541 | 542 | // Shift higher columns of R one step to the left 543 | double[] dummy = R[index]; 544 | for (int j = index + 1; j < r; ++j) 545 | { 546 | R[j - 1] = R[j]; 547 | members[j - 1] = members[j]; 548 | } 549 | members[r - 1] = members[r]; // Shift down origin 550 | R[--r] = dummy; // Relink trash column 551 | 552 | // Zero out subdiagonal entries in R 553 | hessenberg_clear(index); 554 | } 555 | } 556 | } -------------------------------------------------------------------------------- /csharp/test/MiniballTest.cs: -------------------------------------------------------------------------------- 1 | using SEB; 2 | using static System.Math; 3 | 4 | namespace test; 5 | 6 | public class MiniballTest 7 | { 8 | 9 | [Fact] 10 | public void test_almost_cospherical_points_3() 11 | { 12 | var mb = computeFromFile(DataPathfilename("almost_cospherical_points_3.data")); 13 | double[] expectedCenter = { 14 | -1.56087201342490318e-12, -4.71446026502953592e-12, 3.67580472387244665e-12 15 | }; 16 | Assert.Equal(10000, mb.Size); 17 | assertAlmostEquals(expectedCenter, mb.Center); 18 | assertAlmostEquals(1.00000000015068569e+00, mb.SquaredRadius); 19 | } 20 | 21 | [Fact] 22 | public void test_almost_cospherical_points_10() 23 | { 24 | var mb = computeFromFile(DataPathfilename("almost_cospherical_points_10.data")); 25 | 26 | double[] expectedCenter = { 27 | 5.87465071980114019e-12, 28 | -8.50168966753148976e-12, 29 | 5.16296955479470709e-12, 30 | 5.76827248197897234e-12, 31 | 4.79834156807867167e-12, 32 | 3.60221381957526638e-13, 33 | -5.39518968945866875e-12, 34 | 1.10109862800149464e-11, 35 | 5.92724627952881232e-12, 36 | 3.08121800360276428e-12 37 | }; 38 | Assert.Equal(10000, mb.Size); 39 | assertAlmostEquals(expectedCenter, mb.Center); 40 | assertAlmostEquals(1.00000000016537482e+00, mb.SquaredRadius); 41 | } 42 | 43 | [Fact] 44 | public void test_cocircular_points_large_radius_2() 45 | { 46 | var mb = computeFromFile(DataPathfilename("cocircular_points_large_radius_2.data")); 47 | double[] expectedCenter = { 48 | 2.25917732813639420e-08, 2.66532983586183870e-08 49 | }; 50 | Assert.Equal(13824, mb.Size); 51 | assertAlmostEquals(expectedCenter, mb.Center); 52 | assertAlmostEquals(1.12830550249511283e+18, mb.SquaredRadius); 53 | } 54 | 55 | [Fact] 56 | public void test_cocircular_points_small_radius_2() 57 | { 58 | Miniball mb = computeFromFile(DataPathfilename("cocircular_points_small_radius_2.data")); 59 | double[] expectedCenter = { 60 | 0, 0 61 | }; 62 | Assert.Equal(6144, mb.Size); 63 | assertAlmostEquals(expectedCenter, mb.Center); 64 | assertAlmostEquals(3.72870291637512500e+15, mb.SquaredRadius); 65 | } 66 | 67 | [Fact] 68 | public void test_cube_10() 69 | { 70 | Miniball mb = computeFromFile(DataPathfilename("cube_10.data")); 71 | double[] expectedCenter = { 72 | 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 73 | }; 74 | Assert.Equal(1024, mb.Size); 75 | assertAlmostEquals(expectedCenter, mb.Center); 76 | assertAlmostEquals(2.5, mb.SquaredRadius); 77 | } 78 | 79 | [Fact] 80 | public void test_cube_12() 81 | { 82 | Miniball mb = computeFromFile(DataPathfilename("cube_12.data")); 83 | double[] expectedCenter = { 84 | 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 85 | }; 86 | Assert.Equal(4096, mb.Size); 87 | assertAlmostEquals(expectedCenter, mb.Center); 88 | assertAlmostEquals(3, mb.SquaredRadius); 89 | } 90 | 91 | [Fact] 92 | public void test_hurz() 93 | { 94 | Miniball mb = computeFromFile(DataPathfilename("hurz.data")); 95 | double[] expectedCenter = { 96 | -9.32385490743424666e-02, 97 | -1.50055661837044246e-01, 98 | 2.69364248349470529e-02, 99 | -2.40723461491440050e-03, 100 | -6.73931277729599293e-02, 101 | 5.48757211321871177e-02, 102 | -3.77507874171383526e-02, 103 | 1.52069101200107918e-02, 104 | 3.17372740475474271e-02, 105 | 1.19705682509647776e-01, 106 | 2.30776967046545170e-02, 107 | 1.34715212599093803e-01, 108 | -6.77742873939496671e-02, 109 | 8.15765590285277495e-03, 110 | 9.10084642845849501e-02, 111 | 7.59318418646900695e-02, 112 | 6.67014412781230803e-02, 113 | 2.43008063975082983e-02, 114 | 5.75994499483733213e-02, 115 | 2.71948026809261494e-02, 116 | 8.93012380869349226e-03, 117 | 6.78549081409481059e-03, 118 | -3.91898096095426723e-02, 119 | 2.61203895241791072e-02, 120 | 1.30183917339636385e-01, 121 | -9.05159956449119174e-02, 122 | 8.29177344908292452e-02, 123 | -1.01263907022741025e-02, 124 | 7.26859883818094521e-02, 125 | 1.50591656331595884e-02, 126 | -6.68358467554956426e-02, 127 | 1.32664103589673517e-02, 128 | 5.26161351518988074e-02, 129 | -5.95612639904603055e-03, 130 | 1.83611423209012921e-02, 131 | 5.87399305108671430e-02, 132 | 2.88703317032385394e-02, 133 | -7.63799904284965109e-02, 134 | -3.91152746123283185e-02, 135 | -3.89767810320450492e-02, 136 | 2.35807392210171836e-02, 137 | -2.03958450688055126e-02, 138 | -5.39645593710812513e-02, 139 | -1.59803577488685754e-02, 140 | 6.80661505439016240e-02, 141 | -2.98838443491668999e-02, 142 | 6.29793667302477600e-02, 143 | -5.06384997867233358e-02, 144 | -1.12101972723559248e-01, 145 | 2.63368521873624216e-02, 146 | -3.07658266670295631e-02, 147 | -2.64456299786865051e-02, 148 | 1.48886115569620697e-01, 149 | 6.39413771405114711e-02, 150 | 3.46874273101637105e-03, 151 | 5.04544870734000214e-02, 152 | -7.23303966655623881e-02, 153 | 6.69173418830567995e-02, 154 | -2.05141517773424237e-01, 155 | 1.53060995099086744e-01, 156 | 1.09764268432820569e-01, 157 | -4.15657204407630654e-02, 158 | 5.59111099323817001e-02, 159 | -1.08878576328361426e-01, 160 | 6.72514700094211659e-02, 161 | 5.67031662004510370e-02, 162 | 2.98503270757259527e-02, 163 | -2.22211414794732769e-02, 164 | 1.17699103704017344e-01, 165 | -7.74243833358304601e-02, 166 | 1.14144909884642876e-01, 167 | 1.18244885236012495e-01, 168 | -1.16498953956752041e-01, 169 | -1.54335227921318263e-01, 170 | -1.27061966479000893e-01, 171 | 4.93839729837722702e-02, 172 | 9.65276608923077567e-02, 173 | 6.87789555146762766e-02, 174 | 8.02775373675647941e-02, 175 | -2.95398290193190638e-02, 176 | 1.00148883621633628e-01, 177 | 1.41660341680982599e-01, 178 | -1.13077032939844724e-01, 179 | -9.92412686510760061e-02, 180 | -7.34660260569389156e-02, 181 | 2.44119317172067987e-02, 182 | -8.60823590551833689e-02, 183 | 4.81621197168055418e-02, 184 | 2.13685862730959644e-02, 185 | -2.10485109618934463e-02, 186 | 1.25159956599585509e-01, 187 | -9.81387077643431638e-02, 188 | 7.39057728784511953e-02, 189 | -5.74760989576298745e-02, 190 | 9.02697615359867450e-02, 191 | -7.13332356872704767e-03, 192 | -4.58852458824499237e-02, 193 | -4.63562081793597661e-02, 194 | 5.72641924237869832e-02, 195 | 5.81975472026072596e-02, 196 | 9.24491451791044916e-02, 197 | -9.11941779278649667e-02, 198 | 1.30119072412531483e-01, 199 | 1.36620780424713567e-01, 200 | 5.49437946994311352e-02, 201 | -8.13593869366321526e-02, 202 | 8.82168141230831315e-02, 203 | 1.63874559179065585e-02, 204 | 6.33525944638172467e-02, 205 | -7.78800446589289624e-02, 206 | 1.05095502046718692e-01, 207 | 6.49385016232090495e-02, 208 | -8.05548020284199695e-02, 209 | -7.51567403689807900e-02, 210 | 5.64638210880798116e-02, 211 | 3.20522130857825993e-02, 212 | -9.09777076137542745e-02, 213 | -9.76163942088352082e-02, 214 | -6.18280846431228312e-02, 215 | -1.31709582117138319e-01, 216 | -1.97527865667240527e-03, 217 | 7.21931497585995285e-03, 218 | -4.20086728084883079e-02, 219 | -1.97727359026356797e-01, 220 | 9.60228739542908459e-03, 221 | 1.16114754875734708e-01, 222 | 1.34450809189754477e-02, 223 | -4.53777260307672362e-02, 224 | -3.16343163102534713e-02, 225 | 1.28342389158762504e-01, 226 | 3.22817704875962702e-02, 227 | 1.54647738406440208e-02, 228 | 1.47101218437264564e-01, 229 | 2.15748587532079462e-02, 230 | 1.14410676952691956e-01, 231 | -5.61923008472568625e-02, 232 | 5.33700224338453030e-02, 233 | 2.68966630713247251e-02, 234 | -3.85872036409353192e-02, 235 | 3.38504979473726250e-02, 236 | -6.20998780759002694e-02, 237 | 2.05930310272340843e-02, 238 | 2.62773470028755222e-02, 239 | -1.54454477761890429e-02, 240 | 7.57992611852749548e-02, 241 | 2.73988468770113916e-02, 242 | 8.65941702756104076e-02, 243 | 5.07588096831466246e-02, 244 | -6.93803682141353462e-02, 245 | 4.81234642042299016e-02, 246 | 1.30443064335880442e-02, 247 | 8.27268777941885247e-02, 248 | -9.16112442109258546e-02, 249 | -4.45938341297191718e-02, 250 | 8.33022514572618299e-03, 251 | -5.50105572034664814e-03, 252 | 3.42063053108011871e-03, 253 | 2.62405615322419770e-03, 254 | 6.01664324087401378e-02, 255 | -2.07679618603799840e-02, 256 | 2.85058169191099720e-02, 257 | -1.09744548510173290e-01, 258 | -7.77693036824233380e-02, 259 | 1.02404605592325412e-03, 260 | -1.42314434178243930e-02, 261 | -5.62917711091049031e-02, 262 | 1.26842625168207629e-01, 263 | -3.14995069731299759e-02, 264 | -2.32973697443584604e-03, 265 | -1.04374526387181921e-01, 266 | -8.16693564187557230e-02, 267 | 4.21643856974714057e-02, 268 | 3.82603433374553228e-02, 269 | 7.30422312859391326e-02, 270 | 1.21749543666098747e-01, 271 | 6.11472483977238065e-02, 272 | 3.84288077038831277e-02, 273 | -7.17878918962588375e-02, 274 | -5.64552109618581682e-02, 275 | 1.60817706579412106e-01, 276 | 2.99709054001969129e-02, 277 | 3.41409563710960914e-02, 278 | -1.75688728854233277e-02, 279 | -2.63404757837025147e-02, 280 | 1.69281593990837642e-02, 281 | 7.60971983283898479e-02, 282 | -1.81655270703341247e-02, 283 | 3.49304205730180256e-02, 284 | 1.92995515142081568e-03, 285 | 2.16056157812245936e-02, 286 | -5.76236634233693035e-02, 287 | -7.61103361807527121e-03, 288 | -1.64553293154055154e-02, 289 | -4.08614772137683158e-02, 290 | 2.29673439914067565e-02, 291 | -7.45104306720340281e-02, 292 | 2.41470698178044355e-02, 293 | -8.83633789907483091e-02, 294 | -5.45070973771131506e-02, 295 | 4.86914400373976639e-02 296 | }; 297 | Assert.Equal(800, mb.Size); 298 | assertAlmostEquals(expectedCenter, mb.Center); 299 | assertAlmostEquals(7.38649825408094216e+01, mb.SquaredRadius); 300 | } 301 | 302 | [Fact] 303 | public void test_longitude_latitude_model_3() 304 | { 305 | var mb = computeFromFile(DataPathfilename("longitude_latitude_model_3.data")); 306 | double[] expectedCenter = { 307 | 1.09529643705128193e-12, 7.76487134128295763e-12, 2.09782485152182732e-16 308 | }; 309 | Assert.Equal(10000, mb.Size); 310 | assertAlmostEquals(expectedCenter, mb.Center); 311 | assertAlmostEquals(1.00000000015833201e+00, mb.SquaredRadius); 312 | } 313 | 314 | [Fact] 315 | public void test_random_points_3() 316 | { 317 | Miniball mb = computeFromFile(DataPathfilename("random_points_3.data")); 318 | double[] expectedCenter = { 319 | 1.61093103034421342e-02, -3.39403799912667802e-03, -3.31378412407934593e-03 320 | }; 321 | Assert.Equal(10000, mb.Size); 322 | assertAlmostEquals(expectedCenter, mb.Center); 323 | assertAlmostEquals(6.86700124388323507e-01, mb.SquaredRadius); 324 | } 325 | 326 | [Fact] 327 | public void test_random_points_5() 328 | { 329 | Miniball mb = computeFromFile(DataPathfilename("random_points_5.data")); 330 | double[] expectedCenter = { 331 | 1.68879606901127695e-02, 332 | -5.59476980486128209e-03, 333 | -3.50733482301275185e-02, 334 | 4.97024582833288132e-05, 335 | -1.64470438482790457e-02 336 | }; 337 | Assert.Equal(10000, mb.Size); 338 | assertAlmostEquals(expectedCenter, mb.Center); 339 | assertAlmostEquals(9.86950425115668661e-01, mb.SquaredRadius); 340 | } 341 | 342 | [Fact] 343 | public void test_random_points_10() 344 | { 345 | Miniball mb = computeFromFile(DataPathfilename("random_points_10.data")); 346 | double[] expectedCenter = { 347 | -3.68108604382322915e-02, 348 | -2.56379744386364197e-02, 349 | 2.63525258190180009e-02, 350 | -4.39111259663507118e-03, 351 | 5.07655020786604433e-02, 352 | 2.46652284563754105e-02, 353 | 6.32648262334569861e-02, 354 | 1.72315689301736075e-02, 355 | -1.20835421442481560e-02, 356 | 6.39141412880165488e-03 357 | }; 358 | Assert.Equal(10000, mb.Size); 359 | assertAlmostEquals(expectedCenter, mb.Center); 360 | assertAlmostEquals(1.62004498782482909e+00, mb.SquaredRadius); 361 | } 362 | 363 | //[Fact] 364 | [Fact(Skip = "see #6")] 365 | public void test_schnarz() // TODO: see #6 366 | { 367 | Miniball mb = computeFromFile(DataPathfilename("schnarz.data")); 368 | double[] expectedCenter = { 369 | 0, 0 370 | }; 371 | System.Console.WriteLine("schnarz " + mb); 372 | Assert.Equal(10000, mb.Size); 373 | assertAlmostEquals(expectedCenter, mb.Center); 374 | assertAlmostEquals(0, mb.SquaredRadius); 375 | } 376 | 377 | [Fact] 378 | public void test_simplex_10() 379 | { 380 | Miniball mb = computeFromFile(DataPathfilename("simplex_10.data")); 381 | double[] expectedCenter = { 382 | 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 383 | }; 384 | Assert.Equal(10, mb.Size); 385 | assertAlmostEquals(expectedCenter, mb.Center); 386 | assertAlmostEquals(1 - 1 / 10.0, mb.SquaredRadius); 387 | } 388 | 389 | [Fact] 390 | public void test_simplex_15() 391 | { 392 | Miniball mb = computeFromFile(DataPathfilename("simplex_15.data")); 393 | double[] expectedCenter = { 394 | 1 / 15.0, 395 | 1 / 15.0, 396 | 1 / 15.0, 397 | 1 / 15.0, 398 | 1 / 15.0, 399 | 1 / 15.0, 400 | 1 / 15.0, 401 | 1 / 15.0, 402 | 1 / 15.0, 403 | 1 / 15.0, 404 | 1 / 15.0, 405 | 1 / 15.0, 406 | 1 / 15.0, 407 | 1 / 15.0, 408 | 1 / 15.0, 409 | }; 410 | Assert.Equal(15, mb.Size); 411 | assertAlmostEquals(expectedCenter, mb.Center); 412 | assertAlmostEquals(1 - 1 / 15.0, mb.SquaredRadius); 413 | } 414 | 415 | //------------------------------------------------------------- 416 | 417 | Miniball computeFromFile(string pathfilename) 418 | { 419 | ArrayPointSet pts; 420 | using (var sr = new StreamReader(pathfilename)) 421 | { 422 | pts = PointSetUtils.PointsFromStream(sr); 423 | } 424 | var mb = new Miniball(pts); 425 | // System.Console.WriteLine(mb); 426 | return mb; 427 | } 428 | 429 | static string? DataPathname = null; 430 | 431 | static string DataPathfilename(string filename) 432 | { 433 | if (DataPathname is null) 434 | { 435 | var p = AppDomain.CurrentDomain.BaseDirectory; 436 | 437 | while (true) 438 | { 439 | var di = Directory.GetParent(p); 440 | if (di == null) throw new Exception($"couldn't find parent of [{p}]"); 441 | 442 | if (di.Name == "csharp") 443 | { 444 | DataPathname = Path.Combine(di.FullName, 445 | "..", "java", "src", "test", "resources", "com", "dreizak", "miniball", "highdim", "data"); 446 | if (!Directory.Exists(DataPathname)) 447 | throw new Exception($"couldn't locate tests data dir [{DataPathname}]"); 448 | else 449 | break; 450 | } 451 | else 452 | p = di.FullName; 453 | } 454 | } 455 | 456 | return Path.Combine(DataPathname, filename); 457 | } 458 | 459 | static void assertAlmostEquals(double[] expected, double[] actual) 460 | { 461 | for (int i = 0; i < expected.Length; ++i) 462 | assertAlmostEquals(expected[i], actual[i]); 463 | } 464 | 465 | static void assertAlmostEquals(double expected, double actual) 466 | { 467 | Assert.True(Abs(expected - actual) < 1e-15); 468 | } 469 | 470 | } --------------------------------------------------------------------------------