All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cicirello.search.problems.LargestCommonSubgraph Maven / Gradle / Ivy

Go to download

Chips-n-Salsa is a Java library of customizable, hybridizable, iterative, parallel, stochastic, and self-adaptive local search algorithms. The library includes implementations of several stochastic local search algorithms, including simulated annealing, hill climbers, as well as constructive search algorithms such as stochastic sampling. Chips-n-Salsa now also includes genetic algorithms as well as evolutionary algorithms more generally. The library very extensively supports simulated annealing. It includes several classes for representing solutions to a variety of optimization problems. For example, the library includes a BitVector class that implements vectors of bits, as well as classes for representing solutions to problems where we are searching for an optimal vector of integers or reals. For each of the built-in representations, the library provides the most common mutation operators for generating random neighbors of candidate solutions, as well as common crossover operators for use with evolutionary algorithms. Additionally, the library provides extensive support for permutation optimization problems, including implementations of many different mutation operators for permutations, and utilizing the efficiently implemented Permutation class of the JavaPermutationTools (JPT) library. Chips-n-Salsa is customizable, making extensive use of Java's generic types, enabling using the library to optimize other types of representations beyond what is provided in the library. It is hybridizable, providing support for integrating multiple forms of local search (e.g., using a hill climber on a solution generated by simulated annealing), creating hybrid mutation operators (e.g., local search using multiple mutation operators), as well as support for running more than one type of search for the same problem concurrently using multiple threads as a form of algorithm portfolio. Chips-n-Salsa is iterative, with support for multistart metaheuristics, including implementations of several restart schedules for varying the run lengths across the restarts. It also supports parallel execution of multiple instances of the same, or different, stochastic local search algorithms for an instance of a problem to accelerate the search process. The library supports self-adaptive search in a variety of ways, such as including implementations of adaptive annealing schedules for simulated annealing, such as the Modified Lam schedule, implementations of the simpler annealing schedules but which self-tune the initial temperature and other parameters, and restart schedules that adapt to run length.

