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

src.it.unimi.dsi.sux4j.mph.solve.Modulo2System Maven / Gradle / Ivy

Go to download

Sux4j is an implementation of succinct data structure in Java. It provides a number of related implementations covering ranking/selection over bit arrays, compressed lists and minimal perfect hashing.

There is a newer version: 5.4.1
Show newest version
package it.unimi.dsi.sux4j.mph.solve;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*
 * Sux4J: Succinct data structures for Java
 *
 * Copyright (C) 2015-2017 Sebastiano Vigna
 *
 *  This library is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as published by the Free
 *  Software Foundation; either version 3 of the License, or (at your option)
 *  any later version.
 *
 *  This library 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 Lesser General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, see .
 *
 */

import it.unimi.dsi.Util;
import it.unimi.dsi.bits.LongArrayBitVector;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.ints.IntArrayList;

/** Solver for linear systems on F2.
 * Variables are k-dimensional vectors on F2, with 0 ≤ k ≤ 64.
 *
 * @author Sebastiano Vigna
 */

public class Modulo2System {
	private final static Logger LOGGER = LoggerFactory.getLogger(Modulo2System.class);
	private final static boolean DEBUG = false;

	/** An equation on F2. */
	public static class Modulo2Equation {
		/** The vector representing the coefficients (one bit for each variable). */
		protected final LongArrayBitVector bitVector;
		/** The {@link LongArrayBitVector#bits() bv.bits()}, cached. */
		protected final long[] bits;
		/** The constant term. */
		protected long c;
		/** The first variable. It is {@link Integer#MAX_VALUE} if the
		 * first variable is not known. This field must be updated by {@link #updateFirstVar()} to be meaningful. */
		protected int firstVar;
		/** Whether any variable appears on the left side of the equation. */
		private boolean isEmpty;

		/** Creates a new equation.
		 *
		 * @param c the constant term.
		 * @param numVars the number of variables.
		 */
		public Modulo2Equation(final long c, final int numVars){
			this.c = c;
			this.bitVector = LongArrayBitVector.ofLength(numVars);
			this.bits = bitVector.bits();
			this.firstVar = Integer.MAX_VALUE;
			this.isEmpty = true;
		}

		protected Modulo2Equation(final Modulo2Equation equation){
			this.c = equation.c;
			this.bitVector = equation.bitVector.copy();
			this.bits = this.bitVector.bits();
			this.firstVar = equation.firstVar;
			this.isEmpty = equation.isEmpty;
		}

		/** Adds a new variable.
		 *
		 * @param variable a variable.
		 * @return this equation.
		 * @throws IllegalStateException if you try to add twice the same variable.
		 */
		public Modulo2Equation add(final int variable) {
			assert ! bitVector.getBoolean(variable);
			bitVector.set(variable);
			isEmpty = false;
			return this;
		}


