com.analog.lyric.dimple.schedulers.CustomScheduler.java Source code

Java tutorial

Introduction

Here is the source code for com.analog.lyric.dimple.schedulers.CustomScheduler.java

Source

/*******************************************************************************
*   Copyright 2015 Analog Devices, Inc.
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
********************************************************************************/

package com.analog.lyric.dimple.schedulers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.IFactorGraphChild;
import com.analog.lyric.dimple.model.core.INode;
import com.analog.lyric.dimple.model.core.Port;
import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.model.variables.Variable;
import com.analog.lyric.dimple.model.variables.VariableBlock;
import com.analog.lyric.dimple.options.BPOptions;
import com.analog.lyric.dimple.schedulers.schedule.FixedSchedule;
import com.analog.lyric.dimple.schedulers.schedule.ScheduleValidationException;
import com.analog.lyric.dimple.schedulers.scheduleEntry.BlockScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.EdgeScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.IBlockUpdater;
import com.analog.lyric.dimple.schedulers.scheduleEntry.IScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.NodeScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.SubgraphScheduleEntry;
import com.analog.lyric.dimple.solvers.gibbs.GibbsOptions;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph;
import com.analog.lyric.util.misc.Internal;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;

/**
 * A schedule for producing a fixed custom schedule for a given graph.
 * <p>
 * @since 0.08
 * @author Christopher Barber
 */
public class CustomScheduler extends SchedulerBase implements IGibbsScheduler {
    private static final long serialVersionUID = 1L;

    /*-------
     * State
     */

    private final ArrayList<IScheduleEntry> _entries = new ArrayList<>();

    private FactorGraph _graph;

    private final @Nullable SchedulerOptionKey _schedulerKey;

    /*--------------
     * Construction
     */

    /**
     * Construct a new custom scheduler for given graph and scheduler type.
     * <p>
     * @param graph is the graph for which the custom schedule will be generated.
     * @param schedulerType identifies what type of schedules this will produce
     * and will be returned by {@link #applicableSchedulerOptions()}.
     * The schedule type may constrain what types of entries may be added. For
     * instance, Gibbs schedules may not contain edge or factor update entries.
     * @since 0.08
     */
    public CustomScheduler(FactorGraph graph, SchedulerOptionKey schedulerType) {
        _graph = graph;
        _schedulerKey = schedulerType;
    }

    /**
     * @category internal
     */
    @Deprecated
    @Internal
    public CustomScheduler(FactorGraph graph) {
        _graph = graph;
        _schedulerKey = null;
    }

    CustomScheduler(CustomScheduler other, Map<Object, Object> old2new, boolean copyToRoot) {
        _graph = (FactorGraph) old2new.get(other.getGraph());
        _schedulerKey = other._schedulerKey;
        _entries.ensureCapacity(other._entries.size());
        for (IScheduleEntry entry : other._entries) {
            IScheduleEntry entryCopy = entry.copy(old2new, copyToRoot);
            if (entryCopy != null) {
                _entries.add(entryCopy);
            }
        }
    }

    /*----------------------
     * IOptionValue methods
     */

    @Override
    public boolean isMutable() {
        return true;
    }

    /*--------------------
     * IScheduler methods
     */

    /**
     * {@inheritDoc}
     * <p>
     * If {@link #declaredSchedulerType()} is not null, that will be returned. Otherwise this
     * will return a list containing both {@link BPOptions#scheduler} and {@link GibbsOptions#scheduler}.
     */
    @Override
    public List<SchedulerOptionKey> applicableSchedulerOptions() {
        SchedulerOptionKey schedulerKey = _schedulerKey;
        if (schedulerKey != null) {
            return Collections.singletonList(_schedulerKey);
        } else {
            return Arrays.asList(BPOptions.scheduler, GibbsOptions.scheduler);
        }
    }

    @Override
    public IScheduler copy(Map<Object, Object> old2NewMap, boolean copyToRoot) {
        return new CustomScheduler(this, old2NewMap, copyToRoot);
    }

