/******************************************************************************* * Copyright 2012-2014 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.model.core; import static java.lang.String.*; import static java.util.Objects.*; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.UUID; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.collect.ExtendedArrayList; import com.analog.lyric.collect.IndexedArrayList; import com.analog.lyric.collect.Tuple2; import com.analog.lyric.dimple.data.DataLayer; import com.analog.lyric.dimple.data.GenericDataLayer; import com.analog.lyric.dimple.data.IDatum; import com.analog.lyric.dimple.environment.DimpleEnvironment; import com.analog.lyric.dimple.events.DimpleEventListener; import com.analog.lyric.dimple.events.IDimpleEventListener; import com.analog.lyric.dimple.events.IDimpleEventSource; import com.analog.lyric.dimple.exceptions.DimpleException; import com.analog.lyric.dimple.factorfunctions.Uniform; import com.analog.lyric.dimple.factorfunctions.core.CustomFactorFunctionWrapper; import com.analog.lyric.dimple.factorfunctions.core.FactorFunction; import com.analog.lyric.dimple.factorfunctions.core.FactorTable; import com.analog.lyric.dimple.factorfunctions.core.IFactorTable; import com.analog.lyric.dimple.factorfunctions.core.JointFactorFunction; import com.analog.lyric.dimple.factorfunctions.core.JointFactorFunction.Functions; import com.analog.lyric.dimple.factorfunctions.core.TableFactorFunction; import com.analog.lyric.dimple.model.core.Edge.Type; import com.analog.lyric.dimple.model.factors.DiscreteFactor; import com.analog.lyric.dimple.model.factors.Factor; import com.analog.lyric.dimple.model.factors.FactorBase; import com.analog.lyric.dimple.model.factors.FactorList; import com.analog.lyric.dimple.model.repeated.BlastFromThePastFactor; import com.analog.lyric.dimple.model.repeated.FactorGraphStream; import com.analog.lyric.dimple.model.repeated.IVariableStreamSlice; import com.analog.lyric.dimple.model.repeated.VariableStreamBase; import com.analog.lyric.dimple.model.values.Value; import com.analog.lyric.dimple.model.variables.Constant; import com.analog.lyric.dimple.model.variables.Discrete; import com.analog.lyric.dimple.model.variables.IConstantOrVariable; import com.analog.lyric.dimple.model.variables.Variable; import com.analog.lyric.dimple.model.variables.VariableBlock; import com.analog.lyric.dimple.model.variables.VariableList; import com.analog.lyric.dimple.schedulers.CustomScheduler; import com.analog.lyric.dimple.schedulers.IScheduler; import com.analog.lyric.dimple.schedulers.SchedulerOptionKey; import com.analog.lyric.dimple.schedulers.schedule.EmptySchedule; import com.analog.lyric.dimple.schedulers.schedule.FixedSchedule; import com.analog.lyric.dimple.schedulers.schedule.ISchedule; import com.analog.lyric.dimple.solvers.interfaces.IFactorGraphFactory; import com.analog.lyric.dimple.solvers.interfaces.ISolverFactor; import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph; import com.analog.lyric.dimple.solvers.interfaces.ISolverVariable; import com.analog.lyric.options.IOption; import com.analog.lyric.options.IOptionKey; import com.analog.lyric.options.Option; import com.analog.lyric.util.misc.FactorGraphDiffs; import com.analog.lyric.util.misc.IMapList; import com.analog.lyric.util.misc.Internal; import com.analog.lyric.util.misc.MapList; import com.analog.lyric.util.misc.Matlab; import com.analog.lyric.util.test.Helpers; import com.google.common.base.Predicate; import com.google.common.cache.LoadingCache; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import cern.colt.list.IntArrayList; import net.jcip.annotations.Immutable; import net.jcip.annotations.NotThreadSafe; @NotThreadSafe public class FactorGraph extends FactorBase { /*----------- * Constants */ /** * Flags that are reserved for use by this class and should not be * used by subclasses when invoking {@link #setFlags(int)} or {@link #clearFlags()}. */ @SuppressWarnings("hiding") protected static final int RESERVED = 0xFFFFFF00; private static final int FACTOR_ADD_EVENT = 0xFFFF0100; private static final int FACTOR_REMOVE_EVENT = 0xFFFF0200; private static final int VARIABLE_ADD_EVENT = 0xFFFF0400; private static final int VARIABLE_REMOVE_EVENT = 0xFFFF0800; private static final int SUBGRAPH_ADD_EVENT = 0xFFFF1000; private static final int SUBGRAPH_REMOVE_EVENT = 0xFFFF2000; private static final int BOUNDARY_VARIABLE_ADD_EVENT = 0xFFFF4000; private static final int BOUNDARY_VARIABLE_REMOVE_EVENT = 0xFFFF8000; private static final int EVENT_MASK = 0x0000FF00; /*------- * State */ /** * Environment for this graph determined at construction. Unless the constructor * specifies a different one, this will be the value of {@link DimpleEnvironment.active} * when the object was constructed. */ private final DimpleEnvironment _env; /** * Unique identifier for this graph within its environment. */ private final int _graphId; private volatile @Nullable IDimpleEventSource _eventAndOptionParent; private final OwnedVariables _ownedVariables = new OwnedVariables(); private final IndexedArrayList<Variable> _boundaryVariables = new IndexedArrayList<>(); /** * Factors and subgraphs contained directly by this graph. Does not include * factors and subgraphs contained in subgraphs of this graph. */ private final OwnedFactors _ownedFactors = new OwnedFactors(); private final OwnedGraphs _ownedSubGraphs = new OwnedGraphs(); private final OwnedVariableBlocks _ownedVariableBlocks = new OwnedVariableBlocks(); private final OwnedConstants _ownedConstants = new OwnedConstants(); /** * Holds state common to entire tree of FactorGraphs. */ private static class GraphTreeState { private @Nullable DataLayer<? extends IDatum> _defaultConditioningLayer = null; /** * Indicates whether any changes can be made to the graph tree or any of its * component graphs. For use in template graphs. */ private boolean _frozen; /** * Counter that is modified whenever a structural change is made anywhere in the graph tree. */ private long _globalStructureVersion = 0; /** * List of all graphs in the graph tree that shares a common root, which will the first element. * Each graph is indexed by its graph tree index. */ private final ExtendedArrayList<FactorGraph> _graphs; /** * Number of non-null graph entries in {@link #_graphs} list. */ private int _nGraphs = 0; /** * The highest numbered index of any non-null entry of {@links _graphs} list. */ private int _maxGraphTreeIndex = 0; private GraphTreeState(FactorGraph root) { _graphs = new ExtendedArrayList<FactorGraph>(1); addGraph(root); } private GraphTreeState addGraph(FactorGraph graph) { final int index = _graphs.size(); graph._graphTreeIndex = index; _maxGraphTreeIndex = index; _graphs.add(graph); ++_nGraphs; return this; } private void assertNotFrozen() { if (_frozen) { throw new IllegalStateException("Changes cannot be made to frozen graph"); } } private void removeGraph(FactorGraph graph) { int index = graph._graphTreeIndex; assert(graph == _graphs.get(index)); _graphs.set(index, null); if (index == _maxGraphTreeIndex) { // If at end of list, we can reclaim slots at end immediately without affecting other indexes. do { _graphs.remove(index); // There always has to be a graph in this list, so this has to terminate. } while (_graphs.get(--index) == null); _maxGraphTreeIndex = index; _graphs.setSize(index+1); } --_nGraphs; } } private GraphTreeState _graphTreeState; /** * Index of this graph within {@code _graphTreeState._graphs}. */ private int _graphTreeIndex = -1; /** * Incremented for every change to the structure of this graph. */ private long _structureVersion = 0; /** * If not equal to _structureVersion, indicates that the graph siblings list is out-of-date. */ private long _siblingVersionId = -1; // TODO : some state only needs to be in root graph. Put it in common object. private @Nullable IFactorGraphFactory<?> _solverFactory; private @Nullable ISolverFactorGraph _solverFactorGraph; private @Nullable LoadingCache<Functions, JointFactorFunction> _jointFactorCache = null; private @Nullable HashSet<VariableStreamBase<?>> _variableStreams = null; private @Nullable ArrayList<FactorGraphStream> _factorGraphStreams = null; private int _numSteps = 1; private boolean _numStepsInfinite = true; //new identity related members private final HashMap<String, Node> _name2object = new HashMap<>(); private final EdgeStateList _edges; /** * Edges defining the graph's siblings. * <p> * This replaces the sibling indexes from Node, which cannot be used for this due to the * fact the edges may not be a member of this graph's edge list. */ private final ArrayList<EdgeState> _graphSiblings = new ArrayList<>(); /*-------------- * Edge classes */ // Note that because we expect there to be potentially many edges, we go to the effort of // of providing implementations that require less memory. We assume that Java will use 8-byte // alignment, so we aim to have the size be a multiple of that. /** * Edge connecting factor and variable owned by same graph. */ @Immutable private static abstract class LocalEdgeState extends EdgeState { static LocalEdgeState create(int edgeIndex, int factorIndex, int variableIndex) { if (edgeIndex <= SmallLocalEdgeState.EDGE_MASK && factorIndex <= SmallLocalEdgeState.NODE_MASK && variableIndex <= SmallLocalEdgeState.NODE_MASK) { return new SmallLocalEdgeState(edgeIndex, factorIndex, variableIndex); } return new FullLocalEdgeState(edgeIndex, factorIndex, variableIndex); } @Override public String toString() { return String.format("[LocalEdgeState #%d: %d - %d]", factorEdgeIndex(), factorIndex(), variableIndex()); } @Override public final Factor getFactor(FactorGraph fg) { return fg._ownedFactors.get(factorIndex()); } @Override public FactorGraph getFactorParent(FactorGraph graph) { return graph; } @Override public final Variable getVariable(FactorGraph fg) { return fg._ownedVariables.get(variableIndex()); } @Override public FactorGraph getVariableParent(FactorGraph graph) { return graph; } @Override public final boolean isLocal() { return true; } @Override public Type type(FactorGraph graph) { return Edge.Type.LOCAL; } @Override public final int variableLocalId() { return Ids.localIdFromParts(Ids.VARIABLE_TYPE, variableIndex()); } } private static final class SmallLocalEdgeState extends LocalEdgeState { private long _data; private static final int EDGE_BITS = 26; private static final int NODE_BITS = 19; private static final int EDGE_MASK = (1 << EDGE_BITS) - 1; private static final int NODE_MASK = (1 << NODE_BITS) - 1; private static final int FACTOR_OFFSET = EDGE_BITS; private static final int VARIABLE_OFFSET = EDGE_BITS + NODE_BITS; private SmallLocalEdgeState(int edgeIndex, int factorIndex, int variableIndex) { _data = edgeIndex | (long)factorIndex << FACTOR_OFFSET | (long)variableIndex << VARIABLE_OFFSET; } @Override public int edgeIndex(Node node) { return (int)_data & EDGE_MASK; } @Override public int edgeIndexInParent(FactorGraph graph) { return (int)_data & EDGE_MASK; } @Override public int factorEdgeIndex() { return (int)_data & EDGE_MASK; } @Override public int variableEdgeIndex() { return (int)_data & EDGE_MASK; } @Override public int factorIndex() { return (int)(_data >>> FACTOR_OFFSET) & NODE_MASK; } @Override public int variableIndex() { return (int)(_data >>> VARIABLE_OFFSET) & NODE_MASK; } @Override void setEdgeIndexInParent(FactorGraph graph, int newEdgeIndex) { _data = newEdgeIndex | _data & ~(long)EDGE_MASK; } @Override void setFactorIndex(int newIndex) { _data &= ~((long)NODE_MASK << FACTOR_OFFSET); _data |= (long)newIndex << FACTOR_OFFSET; } @Override void setVariableIndex(int newIndex) { _data &= ~((long)NODE_MASK << VARIABLE_OFFSET); _data |= newIndex << VARIABLE_OFFSET; } } private static final class FullLocalEdgeState extends LocalEdgeState { private int _edgeIndex; private int _factorIndex; private int _variableIndex; private FullLocalEdgeState(int edgeIndex, int factorOffset, int variableOffset) { _edgeIndex = edgeIndex; _factorIndex = factorOffset; _variableIndex = variableOffset; } @Override public int edgeIndex(Node node) { return _edgeIndex; } @Override public int edgeIndexInParent(FactorGraph graph) { return _edgeIndex; } @Override public int factorEdgeIndex() { return _edgeIndex; } @Override public int variableEdgeIndex() { return _edgeIndex; } @Override public int factorIndex() { return _factorIndex; } @Override public int variableIndex() { return _variableIndex; } @Override void setEdgeIndexInParent(FactorGraph graph, int newEdgeIndex) { _edgeIndex = newEdgeIndex; } @Override void setFactorIndex(int newIndex) { _factorIndex = newIndex; } @Override void setVariableIndex(int newIndex) { _variableIndex = newIndex; } } /** * Edge connecting factor owned by this graph to one of its boundary variables. */ static abstract class BoundaryEdge extends EdgeState { final Factor _factor; private BoundaryEdge(Factor factor) { _factor = factor; } @Override public String toString() { return String.format("[BoundaryEdge %d/%d: %d (%s) - %d (%s)]", factorEdgeIndex(), variableEdgeIndex(), factorIndex(), _factor.getQualifiedName(), variableIndex(), getVariable().getQualifiedName()); } static BoundaryEdge create(Factor factor, int factorEdgeIndex, int boundaryIndex, int variableEdgeIndex) { if (factorEdgeIndex <= SmallBoundaryEdge.EDGE_MASK && variableEdgeIndex <= SmallBoundaryEdge.EDGE_MASK && boundaryIndex <= SmallBoundaryEdge.VARIABLE_MASK) { return new SmallBoundaryEdge(factor, factorEdgeIndex, boundaryIndex, variableEdgeIndex); } return new FullBoundaryEdge(factor, factorEdgeIndex, boundaryIndex, variableEdgeIndex); } @Override public final Factor getFactor(FactorGraph fg) { return _factor; } @Override public FactorGraph getFactorParent(FactorGraph graph) { return requireNonNull(_factor.getParentGraph()); } final Variable getVariable() { final FactorGraph fg = requireNonNull(_factor.getParentGraph()); return fg._boundaryVariables.get(variableIndex()); } @Override public final Variable getVariable(FactorGraph fg) { return getVariable(); } @Override public FactorGraph getVariableParent(FactorGraph graph) { return requireNonNull(getVariable().getParentGraph()); } @Override public final int factorLocalId() { return _factor.getLocalId(); } @Override public final int factorIndex() { return Ids.indexFromLocalId(_factor.getLocalId()); } @Override public final boolean isLocal() { return false; } @Override public Type type(FactorGraph graph) { return getFactorParent(graph) == graph ? Edge.Type.OUTER : Edge.Type.INNER; } @Override public int variableLocalId() { return Ids.localIdFromParts(Ids.BOUNDARY_VARIABLE_TYPE, variableIndex()); } @Override void setFactorIndex(int newIndex) { // Index comes directly from Factor, so it should already be set assert(newIndex == factorIndex()); } } private static final class SmallBoundaryEdge extends BoundaryEdge { private int _data; private static final int EDGE_BITS = 11; private static final int EDGE_MASK = (1 << EDGE_BITS) - 1; private static final int VARIABLE_BITS = 10; private static final int VARIABLE_MASK = (1 << VARIABLE_BITS) - 1; private static final int FACTOR_EDGE_OFFSET = VARIABLE_BITS; private static final int VARIABLE_EDGE_OFFSET = FACTOR_EDGE_OFFSET + EDGE_BITS; private SmallBoundaryEdge(Factor factor, int factorEdgeIndex, int boundaryIndex, int variableEdgeIndex) { super(factor); _data = boundaryIndex | factorEdgeIndex<<FACTOR_EDGE_OFFSET | variableEdgeIndex<<VARIABLE_EDGE_OFFSET; } @Override public int edgeIndex(Node node) { return node.isVariable() ? variableEdgeIndex() : factorEdgeIndex(); } @Override public int edgeIndexInParent(FactorGraph graph) { return _factor.getParentGraph() == graph ? factorEdgeIndex() : variableEdgeIndex(); } @Override public int factorEdgeIndex() { return (_data >>> FACTOR_EDGE_OFFSET) & EDGE_MASK; } @Override public int variableEdgeIndex() { // Mask not needed because these are the high-end bits return _data >>> VARIABLE_EDGE_OFFSET; } @Override public int variableIndex() { return _data & VARIABLE_MASK; } @Override void setEdgeIndexInParent(FactorGraph graph, int newEdgeIndex) { if (_factor.getParentGraph() == graph) { _data &= ~(EDGE_MASK << FACTOR_EDGE_OFFSET); _data |= newEdgeIndex << FACTOR_EDGE_OFFSET; } else { _data &= ~(EDGE_MASK << VARIABLE_EDGE_OFFSET); _data |= newEdgeIndex << VARIABLE_EDGE_OFFSET; } } @Override void setVariableIndex(int newIndex) { _data &= ~VARIABLE_MASK; _data |= newIndex; } } private static final class FullBoundaryEdge extends BoundaryEdge { private int _factorEdgeIndex; private int _boundaryIndex; private int _variableEdgeIndex; private FullBoundaryEdge(Factor factor, int factorEdgeIndex, int boundaryIndex, int variableEdgeIndex) { super(factor); _factorEdgeIndex = factorEdgeIndex; _boundaryIndex = boundaryIndex; _variableEdgeIndex = variableEdgeIndex; } @Override public int edgeIndex(Node node) { return node.isVariable() ? _variableEdgeIndex : _factorEdgeIndex; } @Override public int edgeIndexInParent(FactorGraph graph) { return _factor.getParentGraph() == graph ? _factorEdgeIndex : _variableEdgeIndex; } @Override public int factorEdgeIndex() { return _factorEdgeIndex; } @Override public int variableEdgeIndex() { return _variableEdgeIndex; } @Override public int variableIndex() { return _boundaryIndex; } @Override void setEdgeIndexInParent(FactorGraph graph, int newEdgeIndex) { if (_factor.getParentGraph() == graph) { _factorEdgeIndex = newEdgeIndex; } else { _variableEdgeIndex = newEdgeIndex; } } @Override void setVariableIndex(int newIndex) { _boundaryIndex = newIndex; } } /*-------------- * Construction */ public FactorGraph() { this(new Variable[0], ""); } public FactorGraph(@Nullable String name) { this(null, name); } /** * @since 0.07 */ public FactorGraph(@Nullable Variable ... boundaryVariables) { this(boundaryVariables, ""); } /** * @since 0.07 */ public FactorGraph(@Nullable Variable[] boundaryVariables, @Nullable String name) { this(boundaryVariables, name, DimpleEnvironment.active().defaultSolver()); } /** * @since 0.07 */ public FactorGraph( @Nullable Variable[] boundaryVariables, @Nullable String name, @Nullable IFactorGraphFactory<?> solver) { this(null, boundaryVariables, name, solver); } private FactorGraph( @Nullable GraphTreeState rootState, @Nullable Variable[] boundaryVariables, @Nullable String name, @Nullable IFactorGraphFactory<?> solver) { super(Ids.INITIAL_GRAPH_ID); _env = DimpleEnvironment.active(); _graphId = _env.factorGraphs().registerIdForFactorGraph(this); _eventAndOptionParent = _env; _graphTreeState = rootState != null ? rootState.addGraph(this) : new GraphTreeState(this); _edges = new EdgeStateList(this); if (boundaryVariables != null) { addBoundaryVariables(boundaryVariables); } if ("".equals(name)) { name = null; } setName(name); if (solver != null) { setSolverFactory(solver); } notifyListenerChanged(); } @Override public final FactorGraph asFactorGraph() { return this; } @Override public final boolean isFactorGraph() { return true; } @Override public NodeType getNodeType() { return NodeType.GRAPH; } @Override public String getClassLabel() { return "Graph"; } /** * Unique identifier of graph within its {@linkplain #getEnvironment() environment}. * <p> * Will be in the range from {@link Ids#GRAPH_ID_MIN} to {@link Ids#GRAPH_ID_MAX}. * <p> * @since 0.08 */ public int getGraphId() { return _graphId; } @Override public long getGlobalId() { if (getParentGraph() == null) { // If there is no parent graph, then use the graph id as the id. return Ids.globalIdFromParts(0, Ids.GRAPH_TYPE, _graphId); } else { return super.getGlobalId(); } } /** * {@inheritDoc} * <p> * For root graphs (that have no {@linkplain #getParentGraph() parent graph}) * the implicitly generated name will be computed by {@link Ids#defaultNameForGraphId(int)} * using the value of {@link #getGraphId()}. */ @Override public String getName() { String name = _name; if (name != null) { return name; } if (getParentGraph() == null) { return Ids.defaultNameForGraphId(_graphId); } else { return Ids.defaultNameForLocalId(getLocalId()); } } /*-------------------------- * IDimpleEnvironmentHolder */ @Override public DimpleEnvironment getEnvironment() { return _env; } /*--------------------- * IDimpleOptionHolder */ /** * {@inheritDoc} * <p> * By default this returns {@link #getParentGraph()} if non null, and otherwise returns * the value of {@link #getEnvironment()} (which means that the environment is the default * root of the event source hierarchy), but it may be also be set to another value using * {@link #setEventAndOptionParent(IDimpleEventSource)}. * <p> * @see #setParentGraph(FactorGraph) * @since 0.07 */ @Override public @Nullable IDimpleEventSource getOptionParent() { return _eventAndOptionParent; } /*------------------- * IDimpleEventSource */ /** * {@inheritDoc} */ @Override public FactorGraph getContainingGraph() { return this; } /** * {@inheritDoc} * <p> * By default this returns {@link #getParentGraph()} if non null, and otherwise returns * the value of {@link #getEnvironment()} (which means that the environment is the default * root of the event source hierarchy), but it may be also be set to another value using * {@link #setEventAndOptionParent(IDimpleEventSource)}. * <p> * @see #setParentGraph(FactorGraph) */ @Override public @Nullable IDimpleEventSource getEventParent() { return _eventAndOptionParent; } @Override public void notifyListenerChanged() { final IDimpleEventListener listener = getEventListener(); int eventMask = 0; if (listener != null) { if (listener.isListeningFor(FactorAddEvent.class, this)) { eventMask |= FACTOR_ADD_EVENT; } if (listener.isListeningFor(FactorRemoveEvent.class, this)) { eventMask |= FACTOR_REMOVE_EVENT; } if (listener.isListeningFor(VariableAddEvent.class, this)) { eventMask |= VARIABLE_ADD_EVENT; } if (listener.isListeningFor(VariableRemoveEvent.class, this)) { eventMask |= VARIABLE_REMOVE_EVENT; } if (listener.isListeningFor(BoundaryVariableAddEvent.class, this)) { eventMask |= BOUNDARY_VARIABLE_ADD_EVENT; } if (listener.isListeningFor(BoundaryVariableRemoveEvent.class, this)) { eventMask |= BOUNDARY_VARIABLE_REMOVE_EVENT; } if (listener.isListeningFor(SubgraphAddEvent.class, this)) { eventMask |= SUBGRAPH_ADD_EVENT; } if (listener.isListeningFor(SubgraphRemoveEvent.class, this)) { eventMask |= SUBGRAPH_REMOVE_EVENT; } } setFlagValue(EVENT_MASK, eventMask); } /** * Sets the option/event parent object to specified value. * <p> * Sets the value returned by {@link #getOptionParent()}/{@link #getEventParent()} to {@code parent}, * which may be null. Note that this will override any previous value set by * {@link #setEventAndOptionParent(IDimpleEventSource)}. * <p> * @param parent * @since 0.07 */ public void setEventAndOptionParent(@Nullable IDimpleEventSource parent) { assertGraphNotFrozen(); _eventAndOptionParent = parent; } /*--------------- * Node methods */ /** * {@inheritDoc} * <p> * In addition to setting the value of @link #getParentGraph()}, this will also * reset the value of {@link #getOptionParent()} and {@link #getEventParent()} to * the new parent graph if non-null and otherwise the value of {@link #getEnvironment()}. */ @Override protected void setParentGraph(@Nullable FactorGraph parentGraph) { super.setParentGraph(parentGraph); if (parentGraph != null) { _eventAndOptionParent = parentGraph; } else { _eventAndOptionParent = _env; } } /*============== * Data methods */ /** * The default conditioning layer to be used with this model, if any. * <p> * The same object is returned for all factor graphs in the graph tree. * * @since 0.08 * @see #createDefaultConditioningLayer() * @see #setDefaultConditioningLayer(DataLayer) */ public @Nullable DataLayer<? extends IDatum> getDefaultConditioningLayer() { return _graphTreeState._defaultConditioningLayer; } /** * Returns the default conditioning layer to be used with this model, creating a new one if necessary. * <p> * This is similar to {@link #getDefaultConditioningLayer()}, but instead of returning null * if there is no layer, this will create a new sparse {@link GenericDataLayer} * and set it on the {@linkplain #getRootGraph() root graph} using {@link #setDefaultConditioningLayer(DataLayer)}. * * @since 0.08 */ public DataLayer<? extends IDatum> createDefaultConditioningLayer() { DataLayer<? extends IDatum> layer = getDefaultConditioningLayer(); if (layer == null) { setDefaultConditioningLayer(layer = new GenericDataLayer(getRootGraph())); } return layer; } /** * Sets the {@linkplain #getDefaultConditioningLayer() default conditioning layer} for this model. * <p> * This may only be set on the root graph for the graph tree and will be shared by all subgraphs. * <p> * If the graph has a designated solver graph ({@link #getSolver()}), this will be set as the * conditioning layer for that solver. * <p> * @throws IllegalStateException if this is not the root graph. * @since 0.08 * @see #createDefaultConditioningLayer() */ public void setDefaultConditioningLayer(@Nullable DataLayer<? extends IDatum> layer) { assertGraphNotFrozen(); if (this != getRootGraph()) { throw new IllegalStateException("The default conditioning layer may only be set on the root graph."); } _graphTreeState._defaultConditioningLayer = layer; ISolverFactorGraph sfg = getSolver(); if (sfg != null) { sfg.setConditioningLayer(layer); } } /*============== * Solver stuff */ private @Nullable ISolverFactorGraph setSolverFactorySubGraph(@Nullable ISolverFactorGraph parentSolverGraph, @Nullable IFactorGraphFactory<?> factory) { _solverFactory = factory; return _solverFactorGraph = parentSolverGraph != null ? parentSolverGraph.getSolverSubgraph(this) : null; } private void setSolverFactorySubGraphRecursive(@Nullable ISolverFactorGraph parentSolverGraph, @Nullable IFactorGraphFactory<?> factory) { ISolverFactorGraph solverGraph = setSolverFactorySubGraph(parentSolverGraph, factory); for (FactorGraph fg : _ownedSubGraphs) fg.setSolverFactorySubGraphRecursive(solverGraph, factory); } /** * Creates solver graph and attaches to this model. * <p> * Same as {@link #setSolverFactory(IFactorGraphFactory)} but does not accept a null * factory and returns a non-null solver graph. * <p> * @param factory * @since 0.06 */ public <SG extends ISolverFactorGraph> SG createSolver(IFactorGraphFactory<SG> factory) { return Objects.requireNonNull(setSolverFactory(Objects.requireNonNull(factory))); } /** * Sets solver for graph. * * @param factory is used to construct the solver graph and will be used to create solver * state for any nodes subsequently added to the graph. If null, then the solver will be * cleared from the graph. * @return The newly created solver graph or else null if {@code factory} was null. */ public @Nullable <SG extends ISolverFactorGraph> SG setSolverFactory(@Nullable IFactorGraphFactory<SG> factory) { assertGraphNotFrozen(); _solverFactory = factory; final FactorGraph parent = getParentGraph(); final ISolverFactorGraph sparent = parent != null ? parent.getSolver() : null; SG solverGraph = factory != null ? factory.createFactorGraph(this, sparent) : null; _solverFactorGraph = solverGraph; if (parent != null) { final ISolverFactorGraph parentSolverGraph = parent._solverFactorGraph; if (parentSolverGraph != null) { parentSolverGraph.recordDefaultSubgraphSolver(this); } } for (FactorGraph fg : _ownedSubGraphs) fg.setSolverFactorySubGraphRecursive(solverGraph, factory); for (FactorGraph graph : FactorGraphIterables.subgraphs(this)) { ISolverFactorGraph sgraph = graph.getSolver(); for (Variable var : graph._ownedVariables) { var.createSolverObject(sgraph); } for (Factor factor : graph._ownedFactors) { factor.createSolverObject(sgraph); } } return solverGraph; } //======================== // // Tables and Functions // //======================== public static boolean allDomainsAreDiscrete(Variable [] vars) { for (int i = 0; i < vars.length; i++) { if (!vars[i].getDomain().isDiscrete()) return false; } return true; } public BlastFromThePastFactor addBlastFromPastFactor(Variable var,Port factorPort) { assertGraphNotFrozen(); boolean setVarSolver = false; // FIXME boundary variable solver logic is hacky if (_solverFactorGraph != null && !variableBelongs(var)) { if (var.getSiblingCount() > 0) throw new DimpleException("Can't connect a variable to multiple graphs"); setVarSolver = true; } // Add any variables that do not yet have a parent if (var.getParentGraph() == null) { addVariables(var); } setVariableSolver(var); BlastFromThePastFactor f; f = new BlastFromThePastFactor(var,factorPort); addFactor(f,var); // Give factor a descriptive name to ease debugging. f.setName(String.format("$BFP_%s_to_%s_%s", factorPort.getSiblingNode().getName(), var.getName(), f.getReverseSiblingNumber(0))); if (setVarSolver) { var.createSolverObject(_solverFactorGraph); } if (_solverFactorGraph != null) f.createSolverObject(_solverFactorGraph); return f; } public FactorGraphStream addRepeatedFactor(FactorGraph nestedGraph, Object ... vars) { return addRepeatedFactorWithBufferSize(nestedGraph,1, vars); } public FactorGraphStream addRepeatedFactorWithBufferSize(FactorGraph nestedGraph, int bufferSize,Object ... vars) { assertGraphNotFrozen(); FactorGraphStream fgs = new FactorGraphStream(this, nestedGraph, bufferSize, vars); ArrayList<FactorGraphStream> fgstreams = _factorGraphStreams; if (fgstreams == null) { _factorGraphStreams = fgstreams = new ArrayList<>(); } fgstreams.add(fgs); HashSet<VariableStreamBase<?>> vstreams = _variableStreams; if (vstreams == null) { _variableStreams = vstreams = new HashSet<>(); } for (Object v : vars) { if (v instanceof IVariableStreamSlice) { vstreams.add(((IVariableStreamSlice<?>) v).getStream()); } } return fgs; } public void setNumStepsInfinite(boolean inf) { _numStepsInfinite = inf; } public boolean getNumStepsInfinite() { return _numStepsInfinite; } public void setNumSteps(int numSteps) { _numSteps = numSteps; } public int getNumSteps() { return _numSteps; } public void advance() { ISolverFactorGraph solver = requireSolver("advance"); final Set<VariableStreamBase<?>> vstreams = _variableStreams; if (vstreams != null) { for (VariableStreamBase<?> vs : vstreams) { vs.advanceState(); } } final List<FactorGraphStream> fgstreams = _factorGraphStreams; if (fgstreams != null) { for (FactorGraphStream s : fgstreams) { s.advance(); } } solver.postAdvance(); } public boolean hasNext() { List<FactorGraphStream> streams = _factorGraphStreams; if (streams == null || streams.size() == 0) return false; for (FactorGraphStream s : streams) if (!s.hasNext()) return false; return true; } /** * {@link FactorGraphStream}s for repeated factors. * @see #addRepeatedFactor(FactorGraph, Object...) * @see #addRepeatedFactorWithBufferSize(FactorGraph, int, Object...) */ public List<FactorGraphStream> getFactorGraphStreams() { List<FactorGraphStream> streams = _factorGraphStreams; if (streams == null) { streams = Collections.emptyList(); } return streams; } /** * Adds a new constant value. * <p> * Add a new constant owned by this graph for use with factors also owned by this graph. * <p> * @param value specifies the constant. It will be {@linkplain Value#immutableClone() cloned immutably} * if necessary. * @return the newly added constant object. * @since 0.08 */ public Constant addConstant(Value value) { assertGraphNotFrozen(); Constant constant = new Constant(value); _ownedConstants.add(constant); constant.setParentGraph(this); return constant; } /** * Adds a new constant value. * <p> * Add a new constant owned by this graph for use with factors also owned by this graph. * <p> * @param object specifies the constant, may be one of: * <ul> * <li>{@link Constant}: if owned by this graph will simply be returned, otherwise a new * constant will be added with the same {@link Constant#value}. * <li>{@link Value}: same as {@link #addConstant(Value)}. * <li>any other {@link Object}: will be wrapped using {@link Value#constant} * </ul> * @return the newly added constant object. * @since 0.08 */ public Constant addConstant(Object object) { if (object instanceof Constant) { Constant constant = (Constant)object; if (constant.getParentGraph() == this) { return constant; } return addConstant(constant.value()); } if (object instanceof Value) { return addConstant((Value)object); } return addConstant(Value.constant(object)); } public Factor addFactor(int [][] indices, double [] weights, Discrete ... vars) { return addFactor(FactorTable.create(indices, weights, vars),vars); } public Factor addFactor(IFactorTable ft, Variable ... vars) { return addFactor(new TableFactorFunction("TableFactorFunction",ft),vars); } public Factor addFactor(FactorFunction factorFunction, Variable ... vars) { return addFactor(factorFunction, (Object[])vars); } public Factor addFactor(String factorFunctionName, Object ... vars) { if (customFactorExists(factorFunctionName)) return addFactor(new CustomFactorFunctionWrapper(factorFunctionName), vars); else { FactorFunction factorFunction = getEnvironment().factorFunctions().instantiate(factorFunctionName); return addFactor(factorFunction, vars); } } public Factor addFactor(String factorFunctionName, Variable...vars) { return addFactor(factorFunctionName, (Object[])vars); } public Factor addFactor(FactorFunction factorFunction, Object ... varsAndConstants) { assertGraphNotFrozen(); final int nArgs = varsAndConstants.length; int[] factorArguments = new int[nArgs]; boolean allVarsDiscrete = true; int nVars = 0; for (int i = 0; i < nArgs; ++i) { final Object arg = varsAndConstants[i]; if (arg instanceof Variable) { ++nVars; Variable var = (Variable)arg; if (!var.hasParentGraph()) { addVariables(var); } factorArguments[i] = _edges.allocateIndex(); setVariableSolver(var); if (!var.getDomain().isDiscrete()) { allVarsDiscrete = false; } } else { factorArguments[i] = addConstant(arg).getLocalId(); } } if (nVars == 0) throw new DimpleException("must pass at least one variable to addFactor"); Factor factor = allVarsDiscrete ? new DiscreteFactor(factorFunction) : new Factor(factorFunction); addOwnedFactor(factor, false); for (int i = 0; i < nArgs; ++i) { final int id = factorArguments[i]; if (Ids.typeIndexFromLocalId(id) != Ids.CONSTANT_TYPE) { addEdge(id, factor, (Variable)varsAndConstants[i]); } } if (nVars != nArgs) { ((Node)factor).setArguments(factorArguments); } final ISolverFactorGraph sfg = _solverFactorGraph; if (sfg != null) { factor.createSolverObject(_solverFactorGraph); sfg.postAddFactor(factor); } return factor; } private void setVariableSolver(Variable v) { if (_solverFactorGraph != null) { //check to see if variable belongs to this graph if (!variableBelongs(v)) { if (v.getSiblingCount() > 0) throw new DimpleException(String.format("Can't connect variable %s to multiple graphs", v)); v.createSolverObject(_solverFactorGraph); } } } /** * True if variable is one of this graph's boundary variables or is * owned by this graph or one of its subgraphs. */ private boolean variableBelongs(Variable v) { // TODO: apart from the boundary variable case, it seems that it would probably be // more efficient to simply walk the ancestor chain from v to see if it hits this graph. if (_ownedVariables.containsNode(v)) return true; if (_boundaryVariables.contains(v)) return true; for (FactorGraph fg : getNestedGraphs()) if (fg.variableBelongs(v)) return true; return false; } /** * Freeze graph tree including this graph from future changes. * <p> * Once frozen, a graph may not be unfrozen. * <p> * Graph may only be frozen if all of the following conditions are met: * <ul> * <li>there is no {@link #getDefaultConditioningLayer() default conditioning layer} * <li>no graph is associated with a {@link #getSolver() solver graph} * <li>{@link #getFactorGraphStreams()} is empty * </ul> * <p> * This is primarily intended for freezing template graphs so that graphs generated from them * can be assured to have the same layout. * <p> * @throws IllegalStateException if any of the conditions listed above are violated. * @since 0.08 * @see #frozen * @see #reindexGraphTree() */ public void freezeGraphTree() { final GraphTreeState state = _graphTreeState; if (state._frozen) return; assertOkToFreezeOrReindex("Cannot freeze graph tree"); // Since graphs are frozen, shrink any arrays down to the minimum required size. for (FactorGraph graph : FactorGraphIterables.subgraphs(getRootGraph())) { graph._edges.trimToSize(); graph._ownedConstants.trimToSize(); graph._ownedFactors.trimToSize(); graph._ownedSubGraphs.trimToSize(); graph._ownedVariableBlocks.trimToSize(); graph._ownedVariables.trimToSize(); graph.updateSiblings(); graph._siblingEdges.trimToSize(); for (Node factor : graph._ownedFactors) { factor.trimToSize(); } for (Node var : graph._ownedVariables) { var.trimToSize(); } } state._frozen = true; } private void assertOkToFreezeOrReindex(String baseErrorMessage) { if (getDefaultConditioningLayer() != null) { throw new IllegalStateException(format("%s: default conditioning layer is not null", baseErrorMessage)); } for (FactorGraph graph : FactorGraphIterables.subgraphs(getRootGraph())) { if (graph._solverFactorGraph != null) { throw new IllegalStateException(format("%s: %s has a solver graph", baseErrorMessage, graph)); } if (graph._factorGraphStreams != null) { throw new IllegalStateException(format("%s: %s has FactorGraphStreams", baseErrorMessage, graph)); } } } /** * Indicate whether graph tree including this graph has been frozen to future changes. * <p> * @since 0.08 * @see #freezeGraphTree */ public boolean frozen() { return _graphTreeState._frozen; } /** * True if child is owned directly by this graph. * * @param node */ public boolean ownsDirectly(FactorGraphChild node) { final boolean owns = node.getParentGraph() == this; assert(owns == ownsDirectly_(node)); return owns; } /** * Slower version of {@link #OwnsDirectly} just used for * checking correctness in assertion. */ private boolean ownsDirectly_(FactorGraphChild node) { switch (Ids.typeIndexFromLocalId(node.getLocalId())) { case Ids.VARIABLE_TYPE: return _ownedVariables.containsNode(node); case Ids.FACTOR_TYPE: return _ownedFactors.containsNode(node); case Ids.GRAPH_TYPE: return _ownedSubGraphs.containsNode(node); case Ids.VARIABLE_BLOCK_TYPE: return _ownedVariableBlocks.containsNode(node); } return false; } /** * Reindex elements of graph tree to compress representation. * <p> * This reallocates space left by removal of factors, variables, subgraphs, edges, etc. * by moving higher-numbered elements into the freed up spots. This will produce a more * compact representation for both solver and data state for the graph. If no elements have been * removed then this should have no effect. Note that {@link #join(Factor...)} and * {@link #join(Variable[], Factor...)} will implicitly remove variables, factors, edges * and subgraphs. * <p> * Graph may only be reindexed if all of the following conditions are met: * <ul> * <li>the graph tree is not {@link #frozen}. * <li>there is no {@link #getDefaultConditioningLayer() default conditioning layer} * <li>no graph is associated with a {@link #getSolver() solver graph} * <li>{@link #getFactorGraphStreams()} is empty * </ul> * <b>Reindexing the graph could invalidate any existing solvers or data layers * that refer to the graphs in the tree, so this should only be used after finishing modifications * to the model, but before associating data or solvers with it.</b> * <p> * @return true if any changes were made. * @throws IllegalStateException if any of the conditions listed above were violated. * @since 0.08 */ public boolean reindexGraphTree() { assertNotFrozen(); assertOkToFreezeOrReindex("Cannot reindex graph tree"); // Renumber and compress graph tree indexes - requires renumbering in VariableBlocks final GraphTreeState state = _graphTreeState; final FactorGraph root = getRootGraph(); final int[] old2newGraphTreeIndex = new int[state._maxGraphTreeIndex + 1]; boolean indexChanged = false; int to = 0; for (int from = 0, max = state._maxGraphTreeIndex; from <= max; ++from) { FactorGraph graph = state._graphs.get(from); if (graph != null) { old2newGraphTreeIndex[from] = to; if (from != to) { indexChanged = true; graph._graphTreeIndex = to; state._graphs.set(to, graph); state._graphs.set(from, null); } ++to; } } if (to != state._graphs.size()) { state._graphs.setSize(to); state._maxGraphTreeIndex = to - 1; } List<VariableBlock> blocks = new ArrayList<>(); for (VariableBlock block : FactorGraphIterables.variableBlocks(root)) { blocks.add(block); if (indexChanged) { ((FactorGraphChild)block).fixGraphTreeIndices(old2newGraphTreeIndex); } } for (FactorGraph graph : FactorGraphIterables.subgraphs(root)) { indexChanged |= graph.reindex(blocks); } return indexChanged; } /** * Reindex elements directly owned by this graph. * <p> * @param blocks list of all {@link VariableBlock}s from graph tree. Used to reindex affected variables. * @return true if any indexes were changed * @since 0.08 */ private boolean reindex(List<VariableBlock> blocks) { int from, to, n; // renumber edges for (from = 0, to = 0, n = _edges.size(); from < n; ++from) { EdgeState edge = _edges.get(from); if (edge != null) { if (from != to) { _edges.set(to, edge); _edges.set(from, null); edge.setEdgeIndexInParent(this, to); switch (edge.type(this)) { case LOCAL: ((Node)edge.getFactor(this)).fixSiblingEdgeStateIndex(edge); edge.getVariable(this).fixSiblingEdgeStateIndex(edge); break; case INNER: edge.getVariable(this).fixSiblingEdgeStateIndex(edge); break; case OUTER: ((Node)edge.getFactor(this)).fixSiblingEdgeStateIndex(edge); break; } } ++to; } } boolean indexChanged = from != to; indexChanged |= _ownedFactors.renumber(false) != null; indexChanged |= _ownedSubGraphs.renumber(false) != null; indexChanged |= _ownedVariableBlocks.renumber(false) != null; int[] old2newVarIndex = _ownedVariables.renumber(true); if (old2newVarIndex != null) { indexChanged = true; for (FactorGraphChild block : blocks) { block.fixVarIndicesForGraph(_graphTreeIndex, old2newVarIndex); } } // Constants and boundary variables currently cannot be removed, so there is currently no need to renumber. return indexChanged; } /** * Removes variables from the graph. * <p> * This simply invokes {@link #remove(Variable)} on each. * * @param variables are the variables to be removed. */ public void removeVariables(Variable ... variables) { for (Variable v : variables) { remove(v); } } /** * Remove variable from the graph. * @param v is the variable to remove * @throws DimpleException if the variable is still connected to some factor or if the variable * is not owned by this graph. * @see #remove(Factor) */ public void remove(Variable v) { assertGraphNotFrozen(); if (v.getSiblingCount() != 0) throw new DimpleException("can only remove a variable if it is no longer connected to a factor"); if (!_ownedVariables.containsNode(v)) { throw new DimpleException("can only currently remove variables that are owned"); } v.createSolverObject(null); ((Node)v).setParentGraph(null); _ownedVariables.removeNode(v); removeNode(v); ISolverVariable svar = v.getSolver(); if (svar != null) { svar.getParentGraph().removeSolverVariable(svar); } if ((_flags & VARIABLE_REMOVE_EVENT) != 0) { raiseEvent(new VariableRemoveEvent(this, v)); } } public void addBoundaryVariables(Variable ... vars) { assertGraphNotFrozen(); for (Variable v : vars) { boolean setSolver = false; // FIXME boundary variable solver logic is hacky if (_solverFactorGraph != null && !variableBelongs(v)) { if (v.getSiblingCount() > 0) throw new DimpleException("Can't connect a variable to multiple graphs"); setSolver = true; } //if (_boundaryVariables.contains(v)) // throw new DimpleException("ERROR name [" + v.getName() + "] already a boundary variable"); //being the root, at least for the moment, //I'm this variable's owner, if it has no other if (v.getParentGraph() == null) { addOwnedVariable(v, false); } _boundaryVariables.add(v); if (v.getParentGraph() != this) { addName(v); } if (setSolver && _solverFactorGraph != null) { v.createSolverObject(_solverFactorGraph); } } if ((_flags & BOUNDARY_VARIABLE_ADD_EVENT) != 0) { for (Variable v : vars) { raiseEvent(new BoundaryVariableAddEvent(this, v)); } } } public void addVariables(Variable... variables) { assertGraphNotFrozen(); for (Variable v : variables) { if (_boundaryVariables.contains(v)) { throw new DimpleException("Cannot take ownership of boundary variable [" + v.getLabel() + "]"); } final FactorGraph parent = v.getParentGraph(); if (parent != null) { throw new DimpleException("Variable [" + v.getLabel() + "] already owned by graph [" + parent.getLabel() + "]"); } addOwnedVariable(v, false); v.createSolverObject(_solverFactorGraph); } } /** * Creates a {@link VariableBlock} containing the specified variables. * @param variables are the variables that will comprise the block. The variables will be added in the * order of the {@linkplain Collection#iterator iterator}. * @since 0.08 * @throws IllegalArgumentException if a variable does not belong to the same tree of graphs as {@code parent}. */ public VariableBlock addVariableBlock(Collection<Variable> variables) { assertGraphNotFrozen(); return _ownedVariableBlocks.addBlock(new VariableBlock(this, variables)); } /** * @see #addVariableBlock(Collection) * @since 0.08 */ public VariableBlock addVariableBlock(Variable ... variables) { return addVariableBlock(Arrays.asList(variables)); } /** * Joining factors replaces all the original factors with one joint factor. * <p> * We take the Cartesian product of the entries of the tables such that the * variables values are consistent. The variable order is determined by taking * all of the variables from the first factor in order, then adding remaining * variables in order from each remaining factor in turn. * <p> * @return the new joint factor * @see #join(Variable[], Factor...) */ public Factor join(Factor ... factors) { assertGraphNotFrozen(); Set<Variable> variables = new LinkedHashSet<Variable>(); for (Factor factor : factors) { final int nVarsInFactor = factor.getSiblingCount(); for (int i = 0; i < nVarsInFactor; ++i) { final Variable variable = factor.getSibling(i); if (!variables.contains(variable)) { variables.add(variable); } } } return join(variables.toArray(new Variable[variables.size()]), factors); } /** * Merges {@code factors} into a single joint factor over the given set of variables. * <p> * @param variables specifies the variables and the order in which they will appear in the new joint factor. * This may include variables that are not in any of the specified {@code factors} but must not omit any * variable that appears in one of the {@code factors} nor should it repeat any variable. * @param factors specifies the factors to be merged. If empty, this will add a new uniform factor over * the specified variables. * @return the new joint factor. If {@code factors} has a single entry with the specified * {@code variables} in the specified order, this will simply return that factor without * modifying the graph. */ public Factor join(final Variable[] variables, Factor ... factors) { assertGraphNotFrozen(); final int nFactors = factors.length; final int nVariables = variables.length; if (nFactors == 0) { return addFactor(Uniform.INSTANCE, variables); } else if (nFactors == 1) { final Factor factor = factors[0]; outer: if (factor.getSiblingCount() == nVariables) { for (int i = 0; i < nVariables; ++i) { if (variables[0] != factor.getSibling(i)) { break outer; } } // Factor already in correct form. return factor; } } // Build map of variables and constants in all factors to its index in the merged factor. final Map<IConstantOrVariable, Integer> argToIndex = new LinkedHashMap<>(); for (int i = 0; i < nVariables; ++i) { argToIndex.put(variables[i], i); } for (Factor factor : factors) { for (Constant constant : factor.getConstants()) { if (!argToIndex.containsKey(constant)) { argToIndex.put(constant, argToIndex.size()); } } } // Build mappings from each factor's variable order to the merged order final BitSet varsUsed = new BitSet(nVariables); ArrayList<Tuple2<FactorFunction, int[]>> oldToNew = new ArrayList<Tuple2<FactorFunction, int[]>>(nFactors); for (Factor factor : factors) { final int nArgsInFactor = factor.getArgumentCount(); final int[] oldToNewIndex = new int[nArgsInFactor]; for (int i = 0; i < nArgsInFactor; ++i) { final IConstantOrVariable arg = factor.getArgument(i); final Integer oldIndex = argToIndex.get(arg); if (oldIndex == null) { throw new DimpleException("Variable %s from factor %s not in variable list for join", arg, factor); } oldToNewIndex[i] = oldIndex.intValue(); if (arg instanceof Variable) { varsUsed.set(oldIndex.intValue()); } } oldToNew.add(Tuple2.create(factor.getFactorFunction(), oldToNewIndex)); } // If there are variables that are not in any factor, create a virtual uniform // factor for those variables IntArrayList extraVariables = null; for (int i = -1; (i = varsUsed.nextClearBit(i + 1)) < nVariables;) { if (extraVariables == null) { extraVariables = new IntArrayList(); } extraVariables.add(i); } if (extraVariables != null) { extraVariables.trimToSize(); oldToNew.add(Tuple2.create((FactorFunction)Uniform.INSTANCE, extraVariables.elements())); } // Create the joint factor function FactorGraph root = getRootGraph(); LoadingCache<Functions,JointFactorFunction> jointFactorCache = root._jointFactorCache; if (jointFactorCache == null) { jointFactorCache = root._jointFactorCache = JointFactorFunction.createCache(); } final JointFactorFunction.Functions jointFunctions = new JointFactorFunction.Functions(oldToNew); final FactorFunction jointFunction = JointFactorFunction.getFromCache(jointFactorCache, jointFunctions); // Determine common parent final List<FactorGraph> uncommonAncestors = new LinkedList<FactorGraph>(); FactorGraph parentGraph = factors[0].getParentGraph(); for (int i = 1; i < nFactors; ++i) { parentGraph = factors[i - 1].getCommonAncestor(factors[i], uncommonAncestors); } if (parentGraph == null) { throw new DimpleException("Cannot join factors because they are not in the same root graph"); } // Remove old factors for (Factor factor : factors) { requireNonNull(factor.getParentGraph()).remove(factor); } // If all factors did not have the same parent, then remove any intermediate subgraphs. for (FactorGraph subgraph : uncommonAncestors) { requireNonNull(subgraph.getParentGraph()).absorbSubgraph(subgraph); } // Add new factor if (nVariables == argToIndex.size()) { return parentGraph.addFactor(jointFunction, variables); } else { return parentGraph.addFactor(jointFunction, Iterables.toArray(argToIndex.keySet(), Object.class)); } } /** * Joining variables creates one joint and discards the originals and modifies * factors to refer to the joints. */ public Variable join(Variable ... variables) { assertGraphNotFrozen(); if (variables.length < 2) throw new DimpleException("need at least two variables"); //If these variables weren't previously part of the graph, add them. for (int i = 0; i < variables.length; i++) { if (variables[i].getParentGraph()==null) addVariables(variables[i]); } //Create a hash of all factors affected. HashSet<Factor> factors = new HashSet<Factor>(); //Go through variables and find affected factors. for (Variable v : variables) { for (int i = 0, endi = v.getSiblingCount(); i < endi; i++) { Factor f = (Factor)v.getConnectedNodeFlat(i); factors.add(f); } } //Create joint variable Variable joint = variables[0].createJointNoFactors(variables); //Variables must first be part of the graph before the factor can join them. addVariables(joint); //Reattach the variable too, just in case. joint.createSolverObject(_solverFactorGraph); //go through each factor that was connected to any of the variables and tell it to join those variables for (Factor f : factors) { f.replaceVariablesWithJoint(variables, joint); //reattach to the solver now that the factor graph has changed f.createSolverObject(_solverFactorGraph); } //Remove the original variables removeVariables(variables); return joint; } /* * Splitting a variable creates a copy and an equals node between the two. * @since 0.07 */ public Variable split(Variable variable) { return split(variable,new Factor[]{}); } /* * Splitting a variable creates a copy and an equals node between the two. * The Factor array specifies which factors should connect to the new variable. * All factors left out of the array remain pointing to the original variable. * @since 0.07 */ public Variable split(Variable variable,Factor ... factorsToBeMovedToCopy) { assertGraphNotFrozen(); return variable.split(this,factorsToBeMovedToCopy); } private FactorBase addFactor(Factor function, Variable ... variables) { assertGraphNotFrozen(); addOwnedFactor(function, false); for (Variable var : variables) { addEdge(function, var); } return function; } @Deprecated public boolean customFactorExists(String funcName) { final ISolverFactorGraph sfg = _solverFactorGraph; if (sfg != null) return sfg.customFactorExists(funcName); else return false; } //============== // // Scheduling // //============== /** * Sets scheduler options applicable to input scheduler. * * @param scheduler if non-null, will be set as an option value for all of the option keys * it lists in its {@linkplain IScheduler#applicableSchedulerOptions() applicableSchedulerOptions()} * method. If null and graph has a {@link #getSolver() current solver}, its * {@linkplain ISolverFactorGraph#getSchedulerKey() scheduler key} will be unset, otherwise * no action will be taken. */ public void setScheduler(@Nullable IScheduler scheduler) { assertGraphNotFrozen(); if (scheduler != null) { for (SchedulerOptionKey key : scheduler.applicableSchedulerOptions()) { key.set(this, scheduler); } } else { final ISolverFactorGraph sgraph = getSolver(); if (sgraph != null) { SchedulerOptionKey key = sgraph.getSchedulerKey(); if (key != null) { unsetOption(key); } } } } //============================ // // Nested Graphs // //============================ public FactorGraph addFactor(FactorGraph subGraph, Variable ... boundaryVariables) { return addGraph(subGraph,boundaryVariables); } /** * Add a new subgraph generated from specified template graph * attached to given boundary variables. * <p> * @param subGraphTemplate * @param boundaryVariables * @return newly created subgraph */ public FactorGraph addGraph(FactorGraph subGraphTemplate, Variable ... boundaryVariables) { assertGraphNotFrozen(); // FIXME: solver logic is hacky // Really we should not try to update solver state while mutating the graph. // Instead wait until we are done... List<Variable> needsSolver = Collections.emptyList(); if (_solverFactorGraph != null) { needsSolver = new ArrayList<>(boundaryVariables.length); for (Variable v : boundaryVariables) { if (!variableBelongs(v)) { needsSolver.add(v); } } } for (Variable v : boundaryVariables) // Add variables to owned variable list if not a boundary variable { if (!_boundaryVariables.contains(v)) { addOwnedVariable(v, false); } } //copy the graph FactorGraph subGraphCopy = new FactorGraph(boundaryVariables, subGraphTemplate,this); for (Variable v : needsSolver) { v.createSolverObject(_solverFactorGraph); } //tell us about it addOwnedSubgraph(subGraphCopy, false); if (_solverFactory != null) { subGraphCopy.setSolverFactory(_solverFactory); } return subGraphCopy; } private void _setParentGraph(@Nullable FactorGraph parentGraph) { boolean noLongerRoot = parentGraph != null && getParentGraph() == null; setParentGraph(parentGraph); //If we were root, and are no longer, // stop references names/UUIDs of boundary variables. if(noLongerRoot) { // FIXME: this seems really hacky - we are depending on someone fixing up the boundary variables // later. for(Variable v : _boundaryVariables) { String explicitName = v.getExplicitName(); if(explicitName != null) { _name2object.remove(explicitName); } if(v.getParentGraph() == this) { _ownedVariables.removeNode(v); ((Node)v).setParentGraph(null); } } } } private FactorGraph(@Nullable Variable[] boundaryVariables, FactorGraph templateGraph, @Nullable FactorGraph parentGraph) { this(boundaryVariables, templateGraph, parentGraph, false, new HashMap<>()); } // Copy constructor -- create a graph incorporating all of the variables, functions, and sub-graphs of the template graph private FactorGraph(@Nullable Variable[] boundaryVariables, FactorGraph templateGraph, @Nullable FactorGraph parentGraph, boolean copyToRoot, Map<Object, Object> old2newObjs) { this(parentGraph != null ? parentGraph._graphTreeState : null, boundaryVariables, templateGraph.getExplicitName(), null); // Add mapping from template to this graph old2newObjs.put(templateGraph, this); // Copy owned variables for (Variable vTemplate : templateGraph._ownedVariables) { if (!templateGraph.isBoundaryVariable(vTemplate)) { Variable vCopy = vTemplate.clone(); //old2newIds.put(vTemplate.getId(), vCopy.getId()); old2newObjs.put(vTemplate,vCopy); addOwnedVariable(vCopy, false); } } // Check boundary variables for consistency if (boundaryVariables == null) { throw new DimpleException("Sub-graph missing boundary variables to connect with parent graph."); } if (boundaryVariables.length != templateGraph._boundaryVariables.size()) { throw new DimpleException(String.format("Boundary variable list does not have the same length (%d) as template graph (%d)\nTemplate graph:[%s]" , boundaryVariables.length , templateGraph._boundaryVariables.size() , templateGraph.toString())); } { int i = 0; for (Variable vTemplate : templateGraph._boundaryVariables) { Variable vBoundary = boundaryVariables[i++]; if (!vBoundary.getDomain().equals(vTemplate.getDomain())) throw new DimpleException("Boundary variable does not have the same domain as template graph. Index: " + (i-1)); old2newObjs.put(vTemplate,vBoundary); } } // Copy constants for (Constant templateConstant : templateGraph._ownedConstants) { Constant constant = addConstant(templateConstant.value()); old2newObjs.put(templateConstant, constant); } // Copy blocks for (VariableBlock templateBlock : templateGraph._ownedVariableBlocks) { Variable[] vars = templateBlock.toArray(new Variable[templateBlock.size()]); for (int i = vars.length; --i>=0;) { vars[i] = (Variable)old2newObjs.get(vars[i]); } VariableBlock block = addVariableBlock(vars); old2newObjs.put(templateBlock, block); } for (FactorGraph subGraph : templateGraph._ownedSubGraphs) { Variable[] vBoundary = new Variable[subGraph._boundaryVariables.size()]; { int i = 0; for (Variable v : subGraph._boundaryVariables) vBoundary[i++] = (Variable)old2newObjs.get(v); } // Add the graph using the appropriate boundary variables FactorGraph newGraph = addGraph(subGraph, vBoundary); old2newObjs.put(subGraph,newGraph); } for (Factor fTemplate : templateGraph._ownedFactors) { Factor fCopy = fTemplate.clone(); old2newObjs.put(fTemplate,fCopy); addName(fCopy); fCopy.setParentGraph(this); _ownedFactors.add(fCopy); for (Variable vTemplate : fTemplate.getSiblings()) { Variable var = (Variable)old2newObjs.get(vTemplate); addEdge(fCopy, var); } if (fTemplate.hasConstants()) { final int nArgs = fTemplate.getArgumentCount(); int[] argids = new int[nArgs]; for (int i = 0, j = 0; i < nArgs; ++i) { final IConstantOrVariable arg = fTemplate.getArgument(i); if (arg instanceof Constant) { argids[i] = ((Constant)old2newObjs.get(arg)).getLocalId(); } else { argids[i] = fCopy.getSiblingEdgeIndex(j++); } } ((Node)fCopy).setArguments(argids); } } // Copy options from template for (IOption<?> option : templateGraph.getLocalOptions()) { IOptionKey<?> key = option.key(); if (key instanceof SchedulerOptionKey) { // TODO perhaps we should generalize this copy operation to support other types // of special option values. SchedulerOptionKey schedulerKey = (SchedulerOptionKey)key; IScheduler scheduler = (IScheduler)requireNonNull(option.value()); scheduler = scheduler.copy(old2newObjs, copyToRoot); option = new Option<IScheduler>(schedulerKey, scheduler); } Option.setOptions(this, option); } _setParentGraph(parentGraph); } public FactorGraph copyRoot() { return copyRoot(new HashMap<Object, Object>()); } public FactorGraph copyRoot(Map<Object, Object> old2newObjs) { FactorGraph root = getRootGraph(); int numBoundaryVariables = root._boundaryVariables.size(); Variable[] rootBoundaryVariables = root._boundaryVariables.toArray(new Variable[numBoundaryVariables]); Variable[] boundaryVariables = new Variable[numBoundaryVariables]; for(int i = 0; i < numBoundaryVariables; ++i) { boundaryVariables[i] = rootBoundaryVariables[i].clone(); } FactorGraph rootCopy = new FactorGraph(boundaryVariables, root, null, true, old2newObjs); return rootCopy; } @Override public Port getPort(int i) { return ((List<Port>)getPorts()).get(i); } /** * Returns ports for edges from factors contained in this graph to its boundary variables. */ @Override public Collection<Port> getPorts() { if (_boundaryVariables.isEmpty()) { return Collections.emptyList(); } ArrayList<Port> ports = new ArrayList<>(); for (Variable var : _boundaryVariables) { if (ownsDirectly(var)) { continue; } for (int i = 0, ni = var.getSiblingCount(); i < ni; ++i) { final Factor factor = var.getSibling(i); if (isAncestorOf(factor)) { for (int j = 0, nj = factor.getSiblingCount(); j < nj; ++j) { if (var == factor.getSibling(j)) { ports.add(factor.getPort(j)); } } } } } return ports; } @Override public List<? extends Variable> getSiblings() { updateSiblings(); return super.getSiblings(); } @Override public int getSiblingCount() { updateSiblings(); return _graphSiblings.size(); } @Override public Variable getSibling(int index) { updateSiblings(); return super.getSibling(index); } private void updateSiblings() { if (_siblingVersionId != _structureVersion) { // Recompute siblings _graphSiblings.clear(); if (!_boundaryVariables.isEmpty()) { for (Variable var : _boundaryVariables) { final FactorGraph varGraph = requireNonNull(var.getParentGraph()); if (ownsDirectly(var)) { continue; } for (int i = 0, ni = var.getSiblingCount(); i < ni; ++i) { final EdgeState edge = var.getSiblingEdgeState(i); if (isAncestorOf(edge.getFactor(varGraph))) { _graphSiblings.add(edge); } } } } _siblingVersionId = _structureVersion; } } @Override public EdgeState getSiblingEdgeState(int i) { updateSiblings(); return _graphSiblings.get(i); } @Override public int indexOfSiblingEdgeState(EdgeState edge) { updateSiblings(); return _graphSiblings.indexOf(edge); } /** * Returns {@link Edge} at given index, if it exists. * <p> * If this returns a non-null {@code edge}, the following will be true: * <blockquote><code> * edge.{@linkplain Edge#edgeIndex() edgeIndex()} == index && * edge.{@linkplain Edge#graph() graph()} == this * </code></blockquote> * * @param index an integer in the range [0, {@link #getGraphEdgeStateMaxIndex}] * @since 0.08 * @throws ArrayIndexOutOfBoundsException if {@code index} is not in valid range */ public @Nullable Edge getGraphEdge(int index) { final EdgeState state = getGraphEdgeState(index); return state != null ? new Edge(this, state) : null; } /** * Returns collection of {@link Edge}s held by this graph. * @since 0.08 */ public Collection<Edge> getGraphEdges() { return _edges.allEdges(); } /** * Returns collection of all {@link EdgeState} objects held by this graph. * @since 0.08 */ public Collection<EdgeState> getGraphEdgeState() { return _edges.allEdgeState(); } /** * Returns {@link EdgeState} at given index, if it exists. * <p> * If this returns a non-null {@code edgeState}, the following will be true: * <blockquote><code> * edgeState.{@linkplain EdgeState#edgeIndexInParent(FactorGraph) edgeIndexInParent(this)} == index * </code></blockquote> * * @param index an integer in the range [0, {@link #getGraphEdgeStateMaxIndex}] * @since 0.08 * @throws ArrayIndexOutOfBoundsException if {@code index} is not in valid range */ public @Nullable EdgeState getGraphEdgeState(int index) { return _edges.get(index); } /** * Returns the maximum index of slots currently reserved for {@link EdgeState} belonging to this graph. * <p> * There may not necessarily be an edge state object currently at this index. * <p> * @return Returns max edge index or -1 if there is currently no space allocated for edge state. * @since 0.08 */ public int getGraphEdgeStateMaxIndex() { return _edges.size() - 1; } //============================ // // Operations on FactorGraph // //============================ /* * This method tries to optimize the BetheFreeEnergy by searching over the space of * FactorTable values. * * numRestarts - Determines how many times to randomly initialize the FactorTable parameters * Because the BetheFreeEnergy function is not convex, random restarts can help * find a better optimum. * numSteps - How many times to change each parameter * * stepScaleFactor - What to mutliply the gradient by for each step. */ public void estimateParameters(Object [] factorsAndTables,int numRestarts,int numSteps, double stepScaleFactor) { HashSet<IFactorTable> sfactorTables = new HashSet<IFactorTable>(); for (Object o : factorsAndTables) { if (o instanceof Factor) { Factor f = (Factor)o; sfactorTables.add(f.getFactorTable()); } else if (o instanceof IFactorTable) { sfactorTables.add((IFactorTable)o); } } IFactorTable [] factorTables = new IFactorTable[sfactorTables.size()]; int i = 0; for (IFactorTable ft : sfactorTables) { factorTables[i] = ft; i++; } estimateParameters(factorTables,numRestarts,numSteps,stepScaleFactor); } public void baumWelch(Object [] factorsAndTables,int numRestarts,int numSteps) { HashSet<IFactorTable> sfactorTables = new HashSet<IFactorTable>(); for (Object o : factorsAndTables) { if (o instanceof Factor) { Factor f = (Factor)o; sfactorTables.add(f.getFactorTable()); } else if (o instanceof IFactorTable) { sfactorTables.add((IFactorTable)o); } } IFactorTable [] factorTables = new IFactorTable[sfactorTables.size()]; int i = 0; for (IFactorTable ft : sfactorTables) { factorTables[i] = ft; i++; } baumWelch(factorTables,numRestarts,numSteps); } public void baumWelch(IFactorTable [] tables,int numRestarts,int numSteps) { requireSolver("baumWelch").baumWelch(tables, numRestarts, numSteps); } public void estimateParameters(IFactorTable [] tables,int numRestarts,int numSteps, double stepScaleFactor) { requireSolver("estimateParameters").estimateParameters(tables, numRestarts, numSteps, stepScaleFactor); } private void addOwnedFactor(Factor factor, boolean absorbedFromSubgraph) { factor.setParentGraph(this); _ownedFactors.add(factor); addName(factor); if ((_flags & FACTOR_ADD_EVENT) != 0) { raiseEvent(new FactorAddEvent(this, factor, absorbedFromSubgraph)); } } private void addOwnedSubgraph(FactorGraph subgraph, boolean absorbedFromSubgraph) { subgraph.setParentGraph(this); _ownedSubGraphs.add(subgraph); addName(subgraph); if ((_flags & SUBGRAPH_ADD_EVENT) != 0) { raiseEvent(new SubgraphAddEvent(this, subgraph, absorbedFromSubgraph)); } } private void addOwnedVariable(Variable variable, boolean absorbedFromSubgraph) { //Only insert if not already there. if (!_ownedVariables.containsNode(variable)) { // Tell us about the variable _ownedVariables.add(variable); // And the variable about us... ((Node)variable).setParentGraph(this); if ((_flags & VARIABLE_ADD_EVENT) != 0) { raiseEvent(new VariableAddEvent(this, variable, absorbedFromSubgraph)); } } addName(variable); } public void recreateMessages() { for (Variable v : getVariablesFlat()) { final ISolverVariable sv = v.getSolver(); if (sv != null) { sv.createNonEdgeSpecificState(); } } } /** * Initializes components of model, and if a solver is set, also initializes the * solver. * <p> * Does the following: * <ol> * <li>Invokes {@link #notifyListenerChanged()} to reconfigure event notifications. * <li>Initializes non-boundary model variables contained directly in the graph (not in subgraphs) * by calling {@link Variable#initialize()} on each. * <li>If not {@link #hasParentGraph()}, initializes boundary variables in the same fashion. * <li>Initializes all model factors contained directly in the graph by calling * {@link Factor#initialize()} on each. * <li>Initializes nested graphs by invoking this method recursively on each. * <li>Finally, if {@link #getSolver()} is not null, invokes {@link ISolverFactorGraph#initialize()} * on the solver graph to initialize solver state. The solver is responsible for initializing * its component variables, factors and any other state. * </ol> */ @Override public void initialize() { super.initialize(); notifyListenerChanged(); for (Variable v : _ownedVariables) { v.initialize(); } for (Factor f : _ownedFactors) { f.initialize(); } for (FactorGraph g : _ownedSubGraphs) { g.initialize(); } final ISolverFactorGraph sfg = _solverFactorGraph; if (sfg != null) { sfg.initialize(); } } private ISolverFactorGraph checkSolverIsSet() { ISolverFactorGraph sfg = _solverFactorGraph; if (sfg == null) throw new DimpleException("solver needs to be set first"); return sfg; } public void solve() { checkSolverIsSet().solve(); } public void solveOneStep() { checkSolverIsSet().solveOneStep(); } public void continueSolve() { checkSolverIsSet().continueSolve(); } /** * Absorbs subgraph into parent graph. * <p> * Tranfers variables, factors and subgraphs owned by {@code subgraph} to this * graph and removes subgraph. * <p> * @param subgraph must be a direct subgraph of this graph. */ public void absorbSubgraph(FactorGraph subgraph) { assertGraphNotFrozen(); if (!ownsDirectly(subgraph)) { throw new DimpleException("Cannot absorb subgraph that is not directly owned."); } final OwnedVariables variables = subgraph._ownedVariables; final OwnedFactors factors = subgraph._ownedFactors; final OwnedGraphs subgraphs = subgraph._ownedSubGraphs; // FIXME: may need to rename nodes when they are transferred to avoid conflicts... // Reparent owned variables & factors & subgraphs for (Variable variable : variables) { addOwnedVariable(variable, true); } for (Factor factor : factors) { addOwnedFactor(factor, true); } for (FactorGraph subsubgraph : subgraphs) { addOwnedSubgraph(subsubgraph, true); } // Clear subgraph state variables.clear(); factors.clear(); subgraphs.clear(); subgraph._boundaryVariables.clear(); // Remove subgraph itself removeNode(subgraph); if ((_flags & SUBGRAPH_REMOVE_EVENT) != 0) { raiseEvent(new SubgraphRemoveEvent(this, subgraph, true)); } } /** * Remove a subgraph and all of its variables and factors from this graph. * Also remove boundary variables of subgraph if they are no longer connected * to anything. * * @param subgraph */ public void remove(FactorGraph subgraph) { assertGraphNotFrozen(); _graphTreeState.assertNotFrozen(); VariableList varList = subgraph.getVariablesFlat(); IMapList<FactorBase> factors = subgraph.getFactorsTop(); List<Variable> boundary = subgraph._boundaryVariables; Variable [] arr = varList.toArray(new Variable[varList.size()]); for (FactorBase f : factors) { final Factor factor = f.asFactor(); if (factor != null) { subgraph.remove(factor); } else { FactorGraph subsubgraph = f.asFactorGraph(); if (subsubgraph != null) { subgraph.remove(subsubgraph); } } } removeVariables(arr); removeNode(subgraph); _ownedSubGraphs.removeNode(subgraph); subgraph.setParentGraph(null); _graphTreeState.removeGraph(subgraph); subgraph._graphTreeState = new GraphTreeState(subgraph); for (Variable v : boundary) { if (v.getSiblingCount() == 0) remove(v); } if ((_flags & SUBGRAPH_REMOVE_EVENT) != 0) { raiseEvent(new SubgraphRemoveEvent(this, subgraph, false)); } } private void removeNode(Node n) { String explicitName = n.getExplicitName(); if(explicitName != null) { _name2object.remove(explicitName); } structureChanged(); } /** * Removes factor from the graph leaving any variables it was connected to. * * @param factor * @throws DimpleException if factor is not owned by the graph. */ public void remove(Factor factor) { assertGraphNotFrozen(); //_ownedFactors; if (!_ownedFactors.containsNode(factor)) { throw new DimpleException("Cannot delete factor. It is not a member of this graph"); } for (int i = factor.getSiblingCount(); --i>=0;) { removeSiblingEdge(factor.getSiblingEdgeState(i)); } _ownedFactors.removeNode(factor); removeNode(factor); factor.setParentGraph(null); ISolverFactor sfactor = factor.getSolver(); if (sfactor != null) { sfactor.getParentGraph().removeSolverFactor(sfactor); } if ((_flags & FACTOR_REMOVE_EVENT) != 0) { raiseEvent(new FactorRemoveEvent(this, factor)); } } //=================== // // Graph algorithms // //=================== /** * True if graph is comprised of a set of one or more disjoint trees. That is, * given any two nodes in the graph there is at most one unique path between them. * <p> * @see #isTree() * @see #isForest(int) * @since 0.05 */ public boolean isForest() { return isForest(Integer.MAX_VALUE); } /** * True if the graph is consists of a set of one or more disjoint trees when considering nodes * down to the specified {@code relativeNestingDepth} of subgraphs. That is, * given any two nodes in the graph that are no deeper than the specified depth * below the root graph, there no more than one unique path between them. * <p> * @see #isForest() * @see #isTree(int) * @since 0.05 * */ public boolean isForest(int relativeNestingDepth) { return isTreeOrForest(relativeNestingDepth, true); } /** * True if the graph is singly connected and not disjoint. That is, * given any two nodes in the graph there is exactly one unique path between them. * <p> * @see #isForest() * @see #isTree(int) */ public boolean isTree() { return isTreeFlat(); } /** * Same as {@link #isTree()}} */ public boolean isTreeFlat() { return isTree(Integer.MAX_VALUE); } /** * True if the top-level of the tree -- ignoring the contents of any subgraphs -- is a tree. * <p> * Same as {@link #isTree(int)} with zero argument. */ public boolean isTreeTop() { return isTree(0); } /** * True if the graph is singly connected and not disjoint when considering nodes * down to the specified {@code relativeNestingDepth} of subgraphs. That is, * given any two nodes in the graph that are no deeper than the specified depth * below the root graph, there is exactly one unique path between them. * <p> * @see #isForest(int) * @see #isTree() */ public boolean isTree(int relativeNestingDepth) { return isTreeOrForest(relativeNestingDepth, false); } private boolean isTreeOrForest(int relativeNestingDepth, boolean checkForForest) { FactorGraph g = this; // Get all the nodes in the graph and all sub-graphs--both variables and // functions (not including boundary variables unless this graph has no // parents, since those will be updated only in that case) IMapList<FactorBase> allIncludedFunctions = g.getFactors(relativeNestingDepth); VariableList allIncludedVariables = g.getVariables(relativeNestingDepth); // Determine the total number of edges in the graph, including // all sub-graphs. Since this is a bipartite graph, we can just count // all ports associated with the variables in the graph int numEdges = 0; for (Variable v : allIncludedVariables) numEdges += v.getSiblingCount(); // Determine the total number of vertices (variable and function nodes) // in the graph, including all sub-graphs int numVertices = allIncludedVariables.size() + allIncludedFunctions.size(); //If there are no variables or functions, this is definitely a tree if (numVertices == 0) return true; // If the number of edges is greater than the number of vertices minus 1, there must be cycles if (numEdges > numVertices - 1) return false; // If the number of edges is less than the number of vertices minus 1, it must not be connected if (!checkForForest && numEdges < numVertices - 1) return false; // If it has the right number of edges, the either it's a tree or it // isn't a connected graph, and could either be a 'forest' or have cycles. Set<INode> allIncludedNodes = new LinkedHashSet<INode>(numVertices); allIncludedNodes.addAll(allIncludedFunctions); allIncludedNodes.addAll(allIncludedVariables); FactorGraphWalker walker = null; while (!allIncludedNodes.isEmpty()) { // First, pick a node arbitrarily; INode n = allIncludedNodes.iterator().next(); if (walker == null) { walker = new FactorGraphWalker(this, n).maxRelativeNestingDepth(relativeNestingDepth); } else { walker.init(this, n); } while (walker.hasNext()) { INode node = walker.next(); if (walker.getCycleCount() > 0) { return false; } if (!allIncludedNodes.remove(node)) { throw new Error("FactorGraph.isTreeOrForest: found node not in list"); } } if (!checkForForest) { break; } } // No cycles were found, but we might not have visited all of the nodes in the // graph. If we have, its a tree/forest. return allIncludedNodes.isEmpty(); } public int [][] getAdjacencyMatrix() { return getAdjacencyMatrixFlat(); } //============== // // Scheduling // //============== public int [][] getAdjacencyMatrix(int relativeNestingDepth) { IMapList<INode> nodes = getNodes(relativeNestingDepth); INode [] array = new INode[nodes.size()]; for (int i = 0; i < nodes.size(); i++) array[i] = nodes.getByIndex(i); return getAdjacencyMatrix(array); } public int [][] getAdjacencyMatrixFlat() { return getAdjacencyMatrix(Integer.MAX_VALUE); } public int [][] getAdjacencyMatrixTop() { return getAdjacencyMatrix(0); } public int [][] getAdjacencyMatrix(Collection<INode> nodes) { INode [] inodes = new INode[nodes.size()]; nodes.toArray(inodes); return getAdjacencyMatrix(inodes); } public int [][] getAdjacencyMatrix(INode [] nodes) { int [][] retval = new int[nodes.length][]; HashMap<INode,Integer> node2index = new HashMap<INode, Integer>(); for (int i = 0; i < nodes.length; i++) { INode node = nodes[i]; node2index.put(node,i); retval[i] = new int[nodes.length]; if (node.getRootGraph() != this.getRootGraph()) throw new DimpleException("expected nodes that are part of this graph"); } for (int i = 0; i < nodes.length; i++) { INode node = nodes[i]; for (int k = 0, endk = node.getSiblingCount(); k < endk; k++) { ArrayList<INode> connectedNodes = node.getConnectedNodeAndParents(k); for (INode n : connectedNodes) { if (node2index.containsKey(n)) { int j = node2index.get(n); retval[i][j] = 1; retval[j][i] = 1; } } } } return retval; } @SuppressWarnings("all") protected IMapList<INode> depthFirstSearchRecursive( INode node, @Nullable INode previousNode, IMapList<INode> foundNodes, IMapList<INode> nodeList, int currentDepth, int maxDepth, int relativeNestingDepth) { foundNodes.add(node); // This node has been found if (currentDepth < maxDepth) { //Collection<Port> ports = node.getPorts(); // Get all the edges from this node for (int i = 0, end = node.getSiblingCount(); i < end; i++) { INode nextNode = node.getConnectedNode(relativeNestingDepth,i); int nextNodeNestingDepth = nextNode.getDepth(); int thisNodeNestingDepth = node.getDepth(); //Deal with overflow int newRelativeNestingDepth = relativeNestingDepth - (nextNodeNestingDepth - thisNodeNestingDepth); if (newRelativeNestingDepth < 0) newRelativeNestingDepth = 0; if (nextNode != previousNode) // Don't go backwards in the search if (nodeList.contains(nextNode)) // Only edges that lead to nodes inside this graph if (!foundNodes.contains(nextNode)) // If found-list doesn't already contain the next node foundNodes.addAll(depthFirstSearchRecursive(nextNode, node, foundNodes, nodeList,currentDepth+1,maxDepth,newRelativeNestingDepth)); } } return foundNodes; } public IMapList<INode> depthFirstSearch(INode root) { return depthFirstSearchFlat(root); } public IMapList<INode> depthFirstSearch(INode root, int searchDepth) { return depthFirstSearch(root,searchDepth,Integer.MAX_VALUE); } public IMapList<INode> depthFirstSearch(INode root, int searchDepth,int relativeNestingDepth) { IMapList<INode> tmp = new MapList<INode>(); if (root.getParentGraph() == null) { tmp.add(root); return tmp; } else { int rootDepth = root.getDepth(); //TODO: check for overflow int offset = rootDepth-this.getDepth()-1; int newDepth = offset+relativeNestingDepth; if (offset > 0 && relativeNestingDepth > 0 && newDepth < 0) newDepth = Integer.MAX_VALUE; IMapList<INode> nodes = this.getNodes(newDepth); if (!nodes.contains(root)) throw new DimpleException("can't search from " + root.getLabel() + " it is not a member of the graph to the specified nesting depth"); //getNodes(relativeNestingLevel); //MapList<INode> tmp = new MapList<INode>(); return depthFirstSearchRecursive(root, null, tmp, nodes, 0, searchDepth, relativeNestingDepth); } } public IMapList<INode> depthFirstSearchFlat(INode root, int searchDepth) { return depthFirstSearch(root, searchDepth, Integer.MAX_VALUE); } public IMapList<INode> depthFirstSearchFlat(INode root) { return depthFirstSearchFlat(root,Integer.MAX_VALUE); } public IMapList<INode> depthFirstSearchTop(INode root, int searchDepth) { return depthFirstSearch(root, searchDepth, 0); } public IMapList<INode> depthFirstSearchTop(INode root) { return depthFirstSearchFlat(root,0); } public boolean isAncestorOf(@Nullable INode node) { if (node == null || node.getParentGraph() == null) return false; while (node != null) { node = node.getParentGraph(); if (node == this) return true; } return false; } //================= // // Introspection // //================= @Override public @Nullable ISolverFactorGraph getSolver() { return _solverFactorGraph; } /** * Returns the number of boundary variables for this graph, if any. * @see #getBoundaryVariable(int) * @since 0.05 */ public int getBoundaryVariableCount() { return _boundaryVariables.size(); } /** * Returns the ith boundary variable for this graph. * * @param i an index in the range [0, {@link #getBoundaryVariableCount()} - 1]. * @since 0.05 */ public Variable getBoundaryVariable(int i) { return _boundaryVariables.get(i); } /** * Returns child from graphs and subgraphs with given global id. * @param globalId is the child's {@linkplain IFactorGraphChild#getGlobalId() global id}. * @return child or null if no such child belongs to this graph or any of its direct or indirect * subgraphs. * @since 0.08 */ public @Nullable IFactorGraphChild getChildByGlobalId(long globalId) { final FactorGraph fg = getSubgraphForGlobalId(globalId); return fg != null ? fg.getChildByLocalId(Ids.localIdFromGlobalId(globalId)) : null; } /** * Returns child from same graph tree with given graph tree id. * @param graphTreeId is the child's {@linkplain IFactorGraphChild#getGraphTreeId() graph tree id}. * @return child or null if no such child belongs to this graph's graph tree. * @since 0.08 */ public @Nullable IFactorGraphChild getChildByGraphTreeId(long graphTreeId) { final FactorGraph fg = getGraphByTreeIndex(Ids.graphTreeIndexFromGraphTreeId(graphTreeId)); return fg != null ? fg.getChildByLocalId(Ids.localIdFromGlobalId(graphTreeId)) : null; } /** * Get child of graph identified by {@code localId}. * <p> * @param localId * @return child or null * @since 0.08 */ public @Nullable IFactorGraphChild getChildByLocalId(int localId) { switch (Ids.typeIndexFromLocalId(localId)) { case Ids.FACTOR_TYPE: return _ownedFactors.getByLocalId(localId); case Ids.GRAPH_TYPE: return _ownedSubGraphs.getByLocalId(localId); case Ids.VARIABLE_TYPE: return _ownedVariables.getByLocalId(localId); case Ids.BOUNDARY_VARIABLE_TYPE: return _boundaryVariables.get(Ids.indexFromLocalId(localId)); case Ids.EDGE_TYPE: return new Edge(this, _edges.get(Ids.indexFromLocalId(localId))); case Ids.FACTOR_PORT_TYPE: return new FactorPort(_edges.get(Ids.indexFromLocalId(localId)), this); case Ids.VARIABLE_PORT_TYPE: return new VariablePort(_edges.get(Ids.indexFromLocalId(localId)), this); case Ids.VARIABLE_BLOCK_TYPE: return _ownedVariableBlocks.getByLocalId(localId); case Ids.CONSTANT_TYPE: return _ownedConstants.getByLocalId(localId); default: return null; } } public @Nullable Constant getConstantByLocalId(int localId) { return Ids.typeIndexFromLocalId(localId) == Ids.CONSTANT_TYPE ? _ownedConstants.getByLocalId(localId) : null; } public Collection<Constant> getOwnedConstants() { return Collections.unmodifiableCollection(_ownedConstants); } /** * Returns the number of variables contained directly in this graph * (i.e. not in subgraphs). * @since 0.05 */ public int getOwnedVariableCount() { return _ownedVariables.size(); } /** * Unmodifiable collection that enumerates factors whose parent is this graph.. * @since 0.08 */ public Collection<Factor> getOwnedFactors() { return Collections.unmodifiableCollection(_ownedFactors); } /** * Unmodifiable collection that enumerates subgraphs whose parent is this graph.. * @since 0.08 */ public Collection<FactorGraph> getOwnedGraphs() { return Collections.unmodifiableCollection(_ownedSubGraphs); } /** * Unmodifiable collection that enumerates variables whose parent is this graph.. * @since 0.08 */ public Collection<Variable> getOwnedVariables() { return Collections.unmodifiableCollection(_ownedVariables); } /** * Unmodifiable collection that enumerates variable blocks owned by this graph. * @since 0.08 */ public Collection<VariableBlock> getOwnedVariableBlocks() { return Collections.unmodifiableCollection(_ownedVariableBlocks); } /** * Returns count of variables that would be returned by {@link #getVariables()}. */ public int getVariableCount() { return getVariableCount(Integer.MAX_VALUE); } /** * Returns count of variables that would be returned by {@link #getVariables(int)}. */ public int getVariableCount(int relativeNestingDepth) { if (relativeNestingDepth == 0) { return _ownedVariables.size(); } else { return FactorGraphIterables.variablesDownto(this, relativeNestingDepth).size(); } } /** * Returns list of all variables in the graph including those in nested graphs. */ public VariableList getVariables() { return new VariableList(FactorGraphIterables.variables(this)); } /** * Returns list of all variables in the graph including those in nested graphs down to * specified {@code relativeNestingDepth} below this graph, where a nesting depth of zero * indicates that nested graphs should not be included. */ public VariableList getVariables(int relativeNestingDepth) { return new VariableList(FactorGraphIterables.variablesDownto(this, relativeNestingDepth)); } public VariableList getVariables(int relativeNestingDepth,boolean forceIncludeBoundaryVariables) { if (forceIncludeBoundaryVariables) { return new VariableList(FactorGraphIterables.variablesAndBoundaryDownto(this, relativeNestingDepth)); } else { return new VariableList(FactorGraphIterables.variablesDownto(this, relativeNestingDepth)); } } public VariableList getVariablesFlat() { return getVariables(); } public VariableList getVariablesFlat(boolean forceIncludeBoundaryVariables) { return getVariables(Integer.MAX_VALUE, forceIncludeBoundaryVariables); } public VariableList getVariablesTop() { return new VariableList(_ownedVariables); } public VariableList getVariablesTop(boolean forceIncludeBoundaryVariables) { return getVariables(0, forceIncludeBoundaryVariables); } public VariableList getBoundaryVariables() { return new VariableList(_boundaryVariables); } public boolean isBoundaryVariable(Variable mv) { return _boundaryVariables.contains(mv); } /** * Looks up variable using its global or graph tree id. * <p> * @param id is either a global id or a graph tree id for the variable. * @return {@code Variable} in same graph tree as this graph that is indexed by given identifier or else * null if there is no object with given identifier in the graph tree. * @throws ClassCastException if {@code id} refers to an object other than a {@code Variable} (such as a * {@code Factor}). */ public @Nullable Variable getVariable(long id) { return Ids.isGlobalId(id) ? (Variable)getNodeByGlobalId(id) : (Variable)getNodeByGraphTreeId(id); } public @Nullable Variable getVariableByLocalId(int id) { switch (id >>> Ids.LOCAL_ID_TYPE_OFFSET) { case Ids.VARIABLE_TYPE: return _ownedVariables.getByLocalId(id); case Ids.BOUNDARY_VARIABLE_TYPE: return _boundaryVariables.get(Ids.indexFromLocalId(id)); default: return null; } } /** * Returns {@link VariableBlock} instance with given local id, if it exists. * @return variable block with given {@code localId} in this graph or else null if id is invalid or * there is no such block. * @see #addVariableBlock(Collection) */ public @Nullable VariableBlock getVariableBlockByLocalId(int localId) { return Ids.typeIndexFromLocalId(localId) == Ids.VARIABLE_BLOCK_TYPE ? _ownedVariableBlocks.getByLocalId(localId) : null; } public FactorList getNonGraphFactors() { return new FactorList(FactorGraphIterables.factors(this)); } public FactorList getNonGraphFactors(int relativeNestingDepth) { return new FactorList(FactorGraphIterables.factorsDownto(this, relativeNestingDepth)); } public FactorList getNonGraphFactorsFlat() { return getNonGraphFactors(); } public FactorList getNonGraphFactorsTop() { return new FactorList(_ownedFactors); } /** * Returns count of factors that would be returned by {@link #getFactors()}. */ public int getFactorCount() { return getFactorCount(Integer.MAX_VALUE); } /** * Returns count of factors that would be returned by {@link #getFactors(int)}. */ public int getFactorCount(int relativeNestingDepth) { if (_ownedSubGraphs.isEmpty() || relativeNestingDepth == 0) { return _ownedFactors.size(); } return FactorGraphIterables.factorsDownto(this, relativeNestingDepth).size(); } /** * Returns a newly constructed collection containing all factors within * the specified nesting depth and subgraphs at the specified depth. *<p> * @see #getFactors(int, IMapList) */ public IMapList<FactorBase> getFactors(int relativeNestingDepth) { return new MapList<>(FactorGraphIterables.factorsAndLeafSubgraphsDownto(this, relativeNestingDepth)); } /** * Add factors from this graph down to a specified subgraph nesting level, * <p> * @param relativeNestingDepth is a non-negative number indicating how many levels * of subgraphs will be explored. Factors at the specified relative depth below the * starting graph or less will be included. Subgraphs at the exact relative depth * will be included, but <em>not</em> those at shallower depth. * <p> * @param factors is the collection to which factors will be added. * @return {@code factors} argument. */ public IMapList<FactorBase> getFactors(int relativeNestingDepth, IMapList<FactorBase> factors) { factors.addAll(FactorGraphIterables.factorsAndLeafSubgraphsDownto(this, relativeNestingDepth)); return factors; } public FactorList getFactors() { return new FactorList(FactorGraphIterables.factors(this)); } /** * Returns newly constructed collection containing all of the factors * and subgraphs that are directly owned by this graph. */ public IMapList<FactorBase> getFactorsTop() { return new MapList<>(FactorGraphIterables.factorsAndLeafSubgraphsDownto(this, 0)); } public @Nullable Factor getFactor(long id) { return (Factor)getNodeByGlobalId(id); } public @Nullable Factor getFactorByLocalId(int id) { return Ids.typeIndexFromLocalId(id) == Ids.FACTOR_TYPE ? _ownedFactors.getByLocalId(id) : null; } public @Nullable FactorGraph getGraphByLocalId(int id) { return Ids.typeIndexFromLocalId(id) == Ids.GRAPH_TYPE ? _ownedSubGraphs.getByLocalId(id) : null; } @Nullable INode getFirstNode() { if (!_ownedSubGraphs.isEmpty()) { return _ownedSubGraphs.getNth(0); } else if (this._ownedFactors.size() > 0) { return _ownedFactors.getNth(0); } else if (this._ownedVariables.size() > 0) { return _ownedVariables.getNth(0); } return null; } public IMapList<INode> getNodes() { return getNodesFlat(); } public IMapList<INode> getNodes(int relativeNestingDepth) { IMapList<FactorBase> factors = getFactors(relativeNestingDepth); VariableList vars = getVariables(relativeNestingDepth); IMapList<INode> retval = new MapList<INode>(); for (Variable v : vars) retval.add(v); for (FactorBase fb : factors) retval.add(fb); return retval; } public IMapList<INode> getNodesFlat() { return getNodes(Integer.MAX_VALUE); } public IMapList<INode> getNodesTop() { return getNodes(0); } /** * Counter that is incremented whenever structure of any graph below shared root changes. * <p> * May be used to verify cached information that depends on the graph structure. * @since 0.08 * @see #structureVersion */ public long graphTreeStructureVersion() { return _graphTreeState._globalStructureVersion; } /** * Counter that is incremented whenever structure of graph changes. * <p> * May be used to verify cached information that depends on the graph structure. * @since 0.08 * @see #graphTreeStructureVersion() */ public long structureVersion() { return _structureVersion; } final void structureChanged() { ++_structureVersion; ++_graphTreeState._globalStructureVersion; } //========= // // Names // //========= private void addName(Node nameable) { String explicitName = nameable.getExplicitName(); //Check + insert name if there is one if (explicitName != null) { Object obj = _name2object.get(explicitName); if (obj != null && obj != nameable) { throw new DimpleException("ERROR variable name " + explicitName + " already in graph"); } _name2object.put(explicitName, nameable); } structureChanged(); } void setChildNameInternal(Node child, @Nullable String newName) { assert(child == getNodeByLocalId(child.getLocalId())); //If new name already here, bad if (newName != null && getObjectByName(newName) != null) { throw new DimpleException("ERROR name '%s' already present in parent", newName); } //remove old name, if there was one String oldExplicitName = child.getExplicitName(); if (oldExplicitName != null) { _name2object.remove(oldExplicitName); } //add new name, if there is one if(newName != null) { _name2object.put(newName, child); } } public @Nullable Node getObjectByName(@Nullable String name) { assertGraphNotFrozen(); IFactorGraphChild child = getChildByName(name); if (child instanceof Node) { return (Node)child; } return null; } /** * Looks up child object by string representation of its name or identifier. * @param nameOrNull may be in one of the folling formats: * <ul> * <li>string representation of child's {@linkplain IFactorGraphChild#getUUID UUID} * <li>dot-qualified name where components are either local names or {@linkplain Ids#defaultNameForLocalId(int) * string version of local identifier}. * </ul> * @return child or null if not found. * @since 0.08 */ public @Nullable IFactorGraphChild getChildByName(final @Nullable String nameOrNull) { IFactorGraphChild obj = null; if (nameOrNull != null && !nameOrNull.isEmpty()) { String name = nameOrNull; if (Ids.isUUIDString(name)) { obj = getChildByUUID(UUID.fromString(name)); } else { String remainder = ""; int dotOffset = name.indexOf('.'); if (dotOffset >= 0) { remainder = name.substring(dotOffset + 1); name = name.substring(0, dotOffset); // Check to see if name refers to this graph. if (name.equals(_name) || _graphId == Ids.graphIdFromDefaultName(name)) { name = remainder; remainder = ""; dotOffset = name.indexOf('.'); if (dotOffset >= 0) { remainder = name.substring(dotOffset + 1); name = name.substring(0, dotOffset); } } } obj = _name2object.get(name); if (obj == null) { obj = getChildByLocalId(Ids.localIdFromDefaultName(name)); } if (!remainder.isEmpty() && obj instanceof FactorGraph) { FactorGraph subgraph = (FactorGraph)obj; obj = subgraph.getChildByName(remainder); } } } return obj; } public @Nullable Node getObjectByUUID(UUID uuid) { return getNodeByGlobalId(Ids.globalIdFromUUID(uuid)); } /** * Looks up child in this graph by its UUID. * @return null if no such child is a member of this graph or its direct or indirect subgraphs. * @since 0.08 */ public @Nullable IFactorGraphChild getChildByUUID(UUID uuid) { return getChildByGlobalId(Ids.globalIdFromUUID(uuid)); } /** * Looks up child in graph tree using given key. * <p> * Supports flexible lookup of children by a variety of different key types. * <p> * @param key must be one of the following: * <p> * <dl> * <dt>{@code null} or {@link Node}</dt> * <dd> Value will simply be returned (even if node does not belong to this graph)</dd> * <dl>{@link Long}</dl> * <dd>If number appears to be a global id according to {@link Ids#isGlobalId} then * {@link #getNodeByGlobalId} will be used to look up the node, otherwise it will be treated as a graph * tree id and passed to {@link #getNodeByGraphTreeId}. * </dd> * <dt>{@link Integer} * <dd>Will be treated as a local identifier and passed to {@link #getNodeByLocalId}. * <dt>{@link String} * <dd>Will be treated as a qualified name and passed to {@link #getObjectByName}. * <dt>{@link UUID} * <dd>Will be passed to {@link #getObjectByUUID}. * </dl> * @throws IllegalArgumentException if {@code key} is not one of the above types. * @since 0.08 */ public @Nullable IFactorGraphChild getChild(@Nullable Object key) { if (key == null || key instanceof IFactorGraphChild) { return (IFactorGraphChild)key; } if (key instanceof Long) { final long id = (Long)key; if (Ids.isGlobalId(id)) { return getChildByGlobalId(id); } else { return getChildByGraphTreeId(id); } } if (key instanceof String) { return getChildByName((String)key); } if (key instanceof UUID) { return getChildByUUID((UUID)key); } if (key instanceof Integer) { return getChildByLocalId((Integer)key); } throw new IllegalArgumentException(String.format( "Key %s is not one of IFactorGraphChild, String, UUID, Long, or Integer", key)); } /** * Returns node in this graph or subgraph with given global id. * @return node with given global id or null if not found or not a * member of this graph or its subgraphs * @since 0.08 */ public @Nullable Node getNodeByGlobalId(long gid) { final FactorGraph fg = getSubgraphForGlobalId(gid); return fg != null ? fg.getNodeByLocalId(Ids.localIdFromGlobalId(gid)) : null ; } /** * Returns node in same graph tree with given {@linkplain INode#getGraphTreeId() graph tree id}, if it exists. * @since 0.08 */ public @Nullable Node getNodeByGraphTreeId(long id) { FactorGraph graph = getGraphByTreeIndex(Ids.graphTreeIndexFromGraphTreeId(id)); return graph != null ? graph.getNodeByLocalId(Ids.localIdFromGraphTreeId(id)) : null; } /** * Returns node directly owned by this graph with given local id. * @return node with given id or null if not found. * @since 0.08 * @see #getNodeByGlobalId * @see #getFactorByLocalId(int) * @see #getVariableByLocalId(int) * @see #getGraphByLocalId(int) */ @Internal public @Nullable Node getNodeByLocalId(int id) { switch (id >>> Ids.LOCAL_ID_TYPE_OFFSET) { case Ids.FACTOR_TYPE: return _ownedFactors.getByLocalId(id); case Ids.GRAPH_TYPE: return _ownedSubGraphs.getByLocalId(id); case Ids.VARIABLE_TYPE: return _ownedVariables.getByLocalId(id); case Ids.BOUNDARY_VARIABLE_TYPE: return _boundaryVariables.get(Ids.indexFromLocalId(id)); default: return null; } } /** * The index of this graph within the tree of graphs sharing a common {@linkplain #getRootGraph() root graph}. * <p> * @since 0.08 * @see #getGraphByTreeIndex(int) */ public final int getGraphTreeIndex() { return _graphTreeIndex; } /** * Returns graph in tree with given graph tree index. * @param index is the {@link #getGraphTreeIndex() graph tree index} of the graph within the tree of graphs * sharing the same {@link #getRootGraph() root graph}. * @return graph with given index or else null. * @since 0.08 * @see #getMaxGraphTreeIndex() */ public @Nullable FactorGraph getGraphByTreeIndex(int index) { return _graphTreeState._graphs.getOrNull(index); } /** * The number of graphs in the tree of graphs below (and including) the {@linkplain #getRootGraph() root graph}. * <p> * @return a positive count of graphs. Equal to one if there are no subgraphs. * @since 0.08 */ public int getGraphHierarchySize() { return _graphTreeState._nGraphs; } /** * The maximum graph tree index among all graphs in the same graph tree. * <p> * @since 0.08 * @see #getGraphByTreeIndex(int) */ public int getMaxGraphTreeIndex() { return _graphTreeState._maxGraphTreeIndex; } public @Nullable Variable getVariableByName(String name) { Variable v = null; Object o = getObjectByName(name); if(o instanceof Variable) { v = (Variable) o; } return v; } public @Nullable Factor getFactorByName(String name) { Factor f = null; Object o = getObjectByName(name); if(o instanceof Factor) { f = (Factor) o; } return f; } public @Nullable FactorGraph getGraphByName(String name) { FactorGraph fg = null; Object o = getObjectByName(name); if(o instanceof FactorGraph) { fg = (FactorGraph) o; } return fg; } //============== // // Scheduling // //============== public @Nullable Variable getVariableByUUID(UUID uuid) { Variable v = null; Object o = getObjectByUUID(uuid); if (o instanceof Variable) { v = (Variable) o; } return v; } public @Nullable Factor getFactorByUUID(UUID uuid) { Factor f = null; Object o = getObjectByUUID(uuid); if(o != null && o instanceof Factor) { f = (Factor) o; } return f; } public @Nullable FactorGraph getGraphByUUID(UUID uuid) { FactorGraph fg = null; Object o = getObjectByUUID(uuid); if(o != null && o instanceof FactorGraph) { fg = (FactorGraph) o; } return fg; } /////////////////////////////////////////////////////////////////////////////////////////////////////////// //Debugging functions @Override public String toString() { return String.format("[FactorGraph %s]", getQualifiedName()); } public boolean isSolverRunning() { final ISolverFactorGraph sfg = _solverFactorGraph; return sfg != null && sfg.isSolverRunning(); } @Override public FactorGraph getRootGraph() { return _graphTreeState._graphs.get(0); } public @Nullable IFactorGraphFactory<?> getFactorGraphFactory() { return _solverFactory; } public double getBetheFreeEnergy() { return requireSolver("getBetheFreeEnergy").getBetheFreeEnergy(); } //================== // FactorGraphDiffs //================== public FactorGraphDiffs getFactorGraphDiffs(FactorGraph b, boolean quickExit, boolean byName) { return FactorGraphDiffs.getFactorGraphDiffs( this, b, quickExit, byName); } public FactorGraphDiffs getFactorGraphDiffsByName(FactorGraph b) { return FactorGraphDiffs.getFactorGraphDiffs( this, b, false, true); } public FactorGraphDiffs getFactorGraphDiffsByUUID(FactorGraph b) { return FactorGraphDiffs.getFactorGraphDiffs( this, b, false, false); } /*------------------ * Internal methods */ @Override protected void addEdge(Factor factor, Variable variable) { addEdge(_edges.allocateIndex(), factor, variable); } private void addEdge(int edgeIndex, Factor factor, Variable variable) { assert(factor.getParentGraph() == this); final FactorGraph variableGraph = requireNonNull(variable.getParentGraph()); EdgeState edge; if (this == variableGraph) { edge = createLocalEdge(edgeIndex, factor, variable); } else { edge = createBoundaryEdge(edgeIndex, variableGraph._edges.size(), factor, variable); variableGraph._edges.add(edge); } _edges.add(edge); ((Node)factor).addSiblingEdgeState(edge); variable.addSiblingEdgeState(edge); structureChanged(); if (!edge.isLocal()) { variableGraph.structureChanged(); } } private EdgeState createLocalEdge(int edgeIndex, Factor factor, Variable variable) { final int factorOffset = Ids.indexFromLocalId(factor.getLocalId()); final int variableOffset = Ids.indexFromLocalId(variable.getLocalId()); return LocalEdgeState.create(edgeIndex, factorOffset, variableOffset); } private EdgeState createBoundaryEdge(int factorEdgeIndex, int variableEdgeIndex, Factor factor, Variable variable) { final int boundaryOffset = _boundaryVariables.indexOf(variable); assert(boundaryOffset >= 0); return BoundaryEdge.create(factor, factorEdgeIndex, boundaryOffset, variableEdgeIndex); } @Override @Internal protected void removeSiblingEdge(EdgeState edge) { final Factor factor = edge.getFactor(this); final Variable variable = edge.getVariable(this); final FactorGraph variableGraph = requireNonNull(variable.getParentGraph()); ((Node)factor).removeSiblingEdgeState(edge); variable.removeSiblingEdgeState(edge); _edges.set(edge.factorEdgeIndex(), null); if (!edge.isLocal()) { variableGraph._edges.set(edge.variableEdgeIndex(), null); } structureChanged(); if (!edge.isLocal()) { variableGraph.structureChanged(); } } /** * @category internal */ @Internal public void replaceEdge(Factor factor, int edgeIndex, Variable newVariable) { assertGraphNotFrozen(); final FactorGraph factorGraph = requireNonNull(factor.getParentGraph()); final EdgeState oldEdge = factor.getSiblingEdgeState(edgeIndex); final Variable oldVariable = oldEdge.getVariable(factorGraph); final FactorGraph oldVariableGraph = requireNonNull(oldVariable.getParentGraph()); final FactorGraph newVariableGraph = requireNonNull(newVariable.getParentGraph()); final int factorEdgeIndex = oldEdge.factorEdgeIndex(); final int oldVariableEdgeIndex = oldEdge.variableEdgeIndex(); final EdgeState newEdge; oldVariable.removeSiblingEdgeState(oldEdge); if (factorGraph == newVariableGraph) { newEdge = createLocalEdge(factorEdgeIndex, factor, newVariable); } else if (oldVariableGraph == newVariableGraph) { newEdge = createBoundaryEdge(factorEdgeIndex, oldVariableEdgeIndex, factor, newVariable); newVariableGraph._edges.set(oldVariableEdgeIndex, newEdge); } else { newEdge = createBoundaryEdge(factorEdgeIndex, newVariableGraph._edges.size(), factor, newVariable); oldVariableGraph._edges.set(oldVariableEdgeIndex, null); newVariableGraph._edges.add(newEdge); } newVariable.addSiblingEdgeState(newEdge); ((Node)factor).replaceSiblingEdgeState(oldEdge, newEdge); factorGraph._edges.set(factorEdgeIndex, newEdge); factorGraph.structureChanged(); if (!oldEdge.isLocal()) { oldVariableGraph.structureChanged(); } if (!newEdge.isLocal()) { newVariableGraph.structureChanged(); } } /** * @category internal */ @Internal public void setSolver(ISolverFactorGraph solver) { assertGraphNotFrozen(); _solverFactorGraph = solver; } /** * Iterates over all boundary variables in this graph. * @see #externalBoundaryVariableIterator() */ final Iterator<Variable> boundaryVariableIterator() { return _boundaryVariables.iterator(); } /** * Iterates over boundary variables not owned by this graph. * @see #boundaryVariableIterator() */ final Iterator<Variable> externalBoundaryVariableIterator() { final FactorGraph fg = this; return Iterators.filter(boundaryVariableIterator(),new Predicate<Variable>() { @NonNullByDefault(false) @Override public boolean apply(Variable var) { return var.getParentGraph() != fg; }}); } final int ownedConstantCount() { return _ownedConstants.size(); } final Iterator<Constant> ownedConstantIterator() { return _ownedConstants.iterator(); } final Iterator<Factor> ownedFactorIterator() { return _ownedFactors.iterator(); } final int ownedFactorCount() { return _ownedFactors.size(); } final Iterator<FactorGraph> ownedGraphIterator() { return _ownedSubGraphs.iterator(); } final int ownedGraphCount() { return _ownedSubGraphs.size(); } final Iterator<Variable> ownedVariableIterator() { return _ownedVariables.iterator(); } final int ownedVariableCount() { return _ownedVariables.size(); } final int ownedVariableBlockCount() { return _ownedVariableBlocks.size(); } final Iterator<VariableBlock> ownedVariableBlockIterator() { return _ownedVariableBlocks.iterator(); } /** * Returns solver graph or throws an error if null. * <p> * For internal use only. */ @Override @Internal public ISolverFactorGraph requireSolver(String method) { ISolverFactorGraph solver = getSolver(); if (solver == null) { throw new NullPointerException(String.format("Solver not set. Required by '%s'", method)); } return solver; } /********************** * Deprecated methods */ @Deprecated public void clearNames() { assertGraphNotFrozen(); Helpers.clearNames(this); } /** * @deprecated use {@link #addVariableBlock(Collection)} instead. */ @Deprecated public int defineVariableGroup(ArrayList<Variable> variableList) { VariableBlock block = addVariableBlock(variableList); return block.getLocalId(); } @Matlab @Deprecated public String getAdjacencyString() { return Helpers.getAdjacencyString(this); } @Deprecated public String getDegreeString() { return Helpers.getDegreeString(this); } @Deprecated public String getDomainSizeString() { return Helpers.getDomainSizeString(this); } @Deprecated public String getDomainString() { return Helpers.getDomainString(this); } @Deprecated public HashMap<Integer, ArrayList<INode>> getFactorsByDegree() { return Helpers.getFactorsByDegree(this); } @Deprecated public FactorList getFactorsFlat() { return getFactors(); } @Matlab @Deprecated public String getFullString() { return Helpers.getFullString(this); } /** * @deprecated Use {@link #getOwnedGraphs()} instead. */ @Deprecated public ArrayList<FactorGraph> getNestedGraphs() { return new ArrayList<>(_ownedSubGraphs); } @Deprecated static public HashMap<Integer, ArrayList<INode>> getNodesByDegree(ArrayList<INode> nodes) { return Helpers.getNodesByDegree(nodes); } @Deprecated public HashMap<Integer, ArrayList<INode>> getNodesByDegree() { return Helpers.getNodesByDegree(this); } @Matlab @Deprecated public String getNodeString() { return Helpers.getNodeString(this); } /** * Returns the ith variable contained directly in this graph * (i.e. not in subgraphs). * @param i an index in the range [0,{@link #getOwnedVariableCount()} - 1] * @since 0.05 * @deprecated As of 0.08 use {@link #getOwnedVariables()} instead. */ @Deprecated public Variable getOwnedVariable(int i) { return _ownedVariables.getNth(i); } /** * Gets schedule on current solver * @deprecated instead use {@linkplain ISolverFactorGraph #getSchedule getSchedule} on {@linkplain #getSolver() * solver graph}. */ @Deprecated public ISchedule getSchedule() { final ISolverFactorGraph sgraph = getSolver(); return sgraph != null ? sgraph.getSchedule() : EmptySchedule.INSTANCE; } /** * Returns the scheduler associated with the current solver, if any. * <p> * @deprecated instead either use {@linkplain ISolverFactorGraph#getScheduler() corresponding method on solver} * or look up the corresponding option value (e.g. * {@linkplain com.analog.lyric.dimple.options.BPOptions#scheduler BPOptions.scheduler} * or {@link com.analog.lyric.dimple.solvers.gibbs.GibbsOptions#scheduler GibbsOptions.scheduler}). */ @Deprecated public @Nullable IScheduler getScheduler() { ISolverFactorGraph sfg = getSolver(); return sfg != null ? sfg.getScheduler() : null; } /** * @deprecated instead get {@linkplain ISchedule#scheduleVersion() scheduleVersion} from * {@linkplain #getSolver() solver graph's} {@linkplain ISolverFactorGraph#getSchedule() schedule}. */ @Deprecated public long getScheduleVersionId() { final ISolverFactorGraph sgraph = getSolver(); return sgraph != null ? sgraph.getSchedule().scheduleVersion() : -1L; } @Deprecated public HashMap<Integer, ArrayList<INode>> getVariablesByDegree() { return Helpers.getVariablesByDegree(this); } @Deprecated public TreeMap<Integer, ArrayList<Variable>> getVariablesByDomainSize() { return Helpers.getVariablesByDomainSize(this); } /** * @deprecated use {@link #getVariableBlockByLocalId(int)} instead */ @Deprecated public @Nullable List<Variable> getVariableGroup(int variableGroupID) { return getVariableBlockByLocalId(variableGroupID); } /** * @deprecated Use {@link #structureVersion() instead}. */ @Deprecated public long getVersionId() { return _structureVersion; } /** * @deprecated Instead use {@link Node#setName} method on {@code child}. */ @Deprecated public void setChildName(Node child, @Nullable String newName) { child.setName(newName); } /** * Sets event listener. * <p> * Sets the value to be returned by {@link #getEventListener()}. * This should only be set on a root graph. * <p> * @param listener is the event listener to be used for this graph and all of its * contents. May be set to null to turn off listening. * @deprecated As of release 0.07, this functionality has been moved to {@link DimpleEnvironment#setEventListener}. * @since 0.06 */ @Deprecated public void setEventListener(@Nullable DimpleEventListener listener) { assertGraphNotFrozen(); getEnvironment().setEventListener(listener); notifyListenerChanged(); } @Deprecated public void setNamesByStructure() { assertGraphNotFrozen(); Helpers.setNamesByStructure(this); } @Deprecated public void setNamesByStructure(String boundaryString, String ownedString, String factorString, String rootGraphString, String childGraphString) { assertGraphNotFrozen(); Helpers.setNamesByStructure(this, boundaryString, ownedString, factorString, rootGraphString, childGraphString); } /*----------------- * Private methods */ void assertGraphNotFrozen() { _graphTreeState.assertNotFrozen(); } //============== // // Scheduling // //============== /** * Sets schedule on current solver. * <p> * If {@code schedule} is a {@link FixedSchedule}, then this will create a new * {@link CustomScheduler} containing the specified schedule entries, otherwise * this will set the schedule on the {@linkplain #getSolver current solver graph}. * <p> * @deprecated instead use {@linkplain ISolverFactorGraph#setSchedule setSchedule} on {@linkplain #getSolver() * solver graph}. */ @Deprecated public void setSchedule(@Nullable ISchedule schedule) { assertGraphNotFrozen(); if (schedule instanceof FixedSchedule) { CustomScheduler scheduler = new CustomScheduler(this); scheduler.addAll(schedule); setScheduler(scheduler); } else if (schedule != null) { requireSolver("setSchedule").setSchedule(schedule); } else { // Don't throw exception if solver is not set when schedule is null ISolverFactorGraph solver = getSolver(); if (solver != null) { solver.setSchedule(schedule); } } } /** * Returns subgraph identified by global id * @param globalId * @return subgraph with graph id matching that in {@code globalId} or null if no such graph, or * not a subgraph of this graph. * @since 0.08 */ private @Nullable FactorGraph getSubgraphForGlobalId(long globalId) { final int graphId = Ids.graphIdFromGlobalId(globalId); if (_graphId == graphId) { return this; } else { FactorGraph fg = getEnvironment().factorGraphs().getGraphWithId(graphId); if (fg != null && isAncestorOf(fg)) { return fg; } } return null; } }