blob: 3a03dafe396a0ce0ab5b497e2d99190988d78501 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.cluster;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Random;
import org.apache.ignite.internal.cluster.graph.FullyConnectedComponentSearcher;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.testframework.GridTestUtils;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
/**
* Class to test correctness of fully-connected component searching algorithm.
*/
@RunWith(Parameterized.class)
public class FullyConnectedComponentSearcherTest {
/** Per test timeout */
@Rule
public Timeout globalTimeout = new Timeout((int)GridTestUtils.DFLT_TEST_TIMEOUT);
/** Adjacency matrix provider for each test. */
private AdjacencyMatrixProvider provider;
/** Minimul acceptable result of size of fully-connected component for each test. */
private int minAcceptableRes;
/**
* @param provider Adjacency matrix.
* @param minAcceptableRes Expected result.
*/
public FullyConnectedComponentSearcherTest(AdjacencyMatrixProvider provider, int minAcceptableRes) {
this.provider = provider;
this.minAcceptableRes = minAcceptableRes;
}
/**
*
*/
@Test
public void testFind() {
BitSet[] matrix = provider.provide();
int nodes = matrix.length;
BitSet all = new BitSet(nodes);
for (int i = 0; i < nodes; i++)
all.set(i);
FullyConnectedComponentSearcher searcher = new FullyConnectedComponentSearcher(matrix);
BitSet res = searcher.findLargest(all);
int size = res.cardinality();
Assert.assertTrue("Actual = " + size + ", Expected = " + minAcceptableRes,
size >= minAcceptableRes);
}
/**
* @return Test dataset.
*/
@Parameterized.Parameters(name = "{index}: search({0}) >= {1}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{new StaticMatrix(new String[] {
"100",
"010",
"001",
}), 1},
{new StaticMatrix(new String[] {
"101",
"010",
"101",
}), 2},
{new StaticMatrix(new String[] {
"1101",
"1111",
"0110",
"1101",
}), 3},
{new StaticMatrix(new String[] {
"1111001",
"1111000",
"1111000",
"1111000",
"0000111",
"0000111",
"1000111",
}), 4},
{new AlmostSplittedMatrix(30, 100, 200), 200},
{new AlmostSplittedMatrix(500, 1000, 2000), 2000},
{new AlmostSplittedMatrix(1000, 2000, 3000), 3000},
{new AlmostSplittedMatrix(30, 22, 25, 33, 27), 33},
{new AlmostSplittedMatrix(1000, 400, 1000, 800), 1000},
{new SeveralConnectionsAreLostMatrix(200, 10), 190},
{new SeveralConnectionsAreLostMatrix(2000, 100), 1900},
{new SeveralConnectionsAreLostMatrix(2000, 500), 1500},
{new SeveralConnectionsAreLostMatrix(4000, 2000), 2000}
});
}
/**
* Provider for adjacency matrix for each test.
*/
interface AdjacencyMatrixProvider {
/**
* @return Adjacency matrix.
*/
BitSet[] provide();
}
/**
* Static graph represented as array of strings. Each cell (i, j) in such matrix means that there is connection
* between node(i) and node(j). Needed mostly to test bruteforce algorithm implementation.
*/
static class StaticMatrix implements AdjacencyMatrixProvider {
/** Matrix. */
private final BitSet[] matrix;
/**
* @param strMatrix String matrix.
*/
public StaticMatrix(@NotNull String[] strMatrix) {
A.ensure(strMatrix.length > 0, "Matrix should not be empty");
for (int i = 0; i < strMatrix.length; i++)
A.ensure(strMatrix[i].length() == strMatrix.length,
"Matrix should be quadratic. Problem row: " + i);
int nodes = strMatrix.length;
matrix = init(nodes);
for (int i = 0; i < nodes; i++)
for (int j = 0; j < nodes; j++)
matrix[i].set(j, strMatrix[i].charAt(j) == '1');
}
/** {@inheritDoc} */
@Override public BitSet[] provide() {
return matrix;
}
/** {@inheritDoc} */
@Override public String toString() {
return "StaticMatrix{" +
"matrix=" + Arrays.toString(matrix) +
'}';
}
}
/**
* A graph splitted on several isolated fully-connected components,
* but each of such component have some connections to another to reach graph connectivity.
* Answer is this case should be the size max(Pi), where Pi size of each fully-connected component.
*/
static class AlmostSplittedMatrix implements AdjacencyMatrixProvider {
/** Partition sizes. */
private final int[] partSizes;
/** Connections between parts. */
private final int connectionsBetweenParts;
/** Matrix. */
private final BitSet[] matrix;
/**
* @param connectionsBetweenParts Connections between parts.
* @param partSizes Partition sizes.
*/
public AlmostSplittedMatrix(int connectionsBetweenParts, int... partSizes) {
A.ensure(connectionsBetweenParts >= 1 + partSizes.length, "There should be at least 1 connection between parts");
A.ensure(partSizes.length >= 2, "The should be at least 2 parts of cluster");
for (int i = 0; i < partSizes.length; i++)
A.ensure(partSizes[i] > 0, "Part size " + (i + 1) + " shouldn't be empty");
this.partSizes = partSizes.clone();
this.connectionsBetweenParts = connectionsBetweenParts;
int nodes = 0;
for (int i = 0; i < partSizes.length; i++)
nodes += partSizes[i];
matrix = init(nodes);
int[] startIdx = new int[partSizes.length];
for (int i = 0, total = 0; i < partSizes.length; i++) {
startIdx[i] = total;
fillAll(matrix, total, total + partSizes[i]);
total += partSizes[i];
}
Random random = new Random(777);
for (int i = 0, part1 = 0; i < connectionsBetweenParts; i++) {
int part2 = (part1 + 1) % partSizes.length;
// Pick 2 random nodes from 2 parts and add connection between them.
int idx1 = random.nextInt(partSizes[part1]) + startIdx[part1];
int idx2 = random.nextInt(partSizes[part2]) + startIdx[part2];
matrix[idx1].set(idx2);
matrix[idx2].set(idx1);
}
}
/** {@inheritDoc} */
@Override public BitSet[] provide() {
return matrix;
}
/** {@inheritDoc} */
@Override public String toString() {
return "AlmostSplittedGraph{" +
"partSizes=" + Arrays.toString(partSizes) +
", connectionsBetweenParts=" + connectionsBetweenParts +
'}';
}
}
/**
* Complete graph with several connections lost choosen randomly.
* In worst case each lost connection decreases potential size of maximal fully-connected component.
* So answer in this test case should be at least N - L, where N - nodes, L - lost connections.
*/
static class SeveralConnectionsAreLostMatrix implements AdjacencyMatrixProvider {
/** Nodes. */
private final int nodes;
/** Lost connections. */
private final int lostConnections;
/** Matrix. */
private final BitSet[] matrix;
/**
* @param nodes Nodes.
* @param lostConnections Lost connections.
*/
public SeveralConnectionsAreLostMatrix(int nodes, int lostConnections) {
A.ensure(nodes > 0, "There should be at least 1 node");
this.nodes = nodes;
this.lostConnections = lostConnections;
this.matrix = init(nodes);
fillAll(matrix, 0, nodes);
Random random = new Random(777);
for (int i = 0; i < lostConnections; i++) {
int idx1 = random.nextInt(nodes);
int idx2 = random.nextInt(nodes);
if (idx1 == idx2)
continue;
matrix[idx1].set(idx2, false);
matrix[idx2].set(idx1, false);
}
}
/** {@inheritDoc} */
@Override public BitSet[] provide() {
return matrix;
}
/** {@inheritDoc} */
@Override public String toString() {
return "SeveralConnectionsAreLost{" +
"nodes=" + nodes +
", lostConnections=" + lostConnections +
'}';
}
}
/**
* Utility method to pre-create adjacency matrix.
*
* @param nodes Nodes in graph.
* @return Adjacency matrix.
*/
private static BitSet[] init(int nodes) {
BitSet[] matrix = new BitSet[nodes];
for (int i = 0; i < nodes; i++)
matrix[i] = new BitSet(nodes);
return matrix;
}
/**
* Utility method to fill all connections between all nodes from {@code fromIdx} and {@code endIdx} exclusive.
*
* @param matrix Adjacency matrix.
* @param fromIdx Lower bound node index inclusive.
* @param endIdx Upper bound node index exclusive.
*/
private static void fillAll(BitSet[] matrix, int fromIdx, int endIdx) {
for (int i = fromIdx; i < endIdx; i++)
for (int j = fromIdx; j < endIdx; j++) {
matrix[i].set(j);
matrix[j].set(i);
}
}
}