/******************************************************************************* * Copyright 2015 Analog Devices, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ package com.analog.lyric.dimple.schedulers.validator; import static java.util.Objects.*; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.analog.lyric.dimple.model.core.EdgeState; import com.analog.lyric.dimple.model.core.FactorGraph; import com.analog.lyric.dimple.model.core.FactorGraphIterables; import com.analog.lyric.dimple.model.core.INode; import com.analog.lyric.dimple.model.core.Node; import com.analog.lyric.dimple.model.core.Port; import com.analog.lyric.dimple.model.factors.Factor; import com.analog.lyric.dimple.model.variables.Variable; import com.analog.lyric.dimple.schedulers.schedule.ISchedule; import com.analog.lyric.dimple.schedulers.schedule.ScheduleValidationException; import com.analog.lyric.dimple.schedulers.scheduleEntry.BlockScheduleEntry; import com.analog.lyric.dimple.schedulers.scheduleEntry.EdgeScheduleEntry; import com.analog.lyric.dimple.schedulers.scheduleEntry.IScheduleEntry; import com.analog.lyric.dimple.schedulers.scheduleEntry.NodeScheduleEntry; import com.analog.lyric.dimple.schedulers.scheduleEntry.SubgraphScheduleEntry; /** * A schedule validator that makes sure that all edges in the graph are visited. * @since 0.08 * @author Christopher Barber */ public class AllEdgeScheduleValidator extends InGraphScheduleValidator { /*------- * State */ /** * Maps graph to BitSet describing which ports have been visited using two bits per * edge with factor->variable before variable->factor */ private Map<FactorGraph, BitSet> _edgeSets = Collections.emptyMap(); /*--------------------------- * ScheduleValidator methods */ @Override public void start(ISchedule schedule) throws ScheduleValidationException { super.start(schedule); // Set bits for all the edges that must be visited. HashMap<FactorGraph, BitSet> edgeSets = new HashMap<>(); final FactorGraph fg = schedule.getFactorGraph(); if (fg == null) { throw new ScheduleValidationException("Schedule is not associated with factor graph."); } for (FactorGraph subgraph : FactorGraphIterables.subgraphs(fg)) { final BitSet edgesToVisit = new BitSet(subgraph.getGraphEdgeStateMaxIndex() * 2 + 2); edgeSets.put(subgraph, edgesToVisit); for (EdgeState edgeState : subgraph.getGraphEdgeState()) { final int edgeOffset = edgeState.edgeIndexInParent(subgraph) * 2; switch (edgeState.type(subgraph)) { case LOCAL: // Add both directions of edge edgesToVisit.set(edgeOffset, edgeOffset + 2); break; case OUTER: // Only add factor to variable direction edgesToVisit.set(edgeOffset); break; case INNER: // Only add variable to factor direction edgesToVisit.set(edgeOffset + 1); } } } _edgeSets = edgeSets; } @SuppressWarnings("deprecation") // SUBSCHEDULE @Override public void validateNext(IScheduleEntry entry) throws ScheduleValidationException { super.validateNext(entry); switch (entry.type()) { case EDGE: { EdgeScheduleEntry edgeEntry = (EdgeScheduleEntry)entry; Port port = edgeEntry.getPort(); FactorGraph graph = port.getParentGraph(); BitSet edgesToVisit = _edgeSets.get(graph); if (edgesToVisit != null) { // ordinal == 0 for FACTOR and 1 for VARIABLE edgesToVisit.clear(port.toEdgeState().edgeIndexInParent(graph) * 2 + port.portType().ordinal()); } break; } case NODE: { final NodeScheduleEntry nodeEntry = (NodeScheduleEntry)entry; final INode node = nodeEntry.getNode(); final FactorGraph graph = node.getParentGraph(); if (graph == null) { throw new ScheduleValidationException("%s has node with no parent graph", entry); } final BitSet edgesToVisit = _edgeSets.get(graph); if (edgesToVisit != null) { final int offset = node.isVariable() ? 1 : 0; for (int i = 0, n = node.getSiblingCount(); i < n; ++i) { edgesToVisit.clear(node.getSiblingEdgeState(i).edgeIndexInParent(graph) * 2 + offset); } } break; } case VARIABLE_BLOCK: for (Variable var : ((BlockScheduleEntry)entry).getBlock()) { for (int i = 0, n = var.getSiblingCount(); i < n; ++i) { FactorGraph graph = var.getParentGraph(); if (graph == null) { throw new ScheduleValidationException("%s contains variable %s with no parent graph", entry, var); } final BitSet edgesToVisit = _edgeSets.get(graph); if (edgesToVisit != null) { edgesToVisit.clear(var.getSiblingEdgeState(i).variableEdgeIndex() + 1); } } } break; case SUBGRAPH: // If we see entry for graph, simply remove edges for graph and all of its subgraphs // and assume schedule for subgraphs will do the right thing. for (FactorGraph subgraph : FactorGraphIterables.subgraphs(((SubgraphScheduleEntry)entry).getSubgraph())) { _edgeSets.remove(subgraph); } break; case SUBSCHEDULE: { for (IScheduleEntry subentry : ((com.analog.lyric.dimple.schedulers.scheduleEntry.SubScheduleEntry)entry).getSchedule()) { validateNext(subentry); } break; } case CUSTOM: throw new ScheduleValidationException("%s does not support custom schedule entry %s", getClass().getSimpleName(), entry); } } @Override public void finish() throws ScheduleValidationException { for (Map.Entry<FactorGraph, BitSet> entry : _edgeSets.entrySet()) { final int firstMissingEdge = entry.getValue().nextSetBit(0); if (firstMissingEdge >= 0) { // Look up edge by its index for the error message FactorGraph graph = entry.getKey(); EdgeState edgeState = requireNonNull(graph.getGraphEdgeState(firstMissingEdge / 2)); Factor factor = edgeState.getFactor(graph); Variable var = edgeState.getVariable(graph); Node from, to; if ((firstMissingEdge & 1) == 0) { from = factor; to = var; } else { from = var; to = factor; } throw new ScheduleValidationException("Missing edge entry from %s to %s in graph %s", from, to, graph); } } _edgeSets = Collections.emptyMap(); super.finish(); } }