		/** Returns an array containing the variables in increasing order.
		 *
		 * 

Mainly for debugging purposes. * * @return an array containing the variables in increasing order. */ public int[] variables() { final IntArrayList variables = new IntArrayList(); for(int i = 0; i < bitVector.length(); i++) if (bitVector.getBoolean(i)) variables.add(i); return variables.toIntArray(); } /** Add another equation to this equation. * * @param equation an equation. */ public void add(final Modulo2Equation equation) { this.c ^= equation.c; final long[] x = this.bits, y = equation.bits; long isNotEmpty = 0; for(int i = x.length; i-- != 0;) isNotEmpty |= (x[i] ^= y[i]); isEmpty = isNotEmpty == 0; } /** Updates the information contained in {@link #firstVar}. */ public void updateFirstVar() { if (isEmpty) firstVar = Integer.MAX_VALUE; else { int i = -1; while(bits[++i] == 0); firstVar = i * 64 + Long.numberOfTrailingZeros(bits[i]); } } public boolean isUnsolvable() { return isEmpty && c != 0; } public boolean isIdentity() { return isEmpty && c == 0; } @Override public int hashCode() { return (int) HashCommon.murmurHash3(c ^ bitVector.hashCode()); } @Override public boolean equals(Object o) { if (! (o instanceof Modulo2Equation)) return false; final Modulo2Equation equation = (Modulo2Equation)o; return c == equation.c && bitVector.equals(equation.bitVector); } /** Returns the modulo-2 scalar product of the two provided bit vectors. * * @param bits a bit vector represented as an array of longs. * @param values an array of long representing the 64-bit values associated with each variable. * @return the modulo-2 scalar product of {@code x} and {code y}. */ public static long scalarProduct(final long[] bits, final long[] values) { long sum = 0; for(int i = bits.length; i-- != 0;) { final int offset = i * 64; long word = bits[i]; while(word != 0) { final int lsb = Long.numberOfTrailingZeros(word); sum ^= values[offset + lsb]; word &= word - 1; } } return sum; } @Override public String toString() { final StringBuilder b = new StringBuilder(); boolean someNonZero = false; for(int i = 0; i < bitVector.length(); i++) { if (bitVector.getBoolean(i)) { if (someNonZero) b.append(" + "); someNonZero = true; b.append("x_").append(i); } } if (! someNonZero) b.append('0'); return b.append(" = ").append(c).toString(); } public Modulo2Equation copy() { return new Modulo2Equation(this); } } /** The number of variables. */ private final int numVars; /** The equations. */ private final ArrayList equations; public Modulo2System(final int numVars) { equations = new ArrayList<>(); this.numVars = numVars; } protected Modulo2System(final int numVars , ArrayList equations) { this.equations = equations; this.numVars = numVars; } public Modulo2System copy() { final ArrayList list = new ArrayList<>(equations.size()); for(final Modulo2Equation equation: equations) list.add(equation.copy()); return new Modulo2System(numVars, list); } /** Adds an equation to the system. * * @param equation an equation with the same number of variables of the system. */ public void add(Modulo2Equation equation) { if (equation.bitVector.length() != numVars) throw new IllegalArgumentException("The number of variables in the equation (" + equation.bitVector.length() + ") does not match the number of variables of the system (" + numVars + ")"); equations.add(equation); } @Override public String toString() { final StringBuilder b = new StringBuilder(); for (int i = 0; i < equations.size(); i++) b.append(equations.get(i)).append('\n'); return b.toString(); } public boolean check(final long[] solution) { assert solution.length == numVars; for(final Modulo2Equation equation: equations) if (equation.c != Modulo2Equation.scalarProduct(equation.bits, solution)) return false; return true; } private boolean echelonForm() { main: for (int i = 0; i < equations.size() - 1; i++) { assert equations.get(i).firstVar != Integer.MAX_VALUE; for (int j = i + 1; j < equations.size(); j++) { // Note that because of exchanges we cannot extract the first assignment final Modulo2Equation eqJ = equations.get(j); final Modulo2Equation eqI = equations.get(i); assert eqI.firstVar != Integer.MAX_VALUE; assert eqJ.firstVar != Integer.MAX_VALUE; final int firstVar = eqI.firstVar; if(firstVar == eqJ.firstVar) { eqI.add(eqJ); if (eqI.isUnsolvable()) return false; if (eqI.isIdentity()) continue main; eqI.updateFirstVar(); } if (eqI.firstVar > eqJ.firstVar) Collections.swap(equations, i, j); } } return true; } /** Solves the system using Gaussian elimination. * * @param solution an array where the solution will be written. * @return true if the system is solvable. */ public boolean gaussianElimination(final long[] solution) { assert solution.length == numVars; for (final Modulo2Equation equation: equations) equation.updateFirstVar(); if (! echelonForm()) return false; for (int i = equations.size(); i-- != 0;) { final Modulo2Equation equation = equations.get(i); if (equation.isIdentity()) continue; assert solution[equation.firstVar] == 0 : equation.firstVar; solution[equation.firstVar] = equation.c ^ Modulo2Equation.scalarProduct(equation.bits, solution); } return true; } /** Solves the system using lazy Gaussian elimination. * *

Warning: this method is very inefficient, as it * scans linearly the equations, builds from scratch the {@code var2Eq} * parameter of {@link #lazyGaussianElimination(Modulo2System, int[][], long[], int[], long[])}, * and finally calls it. It should be used mainly to write unit tests. * * @param solution an array where the solution will be written. * @return true if the system is solvable. */ public boolean lazyGaussianElimination(final long[] solution) { final int[][] var2Eq = new int[numVars][]; final int[] d = new int[numVars]; for(final Modulo2Equation equation: equations) for(int v = (int)equation.bitVector.length(); v-- != 0;) if (equation.bitVector.getBoolean(v)) d[v]++; for(int v = numVars; v-- != 0;) var2Eq[v] = new int[d[v]]; Arrays.fill(d, 0); final long[] c = new long[equations.size()]; for(int e = 0; e < equations.size(); e++) { c[e] = equations.get(e).c; final LongArrayBitVector bitVector = equations.get(e).bitVector; for(int v = (int)bitVector.length(); v-- != 0;) if (bitVector.getBoolean(v)) var2Eq[v][d[v]++] = e; } return lazyGaussianElimination(this, var2Eq, c, Util.identity(numVars), solution); } /** Solves a system using lazy Gaussian elimination. * * @param var2Eq an array of arrays describing, for each variable, in which equation it appears; * equation indices must appear in nondecreasing order; an equation * may appear several times for a given variable, in which case the final coefficient * of the variable in the equation is given by the number of appearances modulo 2 (this weird format is useful * when calling this method from a {@link Linear3SystemSolver}). Note that this array * will be altered if some equation appears multiple time associated with a variable. * @param c the array of known terms, one for each equation. * @param variable the variables with respect to which the system should be solved * (variables not appearing in this array will be simply assigned zero). * @param solution an array where the solution will be written. * @return true if the system is solvable. */ public static boolean lazyGaussianElimination(final int var2Eq[][], final long[] c, final int[] variable, final long[] solution) { return lazyGaussianElimination(null, var2Eq, c, variable, solution); } /** Solves a system using lazy Gaussian elimination. * * @param system a modulo-2 system, or {@code null}, in which case the system will be rebuilt * from the other variables. * @param var2Eq an array of arrays describing, for each variable, in which equation it appears; * equation indices must appear in nondecreasing order; an equation * may appear several times for a given variable, in which case the final coefficient * of the variable in the equation is given by the number of appearances modulo 2 (this weird format is useful * when calling this method from a {@link Linear3SystemSolver}). The resulting system * must be identical to {@code system}. Note that this array * will be altered if some equation appears multiple time associated with a variable. * @param c the array of known terms, one for each equation. * @param variable the variables with respect to which the system should be solved * (variables not appearing in this array will be simply assigned zero). * @param solution an array where the solution will be written. * @return true if the system is solvable. */ public static boolean lazyGaussianElimination(Modulo2System system, final int var2Eq[][], final long[] c, final int[] variable, final long[] solution) { final int numEquations = c.length; if (numEquations == 0) return true; final int numVars = var2Eq.length; assert solution.length == numVars; final boolean buildSystem = system == null; if (buildSystem) { system = new Modulo2System(numVars); for(int i = 0; i < c.length; i++) system.add(new Modulo2Equation(c[i], numVars)); } /* The weight of each variable, that is, the number of equations still * in the queue in which the variable appears. We use zero to * denote pivots of solved equations. */ final int weight[] = new int[numVars]; // The priority of each equation still to be examined (the number of light variables). final int[] priority = new int[numEquations]; for(final int v : variable) { final int[] eq = var2Eq[v]; if (eq.length == 0) continue; int currEq = eq[0]; boolean currCoeff = true; int j = 0; /* We count the number of appearances in an equation and compute * the correct coefficient (which might be zero). If there are * several appearances of the same equation, we compact the array * and, in the end, replace it with a shorter one. */ for(int i = 1; i < eq.length; i++) { if (eq[i] != currEq) { assert eq[i] > currEq; if (currCoeff) { if (buildSystem) system.equations.get(currEq).add(v); weight[v]++; priority[currEq]++; eq[j++] = currEq; } currEq = eq[i]; currCoeff = true; } else currCoeff = ! currCoeff; } if (currCoeff) { if (buildSystem) system.equations.get(currEq).add(v); weight[v]++; priority[currEq]++; eq[j++] = currEq; } // In case we found duplicates, we replace the array with a uniquified one. if (j != eq.length) var2Eq[v] = Arrays.copyOf(var2Eq[v], j); } if (DEBUG) { System.err.println(); System.err.println("===== Going to solve... ======"); System.err.println(); System.err.println(system); } // All variables in a stack returning heavier variables first. final IntArrayList variables; { final int[] t = Util.identity(numVars); final int[] u = new int[t.length]; final int[] count = new int[numEquations + 1]; // CountSort for(int i = t.length; i-- != 0;) count[weight[t[i]]]++; for(int i = 1; i < count.length; i++) count[i] += count[i - 1]; for(int i = t.length; i-- != 0;) u[--count[weight[t[i]]]] = t[i]; variables = IntArrayList.wrap(u); } // The equations that are not dense and have weight <= 1. final IntArrayList equationList = new IntArrayList(); for(int i = priority.length; i-- != 0;) if (priority[i] <= 1) equationList.add(i); // The equations that are part of the dense system (entirely made of active variables). final ArrayList dense = new ArrayList<>(); // The equations that define a solved variable in term of active variables. final ArrayList solved = new ArrayList<>(); // The solved variables (parallel to solved). final IntArrayList pivots = new IntArrayList(); final ArrayList equations = system.equations; // A bit vector containing a 1 in correspondence of each idle variable. final long[] idleNormalized = new long[equations.get(0).bits.length]; Arrays.fill(idleNormalized, -1); int numActive = 0; for(int remaining = equations.size(); remaining != 0;) { if (equationList.isEmpty()) { // Make another variable heavy int var; do var = variables.popInt(); while(weight[var] == 0); numActive++; idleNormalized[var / 64] ^= 1L << (var % 64); if (DEBUG) System.err.println("Making variable " + var + " of weight " + weight[var] + " heavy (" + remaining + " equations to go)"); for(final int equationIndex: var2Eq[var]) if (--priority[equationIndex] == 1) equationList.push(equationIndex); } else { remaining--; final int first = equationList.popInt(); // An equation of weight 0 or 1. final Modulo2Equation equation = equations.get(first); if (DEBUG) System.err.println("Looking at equation " + first + " of priority " + priority[first] + " : " + equation); if (priority[first] == 0) { if (equation.isUnsolvable()) return false; if (equation.isIdentity()) continue; /* This equation must be necessarily solved by standard Gaussian elimination. No updated * is needed, as all its variables are heavy. */ dense.add(equation); } else if (priority[first] == 1) { /* This is solved (in terms of the heavy variables). Let's find the pivot, that is, * the only idle variable. Note that we do not need to update varEquation[] of any variable, as they * are all either heavy (the non-pivot), or appearing only in this equation (the pivot). */ int wordIndex = 0; while((equation.bits[wordIndex] & idleNormalized[wordIndex]) == 0) wordIndex++; final int pivot = wordIndex * 64 + Long.numberOfTrailingZeros(equation.bits[wordIndex] & idleNormalized[wordIndex]); // Record the idle variable and the equation for computing it later. if (DEBUG) System.err.println("Adding to solved variables x_" + pivot + " by equation " + equation); pivots.add(pivot); solved.add(equation); // This forces to skip the pivot when looking for a new variable to be made heavy. weight[pivot] = 0; // Now we need to eliminate the variable from all other equations containing it. for(final int equationIndex: var2Eq[pivot]) { if (equationIndex == first) continue; if (--priority[equationIndex] == 1) equationList.add(equationIndex); if (DEBUG) System.err.print("Replacing equation (" + equationIndex + ") " + equations.get(equationIndex) + " with "); equations.get(equationIndex).add(equation); if (DEBUG) System.err.println(equations.get(equationIndex)); } } } } LOGGER.debug("Active variables: " + numActive + " (" + Util.format(numActive * 100 / numVars) + "%)"); if (DEBUG) { System.err.println("Dense equations: " + dense); System.err.println("Solved equations: " + solved); System.err.println("Pivots: " + pivots); } final Modulo2System denseSystem = new Modulo2System(numVars, dense); if (! denseSystem.gaussianElimination(solution)) return false; // numVars >= denseSystem.numVars if (DEBUG) System.err.println("Solution (dense): " + Arrays.toString(solution)); for (int i = solved.size(); i-- != 0;) { final Modulo2Equation equation = solved.get(i); final int pivot = pivots.getInt(i); assert solution[pivot] == 0 : pivot; solution[pivot] = equation.c ^ Modulo2Equation.scalarProduct(equation.bits, solution); } if (DEBUG) System.err.println("Solution (all): " + Arrays.toString(solution)); return true; } /* Temporary method to test speedup due to lazy Gaussian elimination. */ public static boolean gaussianElimination(final int var2Eq[][], final long[] c, final int[] variable, final long[] solution) { final int numEquations = c.length; if (numEquations == 0) return true; final int numVars = var2Eq.length; assert solution.length == numVars; final Modulo2System system = new Modulo2System(numVars); for(int i = 0; i < c.length; i++) system.add(new Modulo2Equation(c[i], numVars)); for(final int v : variable) { final int[] eq = var2Eq[v]; if (eq.length == 0) continue; int currEq = eq[0]; boolean currCoeff = true; int j = 0; /** We count the number of appearances in an equation and compute * the correct coefficient (which might be zero). If there are * several appearances of the same equation, we compact the array * and, in the end, replace it with a shorter one. */ for(int i = 1; i < eq.length; i++) { if (eq[i] != currEq) { assert eq[i] > currEq; if (currCoeff) { system.equations.get(currEq).add(v); eq[j++] = currEq; } currEq = eq[i]; currCoeff = true; } else currCoeff = ! currCoeff; } if (currCoeff) { system.equations.get(currEq).add(v); eq[j++] = currEq; } // In case we found duplicates, we replace the array with a uniquified one. if (j != eq.length) var2Eq[v] = Arrays.copyOf(var2Eq[v], j); } if (! system.gaussianElimination(solution)) return false; // numVars >= denseSystem.numVars return true; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy