blob: 99e1b8adba3386ddcf8479927942e82210bd6567 [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.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 );
}
//-----------------------------------------------------------------------
}