    /**
     * {@inheritDoc}
     * <p>
     * Returns fixed schedule specified by this custom scheduler (created through
     * the various add* methods).
     * <p>
     * @throws ScheduleValidationException if {@code graph} is not the same as the
     * {@linkplain #getGraph graph} for which this scheduler was constructed.
     */
    @Override
    public FixedSchedule createSchedule(FactorGraph graph) {
        validateForGraph(graph);
        return new FixedSchedule(this, graph, _entries);
    }

    @Override
    public FixedSchedule createSchedule(ISolverFactorGraph solverGraph) {
        return createSchedule(solverGraph.getModelObject());
    }

    @Override
    public boolean isCustomScheduler() {
        return true;
    }

    /**
     * {@inheritDoc}
     * <p>
     * CustomerSchedulers are only valid for use on the {@link #getGraph() graph} for which they were created.
     */
    @Override
    public void validateForGraph(FactorGraph graph) {
        if (graph != _graph) {
            throw new ScheduleValidationException("This scheduler instance can only be used with graph '%s'",
                    _graph);
        }
    }

    /*-------------------------
     * IGibbsScheduler methods
     */

    @Deprecated
    @Override
    public void addBlockScheduleEntry(BlockScheduleEntry blockScheduleEntry) {
        addBlockWithReplacement(blockScheduleEntry.getBlockUpdater(), blockScheduleEntry.getBlock());
    }

    @Override
    public void addBlockWithReplacement(IBlockUpdater blockUpdater, final VariableBlock block) {
        Iterables.removeIf(_entries, new Predicate<IScheduleEntry>() {
            @NonNullByDefault(false)
            @Override
            public boolean apply(IScheduleEntry entry) {
                switch (entry.type()) {
                case NODE:
                    return block.contains(((NodeScheduleEntry) entry).getNode());

                case EDGE:
                    return block.contains(((EdgeScheduleEntry) entry).getNode());

                default:
                    return false;
                }
            }
        });
        addBlock(blockUpdater, block);
    }

    /*-------------------------
     * CustomScheduler methods
     */

    /**
     * Adds all entries to the custom schedule in order.
     * <p>
     * Note that the deprecated {@code SubScheduleEntry} type is not supported by this method.
     * <p>
     * @since 0.08
     */
    public void addAll(Iterable<? extends IScheduleEntry> entries) {
        if (entries instanceof Collection) {
            _entries.ensureCapacity(((Collection<?>) entries).size() + _entries.size());
        }
        for (IScheduleEntry entry : entries) {
            addEntry(entry);
        }
    }

    /**
     * Adds a block schedule entry using given updater and variable block.
     * @since 0.08
     */
    public void addBlock(IBlockUpdater blockUpdater, VariableBlock block) {
        addEntry(new BlockScheduleEntry(blockUpdater, block));
    }

    /**
     * Adds a block schedule entry using given updater and variable block defined from specified variables.
     * <p>
     * This will define a {@link VariableBlock} for the given {@code variables} on the {@linkplain #getGraph()
     * graph} associated with this scheduler.
     * <p>
     * @since 0.08
     * @see #addBlock(IBlockUpdater, VariableBlock)
     */
    public void addBlock(IBlockUpdater blockUpdater, Variable... variables) {
        addBlock(blockUpdater, _graph.addVariableBlock(variables));
    }

    /**
     * Adds an edge schedule entry for the given port.
     * <p>
     * @param edge a {@link Port} object describing the originating node and index of outgoing edge.
     * @since 0.08
     */
    public void addEdge(Port edge) {
        addEdge(edge.getNode(), edge.getSiblingNumber());
    }

    /**
     * Adds edge schedule entries for the given ports.
     * <p>
     * @since 0.08
     * @see #addEdge(Port)
     */
    public void addEdges(Port... edges) {
        for (Port port : edges)
            addEdge(port);
    }