There is a newer version: 7.0.1
Show newest version
/*
 * Chips-n-Salsa: A library of parallel self-adaptive local search algorithms.
 * Copyright (C) 2002-2022 Vincent A. Cicirello
 *
 * This file is part of Chips-n-Salsa (https://chips-n-salsa.cicirello.org/).
 *
 * Chips-n-Salsa is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chips-n-Salsa is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package org.cicirello.search.problems;

import java.util.ArrayList;
import java.util.List;
import java.util.SplittableRandom;
import java.util.random.RandomGenerator;
import org.cicirello.permutations.Permutation;
import org.cicirello.search.representations.BitVector;

/**
 * This class is an implementation of the Largest Common Subgraph problem, an NP-Hard combinatorial
 * optimization problem. In the problem, we are given two graphs G1 and G2.
 * The problem is to find the largest graph Gc, measured in number of edges, that is
 * isomorphic to a subgraph of G1 and a subgraph of G2.
 *
 * 

The {@link #value value} method computes the actual optimization objective, number of edges in * common, which we must maximize. The {@link #cost cost} method transforms this to a minimization * problem by subtracting that count from the number of edges in the smaller of G1 and * G2 (in terms of edges). * *

This implementation assumes representing solutions with a Permutation used to represent a * mapping between the vertexes of the two graphs. Specifically, holding the vertexes of the graph * with the fewer number of vertexes in a fixed order by their vertex id, a solution to the problem * is represented with a Permutation of the vertex ids of the other graph. Position in the * permutation corresponds to the mapping. For example, if p(i) = j, then vertex i in G1 * is mapped to vertex j in G2. This assumes that G1 has at most the number of * vertexes as G2. * * @author Vincent A. Cicirello, https://www.cicirello.org/ */ public final class LargestCommonSubgraph implements IntegerCostOptimizationProblem { private final ArrayList edgesG1; private final BitVector[] adjacencyMatrixG2; private int bound; /** * Constructs a random instance of the largest common subgraph problem. * * @param v The number of vertexes of each graph. * @param density The density of each graph, which is the probability of an edge existing between * a pair of vertexes. It must be in the interval [0.0, 1.0]. * @param isomorphic If true, the two graphs will be isomorphic, which provides an easy way of * generating instances with a known optimal solution. * @throws IllegalArgumentException if v is less than 1. * @throws IllegalArgumentException if density is less than 0.0 or greater than 1.0. */ public LargestCommonSubgraph(int v, double density, boolean isomorphic) { this(v); if (isomorphic) { createIsomorphicRandomInstanceData(v, density, new SplittableRandom()); } else { createRandomInstanceData(v, v, density, density, new SplittableRandom()); } } /** * Constructs a random instance of the largest common subgraph problem. * * @param v1 The number of vertexes of graph 1. * @param v2 The number of vertexes of graph 2. * @param density1 The density of graph 1, which is the probability of an edge existing between a * pair of vertexes. It must be in the interval [0.0, 1.0]. * @param density2 The density of graph 2, which is the probability of an edge existing between a * pair of vertexes. It must be in the interval [0.0, 1.0]. * @throws IllegalArgumentException if v1 and/or v2 is less than 1. * @throws IllegalArgumentException if either density1 or density2 is less than 0.0 or greater * than 1.0. */ public LargestCommonSubgraph(int v1, int v2, double density1, double density2) { this(v2 > v1 ? v2 : v1); if (v1 < v2 || (v1 == v2 && density1 <= density2)) { createRandomInstanceData(v1, v2, density1, density2, new SplittableRandom()); } else { createRandomInstanceData(v2, v1, density2, density1, new SplittableRandom()); } } /** * Constructs a random instance of the largest common subgraph problem. * * @param v The number of vertexes of each graph. * @param density The density of each graph, which is the probability of an edge existing between * a pair of vertexes. It must be in the interval [0.0, 1.0]. * @param isomorphic If true, the two graphs will be isomorphic, which provides an easy way of * generating instances with a known optimal solution. * @param seed The seed for the random number generator to enable replicating an instance. * @throws IllegalArgumentException if v is less than 1. * @throws IllegalArgumentException if density is less than 0.0 or greater than 1.0. */ public LargestCommonSubgraph(int v, double density, boolean isomorphic, long seed) { this(v); if (isomorphic) { createIsomorphicRandomInstanceData(v, density, new SplittableRandom(seed)); } else { createRandomInstanceData(v, v, density, density, new SplittableRandom(seed)); } } /** * Constructs a random instance of the largest common subgraph problem. * * @param v1 The number of vertexes of graph 1. * @param v2 The number of vertexes of graph 2. * @param density1 The density of graph 1, which is the probability of an edge existing between a * pair of vertexes. It must be in the interval [0.0, 1.0]. * @param density2 The density of graph 2, which is the probability of an edge existing between a * pair of vertexes. It must be in the interval [0.0, 1.0]. * @param seed The seed for the random number generator to enable replicating an instance. * @throws IllegalArgumentException if v1 and/or v2 is less than 1. * @throws IllegalArgumentException if either density1 or density2 is less than 0.0 or greater * than 1.0. */ public LargestCommonSubgraph(int v1, int v2, double density1, double density2, long seed) { this(v2 > v1 ? v2 : v1); if (v1 < v2 || (v1 == v2 && density1 <= density2)) { createRandomInstanceData(v1, v2, density1, density2, new SplittableRandom(seed)); } else { createRandomInstanceData(v2, v1, density2, density1, new SplittableRandom(seed)); } } /** * Constructs a random instance of the largest common subgraph problem. * * @param v1 The number of vertexes of graph 1. * @param v2 The number of vertexes of graph 2. * @param edges1 A list of the edges for graph 1. Each of the 2 endpoints of each edge in edges1 * must be in the interval [0, v1). This list must not contain duplicate edges, and also must * not contain both (a, b) and (b, a) since these are the same edge. The behavior of this * class is undefined if either of these are violated. * @param edges2 A list of the edges for graph 2. Each of the 2 endpoints of each edge in edges2 * must be in the interval [0, v2). This list must not contain duplicate edges, and also must * not contain both (a, b) and (b, a) since these are the same edge. The behavior of this * class is undefined if either of these are violated. * @throws IllegalArgumentException if v1 and/or v2 is less than 1. * @throws IllegalArgumentException if any of the endpoints of the edges in edges1 or edges2 are * out of bounds for the corresponding graph. */ public LargestCommonSubgraph(int v1, int v2, List edges1, List edges2) { this(v2 > v1 ? v2 : v1); if (v1 < v2 || (v1 == v2 && edges1.size() <= edges2.size())) { initializeInstanceData(v1, v2, edges1, edges2); } else { initializeInstanceData(v2, v1, edges2, edges1); } } /** * Creates an instance of the Largest Common Subgraph problem for a pair of isomorphic Generalized Petersen * Graphs. One of the graphs for this LCS problem instance is the Generalized Petersen Graph * G(n, k). The second is isomorphic to the first, formed by generating a random relabeling of the * vertex indexes. The Generalized Petersen Graph G(n, k) consists of 2*n vertexes and 3*n edges. * * @param n The parameter n of the Generalized Petersen Graph G(n, k), which must be at least 1. * The graph formed will have 2*n vertexes and 3*n edges. * @param k The parameter k of the Generalized Petersen Graph G(n, k), which must be less than * n/2. * @return An instance of the Largest Common Subgraph problem for a pair of isomorphic Generalized * Petersen Graphs. * @throws IllegalArgumentException if n is less than 1. * @throws IllegalArgumentException if k is not less than n/2 or if k is negative. */ public static LargestCommonSubgraph createInstanceGeneralizedPetersenGraph(int n, int k) { return createInstanceGeneralizedPetersenGraph(n, k, new Permutation(2 * n)); } /** * Creates an instance of the Largest Common Subgraph problem for a pair of isomorphic Generalized Petersen * Graphs. One of the graphs for this LCS problem instance is the Generalized Petersen Graph * G(n, k). The second is isomorphic to the first, formed by generating a random relabeling of the * vertex indexes. The Generalized Petersen Graph G(n, k) consists of 2*n vertexes and 3*n edges. * * @param n The parameter n of the Generalized Petersen Graph G(n, k), which must be at least 1. * The graph formed will have 2*n vertexes and 3*n edges. * @param k The parameter k of the Generalized Petersen Graph G(n, k), which must be less than * n/2. * @param seed The seed for the random number generator used in generating the randomized vertex * index relabeling when generating the second graph of the pair. * @return An instance of the Largest Common Subgraph problem for a pair of isomorphic Generalized * Petersen Graphs. * @throws IllegalArgumentException if n is less than 1. * @throws IllegalArgumentException if k is not less than n/2 or if k is negative. */ public static LargestCommonSubgraph createInstanceGeneralizedPetersenGraph( int n, int k, long seed) { return createInstanceGeneralizedPetersenGraph( n, k, new Permutation(2 * n, new SplittableRandom(seed))); } private LargestCommonSubgraph(int largerV) { edgesG1 = new ArrayList(); adjacencyMatrixG2 = new BitVector[largerV]; } /** * Gets the size of the instance as the number of vertexes in the larger graph. This is the * required permutation length. * * @return the number of vertexes in the larger of the two graphs. */ public int size() { return adjacencyMatrixG2.length; } @Override public int cost(Permutation candidate) { return bound - value(candidate); } @Override public int value(Permutation candidate) { int count = 0; for (InternalEdge e : edgesG1) { if (adjacencyMatrixG2[candidate.get(e.x)].isOne(candidate.get(e.y))) { count++; } } return count; } @Override public int minCost() { return 0; } /** * Determines an upper bound on the possible size in number of edges of the largest common * subgraph. This is simply the smaller of the number of edges of the two graphs. Note it may or * may not be possible to actually find a common subgraph with this number of edges. It is simply * an upper bound. * * @return the minimum of the number of edges in graph 1 and graph 2. */ public int maxValue() { return bound; } /* * package private for testing */ final boolean hasEdge1(int u, int v) { for (InternalEdge e : edgesG1) { if (e.x == u && e.y == v || e.x == v && e.y == u) { return true; } } return false; } /* * package private for testing */ final boolean hasEdge2(int u, int v) { return adjacencyMatrixG2[u].isOne(v); } private void createIsomorphicRandomInstanceData(int v, double density, RandomGenerator gen) { if (v <= 0) { throw new IllegalArgumentException("Graphs must have at least 1 vertex."); } if (density < 0.0) { throw new IllegalArgumentException("The graph density must be non-negative."); } if (density > 1.0) { throw new IllegalArgumentException("The graph density must be no greater than 1.0."); } for (int i = 0; i < v; i++) { adjacencyMatrixG2[i] = new BitVector(v); } Permutation perm = new Permutation(v, gen); for (int i = 0; i < v; i++) { for (int j = i + 1; j < v; j++) { if (gen.nextDouble() < density) { edgesG1.add(new InternalEdge(i, j)); adjacencyMatrixG2[perm.get(i)].flip(perm.get(j)); adjacencyMatrixG2[perm.get(j)].flip(perm.get(i)); } } } bound = edgesG1.size(); } private void createRandomInstanceData( int v1, int v2, double density1, double density2, RandomGenerator gen) { if (v1 <= 0) { throw new IllegalArgumentException("Graphs must have at least 1 vertex."); } if (density1 < 0.0 || density2 < 0.0) { throw new IllegalArgumentException("The graph density must be non-negative."); } if (density1 > 1.0 || density2 > 1.0) { throw new IllegalArgumentException("The graph density must be no greater than 1.0."); } for (int i = 0; i < v1; i++) { for (int j = i + 1; j < v1; j++) { if (gen.nextDouble() < density1) { edgesG1.add(new InternalEdge(i, j)); } } } for (int i = 0; i < v2; i++) { adjacencyMatrixG2[i] = new BitVector(v2); } bound = 0; for (int i = 0; i < v2; i++) { for (int j = i + 1; j < v2; j++) { if (gen.nextDouble() < density2) { adjacencyMatrixG2[i].flip(j); adjacencyMatrixG2[j].flip(i); bound++; } } } if (edgesG1.size() < bound) { bound = edgesG1.size(); } } private void initializeInstanceData(int v1, int v2, List edges1, List edges2) { if (v1 <= 0) { throw new IllegalArgumentException("Graphs must have at least 1 vertex."); } for (Edge e : edges1) { if (e.u >= v1 || e.v >= v1) { throw new IllegalArgumentException("Edge endpoint out of bounds."); } edgesG1.add(new InternalEdge(e)); } for (int i = 0; i < v2; i++) { adjacencyMatrixG2[i] = new BitVector(v2); } for (Edge e : edges2) { if (e.u >= v2 || e.v >= v2) { throw new IllegalArgumentException("Edge endpoint out of bounds."); } adjacencyMatrixG2[e.u].flip(e.v); adjacencyMatrixG2[e.v].flip(e.u); } bound = edges1.size() <= edges2.size() ? edges1.size() : edges2.size(); } private static LargestCommonSubgraph createInstanceGeneralizedPetersenGraph( int n, int k, Permutation p) { if (n <= 0) { throw new IllegalArgumentException("n must be positive"); } if (k > (n - 1) / 2) { throw new IllegalArgumentException("k must be less than 0.5n"); } if (k < 0) { throw new IllegalArgumentException("k must be non-negative"); } int[] u = new int[n]; int[] v = new int[n]; for (int i = 0; i < n; i++) { u[i] = i; v[i] = n + i; } ArrayList edges1 = new ArrayList(); for (int i = 0; i < n; i++) { edges1.add(new Edge(u[i], u[(i + 1) % n])); edges1.add(new Edge(v[i], v[(i + k) % n])); edges1.add(new Edge(u[i], v[i])); } ArrayList edges2 = new ArrayList(); for (Edge e : edges1) { edges2.add(new Edge(p.get(e.u), p.get(e.v))); } return new LargestCommonSubgraph(2 * n, 2 * n, edges1, edges2); } /* * Private internal class for use within the LargestCommonSubgraph class for representing * edges. */ private class InternalEdge { private final int x; private final int y; private InternalEdge(int x, int y) { this.x = x; this.y = y; } private InternalEdge(Edge e) { this.x = e.u; this.y = e.v; } } /** * This class is used to represent edges when specifying instances of the {@link * LargestCommonSubgraph} problem. Instances of this class are immutable. The edges are * undirected. * * @author Vincent A. Cicirello, https://www.cicirello.org/ */ public static final class Edge { private final int u; private final int v; /** * Constructs an undirected edge. * * @param u An endpoint of the edge. * @param v The other endpoint of the edge. */ public Edge(int u, int v) { this.u = u; this.v = v; } /** * Gets one endpoint of the edge. The edge is undirected, so there is no meaning behind which * endpoint is which. Use the {@link getV} method to get the other endpoint. * * @return one of the endpoints */ public int getU() { return u; } /** * Gets one endpoint of the edge. The edge is undirected, so there is no meaning behind which * endpoint is which. Use the {@link getU} method to get the other endpoint. * * @return one of the endpoints */ public int getV() { return v; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy