From 9a72fb58637b384af5d30f3dde6e923af47f25fd Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Tue, 12 Apr 2022 13:38:53 -0400 Subject: [PATCH 01/17] creating the base class and adapting ReachabilityConstraint to it --- .../constraints/ReachabilityConstraint.cpp | 1074 +-------------- .../constraints/ShortestPathConstraint.cpp | 72 ++ .../constraints/TopologySearchConstraint.cpp | 1147 +++++++++++++++++ vertexy/src/public/ConstraintTypes.h | 3 +- .../constraints/ReachabilityConstraint.h | 158 +-- .../constraints/ShortestPathConstraint.h | 60 + .../constraints/TopologySearchConstraint.h | 203 +++ vertexy/src/public/ds/RamalReps.h | 1 + 8 files changed, 1492 insertions(+), 1226 deletions(-) create mode 100644 vertexy/src/private/constraints/ShortestPathConstraint.cpp create mode 100644 vertexy/src/private/constraints/TopologySearchConstraint.cpp create mode 100644 vertexy/src/public/constraints/ShortestPathConstraint.h create mode 100644 vertexy/src/public/constraints/TopologySearchConstraint.h diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index 9bf001f..707b51a 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -58,1082 +58,14 @@ ReachabilityConstraint::ReachabilityConstraint( const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, const ValueSet& edgeBlockedMask) - : IBacktrackingSolverConstraint(params) //ApplyGraphRelation(Params, SourceGraphData, EdgeGraphData)) - , m_edgeWatcher(*this) - , m_sourceGraphData(sourceGraphData) - , m_sourceGraph(sourceGraphData->getSource()) - , m_edgeGraphData(edgeGraphData) - , m_edgeGraph(edgeGraphData->getSource()->getImplementation()) - , m_minGraph(make_shared()) - , m_maxGraph(make_shared()) - , m_explanationGraph(make_shared()) - , m_sourceMask(sourceMask) - , m_requireReachableMask(requireReachableMask) - , m_edgeBlockedMask(edgeBlockedMask) + : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask, false) { - m_notSourceMask = sourceMask.inverted(); - m_notReachableMask = requireReachableMask.inverted(); - m_edgeOpenMask = edgeBlockedMask.inverted(); - for (int i = 0; i < m_sourceGraph->getNumVertices(); ++i) - { - VarID var = sourceGraphData->get(i); - if (var.isValid()) - { - m_variableToSourceVertexIndex[var] = i; - } - } - - vxy_assert(m_edgeGraph->getSource() == m_sourceGraph); - for (int i = 0; i < m_edgeGraph->getNumVertices(); ++i) - { - VarID var = edgeGraphData->get(i); - if (var.isValid()) - { - m_variableToSourceEdgeIndex[var] = i; - } - } -} - -bool ReachabilityConstraint::getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const -{ - // First search through all provided literals to find the minimum graph vertex. - // Note: some vertices will be edges, some will be tiles. - int minGraphVertex = m_sourceGraph->getNumVertices(); - - vector vertices; - vector isEdgeNode; - - vertices.reserve(literals.size()); - isEdgeNode.reserve(literals.size()); - - for (auto& lit : literals) - { - int graphVertex = m_sourceGraphData->indexOf(lit.variable); - if (graphVertex < 0) - { - int edgeNode = m_edgeGraphData->indexOf(lit.variable); - vxy_assert(edgeNode >= 0); - - vertices.push_back(edgeNode); - isEdgeNode.push_back(true); - - int edgeFrom, edgeTo; - bool bidirectional; - m_edgeGraph->getSourceEdgeForVertex(edgeNode, edgeFrom, edgeTo, bidirectional); - - graphVertex = min(edgeFrom, edgeTo); - } - else - { - vertices.push_back(graphVertex); - isEdgeNode.push_back(false); - } - vxy_assert(graphVertex >= 0); - minGraphVertex = min(graphVertex, minGraphVertex); - } - - // We always provide relations in terms of the source graph. - // Relations are anchored to the minimum vertex ID found (maps to top-leftmost in a grid graph). - outRelations.reset(m_sourceGraph, minGraphVertex); - - // create the relations! - outRelations.reserve(literals.size()); - for (int i = 0; i != literals.size(); ++i) - { - bool isEdge = isEdgeNode[i]; - - if (!isEdge) - { - int vertex = vertices[i]; - if (vertex != minGraphVertex) - { - TopologyLink link; - if (!m_sourceGraph->getTopologyLink(minGraphVertex, vertex, link)) - { - // no path specified in graph - // TODO: assert? - outRelations.clear(); - return false; - } - - auto linkRel = make_shared>(m_sourceGraphData, link); - outRelations.addRelation(m_sourceGraphData->get(vertex), linkRel); - } - else - { - auto selfRel = make_shared>(m_sourceGraphData); - outRelations.addRelation(m_sourceGraphData->get(vertex), selfRel); - } - } - else - { - int edgeNode = vertices[i]; - - // edge variable: get the source node if the edge - int edgeOrigin, edgeDestination; - bool bidirectional; - m_edgeGraph->getSourceEdgeForVertex(edgeNode, edgeOrigin, edgeDestination, bidirectional); - - int nodeEdgeIndex = -1; - for (int e = 0; e < m_sourceGraph->getNumOutgoing(edgeOrigin); ++e) - { - if (int testDest; m_sourceGraph->getOutgoingDestination(edgeOrigin, e, testDest) && testDest == edgeDestination) - { - nodeEdgeIndex = e; - break; - } - } - if (nodeEdgeIndex < 0) - { - vxy_assert_msg(false, "Edge node %d has source graph node origin %d, but can't find edgeIndex in source graph!", edgeNode, edgeOrigin); - outRelations.clear(); - return false; - } - - auto nodeToEdgeNodeRel = make_shared>(m_sourceGraph, m_edgeGraph, nodeEdgeIndex); - auto nodeToEdgeVarRel = nodeToEdgeNodeRel->map(make_shared>(m_edgeGraphData)); - - if (edgeOrigin != minGraphVertex) - { - TopologyLink link; - if (!m_sourceGraph->getTopologyLink(minGraphVertex, edgeOrigin, link)) - { - vxy_assert_msg(false, "expected link between vertices %d -> %d", minGraphVertex, edgeOrigin); - outRelations.clear(); - return false; - } - - auto linkRel = make_shared(m_sourceGraph, link); - outRelations.addRelation(m_edgeGraphData->get(edgeNode), linkRel->map(nodeToEdgeVarRel)); - } - else - { - outRelations.addRelation(m_edgeGraphData->get(edgeNode), nodeToEdgeVarRel); - } - } - } - - vxy_assert(outRelations.relations.size() == literals.size()); - return true; -} - -bool ReachabilityConstraint::initialize(IVariableDatabase* db) -{ - // Add all vertices to min/max graphs - for (int vertexIndex = 0; vertexIndex < m_sourceGraph->getNumVertices(); ++vertexIndex) - { - VarID vertexVar = m_sourceGraphData->get(vertexIndex); - - int addedIdx = m_maxGraph->addVertex(); - vxy_assert(addedIdx == vertexIndex); - - addedIdx = m_minGraph->addVertex(); - vxy_assert(addedIdx == vertexIndex); - - addedIdx = m_explanationGraph->addVertex(); - vxy_assert(addedIdx == vertexIndex); - - if (vertexVar.isValid()) - { - m_vertexWatchHandles[vertexVar] = db->addVariableWatch(vertexVar, EVariableWatchType::WatchModification, this); - } - } - - hash_map> edgeCapacities; - - m_totalNumEdges = 0; - - // Add all definitely-open edges to the min graph, and all possibly-open edges to the max graph - m_reachabilityEdgeLookup.resize(m_sourceGraph->getNumVertices()); - for (int sourceVertex = 0; sourceVertex < m_sourceGraph->getNumVertices(); ++sourceVertex) - { - auto foundVertexEdges = edgeCapacities.find(sourceVertex); - if (foundVertexEdges == edgeCapacities.end()) - { - foundVertexEdges = edgeCapacities.insert(sourceVertex).first; - } - - m_reachabilityEdgeLookup[sourceVertex].reserve(m_sourceGraph->getNumOutgoing(sourceVertex)); - for (int edgeIndex = 0; edgeIndex < m_sourceGraph->getNumOutgoing(sourceVertex); ++edgeIndex) - { - int destVertex; - if (m_sourceGraph->getOutgoingDestination(sourceVertex, edgeIndex, destVertex)) - { - m_reachabilityEdgeLookup[sourceVertex].push_back(make_tuple(destVertex, m_totalNumEdges)); - ++m_totalNumEdges; - - int edgeNode = m_edgeGraph->getVertexForSourceEdge(sourceVertex, destVertex); - vxy_assert(edgeNode >= 0); - VarID edgeVar = m_edgeGraphData->get(edgeNode); - - bool edgeIsClosed = true; - if (edgeVar.isValid()) - { - if (definitelyOpenEdge(db, edgeVar)) - { - edgeIsClosed = false; - - m_minGraph->initEdge(sourceVertex, destVertex); - m_maxGraph->initEdge(sourceVertex, destVertex); - m_explanationGraph->initEdge(sourceVertex, destVertex); - } - else if (possiblyOpenEdge(db, edgeVar)) - { - edgeIsClosed = false; - - if (m_edgeWatchHandles.find(edgeVar) == m_edgeWatchHandles.end()) - { - m_edgeWatchHandles[edgeVar] = db->addVariableWatch(edgeVar, EVariableWatchType::WatchModification, &m_edgeWatcher); - } - m_maxGraph->initEdge(sourceVertex, destVertex); - m_explanationGraph->initEdge(sourceVertex, destVertex); - } - } - else - { - edgeIsClosed = false; - - // No variable for this edge, so should always exist - m_minGraph->initEdge(sourceVertex, destVertex); - m_maxGraph->initEdge(sourceVertex, destVertex); - m_explanationGraph->initEdge(sourceVertex, destVertex); - } - - foundVertexEdges->second[destVertex] = edgeIsClosed ? CLOSED_EDGE_FLOW : OPEN_EDGE_FLOW; - - if (!m_sourceGraph->hasEdge(destVertex, sourceVertex)) - { - auto foundDestVertexEdges = edgeCapacities.find(destVertex); - if (foundDestVertexEdges == edgeCapacities.end()) - { - foundDestVertexEdges = edgeCapacities.insert(destVertex).first; - } - foundDestVertexEdges->second[sourceVertex] = 0; - } - } - } - } - - // Build flow graph edges flat list and lookup map - m_flowGraphEdges.reserve(m_totalNumEdges); - m_flowGraphLookup.reserve(m_sourceGraph->getNumVertices()); - for (int sourceVertex = 0; sourceVertex < m_sourceGraph->getNumVertices(); ++sourceVertex) - { - m_flowGraphLookup.emplace_back(m_flowGraphEdges.size(), m_flowGraphEdges.size() + edgeCapacities[sourceVertex].size()); - for (auto it = edgeCapacities[sourceVertex].begin(), itEnd = edgeCapacities[sourceVertex].end(); it != itEnd; ++it) - { - m_flowGraphEdges.push_back({it->first, -1, it->second}); - } - } - - // Fill in the ReverseEdgeIndex for each vertex - for (int sourceVertex = 0; sourceVertex < m_sourceGraph->getNumVertices(); ++sourceVertex) - { - for (int i = get<0>(m_flowGraphLookup[sourceVertex]); i < get<1>(m_flowGraphLookup[sourceVertex]); ++i) - { - if (m_flowGraphEdges[i].reverseEdgeIndex < 0) - { - int destVertex = m_flowGraphEdges[i].endVertex; - bool foundReverse = false; - for (int j = get<0>(m_flowGraphLookup[destVertex]); j < get<1>(m_flowGraphLookup[destVertex]); ++j) - { - if (m_flowGraphEdges[j].endVertex == sourceVertex) - { - m_flowGraphEdges[i].reverseEdgeIndex = j; - vxy_assert(m_flowGraphEdges[j].reverseEdgeIndex < 0); - m_flowGraphEdges[j].reverseEdgeIndex = i; - - foundReverse = true; - break; - } - } - vxy_assert(foundReverse); - } - } - } - - // Register for callback when edges are added/removed from ExplanationGraph, in order to update capacities - m_explanationGraph->getEdgeChangeListener().add([&](bool edgeWasAdded, int from, int to) - { - onExplanationGraphEdgeChange(edgeWasAdded, from, to); - }); - - // Create reachability structures for all variables that are possibly reachability sources - for (int vertex = 0; vertex < m_sourceGraph->getNumVertices(); ++vertex) - { - VarID vertexVar = m_sourceGraphData->get(vertex); - if (vertexVar.isValid() && possiblyIsSource(db, vertexVar)) - { - addSource(vertexVar); - m_initialPotentialSources.push_back(vertexVar); - } - } - - // Constrain all variables that are definitely reachable by any definite reachability source to reachable - // Constrain all variables that are not reachable by all potential reachability sources to unreachable - for (int vertex = 0; vertex < m_sourceGraph->getNumVertices(); ++vertex) - { - VarID vertexVar = m_sourceGraphData->get(vertex); - if (vertexVar.isValid()) - { - EReachabilityDetermination determination = determineReachability(db, vertex); - - if (determination == EReachabilityDetermination::DefinitelyUnreachable) - { - if (!db->constrainToValues(vertexVar, m_notReachableMask, this)) - { - return false; - } - } - else if (determination == EReachabilityDetermination::DefinitelyReachable) - { - if (!db->constrainToValues(vertexVar, m_requireReachableMask, this)) - { - return false; - } - } - } - } - - return true; -} - -void ReachabilityConstraint::reset(IVariableDatabase* db) -{ - for (auto it = m_vertexWatchHandles.begin(), itEnd = m_vertexWatchHandles.end(); it != itEnd; ++it) - { - db->removeVariableWatch(it->first, it->second, this); - } - m_vertexWatchHandles.clear(); - - for (auto it = m_edgeWatchHandles.begin(), itEnd = m_edgeWatchHandles.end(); it != itEnd; ++it) - { - db->removeVariableWatch(it->first, it->second, &m_edgeWatcher); - } - m_edgeWatchHandles.clear(); -} - -bool ReachabilityConstraint::onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& prevValue, bool& removeWatch) -{ - const ValueSet& newValue = db->getPotentialValues(variable); - - if ((prevValue.anyPossible(m_sourceMask) && !newValue.anyPossible(m_sourceMask)) || - (prevValue.anyPossible(m_notReachableMask) && !newValue.anyPossible(m_notReachableMask))) - { - if (!contains(m_vertexProcessList.begin(), m_vertexProcessList.end(), variable)) - { - m_vertexProcessList.push_back(variable); - } - db->queueConstraintPropagation(this); - } - return true; -} - -bool ReachabilityConstraint::EdgeWatcher::onVariableNarrowed(IVariableDatabase* db, VarID var, const ValueSet& prevValue, bool& removeHandle) -{ - const ValueSet& newValue = db->getPotentialValues(var); - if ((prevValue.anyPossible(m_parent.m_edgeBlockedMask) && !newValue.anyPossible(m_parent.m_edgeBlockedMask)) || - (prevValue.anyPossible(m_parent.m_edgeOpenMask) && !newValue.anyPossible(m_parent.m_edgeOpenMask))) - { - m_parent.m_edgeProcessList.push_back(var); - db->queueConstraintPropagation(&m_parent); - } - return true; -} - -bool ReachabilityConstraint::propagate(IVariableDatabase* db) -{ - vxy_assert(!m_edgeChangeFailure); - ValueGuard guardEdgeChangeFailure(m_edgeChangeFailure, false); - - // Process edges first, adding/removing edges from the Min/Max graph, respectively - for (VarID edgeVar : m_edgeProcessList) - { - updateGraphsForEdgeChange(db, edgeVar); - if (m_edgeChangeFailure) - { - return false; - } - } - m_edgeProcessList.clear(); - - #if REACHABILITY_USE_RAMAL_REPS - // Batch-update reachability for all edge changes. This will trigger OnReachabilityChanged callbacks. - { - vxy_assert(!m_edgeChangeFailure); - ValueGuard guardEdgeChange(m_inEdgeChange, true); - ValueGuard guardDb(m_edgeChangeDb, db); - - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - it->second.maxReachability->refresh(); - if (m_edgeChangeFailure) - { - return false; - } - - it->second.minReachability->refresh(); - if (m_edgeChangeFailure) - { - return false; - } - } - } - #endif - - vxy_assert(!m_edgeChangeFailure); - - // Now that reachability info is up to date, process vertices - for (VarID vertexVar : m_vertexProcessList) - { - if (!processVertexVariableChange(db, vertexVar)) - { - return false; - } - } - m_vertexProcessList.clear(); - - return true; -} - -bool ReachabilityConstraint::processVertexVariableChange(IVariableDatabase* db, VarID variable) -{ - if (!db->anyPossible(variable, m_sourceMask)) - { - if (!removeSource(db, variable)) - { - return false; - } - } - - // If this now requires reachability... - if (!db->anyPossible(variable, m_notReachableMask)) - { - int vertex = m_variableToSourceVertexIndex[variable]; - - int numReachableSources = 0; - VarID lastReachableSource = VarID::INVALID; - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - if (it->second.maxReachability->isReachable(vertex)) - { - ++numReachableSources; - lastReachableSource = it->first; - if (numReachableSources > 1) - { - break; - } - } - } - - // If not reachable by any source, then fail - if (numReachableSources == 0) - { - bool success = db->constrainToValues(variable, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); }); - vxy_assert(!success); - return false; - } - // If reachable by a single potential source, that single source must be definite - else if (numReachableSources == 1) - { - if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&](auto params) { return explainRequiredSource(params); })) - { - return false; - } - } - } - - return true; -} - -void ReachabilityConstraint::addSource(VarID source) -{ - int vertex = m_variableToSourceVertexIndex[source]; - - #if REACHABILITY_USE_RAMAL_REPS - auto minReachable = make_shared(m_minGraph, USE_RAMAL_REPS_BATCHING, true, false); - auto maxReachable = make_shared(m_maxGraph, USE_RAMAL_REPS_BATCHING, true, false); - #else - auto minReachable = make_shared(m_minGraph); - auto maxReachable = make_shared(m_maxGraph); - #endif - - minReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); - maxReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); - - // Listen for when reachability changes on the conservative/optimistic graphs - EventListenerHandle minDelHandle = minReachable->onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) - { - if (!m_backtracking && !m_explainingSourceRequirement) - { - vxy_assert(isReachable); - onReachabilityChanged(changedVertex, source, true); - } - }); - - EventListenerHandle maxDelHandle = maxReachable->onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) - { - if (!m_backtracking && !m_explainingSourceRequirement) - { - vxy_assert(!isReachable); - onReachabilityChanged(changedVertex, source, false); - } - }); - - m_reachabilitySources[source] = {minReachable, maxReachable, minDelHandle, maxDelHandle}; -} - -bool ReachabilityConstraint::removeSource(IVariableDatabase* db, VarID source) -{ - if (m_reachabilitySources.find(source) == m_reachabilitySources.end()) - { - return true; - } - - vxy_assert(m_backtrackData.empty() || m_backtrackData.back().level <= db->getDecisionLevel()); - if (m_backtrackData.empty() || m_backtrackData.back().level != db->getDecisionLevel()) - { - m_backtrackData.push_back({db->getDecisionLevel()}); - } - - vxy_assert(m_backtrackData.back().level == db->getDecisionLevel()); - vxy_sanity(!contains(m_backtrackData.back().reachabilitySourcesRemoved.begin(), m_backtrackData.back().reachabilitySourcesRemoved.end(), source)); - m_backtrackData.back().reachabilitySourcesRemoved.push_back(source); - - ReachabilitySourceData sourceData = m_reachabilitySources[source]; - m_reachabilitySources.erase(source); - - sourceData.minReachability->onReachabilityChanged.remove(sourceData.minReachabilityChangedHandle); - sourceData.maxReachability->onReachabilityChanged.remove(sourceData.maxReachabilityChangedHandle); - - int sourceVertex = m_variableToSourceVertexIndex[source]; - - // Look through all vertices that were reachable from this old source. If any are now definitely unreachable, - // mark them as such. - bool failure = false; - auto checkReachability = [&](int vertex, int parent) - { - if (sourceData.maxReachability->isReachable(vertex)) - { - // This vertex is no longer reachable from the removed source, so might be definitely unreachable now - VarID vertexVar = m_sourceGraphData->get(vertex); - if (vertexVar.isValid() && db->anyPossible(vertexVar, m_requireReachableMask)) - { - EReachabilityDetermination determination = determineReachability(db, vertex); - - if (determination == EReachabilityDetermination::DefinitelyUnreachable) - { - sanityCheckUnreachable(db, vertex); - if (!db->constrainToValues(vertexVar, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) - { - failure = true; - return ETopologySearchResponse::Abort; - } - } - else if (determination == EReachabilityDetermination::PossiblyReachable && !db->anyPossible(vertexVar, m_notReachableMask)) - { - // The vertex is marked definitely reachable, but only possibly reachable in the graph. - // If there is only a single potential source that reaches this vertex, then it must now definitely be a source. - VarID lastReachableSource = VarID::INVALID; - int numReachableSources = 0; - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - if (it->second.maxReachability->isReachable(vertex)) - { - ++numReachableSources; - lastReachableSource = it->first; - if (numReachableSources > 1) - { - break; - } - } - } - - vxy_assert(numReachableSources >= 1); - if (numReachableSources == 1) - { - if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); })) - { - failure = true; - return ETopologySearchResponse::Abort; - } - } - } - } - return ETopologySearchResponse::Continue; - } - else - { - return ETopologySearchResponse::Skip; - } - }; - m_dfs.search(*m_sourceGraph.get(), sourceVertex, checkReachability); - - return !failure; -} - -void ReachabilityConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable) -{ - vxy_assert(!m_inEdgeChange); - vxy_assert(!m_edgeChangeFailure); - vxy_assert(m_edgeChangeDb == nullptr); - - ValueGuard guardEdgeChange(m_inEdgeChange, true); - ValueGuard guardDb(m_edgeChangeDb, db); - - int nodeIndex = m_variableToSourceEdgeIndex[variable]; - - // - // If an edge becomes definitely unblocked, add it to the min graph. - // If an edge becomes definitely blocked, remove it from the max graph. - // - // Sources listen to edge changes and call OnReachabilityChanged for any nodes that become (un)reachable from - // that source. The variables will attempt to be constrained based on their (un)reachability; if they cannot, - // then the bEdgeChangeFailure flag is set. - // - - if (db->anyPossible(variable, m_edgeOpenMask) && !db->anyPossible(variable, m_edgeBlockedMask)) - { - int from, to; - bool bidirectional; - m_edgeGraph->getSourceEdgeForVertex(nodeIndex, from, to, bidirectional); - if (!m_minGraph->hasEdge(from, to)) - { - m_minGraph->addEdge(from, to, db->getTimestamp()); - if (bidirectional) - { - m_minGraph->addEdge(to, from, db->getTimestamp()); - } - } - } - else if (db->anyPossible(variable, m_edgeBlockedMask) && !db->anyPossible(variable, m_edgeOpenMask)) - { - int from, to; - bool bidirectional; - m_edgeGraph->getSourceEdgeForVertex(nodeIndex, from, to, bidirectional); - - if (m_maxGraph->hasEdge(from, to)) - { - // Remove from the explanation graph first, so that we can sync to correct time. - m_explanationGraph->removeEdge(from, to, db->getTimestamp()); - if (bidirectional) - { - m_explanationGraph->removeEdge(to, from, db->getTimestamp()); - } - - m_maxGraph->removeEdge(from, to, db->getTimestamp()); - if (bidirectional) - { - m_maxGraph->removeEdge(to, from, db->getTimestamp()); - } - } - } -} - -void ReachabilityConstraint::onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) -{ - vxy_assert(!m_backtracking); - vxy_assert(!m_explainingSourceRequirement); - - vxy_assert(m_edgeChangeDb != nullptr); - vxy_assert(m_inEdgeChange); - - if (m_edgeChangeFailure) - { - // We already failed - avoid further failures that could confuse the conflict analyzer - return; - } - - if (inMinGraph) - { - // See if this vertex is definitely reachable by any source now - if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyReachable) - { - VarID var = m_sourceGraphData->get(vertexIndex); - if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_requireReachableMask, this)) - { - m_edgeChangeFailure = true; - } - } - } - else - { - // vertexIndex became unreachable in the max graph - if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyUnreachable) - { - VarID var = m_sourceGraphData->get(vertexIndex); - sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); - - if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) - { - m_edgeChangeFailure = true; - } - } - } -} - -void ReachabilityConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) -{ - vxy_assert(!m_edgeChangeFailure); - m_edgeProcessList.clear(); - m_vertexProcessList.clear(); - - ValueGuard backtrackGuard(m_backtracking, true); - - while (!m_backtrackData.empty() && m_backtrackData.back().level > level) - { - for (VarID sourceVar : m_backtrackData.back().reachabilitySourcesRemoved) - { - addSource(sourceVar); - } - m_backtrackData.pop_back(); - } - - // Backtrack any edges added/removed after this point - m_minGraph->backtrackUntil(db->getTimestamp()); - m_maxGraph->backtrackUntil(db->getTimestamp()); - m_explanationGraph->backtrackUntil(db->getTimestamp()); - - #if REACHABILITY_USE_RAMAL_REPS - // Batch-update reachability for all edge changes - { - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - it->second.maxReachability->refresh(); - it->second.minReachability->refresh(); - } - vxy_assert(!m_edgeChangeFailure); - } - #endif -} - -ReachabilityConstraint::EReachabilityDetermination ReachabilityConstraint::determineReachability(const IVariableDatabase* db, int vertexIndex) -{ - VarID vertexVar = m_sourceGraphData->get(vertexIndex); - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - if (it->first == vertexVar) - { - // Don't treat reachability as reflective. If a vertex is marked both needing reachability and is a - // reachability source, it needs to be reachable from a DIFFERENT source. - continue; - } - - if (it->second.minReachability->isReachable(vertexIndex)) - { - if (definitelyIsSource(db, it->first)) - { - return EReachabilityDetermination::DefinitelyReachable; - } - else - { - return EReachabilityDetermination::PossiblyReachable; - } - } - else if (it->second.maxReachability->isReachable(vertexIndex)) - { - return EReachabilityDetermination::PossiblyReachable; - } - } - - return EReachabilityDetermination::DefinitelyUnreachable; -} - -// Called whenever an edge is added or removed from the explanation graph, including during backtracking -void ReachabilityConstraint::onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to) -{ - // Keep the edge capacities in sync. - for (int i = get<0>(m_flowGraphLookup[from]); i < get<1>(m_flowGraphLookup[from]); ++i) - { - if (m_flowGraphEdges[i].endVertex == to) - { - m_flowGraphEdges[i].capacity = edgeWasAdded ? OPEN_EDGE_FLOW : CLOSED_EDGE_FLOW; - return; - } - } - vxy_fail(); -} - -vector ReachabilityConstraint::explainNoReachability(const NarrowingExplanationParams& params) const -{ - // return FSolverVariableDatabase::DefaultExplainer(Params); - - vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); - - auto db = params.database; - int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; - - vector lits; - lits.push_back(Literal(params.propagatedVariable, m_notReachableMask)); - - static hash_set edgeVarsRecorded; - edgeVarsRecorded.clear(); - - ValueSet visited; - visited.init(m_initialPotentialSources.size(), false); - - // For each source that could possibly exist... - for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) - { - if (visited[potentialSrcIndex]) - { - continue; - } - visited[potentialSrcIndex] = true; - - VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; - - int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; - if (sourceVertex == conflictVertex) - { - // Reachability sources cannot provide reachability to themselves - continue; - } - - // If this is currently a potential source... - if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) - { - // - // Find the minimum cut of edges that would make this reachable - // - - // Temporarily rewind the explanation graph to this time. - // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges - // will be the same state as when we processed the input variable. - - m_explanationGraph->rewindUntil(params.timestamp); - - vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); - - // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because - // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge - // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow - vector> cutEdges; - m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); - vxy_assert(!cutEdges.empty()); - - // Now that we've found the cut, bring the explanation graph back to current state. - m_explanationGraph->fastForward(); - - for (tuple& edge : cutEdges) - { - int edgeNode = m_edgeGraph->getVertexForSourceEdge(get<0>(edge), get<1>(edge)); - VarID edgeVar = m_edgeGraphData->get(edgeNode); - if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) - { - edgeVarsRecorded.insert(edgeVar); - - vxy_assert(!db->anyPossible(edgeVar, m_edgeOpenMask)); - lits.push_back(Literal(edgeVar, m_edgeOpenMask)); - } - } - - // For every other potential source, see if this graph also holds. It holds if the other source is on the - // same side as this source, hence would have to cross the same edge boundary. - for (int j = potentialSrcIndex + 1; j < m_initialPotentialSources.size(); ++j) - { - if (visited[j]) - { - continue; - } - - int vertex = m_variableToSourceVertexIndex.find(potentialSource)->second; - if (vertex == conflictVertex) - { - continue; - } - - if (db->getPotentialValues(m_initialPotentialSources[j]).anyPossible(m_sourceMask)) - { - if (!m_maxFlowAlgo.onSinkSide(vertex, m_flowGraphEdges, m_flowGraphLookup)) - { - visited[j] = true; - } - } - } - } - // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it - // was. - else - { - lits.push_back(Literal(potentialSource, m_sourceMask)); - } - } - - return lits; -} - -vector ReachabilityConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) -{ - vxy_assert(!m_explainingSourceRequirement); - ValueGuard guard(m_explainingSourceRequirement, true); - - VarID sourceVar = params.propagatedVariable; - int sourceVertex = m_variableToSourceVertexIndex[sourceVar]; - - auto db = params.database; - - vector lits; - lits.push_back(Literal(sourceVar, m_sourceMask)); - - m_maxGraph->rewindUntil(params.timestamp); - - #if REACHABILITY_USE_RAMAL_REPS - { - // Batch-update to rewound graph state - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - it->second.maxReachability->refresh(); - } - } - #endif - - // Create any sources that might've gotten removed since this happened. - // (We'll clean them up after) - vector tempSources; - for (VarID potentialSource : m_initialPotentialSources) - { - if (db->anyPossible(potentialSource, m_sourceMask)) - { - if (m_reachabilitySources.find(potentialSource) == m_reachabilitySources.end()) - { - tempSources.push_back(potentialSource); - - int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; - - ReachabilitySourceData data; - #if REACHABILITY_USE_RAMAL_REPS - data.maxReachability = make_shared(m_maxGraph, false, false, false); - #else - Data.maxReachability = make_shared(MaxGraph); - #endif - data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); - - m_reachabilitySources[potentialSource] = data; - } - } - else if (potentialSource != sourceVar) - { - lits.push_back(Literal(potentialSource, m_sourceMask)); - } - } - - int removedSourceLitIdx = -1; - if (removedSource.isValid()) - { - // This became a required source because RemovedSource was removed, and some definitely-reachable vertices were - // only reachable by this source. - vxy_assert(!db->anyPossible(removedSource, m_sourceMask)); - removedSourceLitIdx = indexOfPredicate(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == removedSource; }); - vxy_assert(removedSourceLitIdx >= 0); - } - - // - // This became a required source because some variable(s) were marked as required, and we are the only - // source that can reach them. Find those variables. - // - bool foundSupports = false; - auto& ourReachability = m_reachabilitySources[sourceVar].maxReachability; - auto searchCallback = [&](int vertex, int parent, int edgeIndex) - { - if (ourReachability->isReachable(vertex)) - { - VarID vertexVar = m_sourceGraphData->get(vertex); - if (!db->anyPossible(vertexVar, m_notReachableMask)) - { - bool reachableFromAnotherSource = false; - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) - { - reachableFromAnotherSource = true; - break; - } - } - - if (!reachableFromAnotherSource) - { - vxy_assert(m_reachabilitySources[sourceVar].maxReachability->isReachable(vertex)); - // make sure we don't add the same literal twice! - auto found = find_if(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == vertexVar; }); - if (found != lits.end()) - { - vxy_assert(found->variable == vertexVar); - found->values.include(m_notReachableMask); - } - else - { - lits.push_back(Literal(vertexVar, m_notReachableMask)); - } - foundSupports = true; - } - } - return ETopologySearchResponse::Continue; - } - else - { - return ETopologySearchResponse::Skip; - } - }; - m_dfs.search(*m_sourceGraph.get(), m_variableToSourceVertexIndex[sourceVar], searchCallback); - vxy_assert(foundSupports); - - for (VarID tempSource : tempSources) - { - m_reachabilitySources.erase(tempSource); - } - - m_maxGraph->fastForward(); - return lits; -} - - -void ReachabilityConstraint::sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex) -{ - #if SANITY_CHECKS - // For each source that could possibly exist... - for (VarID potentialSource : m_initialPotentialSources) - { - int sourceVertex = m_variableToSourceVertexIndex[potentialSource]; - // If this is currently a potential source... - if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) - { - vxy_assert(!TopologySearchAlgorithm::canReach(m_maxGraph, sourceVertex, vertexIndex)); - } - } - #endif -} - - -vector ReachabilityConstraint::getConstrainingVariables() const -{ - vector out; - for (int i = 0; i < m_sourceGraph->getNumVertices(); ++i) - { - VarID var = m_sourceGraphData->get(i); - if (var.isValid()) - { - out.push_back(var); - } - } - - for (int i = 0; i < m_edgeGraph->getNumVertices(); ++i) - { - VarID var = m_edgeGraphData->get(i); - if (var.isValid()) - { - out.push_back(var); - } - } - - return out; } -bool ReachabilityConstraint::checkConflicting(IVariableDatabase* db) const +bool ReachabilityConstraint::isValidDistance(int dist) const { - // TODO - return false; + return dist < INT_MAX; } #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp new file mode 100644 index 0000000..112ced1 --- /dev/null +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -0,0 +1,72 @@ +// Copyright Proletariat, Inc. All Rights Reserved. +#include "constraints/ReachabilityConstraint.h" + +#include "variable/IVariableDatabase.h" +#include "topology/GraphRelations.h" + +using namespace Vertexy; + +#define SANITY_CHECKS VERTEXY_SANITY_CHECKS + +static constexpr int OPEN_EDGE_FLOW = INT_MAX >> 1; +static constexpr int CLOSED_EDGE_FLOW = 1; +static constexpr bool USE_RAMAL_REPS_BATCHING = true; + +ReachabilityConstraint* ReachabilityConstraint::ReachabilityFactory::construct( + const ConstraintFactoryParams& params, + const shared_ptr>& vertexData, + const vector& sourceValues, + const vector& needReachableValues, + const shared_ptr>& edgeData, + const vector& edgeBlockedValues) +{ + // Get an example graph variable + VarID graphVar; + for (int i = 0; i < vertexData->getSource()->getNumVertices(); ++i) + { + if (vertexData->get(i).isValid()) + { + graphVar = vertexData->get(i); + break; + } + } + vxy_assert(graphVar.isValid()); + + // Get an example edge variable + VarID edgeVar; + for (int i = 0; i < edgeData->getSource()->getNumVertices(); ++i) + { + if (edgeData->get(i).isValid()) + { + edgeVar = edgeData->get(i); + break; + } + } + vxy_assert(edgeVar.isValid()); + + ValueSet sourceMask = params.valuesToInternal(graphVar, sourceValues); + ValueSet needReachableMask = params.valuesToInternal(graphVar, needReachableValues); + ValueSet edgeBlockedMask = params.valuesToInternal(edgeVar, edgeBlockedValues); + + return new ReachabilityConstraint(params, vertexData, sourceMask, needReachableMask, edgeData, edgeBlockedMask); +} + +ReachabilityConstraint::ReachabilityConstraint( + const ConstraintFactoryParams& params, + const shared_ptr>& sourceGraphData, + const ValueSet& sourceMask, + const ValueSet& requireReachableMask, + const shared_ptr>& edgeGraphData, + const ValueSet& edgeBlockedMask) + : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask, false) +{ +} + +bool ReachabilityConstraint::isValidDistance(int dist) const +{ + //TODO + return true; +} + + +#undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp new file mode 100644 index 0000000..a70ce3b --- /dev/null +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -0,0 +1,1147 @@ +// Copyright Proletariat, Inc. All Rights Reserved. +#include "constraints/TopologySearchConstraint.h" + +#include "variable/IVariableDatabase.h" +#include "topology/GraphRelations.h" + +using namespace Vertexy; + +#define SANITY_CHECKS VERTEXY_SANITY_CHECKS + +static constexpr int OPEN_EDGE_FLOW = INT_MAX >> 1; +static constexpr int CLOSED_EDGE_FLOW = 1; +static constexpr bool USE_RAMAL_REPS_BATCHING = true; + +ITopologySearchConstraint::ITopologySearchConstraint( + const ConstraintFactoryParams& params, + const shared_ptr>& sourceGraphData, + const ValueSet& sourceMask, + const ValueSet& requireReachableMask, + const shared_ptr>& edgeGraphData, + const ValueSet& edgeBlockedMask, + bool ramalRepsReportDistance) + : IBacktrackingSolverConstraint(params) //ApplyGraphRelation(Params, SourceGraphData, EdgeGraphData)) + , m_edgeWatcher(*this) + , m_sourceGraphData(sourceGraphData) + , m_sourceGraph(sourceGraphData->getSource()) + , m_edgeGraphData(edgeGraphData) + , m_edgeGraph(edgeGraphData->getSource()->getImplementation()) + , m_minGraph(make_shared()) + , m_maxGraph(make_shared()) + , m_explanationGraph(make_shared()) + , m_sourceMask(sourceMask) + , m_requireReachableMask(requireReachableMask) + , m_edgeBlockedMask(edgeBlockedMask) + , m_RamalRepsReportDistance(ramalRepsReportDistance) +{ + m_notSourceMask = sourceMask.inverted(); + m_notReachableMask = requireReachableMask.inverted(); + m_edgeOpenMask = edgeBlockedMask.inverted(); + + for (int i = 0; i < m_sourceGraph->getNumVertices(); ++i) + { + VarID var = sourceGraphData->get(i); + if (var.isValid()) + { + m_variableToSourceVertexIndex[var] = i; + } + } + + vxy_assert(m_edgeGraph->getSource() == m_sourceGraph); + for (int i = 0; i < m_edgeGraph->getNumVertices(); ++i) + { + VarID var = edgeGraphData->get(i); + if (var.isValid()) + { + m_variableToSourceEdgeIndex[var] = i; + } + } +} + +bool ITopologySearchConstraint::getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const +{ + // First search through all provided literals to find the minimum graph vertex. + // Note: some vertices will be edges, some will be tiles. + int minGraphVertex = m_sourceGraph->getNumVertices(); + + vector vertices; + vector isEdgeNode; + + vertices.reserve(literals.size()); + isEdgeNode.reserve(literals.size()); + + for (auto& lit : literals) + { + int graphVertex = m_sourceGraphData->indexOf(lit.variable); + if (graphVertex < 0) + { + int edgeNode = m_edgeGraphData->indexOf(lit.variable); + vxy_assert(edgeNode >= 0); + + vertices.push_back(edgeNode); + isEdgeNode.push_back(true); + + int edgeFrom, edgeTo; + bool bidirectional; + m_edgeGraph->getSourceEdgeForVertex(edgeNode, edgeFrom, edgeTo, bidirectional); + + graphVertex = min(edgeFrom, edgeTo); + } + else + { + vertices.push_back(graphVertex); + isEdgeNode.push_back(false); + } + vxy_assert(graphVertex >= 0); + minGraphVertex = min(graphVertex, minGraphVertex); + } + + // We always provide relations in terms of the source graph. + // Relations are anchored to the minimum vertex ID found (maps to top-leftmost in a grid graph). + outRelations.reset(m_sourceGraph, minGraphVertex); + + // create the relations! + outRelations.reserve(literals.size()); + for (int i = 0; i != literals.size(); ++i) + { + bool isEdge = isEdgeNode[i]; + + if (!isEdge) + { + int vertex = vertices[i]; + if (vertex != minGraphVertex) + { + TopologyLink link; + if (!m_sourceGraph->getTopologyLink(minGraphVertex, vertex, link)) + { + // no path specified in graph + // TODO: assert? + outRelations.clear(); + return false; + } + + auto linkRel = make_shared>(m_sourceGraphData, link); + outRelations.addRelation(m_sourceGraphData->get(vertex), linkRel); + } + else + { + auto selfRel = make_shared>(m_sourceGraphData); + outRelations.addRelation(m_sourceGraphData->get(vertex), selfRel); + } + } + else + { + int edgeNode = vertices[i]; + + // edge variable: get the source node if the edge + int edgeOrigin, edgeDestination; + bool bidirectional; + m_edgeGraph->getSourceEdgeForVertex(edgeNode, edgeOrigin, edgeDestination, bidirectional); + + int nodeEdgeIndex = -1; + for (int e = 0; e < m_sourceGraph->getNumOutgoing(edgeOrigin); ++e) + { + if (int testDest; m_sourceGraph->getOutgoingDestination(edgeOrigin, e, testDest) && testDest == edgeDestination) + { + nodeEdgeIndex = e; + break; + } + } + if (nodeEdgeIndex < 0) + { + vxy_assert_msg(false, "Edge node %d has source graph node origin %d, but can't find edgeIndex in source graph!", edgeNode, edgeOrigin); + outRelations.clear(); + return false; + } + + auto nodeToEdgeNodeRel = make_shared>(m_sourceGraph, m_edgeGraph, nodeEdgeIndex); + auto nodeToEdgeVarRel = nodeToEdgeNodeRel->map(make_shared>(m_edgeGraphData)); + + if (edgeOrigin != minGraphVertex) + { + TopologyLink link; + if (!m_sourceGraph->getTopologyLink(minGraphVertex, edgeOrigin, link)) + { + vxy_assert_msg(false, "expected link between vertices %d -> %d", minGraphVertex, edgeOrigin); + outRelations.clear(); + return false; + } + + auto linkRel = make_shared(m_sourceGraph, link); + outRelations.addRelation(m_edgeGraphData->get(edgeNode), linkRel->map(nodeToEdgeVarRel)); + } + else + { + outRelations.addRelation(m_edgeGraphData->get(edgeNode), nodeToEdgeVarRel); + } + } + } + + vxy_assert(outRelations.relations.size() == literals.size()); + return true; +} + +bool ITopologySearchConstraint::initialize(IVariableDatabase* db) +{ + // Add all vertices to min/max graphs + for (int vertexIndex = 0; vertexIndex < m_sourceGraph->getNumVertices(); ++vertexIndex) + { + VarID vertexVar = m_sourceGraphData->get(vertexIndex); + + int addedIdx = m_maxGraph->addVertex(); + vxy_assert(addedIdx == vertexIndex); + + addedIdx = m_minGraph->addVertex(); + vxy_assert(addedIdx == vertexIndex); + + addedIdx = m_explanationGraph->addVertex(); + vxy_assert(addedIdx == vertexIndex); + + if (vertexVar.isValid()) + { + m_vertexWatchHandles[vertexVar] = db->addVariableWatch(vertexVar, EVariableWatchType::WatchModification, this); + } + } + + hash_map> edgeCapacities; + + m_totalNumEdges = 0; + + // Add all definitely-open edges to the min graph, and all possibly-open edges to the max graph + m_reachabilityEdgeLookup.resize(m_sourceGraph->getNumVertices()); + for (int sourceVertex = 0; sourceVertex < m_sourceGraph->getNumVertices(); ++sourceVertex) + { + auto foundVertexEdges = edgeCapacities.find(sourceVertex); + if (foundVertexEdges == edgeCapacities.end()) + { + foundVertexEdges = edgeCapacities.insert(sourceVertex).first; + } + + m_reachabilityEdgeLookup[sourceVertex].reserve(m_sourceGraph->getNumOutgoing(sourceVertex)); + for (int edgeIndex = 0; edgeIndex < m_sourceGraph->getNumOutgoing(sourceVertex); ++edgeIndex) + { + int destVertex; + if (m_sourceGraph->getOutgoingDestination(sourceVertex, edgeIndex, destVertex)) + { + m_reachabilityEdgeLookup[sourceVertex].push_back(make_tuple(destVertex, m_totalNumEdges)); + ++m_totalNumEdges; + + int edgeNode = m_edgeGraph->getVertexForSourceEdge(sourceVertex, destVertex); + vxy_assert(edgeNode >= 0); + VarID edgeVar = m_edgeGraphData->get(edgeNode); + + bool edgeIsClosed = true; + if (edgeVar.isValid()) + { + if (definitelyOpenEdge(db, edgeVar)) + { + edgeIsClosed = false; + + m_minGraph->initEdge(sourceVertex, destVertex); + m_maxGraph->initEdge(sourceVertex, destVertex); + m_explanationGraph->initEdge(sourceVertex, destVertex); + } + else if (possiblyOpenEdge(db, edgeVar)) + { + edgeIsClosed = false; + + if (m_edgeWatchHandles.find(edgeVar) == m_edgeWatchHandles.end()) + { + m_edgeWatchHandles[edgeVar] = db->addVariableWatch(edgeVar, EVariableWatchType::WatchModification, &m_edgeWatcher); + } + m_maxGraph->initEdge(sourceVertex, destVertex); + m_explanationGraph->initEdge(sourceVertex, destVertex); + } + } + else + { + edgeIsClosed = false; + + // No variable for this edge, so should always exist + m_minGraph->initEdge(sourceVertex, destVertex); + m_maxGraph->initEdge(sourceVertex, destVertex); + m_explanationGraph->initEdge(sourceVertex, destVertex); + } + + foundVertexEdges->second[destVertex] = edgeIsClosed ? CLOSED_EDGE_FLOW : OPEN_EDGE_FLOW; + + if (!m_sourceGraph->hasEdge(destVertex, sourceVertex)) + { + auto foundDestVertexEdges = edgeCapacities.find(destVertex); + if (foundDestVertexEdges == edgeCapacities.end()) + { + foundDestVertexEdges = edgeCapacities.insert(destVertex).first; + } + foundDestVertexEdges->second[sourceVertex] = 0; + } + } + } + } + + // Build flow graph edges flat list and lookup map + m_flowGraphEdges.reserve(m_totalNumEdges); + m_flowGraphLookup.reserve(m_sourceGraph->getNumVertices()); + for (int sourceVertex = 0; sourceVertex < m_sourceGraph->getNumVertices(); ++sourceVertex) + { + m_flowGraphLookup.emplace_back(m_flowGraphEdges.size(), m_flowGraphEdges.size() + edgeCapacities[sourceVertex].size()); + for (auto it = edgeCapacities[sourceVertex].begin(), itEnd = edgeCapacities[sourceVertex].end(); it != itEnd; ++it) + { + m_flowGraphEdges.push_back({it->first, -1, it->second}); + } + } + + // Fill in the ReverseEdgeIndex for each vertex + for (int sourceVertex = 0; sourceVertex < m_sourceGraph->getNumVertices(); ++sourceVertex) + { + for (int i = get<0>(m_flowGraphLookup[sourceVertex]); i < get<1>(m_flowGraphLookup[sourceVertex]); ++i) + { + if (m_flowGraphEdges[i].reverseEdgeIndex < 0) + { + int destVertex = m_flowGraphEdges[i].endVertex; + bool foundReverse = false; + for (int j = get<0>(m_flowGraphLookup[destVertex]); j < get<1>(m_flowGraphLookup[destVertex]); ++j) + { + if (m_flowGraphEdges[j].endVertex == sourceVertex) + { + m_flowGraphEdges[i].reverseEdgeIndex = j; + vxy_assert(m_flowGraphEdges[j].reverseEdgeIndex < 0); + m_flowGraphEdges[j].reverseEdgeIndex = i; + + foundReverse = true; + break; + } + } + vxy_assert(foundReverse); + } + } + } + + // Register for callback when edges are added/removed from ExplanationGraph, in order to update capacities + m_explanationGraph->getEdgeChangeListener().add([&](bool edgeWasAdded, int from, int to) + { + onExplanationGraphEdgeChange(edgeWasAdded, from, to); + }); + + // Create reachability structures for all variables that are possibly reachability sources + for (int vertex = 0; vertex < m_sourceGraph->getNumVertices(); ++vertex) + { + VarID vertexVar = m_sourceGraphData->get(vertex); + if (vertexVar.isValid() && possiblyIsSource(db, vertexVar)) + { + addSource(vertexVar); + m_initialPotentialSources.push_back(vertexVar); + } + } + + // Constrain all variables that are definitely reachable by any definite reachability source to reachable + // Constrain all variables that are not reachable by all potential reachability sources to unreachable + for (int vertex = 0; vertex < m_sourceGraph->getNumVertices(); ++vertex) + { + VarID vertexVar = m_sourceGraphData->get(vertex); + if (vertexVar.isValid()) + { + EReachabilityDetermination determination = determineReachability(db, vertex); + + if (determination == EReachabilityDetermination::DefinitelyUnreachable) + { + if (!db->constrainToValues(vertexVar, m_notReachableMask, this)) + { + return false; + } + } + else if (determination == EReachabilityDetermination::DefinitelyReachable) + { + if (!db->constrainToValues(vertexVar, m_requireReachableMask, this)) + { + return false; + } + } + } + } + + return true; +} + +void ITopologySearchConstraint::reset(IVariableDatabase* db) +{ + for (auto it = m_vertexWatchHandles.begin(), itEnd = m_vertexWatchHandles.end(); it != itEnd; ++it) + { + db->removeVariableWatch(it->first, it->second, this); + } + m_vertexWatchHandles.clear(); + + for (auto it = m_edgeWatchHandles.begin(), itEnd = m_edgeWatchHandles.end(); it != itEnd; ++it) + { + db->removeVariableWatch(it->first, it->second, &m_edgeWatcher); + } + m_edgeWatchHandles.clear(); +} + +bool ITopologySearchConstraint::onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& prevValue, bool& removeWatch) +{ + const ValueSet& newValue = db->getPotentialValues(variable); + + if ((prevValue.anyPossible(m_sourceMask) && !newValue.anyPossible(m_sourceMask)) || + (prevValue.anyPossible(m_notReachableMask) && !newValue.anyPossible(m_notReachableMask))) + { + if (!contains(m_vertexProcessList.begin(), m_vertexProcessList.end(), variable)) + { + m_vertexProcessList.push_back(variable); + } + db->queueConstraintPropagation(this); + } + return true; +} + +bool ITopologySearchConstraint::EdgeWatcher::onVariableNarrowed(IVariableDatabase* db, VarID var, const ValueSet& prevValue, bool& removeHandle) +{ + const ValueSet& newValue = db->getPotentialValues(var); + if ((prevValue.anyPossible(m_parent.m_edgeBlockedMask) && !newValue.anyPossible(m_parent.m_edgeBlockedMask)) || + (prevValue.anyPossible(m_parent.m_edgeOpenMask) && !newValue.anyPossible(m_parent.m_edgeOpenMask))) + { + m_parent.m_edgeProcessList.push_back(var); + db->queueConstraintPropagation(&m_parent); + } + return true; +} + +bool ITopologySearchConstraint::propagate(IVariableDatabase* db) +{ + vxy_assert(!m_edgeChangeFailure); + ValueGuard guardEdgeChangeFailure(m_edgeChangeFailure, false); + + // Process edges first, adding/removing edges from the Min/Max graph, respectively + for (VarID edgeVar : m_edgeProcessList) + { + updateGraphsForEdgeChange(db, edgeVar); + if (m_edgeChangeFailure) + { + return false; + } + } + m_edgeProcessList.clear(); + + #if REACHABILITY_USE_RAMAL_REPS + // Batch-update reachability for all edge changes. This will trigger OnReachabilityChanged callbacks. + { + vxy_assert(!m_edgeChangeFailure); + ValueGuard guardEdgeChange(m_inEdgeChange, true); + ValueGuard guardDb(m_edgeChangeDb, db); + + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + it->second.maxReachability->refresh(); + if (m_edgeChangeFailure) + { + return false; + } + + it->second.minReachability->refresh(); + if (m_edgeChangeFailure) + { + return false; + } + } + } + #endif + + vxy_assert(!m_edgeChangeFailure); + + // Now that reachability info is up to date, process vertices + for (VarID vertexVar : m_vertexProcessList) + { + if (!processVertexVariableChange(db, vertexVar)) + { + return false; + } + } + m_vertexProcessList.clear(); + + return true; +} + +bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* db, VarID variable) +{ + if (!db->anyPossible(variable, m_sourceMask)) + { + if (!removeSource(db, variable)) + { + return false; + } + } + + // If this now requires reachability... + if (!db->anyPossible(variable, m_notReachableMask)) //impossible that it's not reachable + { + int vertex = m_variableToSourceVertexIndex[variable]; + + int numReachableSources = 0; + VarID lastReachableSource = VarID::INVALID; + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + if (it->second.maxReachability->isReachable(vertex) && isValidDistance(it->second.maxReachability->getDistance(vertex))) + { + ++numReachableSources; //should only be incremented if within given range + lastReachableSource = it->first; + if (numReachableSources > 1) + { + break; + } + } + } + + // If not reachable by any source, then fail + if (numReachableSources == 0) + { + bool success = db->constrainToValues(variable, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); }); + vxy_assert(!success); + return false; + } + // If reachable by a single potential source, that single source must be definite + else if (numReachableSources == 1) + { + if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&](auto params) { return explainRequiredSource(params); })) + { + return false; + } + } + } + + return true; +} + +void ITopologySearchConstraint::addSource(VarID source) +{ + int vertex = m_variableToSourceVertexIndex[source]; + + #if REACHABILITY_USE_RAMAL_REPS + auto minReachable = make_shared(m_minGraph, USE_RAMAL_REPS_BATCHING, !m_RamalRepsReportDistance, m_RamalRepsReportDistance); + auto maxReachable = make_shared(m_maxGraph, USE_RAMAL_REPS_BATCHING, !m_RamalRepsReportDistance, m_RamalRepsReportDistance); + #else + auto minReachable = make_shared(m_minGraph); + auto maxReachable = make_shared(m_maxGraph); + #endif + + minReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); + maxReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); + + if (!m_RamalRepsReportDistance) + { + // Listen for when reachability changes on the conservative/optimistic graphs + EventListenerHandle minDelHandle = minReachable->onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) + { + if (!m_backtracking && !m_explainingSourceRequirement) + { + vxy_assert(isReachable); + onReachabilityChanged(changedVertex, source, true); + } + }); + + EventListenerHandle maxDelHandle = maxReachable->onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) + { + if (!m_backtracking && !m_explainingSourceRequirement) + { + vxy_assert(!isReachable); + onReachabilityChanged(changedVertex, source, false); + } + }); + + m_reachabilitySources[source] = { minReachable, maxReachable, minDelHandle, maxDelHandle }; + } + else + { + EventListenerHandle minDistHandle = minReachable->onDistanceChanged.add([this, source](int changedVertex, int distance) + { + if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(distance)) + { + onDistanceChanged(changedVertex, source, true); + } + }); + + EventListenerHandle maxDistHandle = maxReachable->onDistanceChanged.add([this, source](int changedVertex, int distance) + { + if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(distance)) + { + onDistanceChanged(changedVertex, source, false); + } + }); + + m_reachabilitySources[source] = + { + minReachable, maxReachable, + INVALID_EVENT_LISTENER_HANDLE, INVALID_EVENT_LISTENER_HANDLE, + minDistHandle, maxDistHandle + }; + } + +} + +bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source) +{ + if (m_reachabilitySources.find(source) == m_reachabilitySources.end()) + { + return true; + } + + vxy_assert(m_backtrackData.empty() || m_backtrackData.back().level <= db->getDecisionLevel()); + if (m_backtrackData.empty() || m_backtrackData.back().level != db->getDecisionLevel()) + { + m_backtrackData.push_back({db->getDecisionLevel()}); + } + + vxy_assert(m_backtrackData.back().level == db->getDecisionLevel()); + vxy_sanity(!contains(m_backtrackData.back().reachabilitySourcesRemoved.begin(), m_backtrackData.back().reachabilitySourcesRemoved.end(), source)); + m_backtrackData.back().reachabilitySourcesRemoved.push_back(source); + + ReachabilitySourceData sourceData = m_reachabilitySources[source]; + m_reachabilitySources.erase(source); + + if (!m_RamalRepsReportDistance) + { + sourceData.minReachability->onReachabilityChanged.remove(sourceData.minReachabilityChangedHandle); + sourceData.maxReachability->onReachabilityChanged.remove(sourceData.maxReachabilityChangedHandle); + } + else + { + sourceData.minReachability->onDistanceChanged.remove(sourceData.minDistanceChangedHandle); + sourceData.maxReachability->onDistanceChanged.remove(sourceData.maxDistanceChangedHandle); + } + + int sourceVertex = m_variableToSourceVertexIndex[source]; + + // Look through all vertices that were reachable from this old source. If any are now definitely unreachable, + // mark them as such. + bool failure = false; + auto checkReachability = [&](int vertex, int parent) + { + if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(sourceData.maxReachability->getDistance(vertex))) + { + // This vertex is no longer reachable from the removed source, so might be definitely unreachable now + VarID vertexVar = m_sourceGraphData->get(vertex); + if (vertexVar.isValid() && db->anyPossible(vertexVar, m_requireReachableMask)) + { + EReachabilityDetermination determination = determineReachability(db, vertex); + + if (determination == EReachabilityDetermination::DefinitelyUnreachable) + { + sanityCheckUnreachable(db, vertex); + if (!db->constrainToValues(vertexVar, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) + { + failure = true; + return ETopologySearchResponse::Abort; + } + } + else if (determination == EReachabilityDetermination::PossiblyReachable && !db->anyPossible(vertexVar, m_notReachableMask)) + { + // The vertex is marked definitely reachable, but only possibly reachable in the graph. + // If there is only a single potential source that reaches this vertex, then it must now definitely be a source. + VarID lastReachableSource = VarID::INVALID; + int numReachableSources = 0; + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + if (it->second.maxReachability->isReachable(vertex) && isValidDistance(it->second.maxReachability->getDistance(vertex))) + { + ++numReachableSources; + lastReachableSource = it->first; + if (numReachableSources > 1) + { + break; + } + } + } + + vxy_assert(numReachableSources >= 1); + if (numReachableSources == 1) + { + if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); })) + { + failure = true; + return ETopologySearchResponse::Abort; + } + } + } + } + return ETopologySearchResponse::Continue; + } + else + { + return ETopologySearchResponse::Skip; + } + }; + //source was removed. find nodes that relied on this source, check to see if they are reachable by other sources + m_dfs.search(*m_sourceGraph.get(), sourceVertex, checkReachability); + + return !failure; +} + +void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable) +{ + vxy_assert(!m_inEdgeChange); + vxy_assert(!m_edgeChangeFailure); + vxy_assert(m_edgeChangeDb == nullptr); + + ValueGuard guardEdgeChange(m_inEdgeChange, true); + ValueGuard guardDb(m_edgeChangeDb, db); + + int nodeIndex = m_variableToSourceEdgeIndex[variable]; + + // + // If an edge becomes definitely unblocked, add it to the min graph. + // If an edge becomes definitely blocked, remove it from the max graph. + // + // Sources listen to edge changes and call OnReachabilityChanged for any nodes that become (un)reachable from + // that source. The variables will attempt to be constrained based on their (un)reachability; if they cannot, + // then the bEdgeChangeFailure flag is set. + // + + if (db->anyPossible(variable, m_edgeOpenMask) && !db->anyPossible(variable, m_edgeBlockedMask)) + { + int from, to; + bool bidirectional; + m_edgeGraph->getSourceEdgeForVertex(nodeIndex, from, to, bidirectional); + if (!m_minGraph->hasEdge(from, to)) + { + m_minGraph->addEdge(from, to, db->getTimestamp()); + if (bidirectional) + { + m_minGraph->addEdge(to, from, db->getTimestamp()); + } + } + } + else if (db->anyPossible(variable, m_edgeBlockedMask) && !db->anyPossible(variable, m_edgeOpenMask)) + { + int from, to; + bool bidirectional; + m_edgeGraph->getSourceEdgeForVertex(nodeIndex, from, to, bidirectional); + + if (m_maxGraph->hasEdge(from, to)) + { + // Remove from the explanation graph first, so that we can sync to correct time. + m_explanationGraph->removeEdge(from, to, db->getTimestamp()); + if (bidirectional) + { + m_explanationGraph->removeEdge(to, from, db->getTimestamp()); + } + + m_maxGraph->removeEdge(from, to, db->getTimestamp()); + if (bidirectional) + { + m_maxGraph->removeEdge(to, from, db->getTimestamp()); + } + } + } +} + +//are these really the same function? +void ITopologySearchConstraint::onDistanceChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) +{ + onReachabilityChanged(vertexIndex, sourceVar, inMinGraph); +} + +//determine if it's still within the required range +void ITopologySearchConstraint::onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) +{ + vxy_assert(!m_backtracking); + vxy_assert(!m_explainingSourceRequirement); + + vxy_assert(m_edgeChangeDb != nullptr); + vxy_assert(m_inEdgeChange); + + if (m_edgeChangeFailure) + { + // We already failed - avoid further failures that could confuse the conflict analyzer + return; + } + + if (inMinGraph) + { + // See if this vertex is definitely reachable by any source now + if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyReachable) + { + VarID var = m_sourceGraphData->get(vertexIndex); + if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_requireReachableMask, this)) + { + m_edgeChangeFailure = true; + } + } + } + else + { + // vertexIndex became unreachable in the max graph + if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyUnreachable) + { + VarID var = m_sourceGraphData->get(vertexIndex); + sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + + if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) + { + m_edgeChangeFailure = true; + } + } + } +} + +void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) +{ + vxy_assert(!m_edgeChangeFailure); + m_edgeProcessList.clear(); + m_vertexProcessList.clear(); + + ValueGuard backtrackGuard(m_backtracking, true); + + while (!m_backtrackData.empty() && m_backtrackData.back().level > level) + { + for (VarID sourceVar : m_backtrackData.back().reachabilitySourcesRemoved) + { + addSource(sourceVar); + } + m_backtrackData.pop_back(); + } + + // Backtrack any edges added/removed after this point + m_minGraph->backtrackUntil(db->getTimestamp()); + m_maxGraph->backtrackUntil(db->getTimestamp()); + m_explanationGraph->backtrackUntil(db->getTimestamp()); + + #if REACHABILITY_USE_RAMAL_REPS + // Batch-update reachability for all edge changes + { + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + it->second.maxReachability->refresh(); + it->second.minReachability->refresh(); + } + vxy_assert(!m_edgeChangeFailure); + } + #endif +} + +ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint::determineReachability(const IVariableDatabase* db, int vertexIndex) +{ + VarID vertexVar = m_sourceGraphData->get(vertexIndex); + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + if (it->first == vertexVar) + { + // Don't treat reachability as reflective. If a vertex is marked both needing reachability and is a + // reachability source, it needs to be reachable from a DIFFERENT source. + continue; + } + + if (it->second.minReachability->isReachable(vertexIndex) && isValidDistance(it->second.minReachability->getDistance(vertexIndex))) + { + if (definitelyIsSource(db, it->first)) + { + return EReachabilityDetermination::DefinitelyReachable; + } + else + { + return EReachabilityDetermination::PossiblyReachable; + } + } + else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(it->second.maxReachability->getDistance(vertexIndex))) + { + return EReachabilityDetermination::PossiblyReachable; + } + } + + return EReachabilityDetermination::DefinitelyUnreachable; +} + +// Called whenever an edge is added or removed from the explanation graph, including during backtracking +void ITopologySearchConstraint::onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to) +{ + // Keep the edge capacities in sync. + for (int i = get<0>(m_flowGraphLookup[from]); i < get<1>(m_flowGraphLookup[from]); ++i) + { + if (m_flowGraphEdges[i].endVertex == to) + { + m_flowGraphEdges[i].capacity = edgeWasAdded ? OPEN_EDGE_FLOW : CLOSED_EDGE_FLOW; + return; + } + } + vxy_fail(); +} + +vector ITopologySearchConstraint::explainNoReachability(const NarrowingExplanationParams& params) const +{ + // return FSolverVariableDatabase::DefaultExplainer(Params); + + vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); + + auto db = params.database; + int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; + + vector lits; + lits.push_back(Literal(params.propagatedVariable, m_notReachableMask)); + + static hash_set edgeVarsRecorded; + edgeVarsRecorded.clear(); + + ValueSet visited; + visited.init(m_initialPotentialSources.size(), false); + + // For each source that could possibly exist... + for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) + { + if (visited[potentialSrcIndex]) + { + continue; + } + visited[potentialSrcIndex] = true; + + VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; + + int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (sourceVertex == conflictVertex) + { + // Reachability sources cannot provide reachability to themselves + continue; + } + + // If this is currently a potential source... + if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) + { + // + // Find the minimum cut of edges that would make this reachable + // + + // Temporarily rewind the explanation graph to this time. + // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges + // will be the same state as when we processed the input variable. + + m_explanationGraph->rewindUntil(params.timestamp); + + vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); + + // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because + // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge + // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow + vector> cutEdges; + m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); + vxy_assert(!cutEdges.empty()); + + // Now that we've found the cut, bring the explanation graph back to current state. + m_explanationGraph->fastForward(); + + for (tuple& edge : cutEdges) + { + int edgeNode = m_edgeGraph->getVertexForSourceEdge(get<0>(edge), get<1>(edge)); + VarID edgeVar = m_edgeGraphData->get(edgeNode); + if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) + { + edgeVarsRecorded.insert(edgeVar); + + vxy_assert(!db->anyPossible(edgeVar, m_edgeOpenMask)); + lits.push_back(Literal(edgeVar, m_edgeOpenMask)); + } + } + + // For every other potential source, see if this graph also holds. It holds if the other source is on the + // same side as this source, hence would have to cross the same edge boundary. + for (int j = potentialSrcIndex + 1; j < m_initialPotentialSources.size(); ++j) + { + if (visited[j]) + { + continue; + } + + int vertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (vertex == conflictVertex) + { + continue; + } + + if (db->getPotentialValues(m_initialPotentialSources[j]).anyPossible(m_sourceMask)) + { + if (!m_maxFlowAlgo.onSinkSide(vertex, m_flowGraphEdges, m_flowGraphLookup)) + { + visited[j] = true; + } + } + } + } + // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it + // was. + else + { + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } + + return lits; +} + +vector ITopologySearchConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) +{ + vxy_assert(!m_explainingSourceRequirement); + ValueGuard guard(m_explainingSourceRequirement, true); + + VarID sourceVar = params.propagatedVariable; + int sourceVertex = m_variableToSourceVertexIndex[sourceVar]; + + auto db = params.database; + + vector lits; + lits.push_back(Literal(sourceVar, m_sourceMask)); + + m_maxGraph->rewindUntil(params.timestamp); + +#if REACHABILITY_USE_RAMAL_REPS + { + // Batch-update to rewound graph state + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + it->second.maxReachability->refresh(); + } + } +#endif + + // Create any sources that might've gotten removed since this happened. + // (We'll clean them up after) + vector tempSources; + for (VarID potentialSource : m_initialPotentialSources) + { + if (db->anyPossible(potentialSource, m_sourceMask)) + { + if (m_reachabilitySources.find(potentialSource) == m_reachabilitySources.end()) + { + tempSources.push_back(potentialSource); + + int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; + + ReachabilitySourceData data; +#if REACHABILITY_USE_RAMAL_REPS + data.maxReachability = make_shared(m_maxGraph, false, false, false); +#else + Data.maxReachability = make_shared(MaxGraph); +#endif + data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); + + m_reachabilitySources[potentialSource] = data; + } + } + else if (potentialSource != sourceVar) + { + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } + + int removedSourceLitIdx = -1; + if (removedSource.isValid()) + { + // This became a required source because RemovedSource was removed, and some definitely-reachable vertices were + // only reachable by this source. + vxy_assert(!db->anyPossible(removedSource, m_sourceMask)); + removedSourceLitIdx = indexOfPredicate(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == removedSource; }); + vxy_assert(removedSourceLitIdx >= 0); + } + + // + // This became a required source because some variable(s) were marked as required, and we are the only + // source that can reach them. Find those variables. + // + bool foundSupports = false; + auto& ourReachability = m_reachabilitySources[sourceVar].maxReachability; + auto searchCallback = [&](int vertex, int parent, int edgeIndex) + { + if (ourReachability->isReachable(vertex)) + { + VarID vertexVar = m_sourceGraphData->get(vertex); + if (!db->anyPossible(vertexVar, m_notReachableMask)) + { + bool reachableFromAnotherSource = false; + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) + { + reachableFromAnotherSource = true; + break; + } + } + + if (!reachableFromAnotherSource) + { + vxy_assert(m_reachabilitySources[sourceVar].maxReachability->isReachable(vertex)); + // make sure we don't add the same literal twice! + auto found = find_if(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == vertexVar; }); + if (found != lits.end()) + { + vxy_assert(found->variable == vertexVar); + found->values.include(m_notReachableMask); + } + else + { + lits.push_back(Literal(vertexVar, m_notReachableMask)); + } + foundSupports = true; + } + } + return ETopologySearchResponse::Continue; + } + else + { + return ETopologySearchResponse::Skip; + } + }; + m_dfs.search(*m_sourceGraph.get(), m_variableToSourceVertexIndex[sourceVar], searchCallback); + vxy_assert(foundSupports); + + for (VarID tempSource : tempSources) + { + m_reachabilitySources.erase(tempSource); + } + + m_maxGraph->fastForward(); + return lits; +} + + +void ITopologySearchConstraint::sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex) +{ + #if SANITY_CHECKS + // For each source that could possibly exist... + for (VarID potentialSource : m_initialPotentialSources) + { + int sourceVertex = m_variableToSourceVertexIndex[potentialSource]; + // If this is currently a potential source... + if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) + { + vxy_assert(!TopologySearchAlgorithm::canReach(m_maxGraph, sourceVertex, vertexIndex)); + } + } + #endif +} + + +vector ITopologySearchConstraint::getConstrainingVariables() const +{ + vector out; + for (int i = 0; i < m_sourceGraph->getNumVertices(); ++i) + { + VarID var = m_sourceGraphData->get(i); + if (var.isValid()) + { + out.push_back(var); + } + } + + for (int i = 0; i < m_edgeGraph->getNumVertices(); ++i) + { + VarID var = m_edgeGraphData->get(i); + if (var.isValid()) + { + out.push_back(var); + } + } + + return out; +} + +bool ITopologySearchConstraint::checkConflicting(IVariableDatabase* db) const +{ + // TODO + return false; +} + +#undef SANITY_CHECKS diff --git a/vertexy/src/public/ConstraintTypes.h b/vertexy/src/public/ConstraintTypes.h index 8924171..17b33ab 100644 --- a/vertexy/src/public/ConstraintTypes.h +++ b/vertexy/src/public/ConstraintTypes.h @@ -39,7 +39,8 @@ enum class EConstraintType : uint8_t Offset, Table, Reachability, - Sum + Sum, + ShortestPath }; // If set, VariableDBs will cache the state of each variable (solved/unsolved/contradiction), only updating when diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index 1af49c1..8298850 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -1,6 +1,7 @@ // Copyright Proletariat, Inc. All Rights Reserved. #pragma once #include "ConstraintTypes.h" +#include "TopologySearchConstraint.h" #include "IBacktrackingSolverConstraint.h" #include "ISolverConstraint.h" #include "SignedClause.h" @@ -22,7 +23,7 @@ namespace Vertexy * Any variable that is definitely one of SourceValues is considered a reachability source. * Any variable that is definitely on of NeedReachableValues is considered a destination that must remain reachable from AT LEAST one source. */ -class ReachabilityConstraint : public IBacktrackingSolverConstraint +class ReachabilityConstraint : public ITopologySearchConstraint { public: ReachabilityConstraint(const ConstraintFactoryParams& params, @@ -52,162 +53,11 @@ class ReachabilityConstraint : public IBacktrackingSolverConstraint using Factory = ReachabilityFactory; virtual EConstraintType getConstraintType() const override { return EConstraintType::Reachability; } - virtual vector getConstrainingVariables() const override; - virtual bool initialize(IVariableDatabase* db) override; - virtual void reset(IVariableDatabase* db) override; - virtual bool onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& previousValue, bool& removeWatch) override; - virtual bool checkConflicting(IVariableDatabase* db) const override; - virtual bool propagate(IVariableDatabase* db) override; - virtual void backtrack(const IVariableDatabase* db, SolverDecisionLevel level) override; - virtual bool getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const override; protected: - vector explainNoReachability(const NarrowingExplanationParams& params) const; - vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); - enum class EReachabilityDetermination : uint8_t - { - DefinitelyReachable, - // Reachable from a definite source - PossiblyReachable, - // Reachable from a possible source - DefinitelyUnreachable, - // Unreachable from any possible source - }; - - EReachabilityDetermination determineReachability(const IVariableDatabase* db, int vertex); - bool processVertexVariableChange(IVariableDatabase* db, VarID variable); - void updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable); - void onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); - void sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex); - - void onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to); - - void addSource(VarID source); - bool removeSource(IVariableDatabase* db, VarID source); - - inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const - { - return !db->getPotentialValues(var).anyPossible(m_notReachableMask); - } - - inline bool definitelyIsSource(const IVariableDatabase* db, VarID var) const - { - return !db->getPotentialValues(var).anyPossible(m_notSourceMask); - } - - inline bool definitelyNotSource(const IVariableDatabase* db, VarID var) const - { - return !db->getPotentialValues(var).anyPossible(m_sourceMask); - } - - inline bool possiblyIsSource(const IVariableDatabase* db, VarID var) - { - return !definitelyNotSource(db, var); - } - - inline bool possiblyOpenEdge(const IVariableDatabase* db, VarID var) const - { - return db->getPotentialValues(var).anyPossible(m_edgeOpenMask); - } - - inline bool definitelyOpenEdge(const IVariableDatabase* db, VarID var) const - { - return !db->getPotentialValues(var).anyPossible(m_edgeBlockedMask); - } - - inline bool definitelyClosedEdge(const IVariableDatabase* db, VarID var) - { - return !possiblyOpenEdge(db, var); - } - - class EdgeWatcher : public IVariableWatchSink - { - public: - EdgeWatcher(ReachabilityConstraint& parent) - : m_parent(parent) - { - } - - virtual ISolverConstraint* asConstraint() override { return &m_parent; } - virtual bool onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& previousValue, bool& removeWatch) override; - - protected: - ReachabilityConstraint& m_parent; - }; - - EdgeWatcher m_edgeWatcher; - - shared_ptr> m_sourceGraphData; - shared_ptr m_sourceGraph; - shared_ptr> m_edgeGraphData; - shared_ptr m_edgeGraph; - - // Contains edges that DEFINITELY exist. Edges are only added to this graph. - shared_ptr m_minGraph; - // Contains edges that POSSIBLY exist. Edges are only removed from this graph. - shared_ptr m_maxGraph; - // Synchronized with MaxGraph. Used during explanations where we need to temporarily rewind graph state, but we don't - // want to propagate to the source reachability trees. - shared_ptr m_explanationGraph; - - ValueSet m_sourceMask; - ValueSet m_notSourceMask; - - ValueSet m_requireReachableMask; - ValueSet m_notReachableMask; - - ValueSet m_edgeBlockedMask; - ValueSet m_edgeOpenMask; - - hash_map m_vertexWatchHandles; - hash_map m_edgeWatchHandles; - - struct BacktrackData - { - SolverDecisionLevel level; - vector reachabilitySourcesRemoved; - }; - - vector m_backtrackData; - - using ESTreeType = ESTree; - using RamalRepsType = RamalReps; - - struct ReachabilitySourceData - { - #if REACHABILITY_USE_RAMAL_REPS - shared_ptr minReachability; - shared_ptr maxReachability; - #else - shared_ptr minReachability; - shared_ptr maxReachability; - #endif - EventListenerHandle minReachabilityChangedHandle = INVALID_EVENT_LISTENER_HANDLE; - EventListenerHandle maxReachabilityChangedHandle = INVALID_EVENT_LISTENER_HANDLE; - }; - - vector m_vertexProcessList; - vector m_edgeProcessList; - - vector m_initialPotentialSources; - hash_map m_reachabilitySources; - - hash_map m_variableToSourceVertexIndex; - hash_map m_variableToSourceEdgeIndex; - - IVariableDatabase* m_edgeChangeDb = nullptr; - bool m_edgeChangeFailure = false; - bool m_inEdgeChange = false; - bool m_backtracking = false; - bool m_explainingSourceRequirement = false; - int m_totalNumEdges = 0; - - DepthFirstSearchAlgorithm m_dfs; - mutable TMaxFlowMinCutAlgorithm m_maxFlowAlgo; - vector> m_flowGraphEdges; - FlowGraphLookupMap m_flowGraphLookup; - RamalRepsEdgeDefinitions m_reachabilityEdgeLookup; + virtual bool isValidDistance(int dist) const override; + }; } // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h new file mode 100644 index 0000000..be165e0 --- /dev/null +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -0,0 +1,60 @@ +// Copyright Proletariat, Inc. All Rights Reserved. +#pragma once +#include "ConstraintTypes.h" +#include "TopologySearchConstraint.h" +#include "ISolverConstraint.h" +#include "SignedClause.h" +#include "ds/ESTree.h" +#include "ds/RamalReps.h" +#include "topology/BacktrackingDigraphTopology.h" +#include "topology/DigraphEdgeTopology.h" +#include "topology/TopologyVertexData.h" +#include "topology/algo/MaxFlowMinCut.h" +#include "variable/IVariableDatabase.h" + +#define REACHABILITY_USE_RAMAL_REPS 1 + +namespace Vertexy +{ + +/** Constraint to ensure the shortest paths between source and destination nodes + * All destination nodes must have at least 1 shortest path to at least 1 source node + * this shortest path must have a relation with 'distance' + */ +class ShortestPathConstraint : public ITopologySearchConstraint +{ +public: + ShortestPathConstraint(const ConstraintFactoryParams& params, + const shared_ptr>& sourceGraphData, + const ValueSet& sourceMask, + const ValueSet& requireReachableMask, + const shared_ptr>& edgeGraphData, + const ValueSet& edgeBlockedMask + ); + + struct ShortestPathFactory + { + static ShortestPathConstraint* construct( + const ConstraintFactoryParams& params, + // Graph of vertices where reachability is calculated + const shared_ptr>& sourceGraphData, + // Values of vertices in SourceGraph that establish it as a reachability source + const vector& sourceValues, + // Values of vertices in SourceGraph that establish it as needing to be reachable from a source + const vector& needReachableValues, + // The variables for each edge of source graph + const shared_ptr>& edgeGraphData, + // Values of vertices in the edge graph establishing that edge as "off" + const vector& edgeBlockedValues); + }; + + using Factory = ShortestPathFactory; + + virtual EConstraintType getConstraintType() const override { return EConstraintType::ShortestPath; } + +protected: + + virtual bool isValidDistance(int dist) const override; +}; + +} // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h new file mode 100644 index 0000000..bf23f4a --- /dev/null +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -0,0 +1,203 @@ +// Copyright Proletariat, Inc. All Rights Reserved. +#pragma once +#include "ConstraintTypes.h" +#include "IBacktrackingSolverConstraint.h" +#include "ISolverConstraint.h" +#include "SignedClause.h" +#include "ds/ESTree.h" +#include "ds/RamalReps.h" +#include "topology/BacktrackingDigraphTopology.h" +#include "topology/DigraphEdgeTopology.h" +#include "topology/TopologyVertexData.h" +#include "topology/algo/MaxFlowMinCut.h" +#include "variable/IVariableDatabase.h" + +#define REACHABILITY_USE_RAMAL_REPS 1 + +namespace Vertexy +{ + +/** Base class for ReachabilityConstraint and ShortestPathConstraint + */ +class ITopologySearchConstraint : public IBacktrackingSolverConstraint +{ +public: + ITopologySearchConstraint(const ConstraintFactoryParams& params, + const shared_ptr>& sourceGraphData, + const ValueSet& sourceMask, + const ValueSet& requireReachableMask, + const shared_ptr>& edgeGraphData, + const ValueSet& edgeBlockedMask, + bool RamalRepsReportDistance + ); + + virtual vector getConstrainingVariables() const override; + virtual bool initialize(IVariableDatabase* db) override; + virtual void reset(IVariableDatabase* db) override; + virtual bool onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& previousValue, bool& removeWatch) override; + virtual bool checkConflicting(IVariableDatabase* db) const override; + virtual bool propagate(IVariableDatabase* db) override; + virtual void backtrack(const IVariableDatabase* db, SolverDecisionLevel level) override; + virtual bool getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const override; + +protected: + //for now don't worry about explainers + vector explainNoReachability(const NarrowingExplanationParams& params) const; + vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); + + enum class EReachabilityDetermination : uint8_t + { + DefinitelyReachable, + // Reachable from a definite source + PossiblyReachable, + // Reachable from a possible source + DefinitelyUnreachable, + // Unreachable from any possible source + }; + + EReachabilityDetermination determineReachability(const IVariableDatabase* db, int vertex); + //used by propagate, NEEDS TO CHANGE + bool processVertexVariableChange(IVariableDatabase* db, VarID variable); + //used by propagate + void updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable); + void onDistanceChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); + void onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); + void sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex); + + void onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to); + + void addSource(VarID source); + //used by processVertexVariableChange + bool removeSource(IVariableDatabase* db, VarID source); + + //virtual + virtual bool isValidDistance(int dist) const = 0; + + inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const + { + return !db->getPotentialValues(var).anyPossible(m_notReachableMask); + } + + inline bool definitelyIsSource(const IVariableDatabase* db, VarID var) const + { + return !db->getPotentialValues(var).anyPossible(m_notSourceMask); + } + + inline bool definitelyNotSource(const IVariableDatabase* db, VarID var) const + { + return !db->getPotentialValues(var).anyPossible(m_sourceMask); + } + + inline bool possiblyIsSource(const IVariableDatabase* db, VarID var) + { + return !definitelyNotSource(db, var); + } + + inline bool possiblyOpenEdge(const IVariableDatabase* db, VarID var) const + { + return db->getPotentialValues(var).anyPossible(m_edgeOpenMask); + } + + inline bool definitelyOpenEdge(const IVariableDatabase* db, VarID var) const + { + return !db->getPotentialValues(var).anyPossible(m_edgeBlockedMask); + } + + inline bool definitelyClosedEdge(const IVariableDatabase* db, VarID var) + { + return !possiblyOpenEdge(db, var); + } + + class EdgeWatcher : public IVariableWatchSink + { + public: + EdgeWatcher(ITopologySearchConstraint& parent) + : m_parent(parent) + { + } + + virtual ISolverConstraint* asConstraint() override { return &m_parent; } + virtual bool onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& previousValue, bool& removeWatch) override; + + protected: + ITopologySearchConstraint& m_parent; + }; + + EdgeWatcher m_edgeWatcher; + + shared_ptr> m_sourceGraphData; + shared_ptr m_sourceGraph; + shared_ptr> m_edgeGraphData; + shared_ptr m_edgeGraph; + + // Contains edges that DEFINITELY exist. Edges are only added to this graph. + shared_ptr m_minGraph; + // Contains edges that POSSIBLY exist. Edges are only removed from this graph. + shared_ptr m_maxGraph; + // Synchronized with MaxGraph. Used during explanations where we need to temporarily rewind graph state, but we don't + // want to propagate to the source reachability trees. + shared_ptr m_explanationGraph; + + ValueSet m_sourceMask; + ValueSet m_notSourceMask; + + ValueSet m_requireReachableMask; + ValueSet m_notReachableMask; + + ValueSet m_edgeBlockedMask; + ValueSet m_edgeOpenMask; + + hash_map m_vertexWatchHandles; + hash_map m_edgeWatchHandles; + + struct BacktrackData + { + SolverDecisionLevel level; + vector reachabilitySourcesRemoved; + }; + + vector m_backtrackData; + + using ESTreeType = ESTree; + using RamalRepsType = RamalReps; + + struct ReachabilitySourceData + { + #if REACHABILITY_USE_RAMAL_REPS + shared_ptr minReachability; + shared_ptr maxReachability; + #else + shared_ptr minReachability; + shared_ptr maxReachability; + #endif + EventListenerHandle minReachabilityChangedHandle = INVALID_EVENT_LISTENER_HANDLE; + EventListenerHandle maxReachabilityChangedHandle = INVALID_EVENT_LISTENER_HANDLE; + EventListenerHandle minDistanceChangedHandle = INVALID_EVENT_LISTENER_HANDLE; + EventListenerHandle maxDistanceChangedHandle = INVALID_EVENT_LISTENER_HANDLE; + }; + + vector m_vertexProcessList; + vector m_edgeProcessList; + + vector m_initialPotentialSources; + hash_map m_reachabilitySources; + + hash_map m_variableToSourceVertexIndex; + hash_map m_variableToSourceEdgeIndex; + + IVariableDatabase* m_edgeChangeDb = nullptr; + bool m_edgeChangeFailure = false; + bool m_inEdgeChange = false; + bool m_backtracking = false; + bool m_explainingSourceRequirement = false; + int m_totalNumEdges = 0; + bool m_RamalRepsReportDistance = false; + + DepthFirstSearchAlgorithm m_dfs; + mutable TMaxFlowMinCutAlgorithm m_maxFlowAlgo; + vector> m_flowGraphEdges; + FlowGraphLookupMap m_flowGraphLookup; + RamalRepsEdgeDefinitions m_reachabilityEdgeLookup; +}; + +} // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/ds/RamalReps.h b/vertexy/src/public/ds/RamalReps.h index 23b1ae1..f585338 100644 --- a/vertexy/src/public/ds/RamalReps.h +++ b/vertexy/src/public/ds/RamalReps.h @@ -234,6 +234,7 @@ class RamalReps } inline bool isReachable(int vertex) const { return m_vertexDists[vertex] != INT_MAX; } + inline int getDistance(int vertex) const { return m_vertexDists[vertex]; } protected: void addEdge(int from, int to) From 6e73ba3a5afffcadbe26462388c97f3c89951c49 Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Tue, 12 Apr 2022 14:25:49 -0400 Subject: [PATCH 02/17] redid it with virtual functions I think this is better OOP than having a "which derived class am I" variable in the base class. --- .../constraints/ReachabilityConstraint.cpp | 30 ++++++++ .../constraints/ShortestPathConstraint.cpp | 38 ++++++++-- .../constraints/TopologySearchConstraint.cpp | 70 +++---------------- .../constraints/ReachabilityConstraint.h | 3 + .../constraints/ShortestPathConstraint.h | 3 + .../constraints/TopologySearchConstraint.h | 19 +++-- 6 files changed, 86 insertions(+), 77 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index 707b51a..3214146 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -68,4 +68,34 @@ bool ReachabilityConstraint::isValidDistance(int dist) const return dist < INT_MAX; } +shared_ptr> ReachabilityConstraint::makeTopology(const shared_ptr& graph) const +{ + return make_shared(graph, USE_RAMAL_REPS_BATCHING, true, false); +} + + +EventListenerHandle ReachabilityConstraint::addMinCallback(RamalRepsType& minReachable, VarID source) +{ + return minReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) + { + if (!m_backtracking && !m_explainingSourceRequirement) + { + vxy_assert(isReachable); + onReachabilityChanged(changedVertex, source, true); + } + }); +} + +EventListenerHandle ReachabilityConstraint::addMaxCallback(RamalRepsType& maxReachable, VarID source) +{ + return maxReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) + { + if (!m_backtracking && !m_explainingSourceRequirement) + { + vxy_assert(!isReachable); + onReachabilityChanged(changedVertex, source, false); + } + }); +} + #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 112ced1..ff3a52b 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -1,5 +1,5 @@ // Copyright Proletariat, Inc. All Rights Reserved. -#include "constraints/ReachabilityConstraint.h" +#include "constraints/ShortestPathConstraint.h" #include "variable/IVariableDatabase.h" #include "topology/GraphRelations.h" @@ -12,7 +12,7 @@ static constexpr int OPEN_EDGE_FLOW = INT_MAX >> 1; static constexpr int CLOSED_EDGE_FLOW = 1; static constexpr bool USE_RAMAL_REPS_BATCHING = true; -ReachabilityConstraint* ReachabilityConstraint::ReachabilityFactory::construct( +ShortestPathConstraint* ShortestPathConstraint::ShortestPathFactory::construct( const ConstraintFactoryParams& params, const shared_ptr>& vertexData, const vector& sourceValues, @@ -48,10 +48,10 @@ ReachabilityConstraint* ReachabilityConstraint::ReachabilityFactory::construct( ValueSet needReachableMask = params.valuesToInternal(graphVar, needReachableValues); ValueSet edgeBlockedMask = params.valuesToInternal(edgeVar, edgeBlockedValues); - return new ReachabilityConstraint(params, vertexData, sourceMask, needReachableMask, edgeData, edgeBlockedMask); + return new ShortestPathConstraint(params, vertexData, sourceMask, needReachableMask, edgeData, edgeBlockedMask); } -ReachabilityConstraint::ReachabilityConstraint( +ShortestPathConstraint::ShortestPathConstraint( const ConstraintFactoryParams& params, const shared_ptr>& sourceGraphData, const ValueSet& sourceMask, @@ -62,11 +62,39 @@ ReachabilityConstraint::ReachabilityConstraint( { } -bool ReachabilityConstraint::isValidDistance(int dist) const +bool ShortestPathConstraint::isValidDistance(int dist) const { //TODO return true; } +shared_ptr> ShortestPathConstraint::makeTopology(const shared_ptr& graph) const +{ + return make_shared(graph, USE_RAMAL_REPS_BATCHING, false, true); +} + +EventListenerHandle Vertexy::ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, VarID source) +{ + return minReachable.onDistanceChanged.add([this, source](int changedVertex, int distance) + { + if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(distance)) + { + onDistanceChanged(changedVertex, source, true); + } + }); +} + +EventListenerHandle Vertexy::ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, VarID source) +{ + return maxReachable.onDistanceChanged.add([this, source](int changedVertex, int distance) + { + if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(distance)) + { + onDistanceChanged(changedVertex, source, false); + } + }); +} + + #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index a70ce3b..dcdc644 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -18,8 +18,7 @@ ITopologySearchConstraint::ITopologySearchConstraint( const ValueSet& sourceMask, const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, - const ValueSet& edgeBlockedMask, - bool ramalRepsReportDistance) + const ValueSet& edgeBlockedMask) : IBacktrackingSolverConstraint(params) //ApplyGraphRelation(Params, SourceGraphData, EdgeGraphData)) , m_edgeWatcher(*this) , m_sourceGraphData(sourceGraphData) @@ -32,7 +31,6 @@ ITopologySearchConstraint::ITopologySearchConstraint( , m_sourceMask(sourceMask) , m_requireReachableMask(requireReachableMask) , m_edgeBlockedMask(edgeBlockedMask) - , m_RamalRepsReportDistance(ramalRepsReportDistance) { m_notSourceMask = sourceMask.inverted(); m_notReachableMask = requireReachableMask.inverted(); @@ -515,8 +513,8 @@ void ITopologySearchConstraint::addSource(VarID source) int vertex = m_variableToSourceVertexIndex[source]; #if REACHABILITY_USE_RAMAL_REPS - auto minReachable = make_shared(m_minGraph, USE_RAMAL_REPS_BATCHING, !m_RamalRepsReportDistance, m_RamalRepsReportDistance); - auto maxReachable = make_shared(m_maxGraph, USE_RAMAL_REPS_BATCHING, !m_RamalRepsReportDistance, m_RamalRepsReportDistance); + auto minReachable = makeTopology(m_minGraph); + auto maxReachable = makeTopology(m_maxGraph); #else auto minReachable = make_shared(m_minGraph); auto maxReachable = make_shared(m_maxGraph); @@ -525,54 +523,10 @@ void ITopologySearchConstraint::addSource(VarID source) minReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); maxReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); - if (!m_RamalRepsReportDistance) - { - // Listen for when reachability changes on the conservative/optimistic graphs - EventListenerHandle minDelHandle = minReachable->onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) - { - if (!m_backtracking && !m_explainingSourceRequirement) - { - vxy_assert(isReachable); - onReachabilityChanged(changedVertex, source, true); - } - }); - - EventListenerHandle maxDelHandle = maxReachable->onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) - { - if (!m_backtracking && !m_explainingSourceRequirement) - { - vxy_assert(!isReachable); - onReachabilityChanged(changedVertex, source, false); - } - }); - - m_reachabilitySources[source] = { minReachable, maxReachable, minDelHandle, maxDelHandle }; - } - else - { - EventListenerHandle minDistHandle = minReachable->onDistanceChanged.add([this, source](int changedVertex, int distance) - { - if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(distance)) - { - onDistanceChanged(changedVertex, source, true); - } - }); + EventListenerHandle minHandle = addMinCallback(*minReachable, source); + EventListenerHandle maxHandle = addMaxCallback(*maxReachable, source); - EventListenerHandle maxDistHandle = maxReachable->onDistanceChanged.add([this, source](int changedVertex, int distance) - { - if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(distance)) - { - onDistanceChanged(changedVertex, source, false); - } - }); - - m_reachabilitySources[source] = - { - minReachable, maxReachable, - INVALID_EVENT_LISTENER_HANDLE, INVALID_EVENT_LISTENER_HANDLE, - minDistHandle, maxDistHandle - }; - } + m_reachabilitySources[source] = { minReachable, maxReachable, minHandle, maxHandle }; } @@ -596,16 +550,8 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source ReachabilitySourceData sourceData = m_reachabilitySources[source]; m_reachabilitySources.erase(source); - if (!m_RamalRepsReportDistance) - { - sourceData.minReachability->onReachabilityChanged.remove(sourceData.minReachabilityChangedHandle); - sourceData.maxReachability->onReachabilityChanged.remove(sourceData.maxReachabilityChangedHandle); - } - else - { - sourceData.minReachability->onDistanceChanged.remove(sourceData.minDistanceChangedHandle); - sourceData.maxReachability->onDistanceChanged.remove(sourceData.maxDistanceChangedHandle); - } + sourceData.minReachability->onReachabilityChanged.remove(sourceData.minRamalHandle); + sourceData.maxReachability->onReachabilityChanged.remove(sourceData.maxRamalHandle); int sourceVertex = m_variableToSourceVertexIndex[source]; diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index 8298850..e7341d3 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -57,6 +57,9 @@ class ReachabilityConstraint : public ITopologySearchConstraint protected: virtual bool isValidDistance(int dist) const override; + virtual shared_ptr makeTopology(const shared_ptr& graph) const override; + virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) override; + virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) override; }; diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index be165e0..6bc5ae3 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -55,6 +55,9 @@ class ShortestPathConstraint : public ITopologySearchConstraint protected: virtual bool isValidDistance(int dist) const override; + virtual shared_ptr makeTopology(const shared_ptr& graph) const override; + virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) override; + virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) override; }; } // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index bf23f4a..d942757 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -27,8 +27,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint const ValueSet& sourceMask, const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, - const ValueSet& edgeBlockedMask, - bool RamalRepsReportDistance + const ValueSet& edgeBlockedMask ); virtual vector getConstrainingVariables() const override; @@ -70,8 +69,14 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint //used by processVertexVariableChange bool removeSource(IVariableDatabase* db, VarID source); + using ESTreeType = ESTree; + using RamalRepsType = RamalReps; + //virtual virtual bool isValidDistance(int dist) const = 0; + virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; + virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) = 0; + virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) = 0; inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const { @@ -158,9 +163,6 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint vector m_backtrackData; - using ESTreeType = ESTree; - using RamalRepsType = RamalReps; - struct ReachabilitySourceData { #if REACHABILITY_USE_RAMAL_REPS @@ -170,10 +172,8 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint shared_ptr minReachability; shared_ptr maxReachability; #endif - EventListenerHandle minReachabilityChangedHandle = INVALID_EVENT_LISTENER_HANDLE; - EventListenerHandle maxReachabilityChangedHandle = INVALID_EVENT_LISTENER_HANDLE; - EventListenerHandle minDistanceChangedHandle = INVALID_EVENT_LISTENER_HANDLE; - EventListenerHandle maxDistanceChangedHandle = INVALID_EVENT_LISTENER_HANDLE; + EventListenerHandle minRamalHandle = INVALID_EVENT_LISTENER_HANDLE; + EventListenerHandle maxRamalHandle = INVALID_EVENT_LISTENER_HANDLE; }; vector m_vertexProcessList; @@ -191,7 +191,6 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint bool m_backtracking = false; bool m_explainingSourceRequirement = false; int m_totalNumEdges = 0; - bool m_RamalRepsReportDistance = false; DepthFirstSearchAlgorithm m_dfs; mutable TMaxFlowMinCutAlgorithm m_maxFlowAlgo; From 537e7ac2d0ad403a9cac25dbd15d8edb560e14fb Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Tue, 12 Apr 2022 15:09:24 -0400 Subject: [PATCH 03/17] removed redundant function --- vertexy/src/private/constraints/ShortestPathConstraint.cpp | 4 ++-- .../src/private/constraints/TopologySearchConstraint.cpp | 6 ------ vertexy/src/public/constraints/TopologySearchConstraint.h | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index ff3a52b..d54f772 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -79,7 +79,7 @@ EventListenerHandle Vertexy::ShortestPathConstraint::addMinCallback(RamalRepsTyp { if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(distance)) { - onDistanceChanged(changedVertex, source, true); + onReachabilityChanged(changedVertex, source, true); } }); } @@ -90,7 +90,7 @@ EventListenerHandle Vertexy::ShortestPathConstraint::addMaxCallback(RamalRepsTyp { if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(distance)) { - onDistanceChanged(changedVertex, source, false); + onReachabilityChanged(changedVertex, source, false); } }); } diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index dcdc644..611e983 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -678,12 +678,6 @@ void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, } } -//are these really the same function? -void ITopologySearchConstraint::onDistanceChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) -{ - onReachabilityChanged(vertexIndex, sourceVar, inMinGraph); -} - //determine if it's still within the required range void ITopologySearchConstraint::onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) { diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index d942757..b0769af 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -59,7 +59,6 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint bool processVertexVariableChange(IVariableDatabase* db, VarID variable); //used by propagate void updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable); - void onDistanceChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); void onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); void sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex); From 5f34fe1d822006781a0303fdd2f73fac177f06a0 Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Tue, 12 Apr 2022 15:49:17 -0400 Subject: [PATCH 04/17] implemented isValidDistance --- .../constraints/ReachabilityConstraint.cpp | 4 +-- .../constraints/ShortestPathConstraint.cpp | 33 +++++++++++++++---- .../constraints/TopologySearchConstraint.cpp | 10 +++--- .../constraints/ReachabilityConstraint.h | 2 +- .../constraints/ShortestPathConstraint.h | 16 +++++++-- .../constraints/TopologySearchConstraint.h | 2 +- 6 files changed, 48 insertions(+), 19 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index 3214146..2942ce5 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -58,12 +58,12 @@ ReachabilityConstraint::ReachabilityConstraint( const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, const ValueSet& edgeBlockedMask) - : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask, false) + : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask) { } -bool ReachabilityConstraint::isValidDistance(int dist) const +bool ReachabilityConstraint::isValidDistance(const IVariableDatabase* db, int dist) const { return dist < INT_MAX; } diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index d54f772..1d68b8c 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -18,7 +18,9 @@ ShortestPathConstraint* ShortestPathConstraint::ShortestPathFactory::construct( const vector& sourceValues, const vector& needReachableValues, const shared_ptr>& edgeData, - const vector& edgeBlockedValues) + const vector& edgeBlockedValues, + EConstraintOperator op, + VarID distance) { // Get an example graph variable VarID graphVar; @@ -48,7 +50,7 @@ ShortestPathConstraint* ShortestPathConstraint::ShortestPathFactory::construct( ValueSet needReachableMask = params.valuesToInternal(graphVar, needReachableValues); ValueSet edgeBlockedMask = params.valuesToInternal(edgeVar, edgeBlockedValues); - return new ShortestPathConstraint(params, vertexData, sourceMask, needReachableMask, edgeData, edgeBlockedMask); + return new ShortestPathConstraint(params, vertexData, sourceMask, needReachableMask, edgeData, edgeBlockedMask, op, distance); } ShortestPathConstraint::ShortestPathConstraint( @@ -57,15 +59,32 @@ ShortestPathConstraint::ShortestPathConstraint( const ValueSet& sourceMask, const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, - const ValueSet& edgeBlockedMask) - : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask, false) + const ValueSet& edgeBlockedMask, + EConstraintOperator op, + VarID distance) + : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask) + , m_op(op) + , m_distance(distance) { + vxy_assert(m_op != EConstraintOperator::NotEqual); } -bool ShortestPathConstraint::isValidDistance(int dist) const +bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int dist) const { - //TODO - return true; + switch (m_op) + { + case EConstraintOperator::GreaterThan: + return dist > db->getMinimumPossibleValue(m_distance); + case EConstraintOperator::GreaterThanEq: + return dist >= db->getMinimumPossibleValue(m_distance); + case EConstraintOperator::LessThan: + return dist < db->getMaximumPossibleValue(m_distance); + case EConstraintOperator::LessThanEq: + return dist <= db->getMaximumPossibleValue(m_distance); + } + + vxy_assert(false); + return false; } shared_ptr> ShortestPathConstraint::makeTopology(const shared_ptr& graph) const diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 611e983..bdff3ff 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -477,7 +477,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d VarID lastReachableSource = VarID::INVALID; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (it->second.maxReachability->isReachable(vertex) && isValidDistance(it->second.maxReachability->getDistance(vertex))) + if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { ++numReachableSources; //should only be incremented if within given range lastReachableSource = it->first; @@ -560,7 +560,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source bool failure = false; auto checkReachability = [&](int vertex, int parent) { - if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(sourceData.maxReachability->getDistance(vertex))) + if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(db, sourceData.maxReachability->getDistance(vertex))) { // This vertex is no longer reachable from the removed source, so might be definitely unreachable now VarID vertexVar = m_sourceGraphData->get(vertex); @@ -585,7 +585,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source int numReachableSources = 0; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (it->second.maxReachability->isReachable(vertex) && isValidDistance(it->second.maxReachability->getDistance(vertex))) + if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { ++numReachableSources; lastReachableSource = it->first; @@ -768,7 +768,7 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: continue; } - if (it->second.minReachability->isReachable(vertexIndex) && isValidDistance(it->second.minReachability->getDistance(vertexIndex))) + if (it->second.minReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.minReachability->getDistance(vertexIndex))) { if (definitelyIsSource(db, it->first)) { @@ -779,7 +779,7 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: return EReachabilityDetermination::PossiblyReachable; } } - else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(it->second.maxReachability->getDistance(vertexIndex))) + else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))) { return EReachabilityDetermination::PossiblyReachable; } diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index e7341d3..e690dde 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -56,7 +56,7 @@ class ReachabilityConstraint : public ITopologySearchConstraint protected: - virtual bool isValidDistance(int dist) const override; + virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) override; diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index 6bc5ae3..117c635 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -11,6 +11,7 @@ #include "topology/TopologyVertexData.h" #include "topology/algo/MaxFlowMinCut.h" #include "variable/IVariableDatabase.h" +#include "constraints\ConstraintOperator.h" #define REACHABILITY_USE_RAMAL_REPS 1 @@ -29,7 +30,9 @@ class ShortestPathConstraint : public ITopologySearchConstraint const ValueSet& sourceMask, const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, - const ValueSet& edgeBlockedMask + const ValueSet& edgeBlockedMask, + EConstraintOperator op, + VarID distance ); struct ShortestPathFactory @@ -45,7 +48,11 @@ class ShortestPathConstraint : public ITopologySearchConstraint // The variables for each edge of source graph const shared_ptr>& edgeGraphData, // Values of vertices in the edge graph establishing that edge as "off" - const vector& edgeBlockedValues); + const vector& edgeBlockedValues, + // How to compare distance + EConstraintOperator op, + // The variable that stores the distance + VarID distance); }; using Factory = ShortestPathFactory; @@ -54,10 +61,13 @@ class ShortestPathConstraint : public ITopologySearchConstraint protected: - virtual bool isValidDistance(int dist) const override; + virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) override; + + EConstraintOperator m_op; + VarID m_distance; }; } // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index b0769af..782994d 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -72,7 +72,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint using RamalRepsType = RamalReps; //virtual - virtual bool isValidDistance(int dist) const = 0; + virtual bool isValidDistance(const IVariableDatabase* db, int dist) const = 0; virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) = 0; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) = 0; From cb437198921044dc1efe7bbe7877df93b044e636 Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Wed, 13 Apr 2022 09:55:25 -0400 Subject: [PATCH 05/17] added db to callback --- .../constraints/ReachabilityConstraint.cpp | 4 ++-- .../constraints/ShortestPathConstraint.cpp | 16 ++++++++-------- .../public/constraints/ReachabilityConstraint.h | 4 ++-- .../public/constraints/ShortestPathConstraint.h | 4 ++-- .../constraints/TopologySearchConstraint.h | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index 2942ce5..f2f6208 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -74,7 +74,7 @@ shared_ptr> ReachabilityConstraint::makeT } -EventListenerHandle ReachabilityConstraint::addMinCallback(RamalRepsType& minReachable, VarID source) +EventListenerHandle ReachabilityConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) { return minReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) { @@ -86,7 +86,7 @@ EventListenerHandle ReachabilityConstraint::addMinCallback(RamalRepsType& minRea }); } -EventListenerHandle ReachabilityConstraint::addMaxCallback(RamalRepsType& maxReachable, VarID source) +EventListenerHandle ReachabilityConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) { return maxReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) { diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 1d68b8c..b780262 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -66,7 +66,7 @@ ShortestPathConstraint::ShortestPathConstraint( , m_op(op) , m_distance(distance) { - vxy_assert(m_op != EConstraintOperator::NotEqual); + vxy_assert(m_op != EConstraintOperator::NotEqual); //NotEqual not supported } bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int dist) const @@ -83,7 +83,7 @@ bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int di return dist <= db->getMaximumPossibleValue(m_distance); } - vxy_assert(false); + vxy_assert(false); //NotEqual not supported return false; } @@ -92,22 +92,22 @@ shared_ptr> ShortestPathConstraint::makeT return make_shared(graph, USE_RAMAL_REPS_BATCHING, false, true); } -EventListenerHandle Vertexy::ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, VarID source) +EventListenerHandle Vertexy::ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) { - return minReachable.onDistanceChanged.add([this, source](int changedVertex, int distance) + return minReachable.onDistanceChanged.add([this, db, source](int changedVertex, int distance) { - if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(distance)) + if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(db, distance)) { onReachabilityChanged(changedVertex, source, true); } }); } -EventListenerHandle Vertexy::ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, VarID source) +EventListenerHandle Vertexy::ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) { - return maxReachable.onDistanceChanged.add([this, source](int changedVertex, int distance) + return maxReachable.onDistanceChanged.add([this, db, source](int changedVertex, int distance) { - if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(distance)) + if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(db, distance)) { onReachabilityChanged(changedVertex, source, false); } diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index e690dde..854e534 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -58,8 +58,8 @@ class ReachabilityConstraint : public ITopologySearchConstraint virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; - virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) override; - virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) override; + virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; + virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; }; diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index 117c635..ee102b4 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -63,8 +63,8 @@ class ShortestPathConstraint : public ITopologySearchConstraint virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; - virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) override; - virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) override; + virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; + virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; EConstraintOperator m_op; VarID m_distance; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index 782994d..2a05ee2 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -74,8 +74,8 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint //virtual virtual bool isValidDistance(const IVariableDatabase* db, int dist) const = 0; virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; - virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, VarID source) = 0; - virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, VarID source) = 0; + virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) = 0; + virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) = 0; inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const { From 077bd4b1537a2dae0fff5248e39abe767ed641ee Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Wed, 13 Apr 2022 10:31:37 -0400 Subject: [PATCH 06/17] adding db to addSource --- .../private/constraints/TopologySearchConstraint.cpp | 10 +++++----- .../src/public/constraints/TopologySearchConstraint.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index bdff3ff..1c095dd 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -326,7 +326,7 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) VarID vertexVar = m_sourceGraphData->get(vertex); if (vertexVar.isValid() && possiblyIsSource(db, vertexVar)) { - addSource(vertexVar); + addSource(db, vertexVar); m_initialPotentialSources.push_back(vertexVar); } } @@ -508,7 +508,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d return true; } -void ITopologySearchConstraint::addSource(VarID source) +void ITopologySearchConstraint::addSource(const IVariableDatabase* db, VarID source) { int vertex = m_variableToSourceVertexIndex[source]; @@ -523,8 +523,8 @@ void ITopologySearchConstraint::addSource(VarID source) minReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); maxReachable->initialize(vertex, &m_reachabilityEdgeLookup, m_totalNumEdges); - EventListenerHandle minHandle = addMinCallback(*minReachable, source); - EventListenerHandle maxHandle = addMaxCallback(*maxReachable, source); + EventListenerHandle minHandle = addMinCallback(*minReachable, db, source); + EventListenerHandle maxHandle = addMaxCallback(*maxReachable, db, source); m_reachabilitySources[source] = { minReachable, maxReachable, minHandle, maxHandle }; @@ -733,7 +733,7 @@ void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDec { for (VarID sourceVar : m_backtrackData.back().reachabilitySourcesRemoved) { - addSource(sourceVar); + addSource(db, sourceVar); } m_backtrackData.pop_back(); } diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index 2a05ee2..d5a9192 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -64,7 +64,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint void onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to); - void addSource(VarID source); + void addSource(const IVariableDatabase* db, VarID source); //used by processVertexVariableChange bool removeSource(IVariableDatabase* db, VarID source); From 460175f42b6570889387c6a5a8c34e2b8bdb3032 Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Wed, 13 Apr 2022 15:58:07 -0400 Subject: [PATCH 07/17] moved the explainers down to the ReachabilityConstraint --- .../constraints/ReachabilityConstraint.cpp | 235 ++++++++++++++++++ .../constraints/ShortestPathConstraint.cpp | 2 + .../constraints/TopologySearchConstraint.cpp | 229 +---------------- .../constraints/ReachabilityConstraint.h | 3 + .../constraints/TopologySearchConstraint.h | 4 +- 5 files changed, 245 insertions(+), 228 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index f2f6208..eaad8d9 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -98,4 +98,239 @@ EventListenerHandle ReachabilityConstraint::addMaxCallback(RamalRepsType& maxRea }); } +vector ReachabilityConstraint::explainNoReachability(const NarrowingExplanationParams& params) const +{ + return IVariableDatabase::defaultExplainer(params); + + vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); + + auto db = params.database; + int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; + + vector lits; + lits.push_back(Literal(params.propagatedVariable, m_notReachableMask)); + + static hash_set edgeVarsRecorded; + edgeVarsRecorded.clear(); + + ValueSet visited; + visited.init(m_initialPotentialSources.size(), false); + + // For each source that could possibly exist... + for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) + { + if (visited[potentialSrcIndex]) + { + continue; + } + visited[potentialSrcIndex] = true; + + VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; + + int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (sourceVertex == conflictVertex) + { + // Reachability sources cannot provide reachability to themselves + continue; + } + + // If this is currently a potential source... + if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) + { + // + // Find the minimum cut of edges that would make this reachable + // + + // Temporarily rewind the explanation graph to this time. + // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges + // will be the same state as when we processed the input variable. + + m_explanationGraph->rewindUntil(params.timestamp); + + vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); + + // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because + // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge + // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow + vector> cutEdges; + m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); + vxy_assert(!cutEdges.empty()); + + // Now that we've found the cut, bring the explanation graph back to current state. + m_explanationGraph->fastForward(); + + for (tuple& edge : cutEdges) + { + int edgeNode = m_edgeGraph->getVertexForSourceEdge(get<0>(edge), get<1>(edge)); + VarID edgeVar = m_edgeGraphData->get(edgeNode); + if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) + { + edgeVarsRecorded.insert(edgeVar); + + vxy_assert(!db->anyPossible(edgeVar, m_edgeOpenMask)); //hits this with distance = 100, 200 + lits.push_back(Literal(edgeVar, m_edgeOpenMask)); + } + } + + // For every other potential source, see if this graph also holds. It holds if the other source is on the + // same side as this source, hence would have to cross the same edge boundary. + for (int j = potentialSrcIndex + 1; j < m_initialPotentialSources.size(); ++j) + { + if (visited[j]) + { + continue; + } + + int vertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (vertex == conflictVertex) + { + continue; + } + + if (db->getPotentialValues(m_initialPotentialSources[j]).anyPossible(m_sourceMask)) + { + if (!m_maxFlowAlgo.onSinkSide(vertex, m_flowGraphEdges, m_flowGraphLookup)) + { + visited[j] = true; + } + } + } + } + // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it + // was. + else + { + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } + + return lits; +} + +vector ReachabilityConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) +{ + return IVariableDatabase::defaultExplainer(params); + + vxy_assert(!m_explainingSourceRequirement); + ValueGuard guard(m_explainingSourceRequirement, true); + + VarID sourceVar = params.propagatedVariable; + int sourceVertex = m_variableToSourceVertexIndex[sourceVar]; + + auto db = params.database; + + vector lits; + lits.push_back(Literal(sourceVar, m_sourceMask)); + + m_maxGraph->rewindUntil(params.timestamp); + +#if REACHABILITY_USE_RAMAL_REPS + { + // Batch-update to rewound graph state + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + it->second.maxReachability->refresh(); + } + } +#endif + + // Create any sources that might've gotten removed since this happened. + // (We'll clean them up after) + vector tempSources; + for (VarID potentialSource : m_initialPotentialSources) + { + if (db->anyPossible(potentialSource, m_sourceMask)) + { + if (m_reachabilitySources.find(potentialSource) == m_reachabilitySources.end()) + { + tempSources.push_back(potentialSource); + + int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; + + ReachabilitySourceData data; +#if REACHABILITY_USE_RAMAL_REPS + data.maxReachability = make_shared(m_maxGraph, false, false, false); +#else + Data.maxReachability = make_shared(MaxGraph); +#endif + data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); + + m_reachabilitySources[potentialSource] = data; + } + } + else if (potentialSource != sourceVar) + { + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } + + int removedSourceLitIdx = -1; + if (removedSource.isValid()) + { + // This became a required source because RemovedSource was removed, and some definitely-reachable vertices were + // only reachable by this source. + vxy_assert(!db->anyPossible(removedSource, m_sourceMask)); + removedSourceLitIdx = indexOfPredicate(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == removedSource; }); + vxy_assert(removedSourceLitIdx >= 0); + } + + // + // This became a required source because some variable(s) were marked as required, and we are the only + // source that can reach them. Find those variables. + // + bool foundSupports = false; + auto& ourReachability = m_reachabilitySources[sourceVar].maxReachability; + auto searchCallback = [&](int vertex, int parent, int edgeIndex) + { + if (ourReachability->isReachable(vertex)) + { + VarID vertexVar = m_sourceGraphData->get(vertex); + if (!db->anyPossible(vertexVar, m_notReachableMask)) + { + bool reachableFromAnotherSource = false; + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) + { + reachableFromAnotherSource = true; + break; + } + } + + if (!reachableFromAnotherSource) + { + vxy_assert(m_reachabilitySources[sourceVar].maxReachability->isReachable(vertex)); + // make sure we don't add the same literal twice! + auto found = find_if(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == vertexVar; }); + if (found != lits.end()) + { + vxy_assert(found->variable == vertexVar); + found->values.include(m_notReachableMask); + } + else + { + lits.push_back(Literal(vertexVar, m_notReachableMask)); + } + foundSupports = true; + } + } + return ETopologySearchResponse::Continue; + } + else + { + return ETopologySearchResponse::Skip; + } + }; + m_dfs.search(*m_sourceGraph.get(), m_variableToSourceVertexIndex[sourceVar], searchCallback); + vxy_assert(foundSupports); + + for (VarID tempSource : tempSources) + { + m_reachabilitySources.erase(tempSource); + } + + m_maxGraph->fastForward(); + return lits; +} + #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index b780262..d155f7c 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -71,6 +71,8 @@ ShortestPathConstraint::ShortestPathConstraint( bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int dist) const { + int maximum = db->getMaximumPossibleValue(m_distance); + switch (m_op) { case EConstraintOperator::GreaterThan: diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 1c095dd..20be1e4 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -779,7 +779,7 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: return EReachabilityDetermination::PossiblyReachable; } } - else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))) + else if (it->second.maxReachability->isReachable(vertexIndex) /* && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))*/) { return EReachabilityDetermination::PossiblyReachable; } @@ -805,235 +805,12 @@ void ITopologySearchConstraint::onExplanationGraphEdgeChange(bool edgeWasAdded, vector ITopologySearchConstraint::explainNoReachability(const NarrowingExplanationParams& params) const { - // return FSolverVariableDatabase::DefaultExplainer(Params); - - vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); - - auto db = params.database; - int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; - - vector lits; - lits.push_back(Literal(params.propagatedVariable, m_notReachableMask)); - - static hash_set edgeVarsRecorded; - edgeVarsRecorded.clear(); - - ValueSet visited; - visited.init(m_initialPotentialSources.size(), false); - - // For each source that could possibly exist... - for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) - { - if (visited[potentialSrcIndex]) - { - continue; - } - visited[potentialSrcIndex] = true; - - VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; - - int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; - if (sourceVertex == conflictVertex) - { - // Reachability sources cannot provide reachability to themselves - continue; - } - - // If this is currently a potential source... - if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) - { - // - // Find the minimum cut of edges that would make this reachable - // - - // Temporarily rewind the explanation graph to this time. - // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges - // will be the same state as when we processed the input variable. - - m_explanationGraph->rewindUntil(params.timestamp); - - vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); - - // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because - // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge - // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow - vector> cutEdges; - m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); - vxy_assert(!cutEdges.empty()); - - // Now that we've found the cut, bring the explanation graph back to current state. - m_explanationGraph->fastForward(); - - for (tuple& edge : cutEdges) - { - int edgeNode = m_edgeGraph->getVertexForSourceEdge(get<0>(edge), get<1>(edge)); - VarID edgeVar = m_edgeGraphData->get(edgeNode); - if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) - { - edgeVarsRecorded.insert(edgeVar); - - vxy_assert(!db->anyPossible(edgeVar, m_edgeOpenMask)); - lits.push_back(Literal(edgeVar, m_edgeOpenMask)); - } - } - - // For every other potential source, see if this graph also holds. It holds if the other source is on the - // same side as this source, hence would have to cross the same edge boundary. - for (int j = potentialSrcIndex + 1; j < m_initialPotentialSources.size(); ++j) - { - if (visited[j]) - { - continue; - } - - int vertex = m_variableToSourceVertexIndex.find(potentialSource)->second; - if (vertex == conflictVertex) - { - continue; - } - - if (db->getPotentialValues(m_initialPotentialSources[j]).anyPossible(m_sourceMask)) - { - if (!m_maxFlowAlgo.onSinkSide(vertex, m_flowGraphEdges, m_flowGraphLookup)) - { - visited[j] = true; - } - } - } - } - // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it - // was. - else - { - lits.push_back(Literal(potentialSource, m_sourceMask)); - } - } - - return lits; + return IVariableDatabase::defaultExplainer(params); } vector ITopologySearchConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) { - vxy_assert(!m_explainingSourceRequirement); - ValueGuard guard(m_explainingSourceRequirement, true); - - VarID sourceVar = params.propagatedVariable; - int sourceVertex = m_variableToSourceVertexIndex[sourceVar]; - - auto db = params.database; - - vector lits; - lits.push_back(Literal(sourceVar, m_sourceMask)); - - m_maxGraph->rewindUntil(params.timestamp); - -#if REACHABILITY_USE_RAMAL_REPS - { - // Batch-update to rewound graph state - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - it->second.maxReachability->refresh(); - } - } -#endif - - // Create any sources that might've gotten removed since this happened. - // (We'll clean them up after) - vector tempSources; - for (VarID potentialSource : m_initialPotentialSources) - { - if (db->anyPossible(potentialSource, m_sourceMask)) - { - if (m_reachabilitySources.find(potentialSource) == m_reachabilitySources.end()) - { - tempSources.push_back(potentialSource); - - int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; - - ReachabilitySourceData data; -#if REACHABILITY_USE_RAMAL_REPS - data.maxReachability = make_shared(m_maxGraph, false, false, false); -#else - Data.maxReachability = make_shared(MaxGraph); -#endif - data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); - - m_reachabilitySources[potentialSource] = data; - } - } - else if (potentialSource != sourceVar) - { - lits.push_back(Literal(potentialSource, m_sourceMask)); - } - } - - int removedSourceLitIdx = -1; - if (removedSource.isValid()) - { - // This became a required source because RemovedSource was removed, and some definitely-reachable vertices were - // only reachable by this source. - vxy_assert(!db->anyPossible(removedSource, m_sourceMask)); - removedSourceLitIdx = indexOfPredicate(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == removedSource; }); - vxy_assert(removedSourceLitIdx >= 0); - } - - // - // This became a required source because some variable(s) were marked as required, and we are the only - // source that can reach them. Find those variables. - // - bool foundSupports = false; - auto& ourReachability = m_reachabilitySources[sourceVar].maxReachability; - auto searchCallback = [&](int vertex, int parent, int edgeIndex) - { - if (ourReachability->isReachable(vertex)) - { - VarID vertexVar = m_sourceGraphData->get(vertex); - if (!db->anyPossible(vertexVar, m_notReachableMask)) - { - bool reachableFromAnotherSource = false; - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) - { - reachableFromAnotherSource = true; - break; - } - } - - if (!reachableFromAnotherSource) - { - vxy_assert(m_reachabilitySources[sourceVar].maxReachability->isReachable(vertex)); - // make sure we don't add the same literal twice! - auto found = find_if(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == vertexVar; }); - if (found != lits.end()) - { - vxy_assert(found->variable == vertexVar); - found->values.include(m_notReachableMask); - } - else - { - lits.push_back(Literal(vertexVar, m_notReachableMask)); - } - foundSupports = true; - } - } - return ETopologySearchResponse::Continue; - } - else - { - return ETopologySearchResponse::Skip; - } - }; - m_dfs.search(*m_sourceGraph.get(), m_variableToSourceVertexIndex[sourceVar], searchCallback); - vxy_assert(foundSupports); - - for (VarID tempSource : tempSources) - { - m_reachabilitySources.erase(tempSource); - } - - m_maxGraph->fastForward(); - return lits; + return IVariableDatabase::defaultExplainer(params); } diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index 854e534..fdf7131 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -60,6 +60,9 @@ class ReachabilityConstraint : public ITopologySearchConstraint virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; + + virtual vector explainNoReachability(const NarrowingExplanationParams& params) const override; + virtual vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID) override; }; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index d5a9192..b3123c1 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -41,8 +41,8 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint protected: //for now don't worry about explainers - vector explainNoReachability(const NarrowingExplanationParams& params) const; - vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); + virtual vector explainNoReachability(const NarrowingExplanationParams& params) const; + virtual vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); enum class EReachabilityDetermination : uint8_t { From b96a1de9863c792799a8a5577af10b3033ee6dea Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Fri, 15 Apr 2022 11:09:10 -0400 Subject: [PATCH 08/17] about to attempt to restructure the maze --- .../constraints/ReachabilityConstraint.cpp | 4 +- .../constraints/ShortestPathConstraint.cpp | 42 ++++++++++++++----- .../constraints/TopologySearchConstraint.cpp | 10 ++--- .../variable/SolverVariableDatabase.cpp | 12 ++++++ .../src/public/variable/IVariableDatabase.h | 3 ++ vertexyTests/src/private/Maze.cpp | 36 ++++++++++++---- 6 files changed, 83 insertions(+), 24 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index eaad8d9..3e58899 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -100,7 +100,7 @@ EventListenerHandle ReachabilityConstraint::addMaxCallback(RamalRepsType& maxRea vector ReachabilityConstraint::explainNoReachability(const NarrowingExplanationParams& params) const { - return IVariableDatabase::defaultExplainer(params); + //return IVariableDatabase::defaultExplainer(params); vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); @@ -209,7 +209,7 @@ vector ReachabilityConstraint::explainNoReachability(const NarrowingExp vector ReachabilityConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) { - return IVariableDatabase::defaultExplainer(params); + //return IVariableDatabase::defaultExplainer(params); vxy_assert(!m_explainingSourceRequirement); ValueGuard guard(m_explainingSourceRequirement, true); diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index d155f7c..e151f1a 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -71,18 +71,16 @@ ShortestPathConstraint::ShortestPathConstraint( bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int dist) const { - int maximum = db->getMaximumPossibleValue(m_distance); - switch (m_op) { case EConstraintOperator::GreaterThan: - return dist > db->getMinimumPossibleValue(m_distance); + return dist > db->getMinimumPossibleDomainValue(m_distance); case EConstraintOperator::GreaterThanEq: - return dist >= db->getMinimumPossibleValue(m_distance); + return dist >= db->getMinimumPossibleDomainValue(m_distance); case EConstraintOperator::LessThan: - return dist < db->getMaximumPossibleValue(m_distance); + return dist < db->getMaximumPossibleDomainValue(m_distance); case EConstraintOperator::LessThanEq: - return dist <= db->getMaximumPossibleValue(m_distance); + return dist <= db->getMaximumPossibleDomainValue(m_distance); } vxy_assert(false); //NotEqual not supported @@ -94,28 +92,52 @@ shared_ptr> ShortestPathConstraint::makeT return make_shared(graph, USE_RAMAL_REPS_BATCHING, false, true); } -EventListenerHandle Vertexy::ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) +EventListenerHandle ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) { return minReachable.onDistanceChanged.add([this, db, source](int changedVertex, int distance) { - if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(db, distance)) + if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(db, distance)) { onReachabilityChanged(changedVertex, source, true); } }); } -EventListenerHandle Vertexy::ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) +EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) { return maxReachable.onDistanceChanged.add([this, db, source](int changedVertex, int distance) { - if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(db, distance)) + if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(db, distance)) { onReachabilityChanged(changedVertex, source, false); } }); } +//EventListenerHandle ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) +//{ +// return minReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) +// { +// if (!m_backtracking && !m_explainingSourceRequirement) +// { +// vxy_assert(isReachable); +// onReachabilityChanged(changedVertex, source, true); +// } +// }); +//} +// +//EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) +//{ +// return maxReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) +// { +// if (!m_backtracking && !m_explainingSourceRequirement) +// { +// vxy_assert(!isReachable); +// onReachabilityChanged(changedVertex, source, false); +// } +// }); +//} + #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 20be1e4..78c5f0a 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -469,7 +469,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d } // If this now requires reachability... - if (!db->anyPossible(variable, m_notReachableMask)) //impossible that it's not reachable + if (!db->anyPossible(variable, m_notReachableMask)) { int vertex = m_variableToSourceVertexIndex[variable]; @@ -479,7 +479,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d { if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { - ++numReachableSources; //should only be incremented if within given range + ++numReachableSources; lastReachableSource = it->first; if (numReachableSources > 1) { @@ -570,7 +570,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source if (determination == EReachabilityDetermination::DefinitelyUnreachable) { - sanityCheckUnreachable(db, vertex); + //sanityCheckUnreachable(db, vertex); if (!db->constrainToValues(vertexVar, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) { failure = true; @@ -711,7 +711,7 @@ void ITopologySearchConstraint::onReachabilityChanged(int vertexIndex, VarID sou if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyUnreachable) { VarID var = m_sourceGraphData->get(vertexIndex); - sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) { @@ -779,7 +779,7 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: return EReachabilityDetermination::PossiblyReachable; } } - else if (it->second.maxReachability->isReachable(vertexIndex) /* && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))*/) + else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))) { return EReachabilityDetermination::PossiblyReachable; } diff --git a/vertexy/src/private/variable/SolverVariableDatabase.cpp b/vertexy/src/private/variable/SolverVariableDatabase.cpp index cee137b..90519dc 100644 --- a/vertexy/src/private/variable/SolverVariableDatabase.cpp +++ b/vertexy/src/private/variable/SolverVariableDatabase.cpp @@ -40,6 +40,18 @@ vector IVariableDatabase::defaultExplainer(const NarrowingExplanationPa return clauses; } +int IVariableDatabase::getMinimumPossibleDomainValue(VarID varID) const +{ + vxy_assert(varID.isValid()); + return getSolver()->getDomain(varID).getValueForIndex(getMinimumPossibleValue(varID)); +} + +int IVariableDatabase::getMaximumPossibleDomainValue(VarID varID) const +{ + vxy_assert(varID.isValid()); + return getSolver()->getDomain(varID).getValueForIndex(getMaximumPossibleValue(varID)); +} + SolverVariableDatabase::SolverVariableDatabase(ConstraintSolver* inSolver) : IVariableDatabase() , m_solver(inSolver) diff --git a/vertexy/src/public/variable/IVariableDatabase.h b/vertexy/src/public/variable/IVariableDatabase.h index ac74a2d..2fab703 100644 --- a/vertexy/src/public/variable/IVariableDatabase.h +++ b/vertexy/src/public/variable/IVariableDatabase.h @@ -189,6 +189,9 @@ class IVariableDatabase return getPotentialValues(varID).lastIndexOf(true); } + int getMinimumPossibleDomainValue(VarID varID) const; + int getMaximumPossibleDomainValue(VarID varID) const; + // Default explanation for propagation. The explanation is guaranteed to be assertive (i.e. will cause backtracking) but // is not necessarily the tightest explanation possible. (It is generally rather poor.) static vector defaultExplainer(const NarrowingExplanationParams& params); diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index ae67dc6..8c28828 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -3,6 +3,7 @@ #include "ConstraintSolver.h" #include "EATest/EATest.h" #include "constraints/ReachabilityConstraint.h" +#include "constraints/ShortestPathConstraint.h" #include "constraints/TableConstraint.h" #include "constraints/IffConstraint.h" #include "decision/LogOrderHeuristic.h" @@ -17,7 +18,9 @@ using namespace VertexyTests; // Interval of solver steps to print out current maze status. Set to 1 to see each solver step. static constexpr int MAZE_REFRESH_RATE = 0; // The number of keys/doors that should exist in the maze. -static constexpr int NUM_KEYS = 3; +static constexpr int NUM_KEYS = 1; +// Test Shortest Path constraint (slow), recommend 1 key. +static constexpr bool TEST_SHORTEST_PATH = true; // True to print edge variables in FMaze::Print static constexpr bool PRINT_EDGES = false; // Whether to write a decision log as DecisionLog.txt @@ -156,6 +159,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto downTile = make_shared>(tileData, PlanarGridTopology::moveDown()); auto downRightTile = make_shared>(tileData, PlanarGridTopology::moveDown().combine(PlanarGridTopology::moveRight())); + auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 5/*numRows * numCols*/}); // // DECLARE CONSTRAINTS // @@ -321,11 +325,22 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto selfStepTile = make_shared>(stepData); - // If this tile is the entrance in the maze, constrain it to be the entrance in this step. - solver.makeGraphConstraint(grid, - GraphRelationClause(selfStepTile, step_Origin), - vector{GraphRelationClause(selfTile, cell_Entrance)} - ); + if (TEST_SHORTEST_PATH) + { + //if this is the most recent door, constrain it to the be the entrance in this step + solver.makeGraphConstraint(grid, + GraphRelationClause(selfStepTile, step_Origin), + vector{ GraphRelationClause(selfTile, step == 0 ? cell_Entrance : vector{cell_Doors[step - 1]})} + ); + } + else + { + //If this tile is the entrance in the maze, constrain it to be the entrance in this step. + solver.makeGraphConstraint(grid, + GraphRelationClause(selfStepTile, step_Origin), + vector{ GraphRelationClause(selfTile, cell_Entrance) } + ); + } // A tile can never be passable in any step if it is a wall solver.makeGraphConstraint(grid, ENoGood::NoGood, @@ -430,7 +445,14 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV } // Ensure reachability for this step: all Step_Reachable cells must be reachable from Step_Origin cells. - solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid); + if (TEST_SHORTEST_PATH) + { + solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, shortestPathDistance); + } + else + { + solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid); + } } // Uncomment to print out the maze every time the solver backtracks (for debugging) From 84c01a8d40f8edc332416b26152b455a60ac7199 Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:30:05 -0400 Subject: [PATCH 09/17] working on greater than and less than --- .../constraints/ReachabilityConstraint.cpp | 30 +++++ .../constraints/ShortestPathConstraint.cpp | 109 +++++++++++++++++- .../constraints/TopologySearchConstraint.cpp | 55 +++++---- .../constraints/ReachabilityConstraint.h | 2 + .../constraints/ShortestPathConstraint.h | 2 + .../constraints/TopologySearchConstraint.h | 3 + 6 files changed, 175 insertions(+), 26 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index 3e58899..4019576 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -68,6 +68,36 @@ bool ReachabilityConstraint::isValidDistance(const IVariableDatabase* db, int di return dist < INT_MAX; } +bool ReachabilityConstraint::isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +{ + return src.maxReachability->isReachable(vertex); +} + +ITopologySearchConstraint::EReachabilityDetermination ReachabilityConstraint::determineReachabilityHelper( + const IVariableDatabase* db, + const ReachabilitySourceData& src, + int vertex, + VarID srcVertex) const +{ + if (src.minReachability->isReachable(vertex)) + { + if (definitelyIsSource(db, srcVertex)) + { + return EReachabilityDetermination::DefinitelyReachable; + } + else + { + return EReachabilityDetermination::PossiblyReachable; + } + } + else if (src.maxReachability->isReachable(vertex)) + { + return EReachabilityDetermination::PossiblyReachable; + } + + return EReachabilityDetermination::DefinitelyUnreachable; +} + shared_ptr> ReachabilityConstraint::makeTopology(const shared_ptr& graph) const { return make_shared(graph, USE_RAMAL_REPS_BATCHING, true, false); diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index e151f1a..9130ffb 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -87,6 +87,77 @@ bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int di return false; } +//is possibly reachable +bool ShortestPathConstraint::isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +{ + if (!src.maxReachability->isReachable(vertex)) + { + return false; + } + + if (m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) + { + return isValidDistance(db, src.maxReachability->getDistance(vertex)); + } + else + { + return isValidDistance(db, src.minReachability->getDistance(vertex)); + } +} + +ITopologySearchConstraint::EReachabilityDetermination ShortestPathConstraint::determineReachabilityHelper( + const IVariableDatabase* db, + const ReachabilitySourceData& src, + int vertex, + VarID srcVertex) const +{ + //if (src.minReachability->isReachable(vertex)) + //{ + // if (definitelyIsSource(db, srcVertex)) + // { + // return EReachabilityDetermination::DefinitelyReachable; + // } + // else + // { + // return EReachabilityDetermination::PossiblyReachable; + // } + //} + //else if (src.maxReachability->isReachable(vertex)) + //{ + // return EReachabilityDetermination::PossiblyReachable; + //} + + //return EReachabilityDetermination::DefinitelyUnreachable; + + if (src.minReachability->isReachable(vertex)) + { + if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) && !isValidDistance(db, src.minReachability->getDistance(vertex))) + { + return EReachabilityDetermination::DefinitelyUnreachable; + } + + if (definitelyIsSource(db, srcVertex)) + { + return EReachabilityDetermination::DefinitelyReachable; + } + else + { + return EReachabilityDetermination::PossiblyReachable; + } + } + else if (src.maxReachability->isReachable(vertex)) + { + if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) && !isValidDistance(db, src.maxReachability->getDistance(vertex))) + { + return EReachabilityDetermination::DefinitelyUnreachable; + } + + return EReachabilityDetermination::PossiblyReachable; + } + + return EReachabilityDetermination::DefinitelyUnreachable; +} + shared_ptr> ShortestPathConstraint::makeTopology(const shared_ptr& graph) const { return make_shared(graph, USE_RAMAL_REPS_BATCHING, false, true); @@ -94,22 +165,48 @@ shared_ptr> ShortestPathConstraint::makeT EventListenerHandle ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) { - return minReachable.onDistanceChanged.add([this, db, source](int changedVertex, int distance) + return minReachable.onDistanceChanged.add([&](int changedVertex, int distance) { - if (!m_backtracking && !m_explainingSourceRequirement && isValidDistance(db, distance)) + if (!m_backtracking && !m_explainingSourceRequirement) { - onReachabilityChanged(changedVertex, source, true); + if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq)) + { + if (isValidDistance(m_edgeChangeDb, distance)) + { + onReachabilityChanged(changedVertex, source, true); + } + } + else + { + if (minReachable.isReachable(changedVertex)) //TODO: could be an assert + { + onReachabilityChanged(changedVertex, source, true); + } + } } }); } EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) { - return maxReachable.onDistanceChanged.add([this, db, source](int changedVertex, int distance) + return maxReachable.onDistanceChanged.add([&](int changedVertex, int distance) { - if (!m_backtracking && !m_explainingSourceRequirement && !isValidDistance(db, distance)) + if (!m_backtracking && !m_explainingSourceRequirement) { - onReachabilityChanged(changedVertex, source, false); + if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq)) + { + if (!isValidDistance(m_edgeChangeDb, distance)) + { + onReachabilityChanged(changedVertex, source, false); + } + } + else + { + if (!maxReachable.isReachable(changedVertex)) + { + onReachabilityChanged(changedVertex, source, false); + } + } } }); } diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 78c5f0a..dea17b1 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -477,7 +477,8 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d VarID lastReachableSource = VarID::INVALID; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) + if (isReachable(db, it->second, vertex) ) + //if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { ++numReachableSources; lastReachableSource = it->first; @@ -560,7 +561,8 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source bool failure = false; auto checkReachability = [&](int vertex, int parent) { - if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(db, sourceData.maxReachability->getDistance(vertex))) + if (isReachable(db, sourceData, vertex)) + //if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(db, sourceData.maxReachability->getDistance(vertex))) { // This vertex is no longer reachable from the removed source, so might be definitely unreachable now VarID vertexVar = m_sourceGraphData->get(vertex); @@ -585,7 +587,8 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source int numReachableSources = 0; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) + if (isReachable(db, it->second, vertex)) + //if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { ++numReachableSources; lastReachableSource = it->first; @@ -596,8 +599,15 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source } } - vxy_assert(numReachableSources >= 1); - if (numReachableSources == 1) + //vxy_assert(numReachableSources >= 1); + if (numReachableSources == 0) + { + bool success = db->constrainToValues(source, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); }); + vxy_assert(!success); + failure = true; + return ETopologySearchResponse::Abort; + } + else if (numReachableSources == 1) { if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); })) { @@ -768,21 +778,26 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: continue; } - if (it->second.minReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.minReachability->getDistance(vertexIndex))) - { - if (definitelyIsSource(db, it->first)) - { - return EReachabilityDetermination::DefinitelyReachable; - } - else - { - return EReachabilityDetermination::PossiblyReachable; - } - } - else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))) - { - return EReachabilityDetermination::PossiblyReachable; - } + auto determination = determineReachabilityHelper(db, it->second, vertexIndex, it->first); + if (determination != EReachabilityDetermination::DefinitelyUnreachable) + { + return determination; + } + //if (it->second.minReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.minReachability->getDistance(vertexIndex))) + //{ + // if (definitelyIsSource(db, it->first)) + // { + // return EReachabilityDetermination::DefinitelyReachable; + // } + // else + // { + // return EReachabilityDetermination::PossiblyReachable; + // } + //} + //else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))) + //{ + // return EReachabilityDetermination::PossiblyReachable; + //} } return EReachabilityDetermination::DefinitelyUnreachable; diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index fdf7131..ed49cab 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -57,6 +57,8 @@ class ReachabilityConstraint : public ITopologySearchConstraint protected: virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; + virtual bool isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; + virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index ee102b4..47f39c8 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -62,6 +62,8 @@ class ShortestPathConstraint : public ITopologySearchConstraint protected: virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; + virtual bool isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; + virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index b3123c1..f52b0c2 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -71,8 +71,11 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint using ESTreeType = ESTree; using RamalRepsType = RamalReps; + struct ReachabilitySourceData; //virtual virtual bool isValidDistance(const IVariableDatabase* db, int dist) const = 0; + virtual bool isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const = 0; + virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const = 0; virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) = 0; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) = 0; From 61add3f78db8c95c361202869bcc2df220b81abe Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:33:57 -0400 Subject: [PATCH 10/17] still trying to figure out the constraints --- .../constraints/ReachabilityConstraint.cpp | 7 +-- .../constraints/ShortestPathConstraint.cpp | 60 ++++++++++--------- .../constraints/ReachabilityConstraint.h | 3 +- .../constraints/ShortestPathConstraint.h | 7 ++- .../constraints/TopologySearchConstraint.h | 7 +-- vertexyTestHarness/src/SolverTest.cpp | 50 ++++++++-------- vertexyTests/src/private/Maze.cpp | 28 +++++++-- 7 files changed, 89 insertions(+), 73 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index 4019576..b700000 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -63,12 +63,7 @@ ReachabilityConstraint::ReachabilityConstraint( } -bool ReachabilityConstraint::isValidDistance(const IVariableDatabase* db, int dist) const -{ - return dist < INT_MAX; -} - -bool ReachabilityConstraint::isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +bool ReachabilityConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const { return src.maxReachability->isReachable(vertex); } diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 9130ffb..2301f0f 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -88,7 +88,7 @@ bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int di } //is possibly reachable -bool ShortestPathConstraint::isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +bool ShortestPathConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const { if (!src.maxReachability->isReachable(vertex)) { @@ -169,20 +169,21 @@ EventListenerHandle ShortestPathConstraint::addMinCallback(RamalRepsType& minRea { if (!m_backtracking && !m_explainingSourceRequirement) { - if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq)) - { - if (isValidDistance(m_edgeChangeDb, distance)) - { - onReachabilityChanged(changedVertex, source, true); - } - } - else - { - if (minReachable.isReachable(changedVertex)) //TODO: could be an assert - { - onReachabilityChanged(changedVertex, source, true); - } - } + onReachabilityChanged(changedVertex, source, true); + //if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq)) + //{ + // if (isValidDistance(m_edgeChangeDb, distance)) + // { + // onReachabilityChanged(changedVertex, source, true); + // } + //} + //else + //{ + // if (minReachable.isReachable(changedVertex)) //TODO: could be an assert + // { + // onReachabilityChanged(changedVertex, source, true); + // } + //} } }); } @@ -193,20 +194,21 @@ EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxRea { if (!m_backtracking && !m_explainingSourceRequirement) { - if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq)) - { - if (!isValidDistance(m_edgeChangeDb, distance)) - { - onReachabilityChanged(changedVertex, source, false); - } - } - else - { - if (!maxReachable.isReachable(changedVertex)) - { - onReachabilityChanged(changedVertex, source, false); - } - } + onReachabilityChanged(changedVertex, source, false); + //if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq)) + //{ + // if (!isValidDistance(m_edgeChangeDb, distance)) + // { + // onReachabilityChanged(changedVertex, source, false); + // } + //} + //else + //{ + // if (!maxReachable.isReachable(changedVertex)) + // { + // onReachabilityChanged(changedVertex, source, false); + // } + //} } }); } diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index 4a5894d..b3ebcba 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -56,8 +56,7 @@ class ReachabilityConstraint : public ITopologySearchConstraint protected: - virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; - virtual bool isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; + virtual bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index 47f39c8..c49f297 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -2,7 +2,7 @@ #pragma once #include "ConstraintTypes.h" #include "TopologySearchConstraint.h" -#include "ISolverConstraint.h" +#include "IConstraint.h" #include "SignedClause.h" #include "ds/ESTree.h" #include "ds/RamalReps.h" @@ -61,8 +61,9 @@ class ShortestPathConstraint : public ITopologySearchConstraint protected: - virtual bool isValidDistance(const IVariableDatabase* db, int dist) const override; - virtual bool isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; + bool isValidDistance(const IVariableDatabase* db, int dist) const; + + virtual bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index f52b0c2..ac7df2d 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -2,7 +2,7 @@ #pragma once #include "ConstraintTypes.h" #include "IBacktrackingSolverConstraint.h" -#include "ISolverConstraint.h" +#include "IConstraint.h" #include "SignedClause.h" #include "ds/ESTree.h" #include "ds/RamalReps.h" @@ -73,8 +73,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint struct ReachabilitySourceData; //virtual - virtual bool isValidDistance(const IVariableDatabase* db, int dist) const = 0; - virtual bool isReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const = 0; + virtual bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const = 0; virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const = 0; virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) = 0; @@ -123,7 +122,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint { } - virtual ISolverConstraint* asConstraint() override { return &m_parent; } + virtual IConstraint* asConstraint() override { return &m_parent; } virtual bool onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& previousValue, bool& removeWatch) override; protected: diff --git a/vertexyTestHarness/src/SolverTest.cpp b/vertexyTestHarness/src/SolverTest.cpp index ea466d4..c6f67a8 100644 --- a/vertexyTestHarness/src/SolverTest.cpp +++ b/vertexyTestHarness/src/SolverTest.cpp @@ -251,7 +251,7 @@ int test_ruleSCCs() return nErrorCount; } -static constexpr int FORCE_SEED = 0; +static constexpr int FORCE_SEED = 42; static constexpr int NUM_TIMES = 10; static constexpr int MAZE_NUM_ROWS = 15; static constexpr int MAZE_NUM_COLS = 15; @@ -259,7 +259,7 @@ static constexpr int NQUEENS_SIZE = 25; static constexpr int SUDOKU_STARTING_HINTS = 0; static constexpr int TOWERS_NUM_DISCS = 3; static constexpr int KNIGHT_BOARD_DIM = 6; -static constexpr bool PRINT_VERBOSE = false; +static constexpr bool PRINT_VERBOSE = true; int main(int argc, char* argv[]) { @@ -267,29 +267,29 @@ int main(int argc, char* argv[]) using namespace VertexyTests; TestApplication Suite("Solver Tests", argc, argv); - Suite.AddTest("ValueBitset", test_ValueBitset); - Suite.AddTest("Digraph", test_Digraph); - Suite.AddTest("RuleSCCs", test_ruleSCCs); - Suite.AddTest("Clause-Basic", []() { return TestSolvers::solveClauseBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Inequality-Basic", []() { return TestSolvers::solveInequalityBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Cardinality-Basic", []() { return TestSolvers::solveCardinalityBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Cardinality-Shift", []() { return TestSolvers::solveCardinalityShiftProblem(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("AllDifferent-Small", []() { return TestSolvers::solveAllDifferentSmall(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("AllDifferent-Large", []() { return TestSolvers::solveAllDifferentLarge(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Rules-BasicChoice", []() { return TestSolvers::solveRules_basicChoice(FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Rules-BasicDisjunction", []() { return TestSolvers::solveRules_basicDisjunction(FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Rules-BasicCycle", []() { return TestSolvers::solveRules_basicCycle(FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Rules-BasicIncomplete", []() { return TestSolvers::solveRules_incompleteCycle(FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Sum-Basic", []() { return TestSolvers::solveSumBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Sudoku", []() { return SudokuSolver::solve(NUM_TIMES, SUDOKU_STARTING_HINTS, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("TowersOfHanoi", []() { return TowersOfHanoiSolver::solveTowersGrid(NUM_TIMES, TOWERS_NUM_DISCS, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("TowersOfHanoi", []() { return TowersOfHanoiSolver::solveTowersDiskBased(NUM_TIMES, TOWERS_NUM_DISCS, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("TowersOfHanoi", []() { return TowersOfHanoiSolver::solverTowersDiskBasedGraph(NUM_TIMES, TOWERS_NUM_DISCS, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("NQueens-AllDifferent", []() { return NQueensSolvers::solveUsingAllDifferent(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("NQueens-Table", []() { return NQueensSolvers::solveUsingTable(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("NQueens-Graph", []() { return NQueensSolvers::solveUsingGraph(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("KnightTourPacked", []() { return KnightTourSolver::solvePacked(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("KnightTour", []() { return KnightTourSolver::solveAtomic(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("ValueBitset", test_ValueBitset); + //Suite.AddTest("Digraph", test_Digraph); + //Suite.AddTest("RuleSCCs", test_ruleSCCs); + //Suite.AddTest("Clause-Basic", []() { return TestSolvers::solveClauseBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Inequality-Basic", []() { return TestSolvers::solveInequalityBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Cardinality-Basic", []() { return TestSolvers::solveCardinalityBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Cardinality-Shift", []() { return TestSolvers::solveCardinalityShiftProblem(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("AllDifferent-Small", []() { return TestSolvers::solveAllDifferentSmall(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("AllDifferent-Large", []() { return TestSolvers::solveAllDifferentLarge(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Rules-BasicChoice", []() { return TestSolvers::solveRules_basicChoice(FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Rules-BasicDisjunction", []() { return TestSolvers::solveRules_basicDisjunction(FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Rules-BasicCycle", []() { return TestSolvers::solveRules_basicCycle(FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Rules-BasicIncomplete", []() { return TestSolvers::solveRules_incompleteCycle(FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Sum-Basic", []() { return TestSolvers::solveSumBasic(NUM_TIMES, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Sudoku", []() { return SudokuSolver::solve(NUM_TIMES, SUDOKU_STARTING_HINTS, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("TowersOfHanoi", []() { return TowersOfHanoiSolver::solveTowersGrid(NUM_TIMES, TOWERS_NUM_DISCS, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("TowersOfHanoi", []() { return TowersOfHanoiSolver::solveTowersDiskBased(NUM_TIMES, TOWERS_NUM_DISCS, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("TowersOfHanoi", []() { return TowersOfHanoiSolver::solverTowersDiskBasedGraph(NUM_TIMES, TOWERS_NUM_DISCS, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("NQueens-AllDifferent", []() { return NQueensSolvers::solveUsingAllDifferent(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("NQueens-Table", []() { return NQueensSolvers::solveUsingTable(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("NQueens-Graph", []() { return NQueensSolvers::solveUsingGraph(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("KnightTourPacked", []() { return KnightTourSolver::solvePacked(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("KnightTour", []() { return KnightTourSolver::solveAtomic(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); Suite.AddTest("Maze", []() { return MazeSolver::solve(NUM_TIMES, MAZE_NUM_ROWS, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); return Suite.Run(); } diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index 8c28828..a09cbc9 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -159,7 +159,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto downTile = make_shared>(tileData, PlanarGridTopology::moveDown()); auto downRightTile = make_shared>(tileData, PlanarGridTopology::moveDown().combine(PlanarGridTopology::moveRight())); - auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 5/*numRows * numCols*/}); + auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 0/*numRows * numCols*/}); // // DECLARE CONSTRAINTS // @@ -325,12 +325,31 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto selfStepTile = make_shared>(stepData); + auto stepDoorData = solver.makeVariableGraph(stepName, ITopology::adapt(grid), stepDomain, { wstring::CtorSprintf(), TEXT("StepDoor%d-"), step }); + auto selfStepDoorTile = make_shared>(stepDoorData); + // constraint + solver.makeGraphConstraint(grid, + GraphRelationClause(selfStepDoorTile, step_Origin), + vector{ GraphRelationClause(selfTile, step == 0 ? cell_Entrance : vector{cell_Doors[step - 1]})} + ); + + // if stepDoor != reachable, tile can't be a door + solver.makeGraphConstraint(grid, ENoGood::NoGood, + GraphRelationClause(selfStepDoorTile, EClauseSign::Outside, step_Reachable), + GraphRelationClause(selfTile, step == NUM_KEYS ? cell_Exit : vector{ cell_Keys[step] }) + ); + if (TEST_SHORTEST_PATH) { //if this is the most recent door, constrain it to the be the entrance in this step + //solver.makeGraphConstraint(grid, + // GraphRelationClause(selfStepTile, step_door), + // vector{ GraphRelationClause(selfTile, step == 0 ? cell_Entrance : vector{cell_Doors[step - 1]})} + //); + solver.makeGraphConstraint(grid, - GraphRelationClause(selfStepTile, step_Origin), - vector{ GraphRelationClause(selfTile, step == 0 ? cell_Entrance : vector{cell_Doors[step - 1]})} + GraphRelationClause(selfStepTile, step_Origin), + vector{ GraphRelationClause(selfTile, cell_Entrance) } ); } else @@ -447,7 +466,8 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV // Ensure reachability for this step: all Step_Reachable cells must be reachable from Step_Origin cells. if (TEST_SHORTEST_PATH) { - solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, shortestPathDistance); + solver.makeConstraint(stepDoorData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, shortestPathDistance); + solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid); } else { From 107362233934f4f1044aa48ba784a6a62e3a34ac Mon Sep 17 00:00:00 2001 From: prolecengelhard <72163502+prolecengelhard@users.noreply.github.com> Date: Thu, 28 Apr 2022 09:45:40 -0400 Subject: [PATCH 11/17] made a simpler test and renamed a function --- .../constraints/TopologySearchConstraint.cpp | 6 +- vertexyTestHarness/src/SolverTest.cpp | 3 +- vertexyTests/src/private/Maze.cpp | 99 ++++++++++++++++++- vertexyTests/src/public/Maze.h | 2 + 4 files changed, 105 insertions(+), 5 deletions(-) diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index dea17b1..314550a 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -477,7 +477,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d VarID lastReachableSource = VarID::INVALID; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (isReachable(db, it->second, vertex) ) + if (isPossiblyReachable(db, it->second, vertex) ) //if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { ++numReachableSources; @@ -561,7 +561,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source bool failure = false; auto checkReachability = [&](int vertex, int parent) { - if (isReachable(db, sourceData, vertex)) + if (isPossiblyReachable(db, sourceData, vertex)) //if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(db, sourceData.maxReachability->getDistance(vertex))) { // This vertex is no longer reachable from the removed source, so might be definitely unreachable now @@ -587,7 +587,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source int numReachableSources = 0; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (isReachable(db, it->second, vertex)) + if (isPossiblyReachable(db, it->second, vertex)) //if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) { ++numReachableSources; diff --git a/vertexyTestHarness/src/SolverTest.cpp b/vertexyTestHarness/src/SolverTest.cpp index c6f67a8..38b6a05 100644 --- a/vertexyTestHarness/src/SolverTest.cpp +++ b/vertexyTestHarness/src/SolverTest.cpp @@ -290,6 +290,7 @@ int main(int argc, char* argv[]) //Suite.AddTest("NQueens-Graph", []() { return NQueensSolvers::solveUsingGraph(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); //Suite.AddTest("KnightTourPacked", []() { return KnightTourSolver::solvePacked(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); //Suite.AddTest("KnightTour", []() { return KnightTourSolver::solveAtomic(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("Maze", []() { return MazeSolver::solve(NUM_TIMES, MAZE_NUM_ROWS, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("Maze", []() { return MazeSolver::solve(NUM_TIMES, MAZE_NUM_ROWS, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); + Suite.AddTest("MazeSimple", []() { return MazeSolver::solveSimple(NUM_TIMES, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); return Suite.Run(); } diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index a09cbc9..2ae7fd1 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -20,7 +20,7 @@ static constexpr int MAZE_REFRESH_RATE = 0; // The number of keys/doors that should exist in the maze. static constexpr int NUM_KEYS = 1; // Test Shortest Path constraint (slow), recommend 1 key. -static constexpr bool TEST_SHORTEST_PATH = true; +static constexpr bool TEST_SHORTEST_PATH = false; // True to print edge variables in FMaze::Print static constexpr bool PRINT_EDGES = false; // Whether to write a decision log as DecisionLog.txt @@ -60,6 +60,103 @@ class DebugMazeStrategy : public ISolverDecisionHeuristic ConstraintSolver& m_solver; }; +int MazeSolver::solveSimple(int times, int numCols, int seed, bool printVerbose) +{ + int nErrorCount = 0; + + ConstraintSolver solver(TEXT("Maze"), seed); + VERTEXY_LOG("TestMaze(%d)", solver.getSeed()); + + constexpr int BLANK_IDX = 0; + constexpr int ENTRANCE_IDX = 1; + constexpr int EXIT_IDX = 2; + + // Predefined combinations of values for cells + const vector cell_Blank{ BLANK_IDX }; + const vector cell_Entrance{ ENTRANCE_IDX }; + const vector cell_Exit{ EXIT_IDX }; + + // The domain determines the range of values that each tile takes on + SolverVariableDomain tileDomain(0, 2); + + // Create the topology for the maze. + shared_ptr grid = make_shared(numCols, 1); + + // Create a variable for each tile in the maze. + auto tileData = solver.makeVariableGraph(TEXT("TileVars"), ITopology::adapt(grid), tileDomain, TEXT("Cell")); + + for (int x = 0; x < numCols; ++x) + { + uint32_t node = grid->coordinateToIndex(x, 0); + solver.setInitialValues(tileData->get(node), vector{0, 1, 2}); + } + + auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 5/*numRows * numCols*/ }); + + // +// +// CONSTRAINT: Exactly one entrance, one exit, +// + hash_map> globalCardinalities; + globalCardinalities[ENTRANCE_IDX] = make_tuple(1, 1); // Min = 1, Max = 1 + globalCardinalities[EXIT_IDX] = make_tuple(1, 1); // Min = 1, Max = 1 + solver.cardinality(tileData->getData(), globalCardinalities); + + // +// Define a domain for edges between tiles. Each edge is either solid or empty. +// + +// Edge graphs per step: 0 = passable, 1 = impassable + SolverVariableDomain edgeDomain(0, 1); + const vector edge_Empty{ 0 }; + const vector edge_Solid{ 1 }; + + // Create the edge topology for the maze. This creates a parallel graph where each node in Edges corresponds + // to an edge in Grid. + auto edges = make_shared(ITopology::adapt(grid), true, false); + + auto selfTile = make_shared>(tileData); + + auto stepEdgeData = solver.makeVariableGraph(TEXT("EdgeVars"), ITopology::adapt(edges), edgeDomain, TEXT("Edge")); + + auto edgeNodeToEdgeVarRel = make_shared>(stepEdgeData); + + solver.makeConstraint(tileData, cell_Entrance, cell_Exit, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, shortestPathDistance); + + + // + // Solve! + // + for (int i = 0; i < times; ++i) + { + EConstraintSolverResult result = solver.startSolving(); + while (result == EConstraintSolverResult::Unsolved) + { + if (ATTEMPT_SOLUTION_AT >= 0 && solver.getStats().stepCount >= ATTEMPT_SOLUTION_AT) + { + solver.debugAttemptSolution(TEXT("MazeSolution.txt")); + } + + result = solver.step(); + // print out the maze every MAZE_REFRESH_RATE steps + if (printVerbose && MAZE_REFRESH_RATE > 0 && (solver.getStats().stepCount % MAZE_REFRESH_RATE) == 0) + { + //print(stepDatas[0], stepEdgeDatas[0], solver); + } + } + + // Print out the final maze! + if (printVerbose) + { + print(tileData, stepEdgeData, solver); + } + solver.dumpStats(printVerbose); + EATEST_VERIFY(result == EConstraintSolverResult::Solved); + } + + return nErrorCount; +} + int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printVerbose) { int nErrorCount = 0; diff --git a/vertexyTests/src/public/Maze.h b/vertexyTests/src/public/Maze.h index a3eb6aa..c4d381c 100644 --- a/vertexyTests/src/public/Maze.h +++ b/vertexyTests/src/public/Maze.h @@ -16,6 +16,8 @@ class MazeSolver } public: + static int solveSimple(int times, int numCols, int seed, bool printVerbose = true); + static int solve(int times, int numRows, int numCols, int seed, bool printVerbose=true); static int check(const shared_ptr>& Cells, const ConstraintSolver& solver); static void print(const shared_ptr>& cells, const shared_ptr>& edges, const ConstraintSolver& solver); From 388e7c6773a67fa6a90e947c08cce39aa255f241 Mon Sep 17 00:00:00 2001 From: Caue Waneck Date: Wed, 11 May 2022 17:55:24 -0300 Subject: [PATCH 12/17] Fixes so that the ReachabilityConstraint works again --- .../constraints/TopologySearchConstraint.cpp | 32 ++----------------- vertexyTestHarness/src/SolverTest.cpp | 4 +-- vertexyTests/src/private/Maze.cpp | 8 ++--- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 314550a..1ce0cb9 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -599,15 +599,8 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source } } - //vxy_assert(numReachableSources >= 1); - if (numReachableSources == 0) - { - bool success = db->constrainToValues(source, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); }); - vxy_assert(!success); - failure = true; - return ETopologySearchResponse::Abort; - } - else if (numReachableSources == 1) + vxy_assert(numReachableSources >= 1); + if (numReachableSources == 1) { if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); })) { @@ -778,26 +771,7 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: continue; } - auto determination = determineReachabilityHelper(db, it->second, vertexIndex, it->first); - if (determination != EReachabilityDetermination::DefinitelyUnreachable) - { - return determination; - } - //if (it->second.minReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.minReachability->getDistance(vertexIndex))) - //{ - // if (definitelyIsSource(db, it->first)) - // { - // return EReachabilityDetermination::DefinitelyReachable; - // } - // else - // { - // return EReachabilityDetermination::PossiblyReachable; - // } - //} - //else if (it->second.maxReachability->isReachable(vertexIndex) && isValidDistance(db, it->second.maxReachability->getDistance(vertexIndex))) - //{ - // return EReachabilityDetermination::PossiblyReachable; - //} + return determineReachabilityHelper(db, it->second, vertexIndex, it->first); } return EReachabilityDetermination::DefinitelyUnreachable; diff --git a/vertexyTestHarness/src/SolverTest.cpp b/vertexyTestHarness/src/SolverTest.cpp index 38b6a05..99eff2b 100644 --- a/vertexyTestHarness/src/SolverTest.cpp +++ b/vertexyTestHarness/src/SolverTest.cpp @@ -290,7 +290,7 @@ int main(int argc, char* argv[]) //Suite.AddTest("NQueens-Graph", []() { return NQueensSolvers::solveUsingGraph(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); //Suite.AddTest("KnightTourPacked", []() { return KnightTourSolver::solvePacked(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); //Suite.AddTest("KnightTour", []() { return KnightTourSolver::solveAtomic(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); - //Suite.AddTest("Maze", []() { return MazeSolver::solve(NUM_TIMES, MAZE_NUM_ROWS, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); - Suite.AddTest("MazeSimple", []() { return MazeSolver::solveSimple(NUM_TIMES, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); + Suite.AddTest("Maze", []() { return MazeSolver::solve(NUM_TIMES, MAZE_NUM_ROWS, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); + //Suite.AddTest("MazeSimple", []() { return MazeSolver::solveSimple(NUM_TIMES, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); return Suite.Run(); } diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index 2ae7fd1..acc30c0 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -121,7 +121,7 @@ int MazeSolver::solveSimple(int times, int numCols, int seed, bool printVerbose) auto edgeNodeToEdgeVarRel = make_shared>(stepEdgeData); - solver.makeConstraint(tileData, cell_Entrance, cell_Exit, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, shortestPathDistance); + solver.makeConstraint(tileData, cell_Entrance, cell_Exit, stepEdgeData, edge_Solid, EConstraintOperator::LessThan, shortestPathDistance); // @@ -706,15 +706,15 @@ void MazeSolver::print(const shared_ptr>& cells, cons for (int x = 0; x < numCols; ++x) { out.append_sprintf(TEXT("%d"), x); - if (x < 10) + if (x < 9) { out += TEXT(" "); } - if (x < 100) + if (x < 99) { out += TEXT(" "); } - if (PRINT_EDGES && x < 1000) + if (PRINT_EDGES && x < 999) { out += TEXT(" "); } From 07f041402bf6e0658b5ac0763a24dc182a15f345 Mon Sep 17 00:00:00 2001 From: Caue Waneck Date: Tue, 14 Jun 2022 17:11:53 -0300 Subject: [PATCH 13/17] Introduce the split between Valid vs Reachable This will make it easy to identify when we should call the reachability explainer, and when we should call the new validity explainer --- .../constraints/ReachabilityConstraint.cpp | 271 ++---------- .../constraints/ShortestPathConstraint.cpp | 147 +++---- .../constraints/TopologySearchConstraint.cpp | 406 ++++++++++++++---- .../constraints/ReachabilityConstraint.h | 6 +- .../constraints/ShortestPathConstraint.h | 6 +- .../constraints/TopologySearchConstraint.h | 32 +- 6 files changed, 436 insertions(+), 432 deletions(-) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index b700000..d9f0a5f 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -63,36 +63,6 @@ ReachabilityConstraint::ReachabilityConstraint( } -bool ReachabilityConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const -{ - return src.maxReachability->isReachable(vertex); -} - -ITopologySearchConstraint::EReachabilityDetermination ReachabilityConstraint::determineReachabilityHelper( - const IVariableDatabase* db, - const ReachabilitySourceData& src, - int vertex, - VarID srcVertex) const -{ - if (src.minReachability->isReachable(vertex)) - { - if (definitelyIsSource(db, srcVertex)) - { - return EReachabilityDetermination::DefinitelyReachable; - } - else - { - return EReachabilityDetermination::PossiblyReachable; - } - } - else if (src.maxReachability->isReachable(vertex)) - { - return EReachabilityDetermination::PossiblyReachable; - } - - return EReachabilityDetermination::DefinitelyUnreachable; -} - shared_ptr> ReachabilityConstraint::makeTopology(const shared_ptr& graph) const { return make_shared(graph, USE_RAMAL_REPS_BATCHING, true, false); @@ -106,7 +76,7 @@ EventListenerHandle ReachabilityConstraint::addMinCallback(RamalRepsType& minRea if (!m_backtracking && !m_explainingSourceRequirement) { vxy_assert(isReachable); - onReachabilityChanged(changedVertex, source, true); + onVertexChanged(changedVertex, source, true); } }); } @@ -118,244 +88,51 @@ EventListenerHandle ReachabilityConstraint::addMaxCallback(RamalRepsType& maxRea if (!m_backtracking && !m_explainingSourceRequirement) { vxy_assert(!isReachable); - onReachabilityChanged(changedVertex, source, false); + onVertexChanged(changedVertex, source, false); } }); } -vector ReachabilityConstraint::explainNoReachability(const NarrowingExplanationParams& params) const +//determine if it's still within the required range +void ReachabilityConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) { - //return IVariableDatabase::defaultExplainer(params); - - vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); - - auto db = params.database; - int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; - - vector lits; - lits.push_back(Literal(params.propagatedVariable, m_notReachableMask)); - - static hash_set edgeVarsRecorded; - edgeVarsRecorded.clear(); - - ValueSet visited; - visited.init(m_initialPotentialSources.size(), false); - - // For each source that could possibly exist... - for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) - { - if (visited[potentialSrcIndex]) - { - continue; - } - visited[potentialSrcIndex] = true; - - VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; - - int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; - if (sourceVertex == conflictVertex) - { - // Reachability sources cannot provide reachability to themselves - continue; - } - - // If this is currently a potential source... - if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) - { - // - // Find the minimum cut of edges that would make this reachable - // - - // Temporarily rewind the explanation graph to this time. - // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges - // will be the same state as when we processed the input variable. - - m_explanationGraph->rewindUntil(params.timestamp); - - vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); - - // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because - // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge - // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow - vector> cutEdges; - m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); - vxy_assert(!cutEdges.empty()); - - // Now that we've found the cut, bring the explanation graph back to current state. - m_explanationGraph->fastForward(); - - for (tuple& edge : cutEdges) - { - int edgeNode = m_edgeGraph->getVertexForSourceEdge(get<0>(edge), get<1>(edge)); - VarID edgeVar = m_edgeGraphData->get(edgeNode); - if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) - { - edgeVarsRecorded.insert(edgeVar); - - vxy_assert(!db->anyPossible(edgeVar, m_edgeOpenMask)); //hits this with distance = 100, 200 - lits.push_back(Literal(edgeVar, m_edgeOpenMask)); - } - } - - // For every other potential source, see if this graph also holds. It holds if the other source is on the - // same side as this source, hence would have to cross the same edge boundary. - for (int j = potentialSrcIndex + 1; j < m_initialPotentialSources.size(); ++j) - { - if (visited[j]) - { - continue; - } - - int vertex = m_variableToSourceVertexIndex.find(potentialSource)->second; - if (vertex == conflictVertex) - { - continue; - } - - if (db->getPotentialValues(m_initialPotentialSources[j]).anyPossible(m_sourceMask)) - { - if (!m_maxFlowAlgo.onSinkSide(vertex, m_flowGraphEdges, m_flowGraphLookup)) - { - visited[j] = true; - } - } - } - } - // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it - // was. - else - { - lits.push_back(Literal(potentialSource, m_sourceMask)); - } - } - - return lits; -} - -vector ReachabilityConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) -{ - //return IVariableDatabase::defaultExplainer(params); - + vxy_assert(!m_backtracking); vxy_assert(!m_explainingSourceRequirement); - ValueGuard guard(m_explainingSourceRequirement, true); - VarID sourceVar = params.propagatedVariable; - int sourceVertex = m_variableToSourceVertexIndex[sourceVar]; + vxy_assert(m_edgeChangeDb != nullptr); + vxy_assert(m_inEdgeChange); - auto db = params.database; - - vector lits; - lits.push_back(Literal(sourceVar, m_sourceMask)); - - m_maxGraph->rewindUntil(params.timestamp); - -#if REACHABILITY_USE_RAMAL_REPS + if (m_edgeChangeFailure) { - // Batch-update to rewound graph state - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - it->second.maxReachability->refresh(); - } + // We already failed - avoid further failures that could confuse the conflict analyzer + return; } -#endif - // Create any sources that might've gotten removed since this happened. - // (We'll clean them up after) - vector tempSources; - for (VarID potentialSource : m_initialPotentialSources) + if (inMinGraph) { - if (db->anyPossible(potentialSource, m_sourceMask)) + // See if this vertex is definitely reachable by any source now + if (determineValidity(m_edgeChangeDb, vertexIndex) == EValidityDetermination::DefinitelyValid) { - if (m_reachabilitySources.find(potentialSource) == m_reachabilitySources.end()) + VarID var = m_sourceGraphData->get(vertexIndex); + if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_requireValidMask, this)) { - tempSources.push_back(potentialSource); - - int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; - - ReachabilitySourceData data; -#if REACHABILITY_USE_RAMAL_REPS - data.maxReachability = make_shared(m_maxGraph, false, false, false); -#else - Data.maxReachability = make_shared(MaxGraph); -#endif - data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); - - m_reachabilitySources[potentialSource] = data; + m_edgeChangeFailure = true; } } - else if (potentialSource != sourceVar) - { - lits.push_back(Literal(potentialSource, m_sourceMask)); - } - } - - int removedSourceLitIdx = -1; - if (removedSource.isValid()) - { - // This became a required source because RemovedSource was removed, and some definitely-reachable vertices were - // only reachable by this source. - vxy_assert(!db->anyPossible(removedSource, m_sourceMask)); - removedSourceLitIdx = indexOfPredicate(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == removedSource; }); - vxy_assert(removedSourceLitIdx >= 0); } - - // - // This became a required source because some variable(s) were marked as required, and we are the only - // source that can reach them. Find those variables. - // - bool foundSupports = false; - auto& ourReachability = m_reachabilitySources[sourceVar].maxReachability; - auto searchCallback = [&](int vertex, int parent, int edgeIndex) + else { - if (ourReachability->isReachable(vertex)) + // vertexIndex became unreachable in the max graph + if (determineValidity(m_edgeChangeDb, vertexIndex) == EValidityDetermination::DefinitelyUnreachable) { - VarID vertexVar = m_sourceGraphData->get(vertex); - if (!db->anyPossible(vertexVar, m_notReachableMask)) - { - bool reachableFromAnotherSource = false; - for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) - { - if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) - { - reachableFromAnotherSource = true; - break; - } - } + VarID var = m_sourceGraphData->get(vertexIndex); + //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); - if (!reachableFromAnotherSource) - { - vxy_assert(m_reachabilitySources[sourceVar].maxReachability->isReachable(vertex)); - // make sure we don't add the same literal twice! - auto found = find_if(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == vertexVar; }); - if (found != lits.end()) - { - vxy_assert(found->variable == vertexVar); - found->values.include(m_notReachableMask); - } - else - { - lits.push_back(Literal(vertexVar, m_notReachableMask)); - } - foundSupports = true; - } + if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) + { + m_edgeChangeFailure = true; } - return ETopologySearchResponse::Continue; - } - else - { - return ETopologySearchResponse::Skip; } - }; - m_dfs.search(*m_sourceGraph.get(), m_variableToSourceVertexIndex[sourceVar], searchCallback); - vxy_assert(foundSupports); - - for (VarID tempSource : tempSources) - { - m_reachabilitySources.erase(tempSource); } - - m_maxGraph->fastForward(); - return lits; } - #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 2301f0f..bf6900a 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -88,7 +88,7 @@ bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int di } //is possibly reachable -bool ShortestPathConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +bool ShortestPathConstraint::isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) { if (!src.maxReachability->isReachable(vertex)) { @@ -105,57 +105,39 @@ bool ShortestPathConstraint::isPossiblyReachable(const IVariableDatabase* db, co } } -ITopologySearchConstraint::EReachabilityDetermination ShortestPathConstraint::determineReachabilityHelper( +ITopologySearchConstraint::EValidityDetermination ShortestPathConstraint::determineValidityHelper( const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, - VarID srcVertex) const + VarID srcVertex) { - //if (src.minReachability->isReachable(vertex)) - //{ - // if (definitelyIsSource(db, srcVertex)) - // { - // return EReachabilityDetermination::DefinitelyReachable; - // } - // else - // { - // return EReachabilityDetermination::PossiblyReachable; - // } - //} - //else if (src.maxReachability->isReachable(vertex)) - //{ - // return EReachabilityDetermination::PossiblyReachable; - //} - - //return EReachabilityDetermination::DefinitelyUnreachable; - if (src.minReachability->isReachable(vertex)) { if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) && !isValidDistance(db, src.minReachability->getDistance(vertex))) { - return EReachabilityDetermination::DefinitelyUnreachable; + return EValidityDetermination::DefinitelyUnreachable; } if (definitelyIsSource(db, srcVertex)) { - return EReachabilityDetermination::DefinitelyReachable; + return EValidityDetermination::DefinitelyValid; } else { - return EReachabilityDetermination::PossiblyReachable; + return EValidityDetermination::PossiblyValid; } } else if (src.maxReachability->isReachable(vertex)) { if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) && !isValidDistance(db, src.maxReachability->getDistance(vertex))) { - return EReachabilityDetermination::DefinitelyUnreachable; + return EValidityDetermination::DefinitelyUnreachable; } - return EReachabilityDetermination::PossiblyReachable; + return EValidityDetermination::PossiblyValid; } - return EReachabilityDetermination::DefinitelyUnreachable; + return EValidityDetermination::DefinitelyUnreachable; } shared_ptr> ShortestPathConstraint::makeTopology(const shared_ptr& graph) const @@ -169,21 +151,7 @@ EventListenerHandle ShortestPathConstraint::addMinCallback(RamalRepsType& minRea { if (!m_backtracking && !m_explainingSourceRequirement) { - onReachabilityChanged(changedVertex, source, true); - //if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq)) - //{ - // if (isValidDistance(m_edgeChangeDb, distance)) - // { - // onReachabilityChanged(changedVertex, source, true); - // } - //} - //else - //{ - // if (minReachable.isReachable(changedVertex)) //TODO: could be an assert - // { - // onReachabilityChanged(changedVertex, source, true); - // } - //} + onVertexChanged(changedVertex, source, true); } }); } @@ -194,49 +162,68 @@ EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxRea { if (!m_backtracking && !m_explainingSourceRequirement) { - onReachabilityChanged(changedVertex, source, false); - //if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq)) - //{ - // if (!isValidDistance(m_edgeChangeDb, distance)) - // { - // onReachabilityChanged(changedVertex, source, false); - // } - //} - //else - //{ - // if (!maxReachable.isReachable(changedVertex)) - // { - // onReachabilityChanged(changedVertex, source, false); - // } - //} + onVertexChanged(changedVertex, source, false); } }); } -//EventListenerHandle ShortestPathConstraint::addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) -//{ -// return minReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) -// { -// if (!m_backtracking && !m_explainingSourceRequirement) -// { -// vxy_assert(isReachable); -// onReachabilityChanged(changedVertex, source, true); -// } -// }); -//} -// -//EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) -//{ -// return maxReachable.onReachabilityChanged.add([this, source](int changedVertex, bool isReachable) -// { -// if (!m_backtracking && !m_explainingSourceRequirement) -// { -// vxy_assert(!isReachable); -// onReachabilityChanged(changedVertex, source, false); -// } -// }); -//} +vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingExplanationParams& params) +{ + return IVariableDatabase::defaultExplainer(params); +} + +void ShortestPathConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) +{ + vxy_assert(!m_backtracking); + vxy_assert(!m_explainingSourceRequirement); + + vxy_assert(m_edgeChangeDb != nullptr); + vxy_assert(m_inEdgeChange); + + if (m_edgeChangeFailure) + { + // We already failed - avoid further failures that could confuse the conflict analyzer + return; + } + + auto reachability = determineValidity(m_edgeChangeDb, vertexIndex); + // See if this vertex is definitely reachable by any source now + //if (reachability == EReachabilityDetermination::DefinitelyValid) + //{ + // VarID var = m_sourceGraphData->get(vertexIndex); + // if (var.isValid()) + // { + // if (m_edgeChangeDb->getPotentialValues(var).allPossible(m_requireValidityMask)) + // { + // // only constrain if it's possible + // m_edgeChangeDb->constrainToValues(var, m_requireValidityMask, this); + // } + // } + //} + if (reachability == EValidityDetermination::DefinitelyUnreachable) + { + // vertexIndex became unreachable in the max graph + VarID var = m_sourceGraphData->get(vertexIndex); + //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + + if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) + { + m_edgeChangeFailure = true; + } + } + else if (reachability == EValidityDetermination::DefinitelyInvalid) + { + // vertexIndex became unreachable in the max graph + VarID var = m_sourceGraphData->get(vertexIndex); + //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + + if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) + { + m_edgeChangeFailure = true; + } + } +} #undef SANITY_CHECKS diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 1ce0cb9..95997e1 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -29,11 +29,11 @@ ITopologySearchConstraint::ITopologySearchConstraint( , m_maxGraph(make_shared()) , m_explanationGraph(make_shared()) , m_sourceMask(sourceMask) - , m_requireReachableMask(requireReachableMask) + , m_requireValidMask(requireReachableMask) , m_edgeBlockedMask(edgeBlockedMask) { m_notSourceMask = sourceMask.inverted(); - m_notReachableMask = requireReachableMask.inverted(); + m_invalidMask = requireReachableMask.inverted(); m_edgeOpenMask = edgeBlockedMask.inverted(); for (int i = 0; i < m_sourceGraph->getNumVertices(); ++i) @@ -338,18 +338,18 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) VarID vertexVar = m_sourceGraphData->get(vertex); if (vertexVar.isValid()) { - EReachabilityDetermination determination = determineReachability(db, vertex); + EValidityDetermination determination = determineValidity(db, vertex); - if (determination == EReachabilityDetermination::DefinitelyUnreachable) + if (determination == EValidityDetermination::DefinitelyUnreachable) { - if (!db->constrainToValues(vertexVar, m_notReachableMask, this)) + if (!db->constrainToValues(vertexVar, m_invalidMask, this)) { return false; } } - else if (determination == EReachabilityDetermination::DefinitelyReachable) + else if (determination == EValidityDetermination::DefinitelyValid) { - if (!db->constrainToValues(vertexVar, m_requireReachableMask, this)) + if (!db->constrainToValues(vertexVar, m_requireValidMask, this)) { return false; } @@ -380,7 +380,7 @@ bool ITopologySearchConstraint::onVariableNarrowed(IVariableDatabase* db, VarID const ValueSet& newValue = db->getPotentialValues(variable); if ((prevValue.anyPossible(m_sourceMask) && !newValue.anyPossible(m_sourceMask)) || - (prevValue.anyPossible(m_notReachableMask) && !newValue.anyPossible(m_notReachableMask))) + (prevValue.anyPossible(m_invalidMask) && !newValue.anyPossible(m_invalidMask))) { if (!contains(m_vertexProcessList.begin(), m_vertexProcessList.end(), variable)) { @@ -420,7 +420,7 @@ bool ITopologySearchConstraint::propagate(IVariableDatabase* db) m_edgeProcessList.clear(); #if REACHABILITY_USE_RAMAL_REPS - // Batch-update reachability for all edge changes. This will trigger OnReachabilityChanged callbacks. + // Batch-update reachability for all edge changes. This will trigger onVertexChanged callbacks. { vxy_assert(!m_edgeChangeFailure); ValueGuard guardEdgeChange(m_inEdgeChange, true); @@ -469,20 +469,22 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d } // If this now requires reachability... - if (!db->anyPossible(variable, m_notReachableMask)) + if (!db->anyPossible(variable, m_invalidMask)) { int vertex = m_variableToSourceVertexIndex[variable]; - int numReachableSources = 0; - VarID lastReachableSource = VarID::INVALID; + int numValidSources = 0; + VarID lastValidSource = VarID::INVALID; + bool hadAnyReachable = false; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (isPossiblyReachable(db, it->second, vertex) ) - //if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) + bool isReachable = isPossiblyReachable(db, it->second, vertex); + hadAnyReachable |= isReachable; + if (isReachable && isPossiblyValid(db, it->second, vertex) ) { - ++numReachableSources; - lastReachableSource = it->first; - if (numReachableSources > 1) + ++numValidSources; + lastValidSource = it->first; + if (numValidSources > 1) { break; } @@ -490,16 +492,16 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d } // If not reachable by any source, then fail - if (numReachableSources == 0) + if (numValidSources == 0) { - bool success = db->constrainToValues(variable, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); }); + bool success = db->constrainToValues(variable, m_invalidMask, this, [&](auto params) { return hadAnyReachable ? explainInvalid(params) : explainNoReachability(params); }); vxy_assert(!success); return false; } // If reachable by a single potential source, that single source must be definite - else if (numReachableSources == 1) + else if (numValidSources == 1) { - if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&](auto params) { return explainRequiredSource(params); })) + if (!db->constrainToValues(lastValidSource, m_sourceMask, this, [&](auto params) { return explainRequiredSource(params); })) { return false; } @@ -561,48 +563,56 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source bool failure = false; auto checkReachability = [&](int vertex, int parent) { - if (isPossiblyReachable(db, sourceData, vertex)) + if (isPossiblyReachable(db, sourceData, vertex) && isPossiblyValid(db, sourceData, vertex)) //if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(db, sourceData.maxReachability->getDistance(vertex))) { // This vertex is no longer reachable from the removed source, so might be definitely unreachable now VarID vertexVar = m_sourceGraphData->get(vertex); - if (vertexVar.isValid() && db->anyPossible(vertexVar, m_requireReachableMask)) + if (vertexVar.isValid() && db->anyPossible(vertexVar, m_requireValidMask)) { - EReachabilityDetermination determination = determineReachability(db, vertex); + EValidityDetermination determination = determineValidity(db, vertex); - if (determination == EReachabilityDetermination::DefinitelyUnreachable) + if (determination == EValidityDetermination::DefinitelyUnreachable) { //sanityCheckUnreachable(db, vertex); - if (!db->constrainToValues(vertexVar, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) + if (!db->constrainToValues(vertexVar, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) { failure = true; return ETopologySearchResponse::Abort; } } - else if (determination == EReachabilityDetermination::PossiblyReachable && !db->anyPossible(vertexVar, m_notReachableMask)) + else if (determination == EValidityDetermination::DefinitelyInvalid) + { + //sanityCheckUnreachable(db, vertex); + if (!db->constrainToValues(vertexVar, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) + { + failure = true; + return ETopologySearchResponse::Abort; + } + } + else if (determination == EValidityDetermination::PossiblyValid && !db->anyPossible(vertexVar, m_invalidMask)) { // The vertex is marked definitely reachable, but only possibly reachable in the graph. // If there is only a single potential source that reaches this vertex, then it must now definitely be a source. - VarID lastReachableSource = VarID::INVALID; - int numReachableSources = 0; + VarID lastValidSource = VarID::INVALID; + int numValidSources = 0; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (isPossiblyReachable(db, it->second, vertex)) - //if (it->second.maxReachability->isReachable(vertex) && isValidDistance(db, it->second.maxReachability->getDistance(vertex))) + if (isPossiblyReachable(db, it->second, vertex) && isPossiblyValid(db, it->second, vertex)) { - ++numReachableSources; - lastReachableSource = it->first; - if (numReachableSources > 1) + ++numValidSources; + lastValidSource = it->first; + if (numValidSources > 1) { break; } } } - vxy_assert(numReachableSources >= 1); - if (numReachableSources == 1) + vxy_assert(numValidSources >= 1); + if (numValidSources == 1) { - if (!db->constrainToValues(lastReachableSource, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); })) + if (!db->constrainToValues(lastValidSource, m_sourceMask, this, [&, source](auto params) { return explainRequiredSource(params, source); })) { failure = true; return ETopologySearchResponse::Abort; @@ -623,6 +633,37 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source return !failure; } +bool Vertexy::ITopologySearchConstraint::isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) +{ + // in the default implementation, it is always valid. implementors can override this for their invalid implementation + return true; +} + +ITopologySearchConstraint::EValidityDetermination Vertexy::ITopologySearchConstraint::determineValidityHelper( + const IVariableDatabase* db, + const ReachabilitySourceData& src, + int vertex, + VarID srcVertex) +{ + if (src.minReachability->isReachable(vertex)) + { + if (definitelyIsSource(db, srcVertex)) + { + return EValidityDetermination::DefinitelyValid; + } + else + { + return EValidityDetermination::PossiblyValid; + } + } + else if (src.maxReachability->isReachable(vertex)) + { + return EValidityDetermination::PossiblyValid; + } + + return EValidityDetermination::DefinitelyUnreachable; +} + void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable) { vxy_assert(!m_inEdgeChange); @@ -681,49 +722,6 @@ void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, } } -//determine if it's still within the required range -void ITopologySearchConstraint::onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) -{ - vxy_assert(!m_backtracking); - vxy_assert(!m_explainingSourceRequirement); - - vxy_assert(m_edgeChangeDb != nullptr); - vxy_assert(m_inEdgeChange); - - if (m_edgeChangeFailure) - { - // We already failed - avoid further failures that could confuse the conflict analyzer - return; - } - - if (inMinGraph) - { - // See if this vertex is definitely reachable by any source now - if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyReachable) - { - VarID var = m_sourceGraphData->get(vertexIndex); - if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_requireReachableMask, this)) - { - m_edgeChangeFailure = true; - } - } - } - else - { - // vertexIndex became unreachable in the max graph - if (determineReachability(m_edgeChangeDb, vertexIndex) == EReachabilityDetermination::DefinitelyUnreachable) - { - VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); - - if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_notReachableMask, this, [&](auto params) { return explainNoReachability(params); })) - { - m_edgeChangeFailure = true; - } - } - } -} - void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) { vxy_assert(!m_edgeChangeFailure); @@ -759,9 +757,10 @@ void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDec #endif } -ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint::determineReachability(const IVariableDatabase* db, int vertexIndex) +ITopologySearchConstraint::EValidityDetermination ITopologySearchConstraint::determineValidity(const IVariableDatabase* db, int vertexIndex) { VarID vertexVar = m_sourceGraphData->get(vertexIndex); + bool hadAnyInvalid = false; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { if (it->first == vertexVar) @@ -771,10 +770,20 @@ ITopologySearchConstraint::EReachabilityDetermination ITopologySearchConstraint: continue; } - return determineReachabilityHelper(db, it->second, vertexIndex, it->first); + auto validity = determineValidityHelper(db, it->second, vertexIndex, it->first); + switch (validity) + { + case EValidityDetermination::DefinitelyInvalid: + hadAnyInvalid = true; + break; + case EValidityDetermination::DefinitelyUnreachable: + break; + default: + return validity; + } } - return EReachabilityDetermination::DefinitelyUnreachable; + return hadAnyInvalid ? EValidityDetermination::DefinitelyInvalid : EValidityDetermination::DefinitelyUnreachable; } // Called whenever an edge is added or removed from the explanation graph, including during backtracking @@ -794,14 +803,241 @@ void ITopologySearchConstraint::onExplanationGraphEdgeChange(bool edgeWasAdded, vector ITopologySearchConstraint::explainNoReachability(const NarrowingExplanationParams& params) const { - return IVariableDatabase::defaultExplainer(params); + vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); + + auto db = params.database; + int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; + + vector lits; + lits.push_back(Literal(params.propagatedVariable, m_invalidMask)); + + static hash_set edgeVarsRecorded; + edgeVarsRecorded.clear(); + + ValueSet visited; + visited.init(m_initialPotentialSources.size(), false); + + // For each source that could possibly exist... + for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) + { + if (visited[potentialSrcIndex]) + { + continue; + } + visited[potentialSrcIndex] = true; + + VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; + + int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (sourceVertex == conflictVertex) + { + // Reachability sources cannot provide reachability to themselves + continue; + } + + // If this is currently a potential source... + if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) + { + // + // Find the minimum cut of edges that would make this reachable + // + + // Temporarily rewind the explanation graph to this time. + // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges + // will be the same state as when we processed the input variable. + + m_explanationGraph->rewindUntil(params.timestamp); + + vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); + + // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because + // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge + // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow + vector> cutEdges; + m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); + vxy_assert(!cutEdges.empty()); + + // Now that we've found the cut, bring the explanation graph back to current state. + m_explanationGraph->fastForward(); + + for (tuple& edge : cutEdges) + { + int edgeNode = m_edgeGraph->getVertexForSourceEdge(get<0>(edge), get<1>(edge)); + VarID edgeVar = m_edgeGraphData->get(edgeNode); + if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) + { + edgeVarsRecorded.insert(edgeVar); + + vxy_assert(!db->anyPossible(edgeVar, m_edgeOpenMask)); //hits this with distance = 100, 200 + lits.push_back(Literal(edgeVar, m_edgeOpenMask)); + } + } + + // For every other potential source, see if this graph also holds. It holds if the other source is on the + // same side as this source, hence would have to cross the same edge boundary. + for (int j = potentialSrcIndex + 1; j < m_initialPotentialSources.size(); ++j) + { + if (visited[j]) + { + continue; + } + + int vertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (vertex == conflictVertex) + { + continue; + } + + if (db->getPotentialValues(m_initialPotentialSources[j]).anyPossible(m_sourceMask)) + { + if (!m_maxFlowAlgo.onSinkSide(vertex, m_flowGraphEdges, m_flowGraphLookup)) + { + visited[j] = true; + } + } + } + } + // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it + // was. + else + { + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } + + return lits; } vector ITopologySearchConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) { - return IVariableDatabase::defaultExplainer(params); + //return IVariableDatabase::defaultExplainer(params); + + vxy_assert(!m_explainingSourceRequirement); + ValueGuard guard(m_explainingSourceRequirement, true); + + VarID sourceVar = params.propagatedVariable; + int sourceVertex = m_variableToSourceVertexIndex[sourceVar]; + + auto db = params.database; + + vector lits; + lits.push_back(Literal(sourceVar, m_sourceMask)); + + m_maxGraph->rewindUntil(params.timestamp); + +#if REACHABILITY_USE_RAMAL_REPS + { + // Batch-update to rewound graph state + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + it->second.maxReachability->refresh(); + } + } +#endif + + // Create any sources that might've gotten removed since this happened. + // (We'll clean them up after) + vector tempSources; + for (VarID potentialSource : m_initialPotentialSources) + { + if (db->anyPossible(potentialSource, m_sourceMask)) + { + if (m_reachabilitySources.find(potentialSource) == m_reachabilitySources.end()) + { + tempSources.push_back(potentialSource); + + int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; + + ReachabilitySourceData data; +#if REACHABILITY_USE_RAMAL_REPS + data.maxReachability = make_shared(m_maxGraph, false, false, false); +#else + Data.maxReachability = make_shared(MaxGraph); +#endif + data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); + + m_reachabilitySources[potentialSource] = data; + } + } + else if (potentialSource != sourceVar) + { + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } + + int removedSourceLitIdx = -1; + if (removedSource.isValid()) + { + // This became a required source because RemovedSource was removed, and some definitely-reachable vertices were + // only reachable by this source. + vxy_assert(!db->anyPossible(removedSource, m_sourceMask)); + removedSourceLitIdx = indexOfPredicate(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == removedSource; }); + vxy_assert(removedSourceLitIdx >= 0); + } + + // + // This became a required source because some variable(s) were marked as required, and we are the only + // source that can reach them. Find those variables. + // + bool foundSupports = false; + auto& ourReachability = m_reachabilitySources[sourceVar].maxReachability; + auto searchCallback = [&](int vertex, int parent, int edgeIndex) + { + if (ourReachability->isReachable(vertex)) + { + VarID vertexVar = m_sourceGraphData->get(vertex); + if (!db->anyPossible(vertexVar, m_invalidMask)) + { + bool reachableFromAnotherSource = false; + for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) + { + if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) + { + reachableFromAnotherSource = true; + break; + } + } + + if (!reachableFromAnotherSource) + { + vxy_assert(m_reachabilitySources[sourceVar].maxReachability->isReachable(vertex)); + // make sure we don't add the same literal twice! + auto found = find_if(lits.begin(), lits.end(), [&](auto& lit) { return lit.variable == vertexVar; }); + if (found != lits.end()) + { + vxy_assert(found->variable == vertexVar); + found->values.include(m_invalidMask); + } + else + { + lits.push_back(Literal(vertexVar, m_invalidMask)); + } + foundSupports = true; + } + } + return ETopologySearchResponse::Continue; + } + else + { + return ETopologySearchResponse::Skip; + } + }; + m_dfs.search(*m_sourceGraph.get(), m_variableToSourceVertexIndex[sourceVar], searchCallback); + vxy_assert(foundSupports); + + for (VarID tempSource : tempSources) + { + m_reachabilitySources.erase(tempSource); + } + + m_maxGraph->fastForward(); + return lits; } +bool ITopologySearchConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +{ + return src.maxReachability->isReachable(vertex); +} void ITopologySearchConstraint::sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex) { diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index b3ebcba..e1dcdb3 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -56,15 +56,11 @@ class ReachabilityConstraint : public ITopologySearchConstraint protected: - virtual bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; - virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; - virtual vector explainNoReachability(const NarrowingExplanationParams& params) const override; - virtual vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID) override; - + void onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); }; } // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index c49f297..04b1e20 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -63,11 +63,13 @@ class ShortestPathConstraint : public ITopologySearchConstraint bool isValidDistance(const IVariableDatabase* db, int dist) const; - virtual bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const override; - virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const override; + virtual bool isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) override; + virtual EValidityDetermination determineValidityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) override; virtual shared_ptr makeTopology(const shared_ptr& graph) const override; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; + virtual vector explainInvalid(const NarrowingExplanationParams& params) override; + void onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); EConstraintOperator m_op; VarID m_distance; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index ac7df2d..1c3a103 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -40,26 +40,31 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint virtual bool getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const override; protected: - //for now don't worry about explainers - virtual vector explainNoReachability(const NarrowingExplanationParams& params) const; - virtual vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); + vector explainNoReachability(const NarrowingExplanationParams& params) const; + vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); + virtual vector explainInvalid(const NarrowingExplanationParams& params) + { + vxy_fail(); // override this if using DefinitelyInvalid + return vector(); + } - enum class EReachabilityDetermination : uint8_t + enum class EValidityDetermination : uint8_t { - DefinitelyReachable, + DefinitelyValid, // Reachable from a definite source - PossiblyReachable, + PossiblyValid, // Reachable from a possible source DefinitelyUnreachable, // Unreachable from any possible source + DefinitelyInvalid, + // Invalid from any possible source }; - EReachabilityDetermination determineReachability(const IVariableDatabase* db, int vertex); + EValidityDetermination determineValidity(const IVariableDatabase* db, int vertex); //used by propagate, NEEDS TO CHANGE bool processVertexVariableChange(IVariableDatabase* db, VarID variable); //used by propagate void updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable); - void onReachabilityChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); void sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex); void onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to); @@ -73,15 +78,16 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint struct ReachabilitySourceData; //virtual - virtual bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const = 0; - virtual EReachabilityDetermination determineReachabilityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex) const = 0; + bool isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const; + virtual bool isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex); + virtual EValidityDetermination determineValidityHelper(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, VarID srcVertex); virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) = 0; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) = 0; inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const { - return !db->getPotentialValues(var).anyPossible(m_notReachableMask); + return !db->getPotentialValues(var).anyPossible(m_invalidMask); } inline bool definitelyIsSource(const IVariableDatabase* db, VarID var) const @@ -147,8 +153,8 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint ValueSet m_sourceMask; ValueSet m_notSourceMask; - ValueSet m_requireReachableMask; - ValueSet m_notReachableMask; + ValueSet m_requireValidMask; + ValueSet m_invalidMask; ValueSet m_edgeBlockedMask; ValueSet m_edgeOpenMask; From 4944159a4cb62e49ae6adc886e671cedcb7e1a94 Mon Sep 17 00:00:00 2001 From: Caue Waneck Date: Wed, 15 Jun 2022 11:17:30 -0300 Subject: [PATCH 14/17] Working shortest path constraint The explainer is still the default explainer at this point --- .../constraints/ShortestPathConstraint.cpp | 98 +++++++++++++++---- .../constraints/TopologySearchConstraint.cpp | 29 ++++-- .../constraints/ShortestPathConstraint.h | 1 + .../constraints/TopologySearchConstraint.h | 1 + vertexyTestHarness/src/SolverTest.cpp | 2 +- vertexyTests/src/private/Maze.cpp | 16 ++- 6 files changed, 114 insertions(+), 33 deletions(-) diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index bf6900a..04659c7 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -90,19 +90,43 @@ bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int di //is possibly reachable bool ShortestPathConstraint::isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) { - if (!src.maxReachability->isReachable(vertex)) + // we do not need to check for reachability here because we always call isPossiblyReachable before calling this + if (m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) { - return false; - } - - if (m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) - { - return isValidDistance(db, src.maxReachability->getDistance(vertex)); + if (src.minReachability->isReachable(vertex)) + { + if (!isValidDistance(db, src.minReachability->getDistance(vertex))) + { + // the shortest path is too short, so our vertex is definitely unreachable (ie it cannot be a target) + return false; + } + else + { + return true; + } + } + else if (src.maxReachability->isReachable(vertex)) + { + return true; + } } else { - return isValidDistance(db, src.minReachability->getDistance(vertex)); + if (src.minReachability->isReachable(vertex) && isValidDistance(db, src.minReachability->getDistance(vertex))) + { + return true; + } + else if (src.maxReachability->isReachable(vertex)) + { + if (!isValidDistance(db, src.maxReachability->getDistance(vertex))) + { + return false; + } + + return true; + } } + return false; } ITopologySearchConstraint::EValidityDetermination ShortestPathConstraint::determineValidityHelper( @@ -111,30 +135,53 @@ ITopologySearchConstraint::EValidityDetermination ShortestPathConstraint::determ int vertex, VarID srcVertex) { - if (src.minReachability->isReachable(vertex)) + if (m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) { - if ((m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) && !isValidDistance(db, src.minReachability->getDistance(vertex))) + if (src.minReachability->isReachable(vertex)) { - return EValidityDetermination::DefinitelyUnreachable; - } + if (!isValidDistance(db, src.minReachability->getDistance(vertex))) + { + // the shortest path is too short, so our vertex is definitely unreachable (ie it cannot be a target) + return EValidityDetermination::DefinitelyInvalid; + } - if (definitelyIsSource(db, srcVertex)) - { - return EValidityDetermination::DefinitelyValid; + if (definitelyIsSource(db, srcVertex)) + { + return EValidityDetermination::DefinitelyValid; + } + else + { + return EValidityDetermination::PossiblyValid; + } } - else + else if (src.maxReachability->isReachable(vertex)) { return EValidityDetermination::PossiblyValid; } } - else if (src.maxReachability->isReachable(vertex)) + else { - if ((m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) && !isValidDistance(db, src.maxReachability->getDistance(vertex))) + if (src.minReachability->isReachable(vertex) && isValidDistance(db, src.minReachability->getDistance(vertex))) { - return EValidityDetermination::DefinitelyUnreachable; + if (definitelyIsSource(db, srcVertex)) + { + return EValidityDetermination::DefinitelyValid; + } + else + { + return EValidityDetermination::PossiblyValid; + } } + else if (src.maxReachability->isReachable(vertex)) + { + if (!isValidDistance(db, src.maxReachability->getDistance(vertex))) + { + // undo any changes we have made during the current timestamp while processing all changes + return EValidityDetermination::DefinitelyInvalid; + } - return EValidityDetermination::PossiblyValid; + return EValidityDetermination::PossiblyValid; + } } return EValidityDetermination::DefinitelyUnreachable; @@ -172,6 +219,17 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE return IVariableDatabase::defaultExplainer(params); } +void Vertexy::ShortestPathConstraint::createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const +{ + ITopologySearchConstraint::createTempSourceData(data, vertexIndex); +#if REACHABILITY_USE_RAMAL_REPS + data.minReachability = make_shared(m_minGraph, false, false, false); +#else + Data.minReachability = make_shared(MinGraph); +#endif + data.minReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); +} + void ShortestPathConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph) { vxy_assert(!m_backtracking); diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index 95997e1..e2b4e20 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -664,6 +664,16 @@ ITopologySearchConstraint::EValidityDetermination Vertexy::ITopologySearchConstr return EValidityDetermination::DefinitelyUnreachable; } +void Vertexy::ITopologySearchConstraint::createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const +{ +#if REACHABILITY_USE_RAMAL_REPS + data.maxReachability = make_shared(m_maxGraph, false, false, false); +#else + Data.maxReachability = make_shared(MaxGraph); +#endif + data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); +} + void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable) { vxy_assert(!m_inEdgeChange); @@ -854,7 +864,15 @@ vector ITopologySearchConstraint::explainNoReachability(const Narrowing // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge // B) blocked edges are set to a flow capacity of 1, and blocked edges have infinite flow vector> cutEdges; - m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); + // first check if the source and conflict have a common edge, since getMaxFlow doesn't work in this case + if (m_edgeGraph->getVertexForSourceEdge(sourceVertex, conflictVertex) == -1) + { + m_maxFlowAlgo.getMaxFlow(*m_sourceGraph.get(), sourceVertex, conflictVertex, m_flowGraphEdges, m_flowGraphLookup, &cutEdges); + } + else + { + cutEdges.push_back(make_tuple(sourceVertex, conflictVertex)); + } vxy_assert(!cutEdges.empty()); // Now that we've found the cut, bring the explanation graph back to current state. @@ -949,12 +967,7 @@ vector ITopologySearchConstraint::explainRequiredSource(const Narrowing int vertexIndex = m_variableToSourceVertexIndex[potentialSource]; ReachabilitySourceData data; -#if REACHABILITY_USE_RAMAL_REPS - data.maxReachability = make_shared(m_maxGraph, false, false, false); -#else - Data.maxReachability = make_shared(MaxGraph); -#endif - data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); + createTempSourceData(data, vertexIndex); m_reachabilitySources[potentialSource] = data; } @@ -991,7 +1004,7 @@ vector ITopologySearchConstraint::explainRequiredSource(const Narrowing bool reachableFromAnotherSource = false; for (auto it = m_reachabilitySources.begin(), itEnd = m_reachabilitySources.end(); it != itEnd; ++it) { - if (it->first != sourceVar && it->first != vertexVar && it->second.maxReachability->isReachable(vertex)) + if (it->first != sourceVar && it->first != vertexVar && isPossiblyReachable(db, it->second, vertex) && isPossiblyValid(db, it->second, vertex)) { reachableFromAnotherSource = true; break; diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index 04b1e20..2b4ddbd 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -69,6 +69,7 @@ class ShortestPathConstraint : public ITopologySearchConstraint virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; virtual vector explainInvalid(const NarrowingExplanationParams& params) override; + virtual void createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const override; void onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); EConstraintOperator m_op; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index 1c3a103..feebab5 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -84,6 +84,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint virtual shared_ptr makeTopology(const shared_ptr& graph) const = 0; virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) = 0; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) = 0; + virtual void createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const; inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const { diff --git a/vertexyTestHarness/src/SolverTest.cpp b/vertexyTestHarness/src/SolverTest.cpp index 99eff2b..e047671 100644 --- a/vertexyTestHarness/src/SolverTest.cpp +++ b/vertexyTestHarness/src/SolverTest.cpp @@ -290,7 +290,7 @@ int main(int argc, char* argv[]) //Suite.AddTest("NQueens-Graph", []() { return NQueensSolvers::solveUsingGraph(NUM_TIMES, NQUEENS_SIZE, FORCE_SEED, PRINT_VERBOSE); }); //Suite.AddTest("KnightTourPacked", []() { return KnightTourSolver::solvePacked(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); //Suite.AddTest("KnightTour", []() { return KnightTourSolver::solveAtomic(NUM_TIMES, KNIGHT_BOARD_DIM, FORCE_SEED, PRINT_VERBOSE); }); + Suite.AddTest("MazeSimple", []() { return MazeSolver::solveSimple(NUM_TIMES, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); Suite.AddTest("Maze", []() { return MazeSolver::solve(NUM_TIMES, MAZE_NUM_ROWS, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); - //Suite.AddTest("MazeSimple", []() { return MazeSolver::solveSimple(NUM_TIMES, MAZE_NUM_COLS, FORCE_SEED, PRINT_VERBOSE); }); return Suite.Run(); } diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index acc30c0..40aa258 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -20,7 +20,7 @@ static constexpr int MAZE_REFRESH_RATE = 0; // The number of keys/doors that should exist in the maze. static constexpr int NUM_KEYS = 1; // Test Shortest Path constraint (slow), recommend 1 key. -static constexpr bool TEST_SHORTEST_PATH = false; +static constexpr bool TEST_SHORTEST_PATH = true; // True to print edge variables in FMaze::Print static constexpr bool PRINT_EDGES = false; // Whether to write a decision log as DecisionLog.txt @@ -91,7 +91,7 @@ int MazeSolver::solveSimple(int times, int numCols, int seed, bool printVerbose) solver.setInitialValues(tileData->get(node), vector{0, 1, 2}); } - auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 5/*numRows * numCols*/ }); + auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 5 }); // // @@ -256,7 +256,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto downTile = make_shared>(tileData, PlanarGridTopology::moveDown()); auto downRightTile = make_shared>(tileData, PlanarGridTopology::moveDown().combine(PlanarGridTopology::moveRight())); - auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 0/*numRows * numCols*/}); + auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 10 }); // // DECLARE CONSTRAINTS // @@ -464,6 +464,14 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV GraphRelationClause(selfTile, cell_Wall) ); + if (TEST_SHORTEST_PATH) + { + solver.makeGraphConstraint(grid, ENoGood::NoGood, + GraphRelationClause(selfStepDoorTile, step_ReachableOrOrigin), + GraphRelationClause(selfTile, cell_Wall) + ); + } + // If we don't have all the keys at this step... if (step < NUM_KEYS) { @@ -563,7 +571,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV // Ensure reachability for this step: all Step_Reachable cells must be reachable from Step_Origin cells. if (TEST_SHORTEST_PATH) { - solver.makeConstraint(stepDoorData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, shortestPathDistance); + solver.makeConstraint(stepDoorData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::LessThan, shortestPathDistance); solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid); } else From 092f5c072d14984c828071c6b8871b7164a0eeda Mon Sep 17 00:00:00 2001 From: Caue Waneck Date: Wed, 15 Jun 2022 17:46:14 -0300 Subject: [PATCH 15/17] Initial explainInvalid implementation --- .../constraints/ShortestPathConstraint.cpp | 350 +++++++++++++++++- .../constraints/TopologySearchConstraint.cpp | 85 +++-- .../constraints/ShortestPathConstraint.h | 24 +- .../constraints/TopologySearchConstraint.h | 21 +- vertexy/src/public/ds/BacktrackableValue.h | 81 ++++ .../topology/BacktrackingDigraphTopology.h | 2 +- .../src/public/topology/algo/ShortestPath.h | 2 +- 7 files changed, 522 insertions(+), 43 deletions(-) create mode 100644 vertexy/src/public/ds/BacktrackableValue.h diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 04659c7..8186b57 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -67,6 +67,18 @@ ShortestPathConstraint::ShortestPathConstraint( , m_distance(distance) { vxy_assert(m_op != EConstraintOperator::NotEqual); //NotEqual not supported + if (m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) + { + this->m_explanationMinGraph = make_shared(); + } +} + +void ShortestPathConstraint::onInitialArcConsistency(IVariableDatabase* db) +{ + if (this->m_op == EConstraintOperator::LessThan || this->m_op == EConstraintOperator::LessThanEq) + { + m_lastValidTimestamp.clear(); + } } bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int dist) const @@ -120,9 +132,11 @@ bool ShortestPathConstraint::isPossiblyValid(const IVariableDatabase* db, const { if (!isValidDistance(db, src.maxReachability->getDistance(vertex))) { + removeTimestampToCommit(db, src.vertex, vertex); return false; } + addTimestampToCommit(db, src.vertex, vertex); return true; } } @@ -177,9 +191,11 @@ ITopologySearchConstraint::EValidityDetermination ShortestPathConstraint::determ if (!isValidDistance(db, src.maxReachability->getDistance(vertex))) { // undo any changes we have made during the current timestamp while processing all changes + removeTimestampToCommit(db, src.vertex, vertex); return EValidityDetermination::DefinitelyInvalid; } + addTimestampToCommit(db, src.vertex, vertex); return EValidityDetermination::PossiblyValid; } } @@ -216,7 +232,177 @@ EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxRea vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingExplanationParams& params) { - return IVariableDatabase::defaultExplainer(params); + // if we are here, it means that the path was reachable, but we marked it as invalid because of the distance constraint + vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); + + auto db = params.database; + int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; + + vector lits; + lits.push_back(Literal(params.propagatedVariable, m_invalidMask)); + + static hash_set edgeVarsRecorded; + edgeVarsRecorded.clear(); + + ValueSet visited; + visited.init(m_initialPotentialSources.size(), false); + + bool isGreaterOp = m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq; + { + bool hasAnyTimestamp = false; + auto foundLastTimestamp = m_lastValidTimestamp.find(conflictVertex); + if (foundLastTimestamp != m_lastValidTimestamp.end()) + { + for (auto& it : foundLastTimestamp->second) + { + if (it.second.hasValue()) + { + hasAnyTimestamp = true; + break; + } + } + } + + if (!hasAnyTimestamp) + { + // there was never a point where this vertex was valid + // the best explanation is that this edge is too far away / too close from any sources + //for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) + //{ + // VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; + // if (!db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) + // { + // lits.push_back(Literal(potentialSource, m_sourceMask)); + // } + // else + // { + // lits.push_back(Literal(potentialSource, m_sourceMask.inverted())); + // } + //} + //vxy_assert(false); //check if that's happening + return lits; + } + } + + bool foundAny = false; + bool hasUnprocessedSources = false; + // For each source that could possibly exist... + for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) + { + VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; + + int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (sourceVertex == conflictVertex) + { + // Reachability sources cannot provide reachability to themselves + continue; + } + + // check if it's reachable + auto reachabilitySource = m_reachabilitySources.find(potentialSource); + //if (reachabilitySource != m_reachabilitySources.end()) + //{ + // if (!reachabilitySource->second.maxReachability->isReachable(conflictVertex)) + // { + // // it's not reachable, let's let the reachability explainer kick in + // hasUnprocessedSources = true; + // continue; + // } + //} + + SolverTimestamp valueToRewind = -1; + { + auto& lastValid = m_lastValidTimestamp[conflictVertex].find(sourceVertex); + if (lastValid == m_lastValidTimestamp[conflictVertex].end() || !lastValid->second.hasValue()) + { + // there was never a possible path for the source + // ignore this when running shortest path + visited[potentialSrcIndex] = true; + continue; + } + valueToRewind = lastValid->second.getTimestamp(); + } + auto& graph = isGreaterOp ? m_explanationMinGraph : m_explanationMaxGraph; + + if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask) || reachabilitySource != m_reachabilitySources.end()) + { + vector path; + //if (getShortestPathForExplanation(valueToRewind, rewindMinGraph, sourceVertex, conflictVertex, path)) + graph->rewindUntil(valueToRewind); + auto found = m_shortestPathAlgo.find(*graph, sourceVertex, conflictVertex, path); + vxy_assert(found); + graph->fastForward(); + //if () + { + vxy_assert(isValidDistance(db, path.size() - 1)); + } + foundAny = true; + visited[potentialSrcIndex] = true; + // optimization - if the vertices are siblings, and greater than is used, we can safely say that + // this target is only reachable if this source is not a source + //if (isGreaterThan && path.size() == 2) + //{ + // lits.push_back(Literal(potentialSource, m_sourceMask.inverted().excluding(db->getPotentialValues(potentialSource)))); + // continue; + //} + auto& targetMask = isGreaterOp ? m_edgeBlockedMask : m_edgeOpenMask; + bool changedEdge = false; + for (int i = 0; i < (path.size() - 1); ++i) + { + // mark all edges that don't currently have the targetMask + int edge = m_edgeGraph->getVertexForSourceEdge(path[i], path[i + 1]); + auto edgeVar = m_edgeGraphData->get(edge); + if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) + { + if (!db->getValueBefore(edgeVar, params.timestamp).anyPossible(targetMask)) + { + //VERTEXY_LOG(" %s --> %s", db->getSolver()->getVariableName(edgeVar).c_str(), targetMask.toString().c_str()); + edgeVarsRecorded.insert(edgeVar); + lits.push_back(Literal(edgeVar, targetMask)); + changedEdge = true; + } + } + else + { + changedEdge = true; + } + } + vxy_assert(changedEdge); + } + else + { + // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it + // was. + lits.push_back(Literal(potentialSource, m_sourceMask)); + visited[potentialSrcIndex] = true; + foundAny = true; + } + } + vxy_assert(foundAny); + + if (hasUnprocessedSources) + { + // for all other sources, run the reachability explainer + auto otherLits = explainNoReachability(params, &visited); + // we can skip the first one since it's always the propagatedVariable + for (int i = 1; i < otherLits.size(); i++) + { + const auto& lit = otherLits[i]; + if (edgeVarsRecorded.find(lit.variable) == edgeVarsRecorded.end()) + { + // we don't need to add that edge to the edgeVarsRecorded because it was already deduped + lits.push_back(lit); + } + } + } + + //VERTEXY_LOG("BEGIN explainInvalid"); + //for (int i = 0; i < lits.size(); i++) + //{ + // VERTEXY_LOG(" %s --> %s", db->getSolver()->getVariableName(lits[i].variable).c_str(), lits[i].values.toString().c_str()); + //} + + return lits; } void Vertexy::ShortestPathConstraint::createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const @@ -244,26 +430,16 @@ void ShortestPathConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, b return; } + m_processingVertices = true; + m_queuedVertexChanges.insert(vertexIndex); auto reachability = determineValidity(m_edgeChangeDb, vertexIndex); // See if this vertex is definitely reachable by any source now - //if (reachability == EReachabilityDetermination::DefinitelyValid) - //{ - // VarID var = m_sourceGraphData->get(vertexIndex); - // if (var.isValid()) - // { - // if (m_edgeChangeDb->getPotentialValues(var).allPossible(m_requireValidityMask)) - // { - // // only constrain if it's possible - // m_edgeChangeDb->constrainToValues(var, m_requireValidityMask, this); - // } - // } - //} if (reachability == EValidityDetermination::DefinitelyUnreachable) { // vertexIndex became unreachable in the max graph VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + //sanityCheckUnreachable(db, vertexIndex); if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) { @@ -274,13 +450,157 @@ void ShortestPathConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, b { // vertexIndex became unreachable in the max graph VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + //sanityCheckUnreachable(db, vertexIndex); if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) { m_edgeChangeFailure = true; } } + m_processingVertices = false; +} + +void ShortestPathConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) +{ + auto stamp = db->getTimestamp(); + ITopologySearchConstraint::backtrack(db, level); + if (this->m_op == EConstraintOperator::LessThan || this->m_op == EConstraintOperator::LessThanEq) + { + for (auto& vertexData : m_lastValidTimestamp) + { + for (auto& srcData : vertexData.second) + { + srcData.second.backtrackUntil(stamp); + } + } + } + + //for (auto& it = m_cachedKeysToTimestamp.rbegin(); it != m_cachedKeysToTimestamp.rend(); it++) + //{ + // if (it->first <= stamp) + // { + // for (auto& sourceAndDest : it->second) + // { + // m_cachedShortestPaths.erase(sourceAndDest); + // } + // it = m_cachedKeysToTimestamp.erase(it); + // if (it == m_cachedKeysToTimestamp.rend()) + // { + // break; + // } + // } + //} +} + +void ShortestPathConstraint::commitValidTimestamps(const IVariableDatabase* db) +{ + for (auto& toCommitDest : m_validTimestampsToCommit) + { + auto& found = m_lastValidTimestamp.find(toCommitDest.first); + hash_map>* validTimestamp; + if (found == m_lastValidTimestamp.end()) + { + m_lastValidTimestamp[toCommitDest.first] = hash_map>(); + validTimestamp = &m_lastValidTimestamp[toCommitDest.first]; + } + else + { + validTimestamp = &found->second; + } + for (auto& value : toCommitDest.second) + { + // TODO cleanup + if (validTimestamp->find(value.first) == validTimestamp->end()) + { + TBacktrackableValue val; + val.set(value.second, value.second); + (*validTimestamp)[value.first] = val; + } + else + { + (*validTimestamp)[value.first].set(value.second, value.second); + } + } + } +} + +void ShortestPathConstraint::onEdgeChangeFailure(const IVariableDatabase* db) +{ + m_queuedVertexChanges.clear(); + m_validTimestampsToCommit.clear(); +} + +void ShortestPathConstraint::onEdgeChangeSuccess(const IVariableDatabase* db) +{ + commitValidTimestamps(db); + m_validTimestampsToCommit.clear(); +} + +void ShortestPathConstraint::addTimestampToCommit(const IVariableDatabase* db, int sourceVertex, int destVertex) +{ + if (m_explainingSourceRequirement || m_processingVertices) + { + return; + } + + auto timestamp = db->getTimestamp(); + if (m_lastValidTimestamp.find(destVertex) != m_lastValidTimestamp.end() && + m_lastValidTimestamp[destVertex].find(sourceVertex) != m_lastValidTimestamp[destVertex].end()) + { + auto& val = m_lastValidTimestamp[destVertex][sourceVertex]; + if (val.hasValue()) + { + vxy_assert(val.getTimestamp() <= timestamp); + } + } + + if (m_validTimestampsToCommit.find(destVertex) == m_validTimestampsToCommit.end()) + { + m_validTimestampsToCommit[destVertex] = hash_map(); + } + + m_validTimestampsToCommit[destVertex][sourceVertex] = timestamp; +} + +void ShortestPathConstraint::removeTimestampToCommit(const IVariableDatabase* db, int sourceVertex, int destVertex) +{ + m_validTimestampsToCommit.erase(sourceVertex); +} + +void Vertexy::ShortestPathConstraint::processQueuedVertexChanges(IVariableDatabase* db) +{ + m_validTimestampsToCommit.clear(); + for (auto vertexIndex : m_queuedVertexChanges) + { + auto reachability = determineValidity(db, vertexIndex); + + // See if this vertex is definitely reachable by any source now + if (reachability == EValidityDetermination::DefinitelyUnreachable) + { + // vertexIndex became unreachable in the max graph + VarID var = m_sourceGraphData->get(vertexIndex); + //sanityCheckUnreachable(db, vertexIndex); + + if (var.isValid() && !db->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) + { + m_edgeChangeFailure = true; + break; + } + } + else if (reachability == EValidityDetermination::DefinitelyInvalid) + { + // vertexIndex became unreachable in the max graph + VarID var = m_sourceGraphData->get(vertexIndex); + //sanityCheckUnreachable(db, vertexIndex); + + if (var.isValid() && !db->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) + { + m_edgeChangeFailure = true; + break; + } + } + } + m_queuedVertexChanges.clear(); } diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologySearchConstraint.cpp index e2b4e20..426f657 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologySearchConstraint.cpp @@ -27,7 +27,8 @@ ITopologySearchConstraint::ITopologySearchConstraint( , m_edgeGraph(edgeGraphData->getSource()->getImplementation()) , m_minGraph(make_shared()) , m_maxGraph(make_shared()) - , m_explanationGraph(make_shared()) + , m_explanationMaxGraph(make_shared()) + , m_explanationMinGraph(nullptr) , m_sourceMask(sourceMask) , m_requireValidMask(requireReachableMask) , m_edgeBlockedMask(edgeBlockedMask) @@ -192,9 +193,14 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) addedIdx = m_minGraph->addVertex(); vxy_assert(addedIdx == vertexIndex); - addedIdx = m_explanationGraph->addVertex(); + addedIdx = m_explanationMaxGraph->addVertex(); vxy_assert(addedIdx == vertexIndex); + if (m_explanationMinGraph) + { + addedIdx = m_explanationMinGraph->addVertex(); + vxy_assert(addedIdx == vertexIndex); + } if (vertexVar.isValid()) { m_vertexWatchHandles[vertexVar] = db->addVariableWatch(vertexVar, EVariableWatchType::WatchModification, this); @@ -237,7 +243,11 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) m_minGraph->initEdge(sourceVertex, destVertex); m_maxGraph->initEdge(sourceVertex, destVertex); - m_explanationGraph->initEdge(sourceVertex, destVertex); + m_explanationMaxGraph->initEdge(sourceVertex, destVertex); + if (m_explanationMinGraph) + { + m_explanationMinGraph->initEdge(sourceVertex, destVertex); + } } else if (possiblyOpenEdge(db, edgeVar)) { @@ -248,7 +258,7 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) m_edgeWatchHandles[edgeVar] = db->addVariableWatch(edgeVar, EVariableWatchType::WatchModification, &m_edgeWatcher); } m_maxGraph->initEdge(sourceVertex, destVertex); - m_explanationGraph->initEdge(sourceVertex, destVertex); + m_explanationMaxGraph->initEdge(sourceVertex, destVertex); } } else @@ -258,7 +268,11 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) // No variable for this edge, so should always exist m_minGraph->initEdge(sourceVertex, destVertex); m_maxGraph->initEdge(sourceVertex, destVertex); - m_explanationGraph->initEdge(sourceVertex, destVertex); + m_explanationMaxGraph->initEdge(sourceVertex, destVertex); + if (m_explanationMinGraph) + { + m_explanationMinGraph->initEdge(sourceVertex, destVertex); + } } foundVertexEdges->second[destVertex] = edgeIsClosed ? CLOSED_EDGE_FLOW : OPEN_EDGE_FLOW; @@ -315,9 +329,9 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) } // Register for callback when edges are added/removed from ExplanationGraph, in order to update capacities - m_explanationGraph->getEdgeChangeListener().add([&](bool edgeWasAdded, int from, int to) + m_explanationMaxGraph->getEdgeChangeListener().add([&](bool edgeWasAdded, int from, int to) { - onExplanationGraphEdgeChange(edgeWasAdded, from, to); + onExplanationMaxGraphEdgeChange(edgeWasAdded, from, to); }); // Create reachability structures for all variables that are possibly reachability sources @@ -344,6 +358,7 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) { if (!db->constrainToValues(vertexVar, m_invalidMask, this)) { + onEdgeChangeFailure(db); return false; } } @@ -351,11 +366,13 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) { if (!db->constrainToValues(vertexVar, m_requireValidMask, this)) { + onEdgeChangeFailure(db); return false; } } } } + onEdgeChangeSuccess(db); return true; } @@ -414,6 +431,7 @@ bool ITopologySearchConstraint::propagate(IVariableDatabase* db) updateGraphsForEdgeChange(db, edgeVar); if (m_edgeChangeFailure) { + onEdgeChangeFailure(db); return false; } } @@ -431,12 +449,14 @@ bool ITopologySearchConstraint::propagate(IVariableDatabase* db) it->second.maxReachability->refresh(); if (m_edgeChangeFailure) { + onEdgeChangeFailure(db); return false; } it->second.minReachability->refresh(); if (m_edgeChangeFailure) { + onEdgeChangeFailure(db); return false; } } @@ -445,15 +465,24 @@ bool ITopologySearchConstraint::propagate(IVariableDatabase* db) vxy_assert(!m_edgeChangeFailure); + processQueuedVertexChanges(db); + if (m_edgeChangeFailure) + { + onEdgeChangeFailure(db); + return false; + } + // Now that reachability info is up to date, process vertices for (VarID vertexVar : m_vertexProcessList) { if (!processVertexVariableChange(db, vertexVar)) { + onEdgeChangeFailure(db); return false; } } m_vertexProcessList.clear(); + onEdgeChangeSuccess(db); return true; } @@ -494,7 +523,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d // If not reachable by any source, then fail if (numValidSources == 0) { - bool success = db->constrainToValues(variable, m_invalidMask, this, [&](auto params) { return hadAnyReachable ? explainInvalid(params) : explainNoReachability(params); }); + bool success = db->constrainToValues(variable, m_invalidMask, this, [&,hadAnyReachable](auto params) { return hadAnyReachable ? explainInvalid(params) : explainNoReachability(params); }); vxy_assert(!success); return false; } @@ -529,8 +558,7 @@ void ITopologySearchConstraint::addSource(const IVariableDatabase* db, VarID sou EventListenerHandle minHandle = addMinCallback(*minReachable, db, source); EventListenerHandle maxHandle = addMaxCallback(*maxReachable, db, source); - m_reachabilitySources[source] = { minReachable, maxReachable, minHandle, maxHandle }; - + m_reachabilitySources[source] = { minReachable, maxReachable, minHandle, maxHandle, vertex }; } bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source) @@ -564,7 +592,6 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source auto checkReachability = [&](int vertex, int parent) { if (isPossiblyReachable(db, sourceData, vertex) && isPossiblyValid(db, sourceData, vertex)) - //if (sourceData.maxReachability->isReachable(vertex) && isValidDistance(db, sourceData.maxReachability->getDistance(vertex))) { // This vertex is no longer reachable from the removed source, so might be definitely unreachable now VarID vertexVar = m_sourceGraphData->get(vertex); @@ -706,6 +733,15 @@ void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, { m_minGraph->addEdge(to, from, db->getTimestamp()); } + + if (m_explanationMinGraph) + { + m_explanationMinGraph->addEdge(from, to, db->getTimestamp()); + if (bidirectional) + { + m_explanationMinGraph->addEdge(to, from, db->getTimestamp()); + } + } } } else if (db->anyPossible(variable, m_edgeBlockedMask) && !db->anyPossible(variable, m_edgeOpenMask)) @@ -717,10 +753,10 @@ void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, if (m_maxGraph->hasEdge(from, to)) { // Remove from the explanation graph first, so that we can sync to correct time. - m_explanationGraph->removeEdge(from, to, db->getTimestamp()); + m_explanationMaxGraph->removeEdge(from, to, db->getTimestamp()); if (bidirectional) { - m_explanationGraph->removeEdge(to, from, db->getTimestamp()); + m_explanationMaxGraph->removeEdge(to, from, db->getTimestamp()); } m_maxGraph->removeEdge(from, to, db->getTimestamp()); @@ -752,7 +788,11 @@ void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDec // Backtrack any edges added/removed after this point m_minGraph->backtrackUntil(db->getTimestamp()); m_maxGraph->backtrackUntil(db->getTimestamp()); - m_explanationGraph->backtrackUntil(db->getTimestamp()); + m_explanationMaxGraph->backtrackUntil(db->getTimestamp()); + if (m_explanationMinGraph) + { + m_explanationMinGraph->backtrackUntil(db->getTimestamp()); + } #if REACHABILITY_USE_RAMAL_REPS // Batch-update reachability for all edge changes @@ -797,7 +837,7 @@ ITopologySearchConstraint::EValidityDetermination ITopologySearchConstraint::det } // Called whenever an edge is added or removed from the explanation graph, including during backtracking -void ITopologySearchConstraint::onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to) +void ITopologySearchConstraint::onExplanationMaxGraphEdgeChange(bool edgeWasAdded, int from, int to) { // Keep the edge capacities in sync. for (int i = get<0>(m_flowGraphLookup[from]); i < get<1>(m_flowGraphLookup[from]); ++i) @@ -811,7 +851,7 @@ void ITopologySearchConstraint::onExplanationGraphEdgeChange(bool edgeWasAdded, vxy_fail(); } -vector ITopologySearchConstraint::explainNoReachability(const NarrowingExplanationParams& params) const +vector ITopologySearchConstraint::explainNoReachability(const NarrowingExplanationParams& params, ValueSet* alreadyVisitedSources) const { vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); @@ -824,8 +864,9 @@ vector ITopologySearchConstraint::explainNoReachability(const Narrowing static hash_set edgeVarsRecorded; edgeVarsRecorded.clear(); - ValueSet visited; - visited.init(m_initialPotentialSources.size(), false); + ValueSet curVisited; + curVisited.init(m_initialPotentialSources.size(), false); + ValueSet& visited = (alreadyVisitedSources != nullptr ? *alreadyVisitedSources : curVisited); // For each source that could possibly exist... for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) @@ -853,12 +894,12 @@ vector ITopologySearchConstraint::explainNoReachability(const Narrowing // // Temporarily rewind the explanation graph to this time. - // This will trigger OnExplanationGraphEdgeChange() for any edges re-added, so that FlowGraphEdges + // This will trigger onExplanationMaxGraphEdgeChange() for any edges re-added, so that FlowGraphEdges // will be the same state as when we processed the input variable. - m_explanationGraph->rewindUntil(params.timestamp); + m_explanationMaxGraph->rewindUntil(params.timestamp); - vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationGraph, sourceVertex, conflictVertex)); + vxy_sanity(!TopologySearchAlgorithm::canReach(m_explanationMaxGraph, sourceVertex, conflictVertex)); // Find the minimum cut in the maximum flow graph. This will correspond to edges that are disabled, because // A) we know that sourceVertex can't reach conflictVertex without going through a disabled edge @@ -876,7 +917,7 @@ vector ITopologySearchConstraint::explainNoReachability(const Narrowing vxy_assert(!cutEdges.empty()); // Now that we've found the cut, bring the explanation graph back to current state. - m_explanationGraph->fastForward(); + m_explanationMaxGraph->fastForward(); for (tuple& edge : cutEdges) { diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index 2b4ddbd..28ce08f 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -10,8 +10,10 @@ #include "topology/DigraphEdgeTopology.h" #include "topology/TopologyVertexData.h" #include "topology/algo/MaxFlowMinCut.h" +#include "topology/algo/ShortestPath.h" #include "variable/IVariableDatabase.h" -#include "constraints\ConstraintOperator.h" +#include "constraints/ConstraintOperator.h" +#include "ds/BacktrackableValue.h" #define REACHABILITY_USE_RAMAL_REPS 1 @@ -58,6 +60,7 @@ class ShortestPathConstraint : public ITopologySearchConstraint using Factory = ShortestPathFactory; virtual EConstraintType getConstraintType() const override { return EConstraintType::ShortestPath; } + virtual void onInitialArcConsistency(IVariableDatabase* db) override; protected: @@ -71,9 +74,28 @@ class ShortestPathConstraint : public ITopologySearchConstraint virtual vector explainInvalid(const NarrowingExplanationParams& params) override; virtual void createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const override; void onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); + void backtrack(const IVariableDatabase* db, SolverDecisionLevel level) override; EConstraintOperator m_op; VarID m_distance; + + // used to determine the path that is either too long or too short when explaning + ShortestPathAlgorithm m_shortestPathAlgo; + + // used when m_op is LessThan/LessThanEq and records the last timestamp in which a vertex was deemed valid + hash_map>> m_lastValidTimestamp; + hash_map> m_validTimestampsToCommit; + hash_set> m_alwaysForbiddenPaths; + + hash_set m_queuedVertexChanges; + bool m_processingVertices = false; + + void commitValidTimestamps(const IVariableDatabase* db); + void onEdgeChangeFailure(const IVariableDatabase* db) override; + void onEdgeChangeSuccess(const IVariableDatabase* db) override; + void addTimestampToCommit(const IVariableDatabase* db, int sourceVertex, int destVertex); + void removeTimestampToCommit(const IVariableDatabase* db, int sourceVertex, int destVertex); + void processQueuedVertexChanges(IVariableDatabase* db) override; }; } // namespace Vertexy \ No newline at end of file diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologySearchConstraint.h index feebab5..bdbcc19 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologySearchConstraint.h @@ -40,7 +40,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint virtual bool getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const override; protected: - vector explainNoReachability(const NarrowingExplanationParams& params) const; + vector explainNoReachability(const NarrowingExplanationParams& params, ValueSet* alreadyVisitedSources = nullptr) const; vector explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource = VarID::INVALID); virtual vector explainInvalid(const NarrowingExplanationParams& params) { @@ -67,7 +67,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint void updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable); void sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex); - void onExplanationGraphEdgeChange(bool edgeWasAdded, int from, int to); + void onExplanationMaxGraphEdgeChange(bool edgeWasAdded, int from, int to); void addSource(const IVariableDatabase* db, VarID source); //used by processVertexVariableChange @@ -85,6 +85,17 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) = 0; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) = 0; virtual void createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const; + virtual void onEdgeChangeFailure(const IVariableDatabase* db) + { + } + + virtual void onEdgeChangeSuccess(const IVariableDatabase* db) + { + } + + virtual void processQueuedVertexChanges(IVariableDatabase* db) + { + } inline bool definitelyNeedsToReach(const IVariableDatabase* db, VarID var) const { @@ -149,7 +160,10 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint shared_ptr m_maxGraph; // Synchronized with MaxGraph. Used during explanations where we need to temporarily rewind graph state, but we don't // want to propagate to the source reachability trees. - shared_ptr m_explanationGraph; + shared_ptr m_explanationMaxGraph; + // Synchronized with MinGraph. Used during explanations where we need to temporarily rewind graph state, but we don't + // want to propagate to the source reachability trees. + shared_ptr m_explanationMinGraph; ValueSet m_sourceMask; ValueSet m_notSourceMask; @@ -182,6 +196,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint #endif EventListenerHandle minRamalHandle = INVALID_EVENT_LISTENER_HANDLE; EventListenerHandle maxRamalHandle = INVALID_EVENT_LISTENER_HANDLE; + int vertex; }; vector m_vertexProcessList; diff --git a/vertexy/src/public/ds/BacktrackableValue.h b/vertexy/src/public/ds/BacktrackableValue.h new file mode 100644 index 0000000..04560d1 --- /dev/null +++ b/vertexy/src/public/ds/BacktrackableValue.h @@ -0,0 +1,81 @@ +#pragma once +#include "ConstraintTypes.h" +#include "EASTL/vector.h" +#include "util/Asserts.h" + +namespace Vertexy +{ +template +struct TTimestampAndValue +{ + InElementType value; + SolverTimestamp timestamp; +}; + +template +class TBacktrackableValue +{ +public: + TBacktrackableValue() {} + + void set(SolverTimestamp timestamp, InElementType&& value) + { + vxy_assert(this->timesAndValues.size() == 0 || this->timesAndValues.back().timestamp <= timestamp); + this->timesAndValues.push_back({ move(value), timestamp }); + } + + void set(SolverTimestamp timestamp, const InElementType& value) + { + vxy_assert(this->timesAndValues.size() == 0 || this->timesAndValues.back().timestamp <= timestamp); + this->timesAndValues.push_back({ value, timestamp }); + } + + bool hasValue() + { + return this->timesAndValues.size() > 0; + } + + const InElementType& get() const + { + return this->timesAndValues.back().value; + } + + int getIndexBefore(SolverTimestamp timestamp) const + { + auto size = this->timesAndValues.size(); + while (size > 0 && this->timesAndValues[size - 1].timestamp >= timestamp) + { + size--; + } + return size - 1; + } + + const InElementType& getValueForIndex(int index) const + { + return this->timesAndValues[index].value; + } + + const InElementType& getTimestampForIndex(int index) const + { + return this->timesAndValues[index].timestamp; + } + + const SolverTimestamp getTimestamp() + { + return this->timesAndValues.back().timestamp; + } + + void backtrackUntil(SolverTimestamp timestamp) + { + auto size = this->timesAndValues.size(); + while (size > 0 && this->timesAndValues[size - 1].timestamp > timestamp) + { + size--; + this->timesAndValues.pop_back(); + } + } + +private: + vector> timesAndValues; +}; +} diff --git a/vertexy/src/public/topology/BacktrackingDigraphTopology.h b/vertexy/src/public/topology/BacktrackingDigraphTopology.h index c319186..78f3c16 100644 --- a/vertexy/src/public/topology/BacktrackingDigraphTopology.h +++ b/vertexy/src/public/topology/BacktrackingDigraphTopology.h @@ -99,7 +99,7 @@ class BacktrackingDigraphTopology : public TDigraphTopologyBase 0) || m_lastHistoryIdx < m_history.size() - 1; } protected: struct HistoryRecord diff --git a/vertexy/src/public/topology/algo/ShortestPath.h b/vertexy/src/public/topology/algo/ShortestPath.h index 4fb0d9d..ee06ca8 100644 --- a/vertexy/src/public/topology/algo/ShortestPath.h +++ b/vertexy/src/public/topology/algo/ShortestPath.h @@ -18,7 +18,7 @@ class ShortestPathAlgorithm inline bool find(const TTopology& topology, int startVertex, int endVertex, vector& outPath) { vector parentLinks; - parentLinks.resize(topology->getNumVertices(), -1); + parentLinks.resize(topology.getNumVertices(), -1); m_bfs.search(topology, startVertex, [&](int vertex, int parent) { From fdc4d2a0bde7343bf875c956d8ebd86933b401dc Mon Sep 17 00:00:00 2001 From: Caue Waneck Date: Thu, 16 Jun 2022 16:33:36 -0300 Subject: [PATCH 16/17] Working greater than explainer Also general code cleanup --- .../constraints/ShortestPathConstraint.cpp | 197 +++++++++--------- vertexyTests/src/private/Maze.cpp | 15 +- 2 files changed, 107 insertions(+), 105 deletions(-) diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 8186b57..8e8fb79 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -67,7 +67,7 @@ ShortestPathConstraint::ShortestPathConstraint( , m_distance(distance) { vxy_assert(m_op != EConstraintOperator::NotEqual); //NotEqual not supported - if (m_op == EConstraintOperator::LessThan || m_op == EConstraintOperator::LessThanEq) + if (m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) { this->m_explanationMinGraph = make_shared(); } @@ -75,10 +75,7 @@ ShortestPathConstraint::ShortestPathConstraint( void ShortestPathConstraint::onInitialArcConsistency(IVariableDatabase* db) { - if (this->m_op == EConstraintOperator::LessThan || this->m_op == EConstraintOperator::LessThanEq) - { - m_lastValidTimestamp.clear(); - } + m_lastValidTimestamp.clear(); } bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int dist) const @@ -110,10 +107,12 @@ bool ShortestPathConstraint::isPossiblyValid(const IVariableDatabase* db, const if (!isValidDistance(db, src.minReachability->getDistance(vertex))) { // the shortest path is too short, so our vertex is definitely unreachable (ie it cannot be a target) + removeTimestampToCommit(db, src.vertex, vertex); return false; } else { + addTimestampToCommit(db, src.vertex, vertex); return true; } } @@ -156,9 +155,11 @@ ITopologySearchConstraint::EValidityDetermination ShortestPathConstraint::determ if (!isValidDistance(db, src.minReachability->getDistance(vertex))) { // the shortest path is too short, so our vertex is definitely unreachable (ie it cannot be a target) + removeTimestampToCommit(db, src.vertex, vertex); return EValidityDetermination::DefinitelyInvalid; } + addTimestampToCommit(db, src.vertex, vertex); if (definitelyIsSource(db, srcVertex)) { return EValidityDetermination::DefinitelyValid; @@ -247,7 +248,7 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE ValueSet visited; visited.init(m_initialPotentialSources.size(), false); - bool isGreaterOp = m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq; + const bool isGreaterOp = m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq; { bool hasAnyTimestamp = false; auto foundLastTimestamp = m_lastValidTimestamp.find(conflictVertex); @@ -267,25 +268,31 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE { // there was never a point where this vertex was valid // the best explanation is that this edge is too far away / too close from any sources - //for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) - //{ - // VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; - // if (!db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) - // { - // lits.push_back(Literal(potentialSource, m_sourceMask)); - // } - // else - // { - // lits.push_back(Literal(potentialSource, m_sourceMask.inverted())); - // } - //} - //vxy_assert(false); //check if that's happening + for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) + { + VarID potentialSource = m_initialPotentialSources[potentialSrcIndex]; + int sourceVertex = m_variableToSourceVertexIndex.find(potentialSource)->second; + if (sourceVertex == conflictVertex) + { + continue; + } + + if (!db->getPotentialValues(potentialSource).anyPossible(m_sourceMask)) + { + // best explanation is that any source that isn't a source currently would make it reachable + lits.push_back(Literal(potentialSource, m_sourceMask)); + } + } return lits; } } bool foundAny = false; bool hasUnprocessedSources = false; + if (isGreaterOp) + { + m_explanationMinGraph->rewindUntil(params.timestamp); + } // For each source that could possibly exist... for (int potentialSrcIndex = 0; potentialSrcIndex < m_initialPotentialSources.size(); ++potentialSrcIndex) { @@ -300,16 +307,6 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE // check if it's reachable auto reachabilitySource = m_reachabilitySources.find(potentialSource); - //if (reachabilitySource != m_reachabilitySources.end()) - //{ - // if (!reachabilitySource->second.maxReachability->isReachable(conflictVertex)) - // { - // // it's not reachable, let's let the reachability explainer kick in - // hasUnprocessedSources = true; - // continue; - // } - //} - SolverTimestamp valueToRewind = -1; { auto& lastValid = m_lastValidTimestamp[conflictVertex].find(sourceVertex); @@ -322,29 +319,43 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE } valueToRewind = lastValid->second.getTimestamp(); } - auto& graph = isGreaterOp ? m_explanationMinGraph : m_explanationMaxGraph; if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask) || reachabilitySource != m_reachabilitySources.end()) { vector path; - //if (getShortestPathForExplanation(valueToRewind, rewindMinGraph, sourceVertex, conflictVertex, path)) - graph->rewindUntil(valueToRewind); - auto found = m_shortestPathAlgo.find(*graph, sourceVertex, conflictVertex, path); - vxy_assert(found); - graph->fastForward(); - //if () + // so this is a bit confusing, bear with me: + auto timestampToCheck = isGreaterOp ? valueToRewind : params.timestamp; + if (isGreaterOp) { + // if this is a greater than op, the only possibility for it to be invalid is if the min graph has a path that is lower + // than our constraint. In that case, we want to find the path that is too short currently (on the timestamp of the triggered + // explanation), and compare each edge against the last valid version of it + // any edge that was blocked but now is not will get into the explanation + if (!m_shortestPathAlgo.find(*m_explanationMinGraph, sourceVertex, conflictVertex, path)) + { + // it's actually unreachable right now, so let's let the reachability explainer kick in (by not setting visited = true) + hasUnprocessedSources = true; + continue; + } + } + else + { + // in case this is a less than op, the only possibility for it to be invalid is if the max graph has a path that is too long + // so, in that case, what we want to do is rewind our explanation max graph to the last time it was valid, and get the shortest + // path at that point. Then, we can compare each edge against the timestamp of the conflict, and set to open any edge that + // is now blocked + m_explanationMaxGraph->rewindUntil(valueToRewind); + auto found = m_shortestPathAlgo.find(*m_explanationMaxGraph, sourceVertex, conflictVertex, path); + vxy_assert(found); + // TODO optimize: we can sort the values to rewind in such a way that we can do many partial rewinds instead of rewinding, + // and then fast forwarding every time + m_explanationMaxGraph->fastForward(); vxy_assert(isValidDistance(db, path.size() - 1)); } + foundAny = true; visited[potentialSrcIndex] = true; - // optimization - if the vertices are siblings, and greater than is used, we can safely say that - // this target is only reachable if this source is not a source - //if (isGreaterThan && path.size() == 2) - //{ - // lits.push_back(Literal(potentialSource, m_sourceMask.inverted().excluding(db->getPotentialValues(potentialSource)))); - // continue; - //} + auto& targetMask = isGreaterOp ? m_edgeBlockedMask : m_edgeOpenMask; bool changedEdge = false; for (int i = 0; i < (path.size() - 1); ++i) @@ -354,9 +365,8 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE auto edgeVar = m_edgeGraphData->get(edge); if (edgeVarsRecorded.find(edgeVar) == edgeVarsRecorded.end()) { - if (!db->getValueBefore(edgeVar, params.timestamp).anyPossible(targetMask)) + if (!db->getValueBefore(edgeVar, timestampToCheck).anyPossible(targetMask)) { - //VERTEXY_LOG(" %s --> %s", db->getSolver()->getVariableName(edgeVar).c_str(), targetMask.toString().c_str()); edgeVarsRecorded.insert(edgeVar); lits.push_back(Literal(edgeVar, targetMask)); changedEdge = true; @@ -371,14 +381,19 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE } else { - // Not currently a potential source. For now, the conservative explanation is that we'd be able to reach if it - // was. + // Not currently a potential source. It's safe to assume that it would be valid in case that was a potention source + // because we are only at this point if valueToRewind was set (so there was a moment in time where this source was a valid + // source for the target) lits.push_back(Literal(potentialSource, m_sourceMask)); visited[potentialSrcIndex] = true; foundAny = true; } } vxy_assert(foundAny); + if (isGreaterOp) + { + m_explanationMinGraph->fastForward(); + } if (hasUnprocessedSources) { @@ -396,12 +411,6 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE } } - //VERTEXY_LOG("BEGIN explainInvalid"); - //for (int i = 0; i < lits.size(); i++) - //{ - // VERTEXY_LOG(" %s --> %s", db->getSolver()->getVariableName(lits[i].variable).c_str(), lits[i].values.toString().c_str()); - //} - return lits; } @@ -434,7 +443,29 @@ void ShortestPathConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, b m_queuedVertexChanges.insert(vertexIndex); auto reachability = determineValidity(m_edgeChangeDb, vertexIndex); - // See if this vertex is definitely reachable by any source now + // TODO understand why uncommenting both of these make everything run much, much slower + //if (reachability == EValidityDetermination::DefinitelyValid) + //{ + // // vertexIndex became unreachable in the max graph + // VarID var = m_sourceGraphData->get(vertexIndex); + + // if (var.isValid() && m_edgeChangeDb->getPotentialValues(var).anyPossible(m_requireValidMask)) + // { + // auto ret = m_edgeChangeDb->constrainToValues(var, m_requireValidMask, this); + // vxy_assert(ret); + // } + //} + //else if (reachability == EValidityDetermination::PossiblyValid) + //{ + // // vertexIndex became unreachable in the max graph + // VarID var = m_sourceGraphData->get(vertexIndex); + + // m_edgeChangeDb-> + // if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask.inverted(), this)) + // { + // m_edgeChangeFailure = true; + // } + //} if (reachability == EValidityDetermination::DefinitelyUnreachable) { // vertexIndex became unreachable in the max graph @@ -464,32 +495,15 @@ void ShortestPathConstraint::backtrack(const IVariableDatabase* db, SolverDecisi { auto stamp = db->getTimestamp(); ITopologySearchConstraint::backtrack(db, level); - if (this->m_op == EConstraintOperator::LessThan || this->m_op == EConstraintOperator::LessThanEq) + + // TODO optimize: make a better data structure so we don't need to touch all vertices just to backtrack the ones that can be backtracked + for (auto& vertexData : m_lastValidTimestamp) { - for (auto& vertexData : m_lastValidTimestamp) + for (auto& srcData : vertexData.second) { - for (auto& srcData : vertexData.second) - { - srcData.second.backtrackUntil(stamp); - } + srcData.second.backtrackUntil(stamp); } } - - //for (auto& it = m_cachedKeysToTimestamp.rbegin(); it != m_cachedKeysToTimestamp.rend(); it++) - //{ - // if (it->first <= stamp) - // { - // for (auto& sourceAndDest : it->second) - // { - // m_cachedShortestPaths.erase(sourceAndDest); - // } - // it = m_cachedKeysToTimestamp.erase(it); - // if (it == m_cachedKeysToTimestamp.rend()) - // { - // break; - // } - // } - //} } void ShortestPathConstraint::commitValidTimestamps(const IVariableDatabase* db) @@ -509,7 +523,6 @@ void ShortestPathConstraint::commitValidTimestamps(const IVariableDatabase* db) } for (auto& value : toCommitDest.second) { - // TODO cleanup if (validTimestamp->find(value.first) == validTimestamp->end()) { TBacktrackableValue val; @@ -570,35 +583,11 @@ void ShortestPathConstraint::removeTimestampToCommit(const IVariableDatabase* db void Vertexy::ShortestPathConstraint::processQueuedVertexChanges(IVariableDatabase* db) { m_validTimestampsToCommit.clear(); + vxy_assert(m_processingVertices == false); for (auto vertexIndex : m_queuedVertexChanges) { - auto reachability = determineValidity(db, vertexIndex); - - // See if this vertex is definitely reachable by any source now - if (reachability == EValidityDetermination::DefinitelyUnreachable) - { - // vertexIndex became unreachable in the max graph - VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(db, vertexIndex); - - if (var.isValid() && !db->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) - { - m_edgeChangeFailure = true; - break; - } - } - else if (reachability == EValidityDetermination::DefinitelyInvalid) - { - // vertexIndex became unreachable in the max graph - VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(db, vertexIndex); - - if (var.isValid() && !db->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) - { - m_edgeChangeFailure = true; - break; - } - } + // we need to run determineValidity again for all these vertices so we can update our valid timestamps + determineValidity(db, vertexIndex); } m_queuedVertexChanges.clear(); } diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index 40aa258..93345f6 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -256,7 +256,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto downTile = make_shared>(tileData, PlanarGridTopology::moveDown()); auto downRightTile = make_shared>(tileData, PlanarGridTopology::moveDown().combine(PlanarGridTopology::moveRight())); - auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 10 }); + auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 20 }); // // DECLARE CONSTRAINTS // @@ -411,6 +411,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV // vector>> stepDatas; vector>> stepEdgeDatas; + vector>> stepDoorDatas; for (int step = 0; step < NUM_KEYS + 1; ++step) { // @@ -423,6 +424,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto selfStepTile = make_shared>(stepData); auto stepDoorData = solver.makeVariableGraph(stepName, ITopology::adapt(grid), stepDomain, { wstring::CtorSprintf(), TEXT("StepDoor%d-"), step }); + stepDoorDatas.push_back(stepDoorData); auto selfStepDoorTile = make_shared>(stepDoorData); // constraint solver.makeGraphConstraint(grid, @@ -621,6 +623,17 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV if (printVerbose && MAZE_REFRESH_RATE > 0 && (solver.getStats().stepCount % MAZE_REFRESH_RATE) == 0) { print(stepDatas[0], stepEdgeDatas[0], solver); + VERTEXY_LOG("step door data"); + print(stepDoorDatas[1], stepEdgeDatas[1], solver); + VERTEXY_LOG("step data"); + print(stepDatas[1], stepEdgeDatas[1], solver); + VERTEXY_LOG("start edge data"); + for (int i = 0; i < (numRows * numCols) - 1; i++) + { + auto edge = stepEdgeDatas[1]->get(i); + VERTEXY_LOG(" %s --> %s", solver.getVariableName(edge).c_str(), solver.getVariableDB()->getPotentialValues(edge).toString().c_str()); + } + VERTEXY_LOG("end edge data"); } } From 80ae90e68724b1bd679e3efa80716020efa46131 Mon Sep 17 00:00:00 2001 From: Caue Waneck Date: Tue, 28 Jun 2022 19:28:28 -0300 Subject: [PATCH 17/17] PR feedback, part one --- .../constraints/ReachabilityConstraint.cpp | 4 +- .../constraints/ShortestPathConstraint.cpp | 143 ++++++++++-------- ...t.cpp => TopologyConnectionConstraint.cpp} | 52 +++---- .../constraints/ReachabilityConstraint.h | 4 +- .../constraints/ShortestPathConstraint.h | 11 +- ...raint.h => TopologyConnectionConstraint.h} | 12 +- vertexyTests/src/private/Maze.cpp | 10 +- 7 files changed, 123 insertions(+), 113 deletions(-) rename vertexy/src/private/constraints/{TopologySearchConstraint.cpp => TopologyConnectionConstraint.cpp} (92%) rename vertexy/src/public/constraints/{TopologySearchConstraint.h => TopologyConnectionConstraint.h} (95%) diff --git a/vertexy/src/private/constraints/ReachabilityConstraint.cpp b/vertexy/src/private/constraints/ReachabilityConstraint.cpp index d9f0a5f..6fe2a6d 100644 --- a/vertexy/src/private/constraints/ReachabilityConstraint.cpp +++ b/vertexy/src/private/constraints/ReachabilityConstraint.cpp @@ -58,7 +58,7 @@ ReachabilityConstraint::ReachabilityConstraint( const ValueSet& requireReachableMask, const shared_ptr>& edgeGraphData, const ValueSet& edgeBlockedMask) - : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask) + : TopologyConnectionConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask) { } @@ -126,7 +126,7 @@ void ReachabilityConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, b if (determineValidity(m_edgeChangeDb, vertexIndex) == EValidityDetermination::DefinitelyUnreachable) { VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); + sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) { diff --git a/vertexy/src/private/constraints/ShortestPathConstraint.cpp b/vertexy/src/private/constraints/ShortestPathConstraint.cpp index 8e8fb79..3a696ac 100644 --- a/vertexy/src/private/constraints/ShortestPathConstraint.cpp +++ b/vertexy/src/private/constraints/ShortestPathConstraint.cpp @@ -20,7 +20,7 @@ ShortestPathConstraint* ShortestPathConstraint::ShortestPathFactory::construct( const shared_ptr>& edgeData, const vector& edgeBlockedValues, EConstraintOperator op, - VarID distance) + int distance) { // Get an example graph variable VarID graphVar; @@ -61,8 +61,8 @@ ShortestPathConstraint::ShortestPathConstraint( const shared_ptr>& edgeGraphData, const ValueSet& edgeBlockedMask, EConstraintOperator op, - VarID distance) - : ITopologySearchConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask) + int distance) + : TopologyConnectionConstraint(params, sourceGraphData, sourceMask, requireReachableMask, edgeGraphData, edgeBlockedMask) , m_op(op) , m_distance(distance) { @@ -83,13 +83,13 @@ bool ShortestPathConstraint::isValidDistance(const IVariableDatabase* db, int di switch (m_op) { case EConstraintOperator::GreaterThan: - return dist > db->getMinimumPossibleDomainValue(m_distance); + return dist > m_distance; case EConstraintOperator::GreaterThanEq: - return dist >= db->getMinimumPossibleDomainValue(m_distance); + return dist >= m_distance; case EConstraintOperator::LessThan: - return dist < db->getMaximumPossibleDomainValue(m_distance); + return dist < m_distance; case EConstraintOperator::LessThanEq: - return dist <= db->getMaximumPossibleDomainValue(m_distance); + return dist <= m_distance; } vxy_assert(false); //NotEqual not supported @@ -142,7 +142,7 @@ bool ShortestPathConstraint::isPossiblyValid(const IVariableDatabase* db, const return false; } -ITopologySearchConstraint::EValidityDetermination ShortestPathConstraint::determineValidityHelper( +TopologyConnectionConstraint::EValidityDetermination ShortestPathConstraint::determineValidityHelper( const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, @@ -234,7 +234,7 @@ EventListenerHandle ShortestPathConstraint::addMaxCallback(RamalRepsType& maxRea vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingExplanationParams& params) { // if we are here, it means that the path was reachable, but we marked it as invalid because of the distance constraint - vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); + vxy_sanity_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); auto db = params.database; int conflictVertex = m_variableToSourceVertexIndex.find(params.propagatedVariable)->second; @@ -414,9 +414,43 @@ vector Vertexy::ShortestPathConstraint::explainInvalid(const NarrowingE return lits; } +void Vertexy::ShortestPathConstraint::sanityCheckInvalid(IVariableDatabase* db, int vertexIndex) +{ + #if SANITY_CHECKS + // For each source that could possibly exist... + bool foundInvalid = false; + for (VarID potentialSource : m_initialPotentialSources) + { + int sourceVertex = m_variableToSourceVertexIndex[potentialSource]; + // If this is currently a potential source... + if (db->getPotentialValues(potentialSource).anyPossible(m_sourceMask) || m_reachabilitySources.find(potentialSource) != m_reachabilitySources.end()) + { + vector path; + if (m_op == EConstraintOperator::GreaterThan || m_op == EConstraintOperator::GreaterThanEq) + { + if (m_shortestPathAlgo.find(*m_explanationMinGraph, sourceVertex, vertexIndex, path)) + { + foundInvalid = true; + vxy_assert(!isValidDistance(db, path.size() - 1)); + } + } + else + { + if (m_shortestPathAlgo.find(*m_explanationMaxGraph, sourceVertex, vertexIndex, path)) + { + foundInvalid = true; + vxy_assert(!isValidDistance(db, path.size() - 1)); + } + } + } + } + vxy_assert(foundInvalid); + #endif +} + void Vertexy::ShortestPathConstraint::createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const { - ITopologySearchConstraint::createTempSourceData(data, vertexIndex); + TopologyConnectionConstraint::createTempSourceData(data, vertexIndex); #if REACHABILITY_USE_RAMAL_REPS data.minReachability = make_shared(m_minGraph, false, false, false); #else @@ -443,58 +477,25 @@ void ShortestPathConstraint::onVertexChanged(int vertexIndex, VarID sourceVar, b m_queuedVertexChanges.insert(vertexIndex); auto reachability = determineValidity(m_edgeChangeDb, vertexIndex); - // TODO understand why uncommenting both of these make everything run much, much slower - //if (reachability == EValidityDetermination::DefinitelyValid) - //{ - // // vertexIndex became unreachable in the max graph - // VarID var = m_sourceGraphData->get(vertexIndex); - - // if (var.isValid() && m_edgeChangeDb->getPotentialValues(var).anyPossible(m_requireValidMask)) - // { - // auto ret = m_edgeChangeDb->constrainToValues(var, m_requireValidMask, this); - // vxy_assert(ret); - // } - //} - //else if (reachability == EValidityDetermination::PossiblyValid) - //{ - // // vertexIndex became unreachable in the max graph - // VarID var = m_sourceGraphData->get(vertexIndex); - - // m_edgeChangeDb-> - // if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask.inverted(), this)) - // { - // m_edgeChangeFailure = true; - // } - //} if (reachability == EValidityDetermination::DefinitelyUnreachable) { // vertexIndex became unreachable in the max graph VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(db, vertexIndex); + // we cannot sanity check now because not all RamalReps have been updated - see processQueuedVertexChanges + sanityCheckUnreachable(m_edgeChangeDb, vertexIndex); if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) { m_edgeChangeFailure = true; } } - else if (reachability == EValidityDetermination::DefinitelyInvalid) - { - // vertexIndex became unreachable in the max graph - VarID var = m_sourceGraphData->get(vertexIndex); - //sanityCheckUnreachable(db, vertexIndex); - - if (var.isValid() && !m_edgeChangeDb->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) - { - m_edgeChangeFailure = true; - } - } m_processingVertices = false; } void ShortestPathConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) { auto stamp = db->getTimestamp(); - ITopologySearchConstraint::backtrack(db, level); + TopologyConnectionConstraint::backtrack(db, level); // TODO optimize: make a better data structure so we don't need to touch all vertices just to backtrack the ones that can be backtracked for (auto& vertexData : m_lastValidTimestamp) @@ -511,28 +512,10 @@ void ShortestPathConstraint::commitValidTimestamps(const IVariableDatabase* db) for (auto& toCommitDest : m_validTimestampsToCommit) { auto& found = m_lastValidTimestamp.find(toCommitDest.first); - hash_map>* validTimestamp; - if (found == m_lastValidTimestamp.end()) - { - m_lastValidTimestamp[toCommitDest.first] = hash_map>(); - validTimestamp = &m_lastValidTimestamp[toCommitDest.first]; - } - else - { - validTimestamp = &found->second; - } + auto validTimestamp = m_lastValidTimestamp.insert(toCommitDest.first).first; for (auto& value : toCommitDest.second) { - if (validTimestamp->find(value.first) == validTimestamp->end()) - { - TBacktrackableValue val; - val.set(value.second, value.second); - (*validTimestamp)[value.first] = val; - } - else - { - (*validTimestamp)[value.first].set(value.second, value.second); - } + validTimestamp->second.insert(value.first).first->second.set(value.second, value.second); } } } @@ -556,6 +539,7 @@ void ShortestPathConstraint::addTimestampToCommit(const IVariableDatabase* db, i return; } + #if VERTEXY_SANITY_CHECKS auto timestamp = db->getTimestamp(); if (m_lastValidTimestamp.find(destVertex) != m_lastValidTimestamp.end() && m_lastValidTimestamp[destVertex].find(sourceVertex) != m_lastValidTimestamp[destVertex].end()) @@ -566,6 +550,7 @@ void ShortestPathConstraint::addTimestampToCommit(const IVariableDatabase* db, i vxy_assert(val.getTimestamp() <= timestamp); } } + #endif if (m_validTimestampsToCommit.find(destVertex) == m_validTimestampsToCommit.end()) { @@ -587,7 +572,31 @@ void Vertexy::ShortestPathConstraint::processQueuedVertexChanges(IVariableDataba for (auto vertexIndex : m_queuedVertexChanges) { // we need to run determineValidity again for all these vertices so we can update our valid timestamps - determineValidity(db, vertexIndex); + auto validity = determineValidity(db, vertexIndex); + if (validity == EValidityDetermination::DefinitelyValid) + { + // vertexIndex became unreachable in the max graph + VarID var = m_sourceGraphData->get(vertexIndex); + + if (var.isValid() && !db->constrainToValues(var, m_requireValidMask, this)) + { + m_edgeChangeFailure = true; + break; + } + } + else if (validity == EValidityDetermination::DefinitelyInvalid) + { + // vertexIndex became unreachable in the max graph + VarID var = m_sourceGraphData->get(vertexIndex); + // we cannot sanity check now because not all RamalReps have been updated - see processQueuedVertexChanges + sanityCheckInvalid(db, vertexIndex); + + if (var.isValid() && !db->constrainToValues(var, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) + { + m_edgeChangeFailure = true; + break; + } + } } m_queuedVertexChanges.clear(); } diff --git a/vertexy/src/private/constraints/TopologySearchConstraint.cpp b/vertexy/src/private/constraints/TopologyConnectionConstraint.cpp similarity index 92% rename from vertexy/src/private/constraints/TopologySearchConstraint.cpp rename to vertexy/src/private/constraints/TopologyConnectionConstraint.cpp index 426f657..ade2ba5 100644 --- a/vertexy/src/private/constraints/TopologySearchConstraint.cpp +++ b/vertexy/src/private/constraints/TopologyConnectionConstraint.cpp @@ -1,5 +1,5 @@ // Copyright Proletariat, Inc. All Rights Reserved. -#include "constraints/TopologySearchConstraint.h" +#include "constraints/TopologyConnectionConstraint.h" #include "variable/IVariableDatabase.h" #include "topology/GraphRelations.h" @@ -12,7 +12,7 @@ static constexpr int OPEN_EDGE_FLOW = INT_MAX >> 1; static constexpr int CLOSED_EDGE_FLOW = 1; static constexpr bool USE_RAMAL_REPS_BATCHING = true; -ITopologySearchConstraint::ITopologySearchConstraint( +TopologyConnectionConstraint::TopologyConnectionConstraint( const ConstraintFactoryParams& params, const shared_ptr>& sourceGraphData, const ValueSet& sourceMask, @@ -57,7 +57,7 @@ ITopologySearchConstraint::ITopologySearchConstraint( } } -bool ITopologySearchConstraint::getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const +bool TopologyConnectionConstraint::getGraphRelations(const vector& literals, ConstraintGraphRelationInfo& outRelations) const { // First search through all provided literals to find the minimum graph vertex. // Note: some vertices will be edges, some will be tiles. @@ -180,7 +180,7 @@ bool ITopologySearchConstraint::getGraphRelations(const vector& literal return true; } -bool ITopologySearchConstraint::initialize(IVariableDatabase* db) +bool TopologyConnectionConstraint::initialize(IVariableDatabase* db) { // Add all vertices to min/max graphs for (int vertexIndex = 0; vertexIndex < m_sourceGraph->getNumVertices(); ++vertexIndex) @@ -377,7 +377,7 @@ bool ITopologySearchConstraint::initialize(IVariableDatabase* db) return true; } -void ITopologySearchConstraint::reset(IVariableDatabase* db) +void TopologyConnectionConstraint::reset(IVariableDatabase* db) { for (auto it = m_vertexWatchHandles.begin(), itEnd = m_vertexWatchHandles.end(); it != itEnd; ++it) { @@ -392,7 +392,7 @@ void ITopologySearchConstraint::reset(IVariableDatabase* db) m_edgeWatchHandles.clear(); } -bool ITopologySearchConstraint::onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& prevValue, bool& removeWatch) +bool TopologyConnectionConstraint::onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& prevValue, bool& removeWatch) { const ValueSet& newValue = db->getPotentialValues(variable); @@ -408,7 +408,7 @@ bool ITopologySearchConstraint::onVariableNarrowed(IVariableDatabase* db, VarID return true; } -bool ITopologySearchConstraint::EdgeWatcher::onVariableNarrowed(IVariableDatabase* db, VarID var, const ValueSet& prevValue, bool& removeHandle) +bool TopologyConnectionConstraint::EdgeWatcher::onVariableNarrowed(IVariableDatabase* db, VarID var, const ValueSet& prevValue, bool& removeHandle) { const ValueSet& newValue = db->getPotentialValues(var); if ((prevValue.anyPossible(m_parent.m_edgeBlockedMask) && !newValue.anyPossible(m_parent.m_edgeBlockedMask)) || @@ -420,7 +420,7 @@ bool ITopologySearchConstraint::EdgeWatcher::onVariableNarrowed(IVariableDatabas return true; } -bool ITopologySearchConstraint::propagate(IVariableDatabase* db) +bool TopologyConnectionConstraint::propagate(IVariableDatabase* db) { vxy_assert(!m_edgeChangeFailure); ValueGuard guardEdgeChangeFailure(m_edgeChangeFailure, false); @@ -487,7 +487,7 @@ bool ITopologySearchConstraint::propagate(IVariableDatabase* db) return true; } -bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* db, VarID variable) +bool TopologyConnectionConstraint::processVertexVariableChange(IVariableDatabase* db, VarID variable) { if (!db->anyPossible(variable, m_sourceMask)) { @@ -540,7 +540,7 @@ bool ITopologySearchConstraint::processVertexVariableChange(IVariableDatabase* d return true; } -void ITopologySearchConstraint::addSource(const IVariableDatabase* db, VarID source) +void TopologyConnectionConstraint::addSource(const IVariableDatabase* db, VarID source) { int vertex = m_variableToSourceVertexIndex[source]; @@ -561,7 +561,7 @@ void ITopologySearchConstraint::addSource(const IVariableDatabase* db, VarID sou m_reachabilitySources[source] = { minReachable, maxReachable, minHandle, maxHandle, vertex }; } -bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source) +bool TopologyConnectionConstraint::removeSource(IVariableDatabase* db, VarID source) { if (m_reachabilitySources.find(source) == m_reachabilitySources.end()) { @@ -601,7 +601,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source if (determination == EValidityDetermination::DefinitelyUnreachable) { - //sanityCheckUnreachable(db, vertex); + sanityCheckUnreachable(db, vertex); if (!db->constrainToValues(vertexVar, m_invalidMask, this, [&](auto params) { return explainNoReachability(params); })) { failure = true; @@ -610,7 +610,7 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source } else if (determination == EValidityDetermination::DefinitelyInvalid) { - //sanityCheckUnreachable(db, vertex); + sanityCheckInvalid(db, vertex); if (!db->constrainToValues(vertexVar, m_invalidMask, this, [&](auto params) { return explainInvalid(params); })) { failure = true; @@ -660,13 +660,13 @@ bool ITopologySearchConstraint::removeSource(IVariableDatabase* db, VarID source return !failure; } -bool Vertexy::ITopologySearchConstraint::isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) +bool Vertexy::TopologyConnectionConstraint::isPossiblyValid(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) { // in the default implementation, it is always valid. implementors can override this for their invalid implementation return true; } -ITopologySearchConstraint::EValidityDetermination Vertexy::ITopologySearchConstraint::determineValidityHelper( +TopologyConnectionConstraint::EValidityDetermination Vertexy::TopologyConnectionConstraint::determineValidityHelper( const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex, @@ -691,7 +691,7 @@ ITopologySearchConstraint::EValidityDetermination Vertexy::ITopologySearchConstr return EValidityDetermination::DefinitelyUnreachable; } -void Vertexy::ITopologySearchConstraint::createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const +void Vertexy::TopologyConnectionConstraint::createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const { #if REACHABILITY_USE_RAMAL_REPS data.maxReachability = make_shared(m_maxGraph, false, false, false); @@ -701,7 +701,7 @@ void Vertexy::ITopologySearchConstraint::createTempSourceData(ReachabilitySource data.maxReachability->initialize(vertexIndex, &m_reachabilityEdgeLookup, m_totalNumEdges); } -void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable) +void TopologyConnectionConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable) { vxy_assert(!m_inEdgeChange); vxy_assert(!m_edgeChangeFailure); @@ -768,7 +768,7 @@ void ITopologySearchConstraint::updateGraphsForEdgeChange(IVariableDatabase* db, } } -void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) +void TopologyConnectionConstraint::backtrack(const IVariableDatabase* db, SolverDecisionLevel level) { vxy_assert(!m_edgeChangeFailure); m_edgeProcessList.clear(); @@ -807,7 +807,7 @@ void ITopologySearchConstraint::backtrack(const IVariableDatabase* db, SolverDec #endif } -ITopologySearchConstraint::EValidityDetermination ITopologySearchConstraint::determineValidity(const IVariableDatabase* db, int vertexIndex) +TopologyConnectionConstraint::EValidityDetermination TopologyConnectionConstraint::determineValidity(const IVariableDatabase* db, int vertexIndex) { VarID vertexVar = m_sourceGraphData->get(vertexIndex); bool hadAnyInvalid = false; @@ -837,7 +837,7 @@ ITopologySearchConstraint::EValidityDetermination ITopologySearchConstraint::det } // Called whenever an edge is added or removed from the explanation graph, including during backtracking -void ITopologySearchConstraint::onExplanationMaxGraphEdgeChange(bool edgeWasAdded, int from, int to) +void TopologyConnectionConstraint::onExplanationMaxGraphEdgeChange(bool edgeWasAdded, int from, int to) { // Keep the edge capacities in sync. for (int i = get<0>(m_flowGraphLookup[from]); i < get<1>(m_flowGraphLookup[from]); ++i) @@ -851,7 +851,7 @@ void ITopologySearchConstraint::onExplanationMaxGraphEdgeChange(bool edgeWasAdde vxy_fail(); } -vector ITopologySearchConstraint::explainNoReachability(const NarrowingExplanationParams& params, ValueSet* alreadyVisitedSources) const +vector TopologyConnectionConstraint::explainNoReachability(const NarrowingExplanationParams& params, ValueSet* alreadyVisitedSources) const { vxy_assert_msg(m_variableToSourceVertexIndex.find(params.propagatedVariable) != m_variableToSourceVertexIndex.end(), "Not a vertex variable?"); @@ -967,7 +967,7 @@ vector ITopologySearchConstraint::explainNoReachability(const Narrowing return lits; } -vector ITopologySearchConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) +vector TopologyConnectionConstraint::explainRequiredSource(const NarrowingExplanationParams& params, VarID removedSource) { //return IVariableDatabase::defaultExplainer(params); @@ -1088,12 +1088,12 @@ vector ITopologySearchConstraint::explainRequiredSource(const Narrowing return lits; } -bool ITopologySearchConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const +bool TopologyConnectionConstraint::isPossiblyReachable(const IVariableDatabase* db, const ReachabilitySourceData& src, int vertex) const { return src.maxReachability->isReachable(vertex); } -void ITopologySearchConstraint::sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex) +void TopologyConnectionConstraint::sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex) { #if SANITY_CHECKS // For each source that could possibly exist... @@ -1110,7 +1110,7 @@ void ITopologySearchConstraint::sanityCheckUnreachable(IVariableDatabase* db, in } -vector ITopologySearchConstraint::getConstrainingVariables() const +vector TopologyConnectionConstraint::getConstrainingVariables() const { vector out; for (int i = 0; i < m_sourceGraph->getNumVertices(); ++i) @@ -1134,7 +1134,7 @@ vector ITopologySearchConstraint::getConstrainingVariables() const return out; } -bool ITopologySearchConstraint::checkConflicting(IVariableDatabase* db) const +bool TopologyConnectionConstraint::checkConflicting(IVariableDatabase* db) const { // TODO return false; diff --git a/vertexy/src/public/constraints/ReachabilityConstraint.h b/vertexy/src/public/constraints/ReachabilityConstraint.h index e1dcdb3..b0162ef 100644 --- a/vertexy/src/public/constraints/ReachabilityConstraint.h +++ b/vertexy/src/public/constraints/ReachabilityConstraint.h @@ -1,7 +1,7 @@ // Copyright Proletariat, Inc. All Rights Reserved. #pragma once #include "ConstraintTypes.h" -#include "TopologySearchConstraint.h" +#include "TopologyConnectionConstraint.h" #include "IBacktrackingSolverConstraint.h" #include "IConstraint.h" #include "SignedClause.h" @@ -23,7 +23,7 @@ namespace Vertexy * Any variable that is definitely one of SourceValues is considered a reachability source. * Any variable that is definitely on of NeedReachableValues is considered a destination that must remain reachable from AT LEAST one source. */ -class ReachabilityConstraint : public ITopologySearchConstraint +class ReachabilityConstraint : public TopologyConnectionConstraint { public: ReachabilityConstraint(const ConstraintFactoryParams& params, diff --git a/vertexy/src/public/constraints/ShortestPathConstraint.h b/vertexy/src/public/constraints/ShortestPathConstraint.h index 28ce08f..e2ea992 100644 --- a/vertexy/src/public/constraints/ShortestPathConstraint.h +++ b/vertexy/src/public/constraints/ShortestPathConstraint.h @@ -1,7 +1,7 @@ // Copyright Proletariat, Inc. All Rights Reserved. #pragma once #include "ConstraintTypes.h" -#include "TopologySearchConstraint.h" +#include "TopologyConnectionConstraint.h" #include "IConstraint.h" #include "SignedClause.h" #include "ds/ESTree.h" @@ -24,7 +24,7 @@ namespace Vertexy * All destination nodes must have at least 1 shortest path to at least 1 source node * this shortest path must have a relation with 'distance' */ -class ShortestPathConstraint : public ITopologySearchConstraint +class ShortestPathConstraint : public TopologyConnectionConstraint { public: ShortestPathConstraint(const ConstraintFactoryParams& params, @@ -34,7 +34,7 @@ class ShortestPathConstraint : public ITopologySearchConstraint const shared_ptr>& edgeGraphData, const ValueSet& edgeBlockedMask, EConstraintOperator op, - VarID distance + int distance ); struct ShortestPathFactory @@ -54,7 +54,7 @@ class ShortestPathConstraint : public ITopologySearchConstraint // How to compare distance EConstraintOperator op, // The variable that stores the distance - VarID distance); + int distance); }; using Factory = ShortestPathFactory; @@ -72,12 +72,13 @@ class ShortestPathConstraint : public ITopologySearchConstraint virtual EventListenerHandle addMinCallback(RamalRepsType& minReachable, const IVariableDatabase* db, VarID source) override; virtual EventListenerHandle addMaxCallback(RamalRepsType& maxReachable, const IVariableDatabase* db, VarID source) override; virtual vector explainInvalid(const NarrowingExplanationParams& params) override; + virtual void sanityCheckInvalid(IVariableDatabase* db, int vertexIndex) override; virtual void createTempSourceData(ReachabilitySourceData& data, int vertexIndex) const override; void onVertexChanged(int vertexIndex, VarID sourceVar, bool inMinGraph); void backtrack(const IVariableDatabase* db, SolverDecisionLevel level) override; EConstraintOperator m_op; - VarID m_distance; + int m_distance; // used to determine the path that is either too long or too short when explaning ShortestPathAlgorithm m_shortestPathAlgo; diff --git a/vertexy/src/public/constraints/TopologySearchConstraint.h b/vertexy/src/public/constraints/TopologyConnectionConstraint.h similarity index 95% rename from vertexy/src/public/constraints/TopologySearchConstraint.h rename to vertexy/src/public/constraints/TopologyConnectionConstraint.h index bdbcc19..3ae08e7 100644 --- a/vertexy/src/public/constraints/TopologySearchConstraint.h +++ b/vertexy/src/public/constraints/TopologyConnectionConstraint.h @@ -19,10 +19,10 @@ namespace Vertexy /** Base class for ReachabilityConstraint and ShortestPathConstraint */ -class ITopologySearchConstraint : public IBacktrackingSolverConstraint +class TopologyConnectionConstraint : public IBacktrackingSolverConstraint { public: - ITopologySearchConstraint(const ConstraintFactoryParams& params, + TopologyConnectionConstraint(const ConstraintFactoryParams& params, const shared_ptr>& sourceGraphData, const ValueSet& sourceMask, const ValueSet& requireReachableMask, @@ -66,6 +66,10 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint //used by propagate void updateGraphsForEdgeChange(IVariableDatabase* db, VarID variable); void sanityCheckUnreachable(IVariableDatabase* db, int vertexIndex); + virtual void sanityCheckInvalid(IVariableDatabase* db, int vertexIndex) + { + vxy_fail(); // override me! + } void onExplanationMaxGraphEdgeChange(bool edgeWasAdded, int from, int to); @@ -135,7 +139,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint class EdgeWatcher : public IVariableWatchSink { public: - EdgeWatcher(ITopologySearchConstraint& parent) + EdgeWatcher(TopologyConnectionConstraint& parent) : m_parent(parent) { } @@ -144,7 +148,7 @@ class ITopologySearchConstraint : public IBacktrackingSolverConstraint virtual bool onVariableNarrowed(IVariableDatabase* db, VarID variable, const ValueSet& previousValue, bool& removeWatch) override; protected: - ITopologySearchConstraint& m_parent; + TopologyConnectionConstraint& m_parent; }; EdgeWatcher m_edgeWatcher; diff --git a/vertexyTests/src/private/Maze.cpp b/vertexyTests/src/private/Maze.cpp index 93345f6..1f02f92 100644 --- a/vertexyTests/src/private/Maze.cpp +++ b/vertexyTests/src/private/Maze.cpp @@ -91,9 +91,6 @@ int MazeSolver::solveSimple(int times, int numCols, int seed, bool printVerbose) solver.setInitialValues(tileData->get(node), vector{0, 1, 2}); } - auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 5 }); - - // // // CONSTRAINT: Exactly one entrance, one exit, // @@ -121,7 +118,7 @@ int MazeSolver::solveSimple(int times, int numCols, int seed, bool printVerbose) auto edgeNodeToEdgeVarRel = make_shared>(stepEdgeData); - solver.makeConstraint(tileData, cell_Entrance, cell_Exit, stepEdgeData, edge_Solid, EConstraintOperator::LessThan, shortestPathDistance); + solver.makeConstraint(tileData, cell_Entrance, cell_Exit, stepEdgeData, edge_Solid, EConstraintOperator::GreaterThan, 5); // @@ -256,7 +253,6 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV auto downTile = make_shared>(tileData, PlanarGridTopology::moveDown()); auto downRightTile = make_shared>(tileData, PlanarGridTopology::moveDown().combine(PlanarGridTopology::moveRight())); - auto shortestPathDistance = solver.makeVariable(TEXT("DIST"), vector{ 20 }); // // DECLARE CONSTRAINTS // @@ -470,7 +466,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV { solver.makeGraphConstraint(grid, ENoGood::NoGood, GraphRelationClause(selfStepDoorTile, step_ReachableOrOrigin), - GraphRelationClause(selfTile, cell_Wall) + GraphRelationClause(selfStepTile, EClauseSign::Outside, step_ReachableOrOrigin) ); } @@ -573,7 +569,7 @@ int MazeSolver::solve(int times, int numRows, int numCols, int seed, bool printV // Ensure reachability for this step: all Step_Reachable cells must be reachable from Step_Origin cells. if (TEST_SHORTEST_PATH) { - solver.makeConstraint(stepDoorData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::LessThan, shortestPathDistance); + solver.makeConstraint(stepDoorData, step_Origin, step_Reachable, stepEdgeData, edge_Solid, EConstraintOperator::LessThan, 10); solver.makeConstraint(stepData, step_Origin, step_Reachable, stepEdgeData, edge_Solid); } else