    /**
     * Adds edge schedule entry for given node and {@code siblingNumber} of outgoing edge.
     * @since 0.08
     */
    public void addEdge(INode node, int siblingNumber) {
        addEntry(new EdgeScheduleEntry(node, siblingNumber));
    }

    /**
     * Adds edge update from {@code source} to {@code target} nodes.
     * <p>
     * If there is more than one edge between {@code source} and {@code target}, then
     * this will produce an update for the lowest numbered edge from the perspective of
     * {@code source}.
     * <p>
     * @param source either a variable or factor
     * @param target a factor or variable that is connected to {@code source}.
     * @since 0.08
     * @see #addEdge(INode, int)
     */
    public void addEdge(INode source, INode target) {
        addEdge(source, source.findSibling(target));
    }

    /**
     * Add node update for given {@code factor}.
     * @since 0.08
     */
    public void addFactor(Factor factor) {
        addEntry(new NodeScheduleEntry(factor));
    }

    /**
     * Add node update for {@code factors} in given order.
     * @since 0.08
     */
    public void addFactors(Factor... factors) {
        for (Factor factor : factors)
            addFactor(factor);
    }

    /**
     * Add node update for given {@code node}.
     * <p>
     * If {@code node} is a {@link FactorGraph}, a subgraph schedule entry will be added, otherwise
     * a node update entry will be added.
     * @since 0.08
     */
    public void addNode(INode node) {
        addEntry(node instanceof FactorGraph ? new SubgraphScheduleEntry((FactorGraph) node)
                : new NodeScheduleEntry(node));
    }

    /**
     * Adds node updates for {@code nodes} in given order.
     * @since 0.08
     * @see #addNode(INode)
     */
    public void addNodes(INode... nodes) {
        for (INode node : nodes) {
            addNode(node);
        }
    }

    /**
     * Adds edge entries following path through specified nodes.
     * <p>
     * Creates edge update entries starting from first node and terminating with last node.
     * <p>
     * @param nodes each node must not be separated from the previous node by more than one other node
     * and there must be only one possible path between nodes.
     * 
     * @since 0.08
     */
    public void addPath(INode... nodes) {
        addPath(Arrays.asList(nodes));
    }

    /**
     * Adds edge entries following path through specified nodes.
     * <p>
     * Creates edge update entries starting from first node and terminating with last node.
     * <p>
     * @param nodes each node must not be separated from the previous node by more than one other node
     * and there must be only one possible path between nodes.
     * 
     * @since 0.08
     */
    public void addPath(List<INode> nodes) {
        // TODO - extend this method to support nodes that are farther than two edges apart if there is a
        // unique path.

        if (nodes.size() < 2) {
            throw new ScheduleValidationException("addPath requires at least two nodes");
        }

        final Iterator<INode> iter = nodes.iterator();
        for (INode from = iter.next(), to = null; iter.hasNext() && (to = iter.next()) != null; from = to) {
            int toi = from.findSibling(to);
            if (toi >= 0) {
                if (from.findSibling(to, toi + 1) >= 0) {
                    throw new ScheduleValidationException("There is not a unique path from %s to %s", from, to);
                }
                addEdge(from, to);
            } else {
                // Not connected directly. See if there is a common sibling.
                Set<INode> commonSiblings = new HashSet<>(from.getSiblings());
                commonSiblings.retainAll(to.getSiblings());

                switch (commonSiblings.size()) {
                case 0:
                    throw new ScheduleValidationException(
                            "Nodes %s and %s are not adjacent and don't share common sibling", from, to);
                case 1:
                    INode middle = Iterables.getOnlyElement(commonSiblings);
                    addEdge(from, middle);
                    addEdge(middle, to);
                    break;
                default:
                    throw new ScheduleValidationException("Nodes %s and %s are connected by more than one path",
                            from, to);
                }
            }
        }
    }

    /**
     * Adds node update for given {@code variable}.
     * @param variable
     * @since 0.08
     */
    public void addVariable(Variable variable) {
        addEntry(new NodeScheduleEntry(variable));
    }

