| /** |
| * 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.maven.mercury.metadata.sat; |
| |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.maven.mercury.artifact.ArtifactMetadata; |
| import org.apache.maven.mercury.artifact.MetadataTreeNode; |
| import org.apache.maven.mercury.event.EventManager; |
| import org.apache.maven.mercury.event.EventTypeEnum; |
| import org.apache.maven.mercury.event.GenericEvent; |
| import org.apache.maven.mercury.event.MercuryEventListener; |
| import org.apache.maven.mercury.logging.IMercuryLogger; |
| import org.apache.maven.mercury.logging.MercuryLoggerManager; |
| import org.apache.maven.mercury.metadata.MetadataTreeNodeGAComparator; |
| import org.apache.maven.mercury.metadata.MetadataTreeNodeGAVComparator; |
| import org.codehaus.plexus.lang.DefaultLanguage; |
| import org.codehaus.plexus.lang.Language; |
| import org.sat4j.core.Vec; |
| import org.sat4j.core.VecInt; |
| import org.sat4j.pb.IPBSolver; |
| import org.sat4j.pb.ObjectiveFunction; |
| import org.sat4j.pb.SolverFactory; |
| import org.sat4j.specs.ContradictionException; |
| import org.sat4j.specs.IConstr; |
| import org.sat4j.specs.IVec; |
| import org.sat4j.specs.IVecInt; |
| import org.sat4j.specs.TimeoutException; |
| |
| /** |
| * Default SAT4J implementation. |
| * |
| * @author Oleg Gusakov |
| * @version $Id$ |
| */ |
| public class DefaultSatSolver |
| implements SatSolver |
| { |
| private static final IMercuryLogger LOG = MercuryLoggerManager.getLogger( DefaultSatSolver.class ); |
| private static final Language LANG = new DefaultLanguage( DefaultSatSolver.class ); |
| |
| protected SatContext _context; |
| protected IPBSolver _solver = SolverFactory.newEclipseP2(); |
| protected MetadataTreeNode _root; |
| |
| protected EventManager _eventManager; |
| |
| public static final BigInteger TWO = new BigInteger("2"); |
| |
| protected static final Comparator<MetadataTreeNode> gaComparator = new MetadataTreeNodeGAComparator(); |
| //----------------------------------------------------------------------- |
| public static SatSolver create( MetadataTreeNode tree ) |
| throws SatException |
| { |
| return new DefaultSatSolver( tree ); |
| } |
| //----------------------------------------------------------------------- |
| public static SatSolver create( MetadataTreeNode tree, EventManager eventManager ) |
| throws SatException |
| { |
| return new DefaultSatSolver( tree, eventManager ); |
| } |
| //----------------------------------------------------------------------- |
| public DefaultSatSolver( MetadataTreeNode tree, EventManager eventManager ) |
| throws SatException |
| { |
| this._eventManager = eventManager; |
| GenericEvent event = null; |
| |
| if( tree == null) |
| throw new SatException( LANG.getMessage( "null.tree.arg" ) ); |
| |
| try |
| { |
| if( _eventManager != null ) |
| event = new GenericEvent( EventTypeEnum.satSolver, EVENT_CREATE_SOLVER, tree.toString() ); |
| |
| if( tree.getId() == 0 ) |
| MetadataTreeNode.reNumber( tree, 1 ); |
| |
| int nNodes = tree.countDistinctNodes(); |
| |
| LOG.debug( "SatContext: # of variables: "+nNodes ); |
| |
| _context = new SatContext( nNodes ); |
| _solver.newVar( tree.countNodes() ); |
| _root = tree; |
| |
| try |
| { |
| addNode( tree ); |
| } |
| catch (ContradictionException e) |
| { |
| throw new SatException(e); |
| } |
| } |
| finally |
| { |
| if( _eventManager != null ) |
| { |
| event.stop(); |
| _eventManager.fireEvent( event ); |
| } |
| } |
| } |
| //----------------------------------------------------------------------- |
| public DefaultSatSolver( MetadataTreeNode tree ) |
| throws SatException |
| { |
| this( tree, null ); |
| } |
| //----------------------------------------------------------------------- |
| public final void applyPolicies( List<Comparator<MetadataTreeNode>> comparators ) |
| throws SatException |
| { |
| if( comparators == null || comparators.size() < 1 ) |
| return; |
| |
| if( _root == null ) |
| throw new SatException( "cannot apply policies to a null tree" ); |
| |
| // TODO og: assumption - around 128 GA's per tree. If more - map reallocates - slow down. |
| // TODO og: MERCURY-40 |
| Map<String, List<MetadataTreeNode>> buckets = new LinkedHashMap<String, List<MetadataTreeNode>>(128); |
| fillBuckets( buckets, _root ); |
| sortBuckets( buckets, comparators ); |
| useBuckets( buckets ); |
| } |
| //----------------------------------------------------------------------- |
| private void useBuckets( Map<String, List<MetadataTreeNode>> buckets ) |
| throws SatException |
| { |
| if( buckets == null || buckets.size() < 1 ) |
| return; |
| |
| IVecInt vars = new VecInt( 128 ); |
| IVec<BigInteger> coeffs = new Vec<BigInteger>( 128 ); |
| |
| int count = 0; |
| |
| for( String key : buckets.keySet() ) |
| { |
| List<MetadataTreeNode> bucket = buckets.get( key ); |
| |
| // oleg: ? this is needed if optimization is "maximize" |
| // Collections.reverse( bucket ); |
| |
| int bucketSize = bucket.size(); |
| |
| boolean bigBucket = bucketSize > 1; |
| |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( "\n\nBucket "+key ); |
| |
| IVecInt bucketVars = new VecInt( bucketSize ); |
| |
| for( int i=0; i<bucketSize; i++ ) |
| { |
| MetadataTreeNode n = bucket.get(i); |
| |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( n.toString() ); |
| |
| SatVar var = _context.findOrAdd(n); |
| int varLiteral = var.getLiteral(); |
| |
| bucketVars.push( varLiteral ); |
| |
| if( bigBucket ) |
| { |
| vars.push( varLiteral ); |
| |
| BigInteger cf = TWO.pow( count++ ); |
| |
| coeffs.push( cf ); |
| |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( " "+cf+" x"+var.getLiteral() ); |
| } |
| |
| } |
| |
| try |
| { |
| if( bucketVars != null && !bucketVars.isEmpty() ) |
| { |
| _solver.addAtMost( bucketVars, 1 ); |
| _solver.addAtLeast( bucketVars, 1 ); |
| } |
| } |
| catch( ContradictionException e ) |
| { |
| throw new SatException(e); |
| } |
| |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( "\n" ); |
| } |
| |
| if( vars.isEmpty() ) |
| return; |
| |
| _solver.setObjectiveFunction( new ObjectiveFunction( vars, coeffs ) ); |
| } |
| //----------------------------------------------------------------------- |
| protected static final void sortBuckets( |
| Map<String, List<MetadataTreeNode>> buckets |
| , List<Comparator<MetadataTreeNode>> comparators |
| ) |
| { |
| Comparator<MetadataTreeNode> lastComparator; |
| for( List<MetadataTreeNode> bucket : buckets.values() ) |
| { |
| lastComparator = gaComparator; |
| for( Comparator<MetadataTreeNode> comparator : comparators ) |
| { |
| sortBucket( bucket, comparator, lastComparator ); |
| lastComparator = comparator; |
| } |
| // due to the nature of Comparator need to reverse the result |
| // as the best fit is now last |
| Collections.reverse( bucket ); |
| |
| // the best fit now first, and we don't need duplicate GAVs |
| removeDuplicateGAVs( bucket ); |
| |
| } |
| } |
| //----------------------------------------------------------------------- |
| // remove duplicates, preserving the order. The first one is the most fit, |
| // so need to delete from tail |
| protected static final void removeDuplicateGAVs( List<MetadataTreeNode> bucket ) |
| { |
| if( bucket == null || bucket.size() < 2 ) |
| return; |
| |
| Comparator<MetadataTreeNode> gav = new MetadataTreeNodeGAVComparator(); |
| |
| int len = bucket.size(); |
| int [] dups = new int[ len-1 ]; |
| int cnt = 0; |
| |
| for( int i=1; i<len; i++ ) |
| { |
| MetadataTreeNode ti = bucket.get(i); |
| |
| for( int j=0; j<i; j++ ) |
| if( gav.compare( ti, bucket.get(j) ) == 0 ) |
| { |
| dups[cnt++] = i; |
| break; |
| } |
| } |
| |
| if( cnt > 0 ) |
| for( int i=0; i<cnt; i++ ) |
| bucket.remove( dups[cnt-i-1] ); |
| } |
| //----------------------------------------------------------------------- |
| /** |
| * reorders the bucket's lastComparator equal subsets with comparator. |
| */ |
| protected static final void sortBucket( |
| List<MetadataTreeNode> bucket |
| , Comparator<MetadataTreeNode> comparator |
| , Comparator<MetadataTreeNode> lastComparator |
| ) |
| { |
| if( bucket == null || bucket.size() < 2 ) |
| return; |
| |
| int bLen = bucket.size(); |
| MetadataTreeNode [] temp = bucket.toArray( new MetadataTreeNode[ bLen ] ); |
| |
| int wStart = -1; |
| int wLen = 0; |
| MetadataTreeNode [] work = new MetadataTreeNode[ bLen ]; |
| |
| MetadataTreeNode lastNode = null; |
| for( int i=0; i<bLen; i++ ) |
| { |
| MetadataTreeNode n = temp[i]; |
| |
| if( lastNode == null ) |
| { |
| lastNode = n; |
| continue; |
| } |
| |
| if( lastComparator.compare(lastNode, n) == 0 ) |
| { |
| if( wLen == 0 ) |
| { |
| work[ wLen++ ] = lastNode; |
| wStart = i-1; |
| } |
| |
| work[ wLen++ ] = n; |
| |
| lastNode = n; |
| |
| if( i < (bLen-1) ) |
| continue; |
| } |
| |
| if( wLen > 0 ) // eq list formed |
| { |
| reorder( work, wLen, comparator ); |
| for( int j=0; j<wLen; j++ ) |
| temp[ wStart+j ] = work[ j ]; |
| wLen = 0; |
| wStart = -1; |
| } |
| |
| lastNode = n; |
| } |
| |
| bucket.clear(); |
| for( int i=0; i<bLen; i++ ) |
| bucket.add( temp[ i ] ); |
| } |
| //----------------------------------------------------------------------- |
| private static final void reorder( |
| MetadataTreeNode[] work |
| , int wLen |
| , Comparator<MetadataTreeNode> comparator |
| ) |
| { |
| MetadataTreeNode[] temp = new MetadataTreeNode[ wLen ]; |
| |
| for( int i=0; i< wLen; i++ ) |
| temp[i] = work[i]; |
| |
| Arrays.sort( temp, comparator ); |
| |
| for( int i=0; i<wLen; i++ ) |
| work[i] = temp[i]; |
| |
| } |
| //----------------------------------------------------------------------- |
| protected static final void fillBuckets( |
| Map<String, List<MetadataTreeNode>> buckets |
| , MetadataTreeNode node |
| ) |
| { |
| String ga = node.getMd().getGA(); |
| List<MetadataTreeNode> bucket = buckets.get(ga); |
| if( bucket == null ) |
| { |
| // TODO og: assumption - around 32 GAVs per GA |
| bucket = new ArrayList<MetadataTreeNode>( 32 ); |
| buckets.put( ga, bucket ); |
| } |
| |
| bucket.add( node ); |
| |
| if( ! node.hasChildren() ) |
| return; |
| |
| for( MetadataTreeNode kid : node.getChildren() ) |
| { |
| fillBuckets( buckets, kid ); |
| } |
| } |
| //----------------------------------------------------------------------- |
| private final void addPB( IVecInt lits, IVec<BigInteger> coeff, boolean ge, BigInteger cardinality ) |
| throws ContradictionException |
| { |
| _solver.addPseudoBoolean( lits, coeff, ge, cardinality ); |
| |
| if( LOG.isDebugEnabled() ) |
| LOG.debug("PB: "); |
| |
| for( int i=0; i<lits.size(); i++ ) |
| { |
| int co = Integer.parseInt( ""+coeff.get(i) ); |
| String sign = co < 0 ? "-" : "+"; |
| int val = Math.abs(co); |
| String space = val == 1 ? "" : " "; |
| |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( " " + sign + (val==1?"":val) + space + "x"+lits.get(i) ); |
| } |
| if( LOG.isDebugEnabled() ) |
| LOG.debug(( ge ? " >= " : " < ")+" "+cardinality ); |
| } |
| //----------------------------------------------------------------------- |
| private final Map<ArtifactMetadata, List<MetadataTreeNode>> processChildren( |
| List<ArtifactMetadata> queries |
| , List<MetadataTreeNode> children |
| ) |
| throws SatException |
| { |
| if( queries == null || queries.size() < 1 ) |
| return null; |
| |
| if( children == null || children.size() < 1 ) |
| throw new SatException("there are queries, but not results. Queries: "+queries); |
| |
| // TODO og: MERCURY-40 |
| Map<ArtifactMetadata, List<MetadataTreeNode>> res = new LinkedHashMap<ArtifactMetadata, List<MetadataTreeNode>>( queries.size() ); |
| |
| for( ArtifactMetadata q : queries ) |
| { |
| List<MetadataTreeNode> bucket = new ArrayList<MetadataTreeNode>(4); |
| String queryGA = q.getGA(); |
| |
| for( MetadataTreeNode tn : children ) |
| { |
| if( tn.getMd() == null ) |
| throw new SatException("resulting tree node without metadata for query "+q ); |
| |
| if( queryGA.equals( tn.getMd().getGA()) ) |
| { |
| bucket.add(tn); |
| } |
| } |
| |
| if( bucket.size() < 1 ) |
| throw new SatException("No children for query "+queryGA ); |
| |
| res.put( q, bucket ); |
| } |
| |
| return res; |
| } |
| //----------------------------------------------------------------------- |
| private final void addNode( MetadataTreeNode node ) |
| throws ContradictionException, SatException |
| { |
| if( node == null ) |
| return; |
| |
| if( node.getMd() == null ) |
| throw new SatException("found a node without metadata"); |
| |
| SatVar nodeLit = _context.findOrAdd( node ); |
| |
| // this one is a must :) |
| if( node.getParent() == null ) |
| addPB( SatHelper.getSmallOnes( nodeLit.getLiteral() ), SatHelper.getBigOnes(1,false), true, BigInteger.ONE ); |
| |
| if( ! node.hasChildren() ) |
| return; |
| |
| Map<ArtifactMetadata,List<MetadataTreeNode>> kids = processChildren( node.getQueries(), node.getChildren() ); |
| |
| // leaf node in this scope |
| if( kids == null ) |
| return; |
| |
| for( Map.Entry<ArtifactMetadata,List<MetadataTreeNode>> kid : kids.entrySet() ) |
| { |
| ArtifactMetadata query = kid.getKey(); |
| |
| List<MetadataTreeNode> range = kid.getValue(); |
| |
| if( range.size() > 1 ) |
| { |
| addRange( nodeLit.getLiteral(), range, query.isOptional() ); |
| for( MetadataTreeNode tn : range ) |
| { |
| addNode( tn ); |
| } |
| } |
| else |
| { |
| MetadataTreeNode child = range.get(0); |
| SatVar kidLit = _context.findOrAdd( child ); |
| |
| addPB( SatHelper.getSmallOnes( new int [] { nodeLit.getLiteral(), kidLit.getLiteral() } ) |
| , SatHelper.getBigOnes( 1, -1 ) |
| , true, BigInteger.ZERO |
| ); |
| // addRange( nodeLit.getLiteral(), range, query.isOptional() ); |
| addNode( child ); |
| } |
| |
| } |
| } |
| //----------------------------------------------------------------------- |
| private final int [] addRange( int parentLiteral, List<MetadataTreeNode> range, boolean optional ) |
| throws ContradictionException, SatException |
| { |
| SatVar literal; |
| |
| int [] literals = new int[ range.size() ]; |
| |
| int count = 0; |
| |
| for( MetadataTreeNode tn : range ) |
| { |
| literal = _context.findOrAdd( tn ); |
| literals[count++] = literal.getLiteral(); |
| |
| // implication to parent |
| addPB( SatHelper.getSmallOnes( new int [] { parentLiteral, literal.getLiteral() } ) |
| , SatHelper.getBigOnes( 1, -1 ) |
| , true, BigInteger.ZERO |
| ); |
| } |
| |
| IVecInt rangeVector = SatHelper.getSmallOnes( literals ); |
| |
| if( optional ) // Sxi >= 0 |
| { |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( "optional range: atMost 1: "+ SatHelper.vectorToString( rangeVector) ); |
| |
| _solver.addAtMost( rangeVector, 1 ); |
| } |
| else // Sxi = 1 |
| { |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( "range: " + SatHelper.vectorToString( rangeVector) ); |
| |
| IConstr atLeast = _solver.addAtLeast( rangeVector, 1 ); |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( "atLeast: " + SatHelper.vectorToString( atLeast) ); |
| |
| IConstr atMost = _solver.addAtMost( rangeVector, 1 ); |
| if( LOG.isDebugEnabled() ) |
| LOG.debug( "atMost: " + SatHelper.vectorToString( atMost) ); |
| |
| } |
| |
| return literals; |
| } |
| //----------------------------------------------------------------------- |
| public final List<ArtifactMetadata> solve() |
| throws SatException |
| { |
| List<ArtifactMetadata> res = null; |
| GenericEvent event = null; |
| |
| try |
| { |
| if( _eventManager != null ) |
| event = new GenericEvent( EventTypeEnum.satSolver, EVENT_SOLVE, _root.toString() ); |
| |
| if( _solver.isSatisfiable() ) |
| { |
| res = new ArrayList<ArtifactMetadata>( _root.countNodes() ); |
| |
| int [] model = _solver.model(); |
| |
| if( LOG.isDebugEnabled() ) |
| if( model != null ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| String comma = ""; |
| for( int m : model ) |
| { |
| sb.append( comma+m ); |
| comma = ", "; |
| } |
| LOG.debug( '['+sb.toString()+']' ); |
| } |
| else |
| LOG.debug( "model is null" ); |
| |
| for( int i : model ) |
| if( i > 0 ) |
| res.add( _context.getMd( i ) ); |
| } |
| } |
| catch (TimeoutException e) |
| { |
| throw new SatException( e ); |
| } |
| finally |
| { |
| if( _eventManager != null ) |
| { |
| event.stop(); |
| _eventManager.fireEvent( event ); |
| } |
| } |
| return res; |
| } |
| //----------------------------------------------------------------------- |
| public final MetadataTreeNode solveAsTree() |
| throws SatException |
| { |
| try |
| { |
| if( _solver.isSatisfiable() ) |
| { |
| int [] model = _solver.model(); |
| |
| if( LOG.isDebugEnabled() ) |
| if( model != null ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| String comma = ""; |
| for( int m : model ) |
| { |
| sb.append( comma+m ); |
| comma = ", "; |
| } |
| LOG.debug( '['+sb.toString()+']' ); |
| } |
| else |
| LOG.debug( "model is null" ); |
| |
| return _context.getSolutionSubtree( _root, model ); |
| } |
| return null; |
| } |
| catch (TimeoutException e) |
| { |
| throw new SatException( e ); |
| } |
| |
| } |
| //----------------------------------------------------------------------- |
| public void register( MercuryEventListener listener ) |
| { |
| if( _eventManager == null ) |
| _eventManager = new EventManager(); |
| |
| _eventManager.register( listener ); |
| } |
| |
| public void setEventManager( EventManager eventManager ) |
| { |
| _eventManager = eventManager; |
| } |
| |
| public void unRegister( MercuryEventListener listener ) |
| { |
| if( _eventManager != null ) |
| _eventManager.unRegister( listener ); |
| } |
| //----------------------------------------------------------------------- |
| } |