/*******************************************************************************
* 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.matlabproxy;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.Nullable;
import com.analog.lyric.dimple.environment.DimpleEnvironment;
import com.analog.lyric.dimple.exceptions.DimpleException;
import com.analog.lyric.dimple.model.core.INode;
import com.analog.lyric.dimple.model.core.Node;
import com.analog.lyric.dimple.options.DimpleOptionRegistry;
import com.analog.lyric.dimple.schedulers.CustomScheduler;
import com.analog.lyric.dimple.schedulers.IGibbsScheduler;
import com.analog.lyric.dimple.schedulers.IScheduler;
import com.analog.lyric.dimple.schedulers.SchedulerOptionKey;
import com.analog.lyric.dimple.schedulers.schedule.ScheduleValidationException;
import com.analog.lyric.dimple.schedulers.scheduleEntry.IBlockUpdater;
import com.analog.lyric.options.IOptionKey;
import com.analog.lyric.options.OptionKey;
import com.analog.lyric.util.misc.Matlab;
/**
* MATLAB proxy for {@link IScheduler}
* @since 0.08
* @author Christopher Barber
*/
@Matlab(wrapper="Scheduler")
public class PScheduler extends PObject
{
/*-------
* State
*/
private final IScheduler _scheduler;
/*--------------
* Construction
*/
PScheduler(IScheduler scheduler)
{
_scheduler = scheduler;
}
/**
* Constructs a custom scheduler with specified entries.
* <p>
* @param pgraph identifies the graph for which the scheduler is being created
* @since 0.08
*/
PScheduler(PFactorGraphVector pgraph, SchedulerOptionKey schedulerKey, @Nullable Object[] scheduleEntries)
{
this(new CustomScheduler(pgraph.getGraph(), schedulerKey));
addCustomEntries(scheduleEntries);
}
/**
* @deprecated only to support Matlab FactorGraph.Schedule setter
*/
@Deprecated
PScheduler(PFactorGraphVector pgraph, @Nullable Object[] scheduleEntries)
{
this(new CustomScheduler(pgraph.getGraph()));
addCustomEntries(scheduleEntries);
}
PScheduler(PFactorGraphVector pgraph, String schedulerKey, @Nullable Object[] scheduleEntries)
{
this(pgraph, lookupSchedulerKey(pgraph.getGraph().getEnvironment(), schedulerKey), scheduleEntries);
}
private static SchedulerOptionKey lookupSchedulerKey(DimpleEnvironment env, String schedulerKey)
{
final DimpleOptionRegistry options = env.optionRegistry();
IOptionKey<?> key = null;
if (schedulerKey.contains("."))
{
// If str contains a dot require an exact match.
key = options.get(schedulerKey);
}
else
{
// Otherwise use a regexp
ArrayList<IOptionKey<?>> keys = options.getAllMatching(Pattern.quote(schedulerKey) + "\\w+\\.scheduler");
switch (keys.size())
{
case 0:
break;
case 1:
key = keys.get(0);
break;
default:
throw new ScheduleValidationException("'%s' is ambiguous could be any of: %s", schedulerKey, keys);
}
}
if (key == null)
{
throw new ScheduleValidationException("'%s' does not refer to a known option key", schedulerKey);
}
if (!(key instanceof SchedulerOptionKey))
{
throw new ScheduleValidationException("'%s' does not refer to a scheduler option key", schedulerKey);
}
return (SchedulerOptionKey)key;
}
/*-----------------
* PObject methods
*/
@Override
public IScheduler getDelegate()
{
return _scheduler;
}
@Override
public IScheduler getModelerObject()
{
return _scheduler;
}
@Override
public boolean isScheduler()
{
return true;
}
/*--------------------
* PScheduler methods
*/
public void addBlockScheduleEntry(IBlockUpdater blockUpdater, PVariableBlock block)
{
if (_scheduler instanceof IGibbsScheduler)
{
IGibbsScheduler scheduler = (IGibbsScheduler)_scheduler;
scheduler.addBlockWithReplacement(blockUpdater, block.getDelegate());
}
else
{
throw new UnsupportedOperationException("addBlockScheduleEntry cannot be used on non-Gibbs scheduler");
}
}
public void addBlockScheduleEntry(IBlockUpdater blockUpdater, Object[] variables)
{
addBlockScheduleEntry(blockUpdater, PFactorGraphVector.addVariableBlock(null, variables));
}
/**
* Add entries to a custom schedule.
* <p>
* @param scheduleEntries an ordered list of the following types of entries:
* <ul>
* <li>{@link SchedulerOptionKey} instance or its {@linkplain OptionKey#qualifiedName() qualified name}
* may be specified as the first entry in the list to identify the scheduler type.
* <li>{@link PNodeVector} containing node entries
* <li>An {@code Object[]} array containing two elements specifying an edge schedule entry. The first
* element is a single-node {@link PNodeVector} specifying the originating node, and the second entry
* specifying the destination node for the edge update. If there is more than one edge connecting the
* source and destination, this specifies the lowest numbered one with respect to the origin node.
* <li>A n {@code Object[]} array containing a block schedule entry specification. The first element
* is an instance of {@link IBlockUpdater} and the remaining entries should contain the variables in
* the block.
* </ul>
* @throws UnsupportedOperationException if this does not hold a {@link CustomScheduler}.
* @since 0.08
*/
public void addCustomEntries(@Nullable Object[] scheduleEntries)
{
final CustomScheduler scheduler = assertCustom();
if (scheduleEntries == null)
{
return;
}
//Convert schedule to a list of nodes and edges
for (Object entry : scheduleEntries)
{
if (entry instanceof Object[])
{
final Object[] array = (Object[])entry;
if (array.length >= 2 && array[0] instanceof IBlockUpdater)
{
// This is a block schedule entry
final IBlockUpdater blockUpdater = (IBlockUpdater)array[0];
if (array[1] instanceof PVariableBlock)
{
scheduler.addBlock(blockUpdater, ((PVariableBlock)array[1]).getDelegate());
}
else
{
scheduler.addBlock(blockUpdater, PHelpers.convertToVariableArray(array, 1));
}
}
else
{
// Entry is a pair of nodes, that represent an edge
if (array.length != 2)
throw new DimpleException("Length of array containing edge must be 2");
INode node1 = PHelpers.convertToNode(array[0]);
INode node2 = PHelpers.convertToNode(array[1]);
int portNum = node1.findSibling(node2);
scheduler.addEdge(node1, portNum);
}
}
else
{
for (Node node : PHelpers.convertToNodeArray(entry))
{
scheduler.addNode(node);
}
}
}
}
public void addCustomPath(Object[] nodes)
{
ArrayList<INode> list = new ArrayList<>(nodes.length);
for (Object obj : nodes)
{
if (obj instanceof INode)
{
list.add((INode)obj);
}
else if (obj instanceof PNodeVector)
{
PNodeVector vec = (PNodeVector)obj;
if (vec.size() != 1)
{
throw new IllegalArgumentException("Can only use scalar nodes in path.");
}
list.add(vec.getModelerNode(0));
}
}
assertCustom().addPath(list);
}
public String[] applicableSchedulerOptions()
{
final List<? extends SchedulerOptionKey> keys = _scheduler.applicableSchedulerOptions();
final int n = keys.size();
final String[] names = new String[n];
for (int i = 0; i < n; ++i)
{
names[i] = keys.get(i).qualifiedName();
}
return names;
}
/*-----------------
* Private methods
*/
private CustomScheduler assertCustom()
{
if (!(_scheduler instanceof CustomScheduler))
{
throw new UnsupportedOperationException(String.format("%s is not a CustomScheduler", _scheduler));
}
return (CustomScheduler)_scheduler;
}
}