    /**
     * Adds node update for {@code variables} in given order.
     * @since 0.08
     * @see #addVariable(Variable)
     */
    public void addVariables(Variable... variables) {
        int size = _entries.size();
        try {
            for (Variable var : variables) {
                addVariable(var);
            }
            size = _entries.size();
        } finally {
            // If an exception is thrown, remove any added entries.
            for (int i = _entries.size(); --i >= size;) {
                _entries.remove(i);
            }
        }
    }

    /**
     * Adds a subgraph schedule entry for given subgraph.
     * <p>
     * @param graph must be a subgraph of {@linkplain #getGraph() graph} associated with scheduler.
     * @since 0.08
     */
    public void addSubgraph(FactorGraph graph) {
        addEntry(new SubgraphScheduleEntry(graph));
    }

    /**
     * The scheduler type that was declared when this scheduler was constructed.
     * <p>
     * This should only be null if custom scheduler was created using deprecated
     * {@link FactorGraph#setSchedule} interface or through the MATLAB interface.
     * <p>
     * @since 0.08
     * @see #CustomScheduler(FactorGraph, SchedulerOptionKey)
     * @see #applicableSchedulerOptions()
     */
    public @Nullable SchedulerOptionKey declaredSchedulerType() {
        return _schedulerKey;
    }

    /**
     * The graph for which the scheduler was constructed.
     * <p>
     * The scheduler will not be able to be used on graphs other than this one and any entries
     * added to the custom schedule must be for objects that are in this graph or its subgraphs.
     * @since 0.08
     * @see #CustomScheduler(FactorGraph, SchedulerOptionKey)
     */
    public FactorGraph getGraph() {
        return _graph;
    }

    /*-----------------
     * Private methods
     */

    /**
     * Validates entry, infers scheduler key if necessary, and adds to list.
     */
    @SuppressWarnings("deprecation") // for SUBSCHEDULE
    private void addEntry(IScheduleEntry entry) {
        switch (entry.type()) {
        case EDGE: {
            EdgeScheduleEntry edgeEntry = (EdgeScheduleEntry) entry;
            INode node = edgeEntry.getNode();
            assertInGraph(node);
            if (_schedulerKey == GibbsOptions.scheduler) {
                throw new ScheduleValidationException("Cannot use edge entry with Gibbs schedule");
            }
            break;
        }
        case NODE: {
            NodeScheduleEntry nodeEntry = (NodeScheduleEntry) entry;
            INode node = nodeEntry.getNode();
            if (!node.isVariable() && _schedulerKey == GibbsOptions.scheduler) {
                throw new ScheduleValidationException("Cannot use factor node entry with Gibbs schedule");
            }
            assertInGraph(node);
            break;
        }
        case SUBGRAPH: {
            SubgraphScheduleEntry graphEntry = (SubgraphScheduleEntry) entry;
            FactorGraph subgraph = graphEntry.getSubgraph();
            assertInGraph(subgraph);
            break;
        }
        case SUBSCHEDULE:
            throw new ScheduleValidationException("Cannot add SubScheduleEntry to CustomScheduler");
        case VARIABLE_BLOCK: {
            BlockScheduleEntry blockEntry = (BlockScheduleEntry) entry;
            assertInGraph(blockEntry.getBlock());
            break;
        }
        case CUSTOM:
            break;
        }

        _entries.add(entry);
    }

    /**
     * Verifies that child is in graph tree rooted at {@link _graph} or is a boundary
     * variable of the graph.
     */
    private void assertInGraph(IFactorGraphChild child) {
        final FactorGraph parent = child.getParentGraph();
        if (_graph != parent && !_graph.isAncestorOf(child.getContainingGraph())
                && !(child instanceof Variable && _graph.isBoundaryVariable((Variable) child))) {
            throw new ScheduleValidationException(
                    "Cannot add entry containing %s because it is not in root graph %s", child, _graph);
        }
    }
}