/*******************************************************************************
* 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.solvers.core;
import java.util.logging.Level;
import com.analog.lyric.collect.ReleasableIterator;
import com.analog.lyric.dimple.environment.DimpleEnvironment;
import com.analog.lyric.dimple.exceptions.DimpleException;
import com.analog.lyric.dimple.factorfunctions.core.CustomFactorFunctionWrapper;
import com.analog.lyric.dimple.factorfunctions.core.FactorFunction;
import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactor;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph;
import com.analog.lyric.options.IOptionHolder;
import com.analog.lyric.options.OptionKey;
import com.analog.lyric.options.OptionLookupIterator;
import com.analog.lyric.util.misc.Matlab;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
/**
* Special option key for declaring options that let users add custom solver factors.
* <p>
* This will be used to lookup and invoke creators for solver factors for the solver
* for which the option is defined. For instance,
* {@link com.analog.lyric.dimple.solvers.sumproduct.SumProductSolverGraph SumProductSolverGraphs} use the
* {@link com.analog.lyric.dimple.solvers.sumproduct.SumProductOptions#customFactors SumProductOptions.customFactors}
* option to create solver factors. Users can customize solver factor creation for specific factor functions
* by adding entries to {@link CustomFactors} using the appropriate option. Since custom factors are often not specific
* to a particular graph, this will often be done by setting the option on the {@linkplain DimpleEnvironment#active()
* active Dimple environment} so that it will be inherited by all solver graphs. For instance, one might define
* a new custom factor association for sum-product using:
* <p>
* <blockquote><pre>
* SumProductOptions.customFactors
* .getOrCreate(DimpleEnvironment.active()
* .add(Xor.class, MyCustomXor.class);
* </pre></blockquote>
* <p>
* @param <SFactor> is the base class of any solver factors that will be created by {@link CustomFactors}
* values associated with this key.
* @param <SGraph> is the base class for the solver graph for which solver factor is created
* @param <CF> is the concrete subclass of {@link CustomFactors} that can be used as this option's values.
* <p>
* @since 0.08
* @author Christopher Barber
*/
public class CustomFactorsOptionKey<SFactor extends ISolverFactor, SGraph extends ISolverFactorGraph,
CF extends CustomFactors<SFactor,SGraph>>
extends OptionKey<CF>
{
private static final long serialVersionUID = 1L;
private final Class<CF> _type;
private final Supplier<CF> _defaultValueSupplier;
/*--------------
* Construction
*/
/**
* Constructor intended for use in declaration of static constant field containing option key instance.
* <p>
* Example:
* <pre>
* class MyOptions
* {
* public static final CustomFactorsOptionKey<MySFactor, MySolverGraph> customFactors =
* new CustomFactorsOptionKey<>(MyOptions.class, "customFactors", MyCustomFactors.class);
* }
* </pre>
* <p>
* @param declaringClass is the class in which the static field containing the instance is declared.
* @param name is the name of the static field
* @param type is the concrete subclass of {@link CustomFactors} stored by this option key.
* @since 0.08
*/
public CustomFactorsOptionKey(Class<?> declaringClass, String name, final Class<CF> type)
{
super(declaringClass, name);
_type = type;
_defaultValueSupplier = Suppliers.memoize(new Supplier<CF>() {
@Override
public CF get()
{
try
{
final CF customFactors = type.newInstance();
customFactors.addBuiltins();
customFactors.freeze();
return customFactors;
}
catch (InstantiationException | IllegalAccessException ex)
{
throw new DimpleException(ex);
}
}
});
}
/*--------------------
* IOptionKey methods
*/
@Override
public Class<CF> type()
{
return _type;
}
@Override
public synchronized CF defaultValue()
{
return _defaultValueSupplier.get();
}
/*---------------
* Local methods
*/
/**
* Creates an empty {@link CustomFactors} for use with this option key.
* <p>
* @return empty new instance of the appropriate concrete {@link #type()}.
* @throws DimpleException if instance cannot be created using reflection on the
* default no-argument constructor.
* <p>
* @since 0.08
* @see #getOrCreate(IOptionHolder)
*/
@Matlab
public CF createValue()
{
try
{
return type().newInstance();
}
catch (InstantiationException | IllegalAccessException ex)
{
// This should not happen unless CF class is abstract, not public or doesn't
// have a default constructor.
throw new DimpleException(ex);
}
}
/**
* Locally lookup CustomFactors instance for this key, creating and adding if not already set on {@code holder}.
* <p>
* Creation uses reflection to invoke the default constructor so the concrete {@link #type()} must be
* public and have a public no-argument constructor.
* <p>
* @since 0.08
* @see #createValue()
*/
public CF getOrCreate(IOptionHolder holder)
{
CF customFactors = holder.getLocalOption(this);
if (customFactors == null)
{
set(holder, customFactors = createValue());
}
return customFactors;
}
/**
* Creates new solver factor for given factor for given solver graph.
* <p>
* This is intended to be invoked by implementations of {@link SFactorGraphBase#createFactor(Factor)}, as in:
* <pre>
* public ISolverFactor createFactor(Factor factor)
* {
* return MyOptions.customFactors.createFactor(factor, this);
* }
* </pre>
* <p>
* This function will look in {@link CustomFactors} objects set under this key in the
* option delegate chain starting with {@code sgraph} and ending with the {@link #defaultValue()} for
* this key instance to find matching {@linkplain ISolverFactorCreator}s
* and invoke them in turn, catching and saving any exceptions. The first solver factor successfully produced
* will be returned. If no solver factor was produced and factor is a {@link CustomFactorFunctionWrapper},
* then a {@link SolverFactorCreationException} will be thrown because there is no real factor to fall back on,
* otherwise this will use the {@link CustomFactors#createDefault(Factor, ISolverFactorGraph) createDefault}
* method of the first {@link CustomFactors} object to create the solver factor, and any exceptions thrown
* by that method will be passed through.
* <p>
* This method will log any exceptions thrown by factor creation routines at log {@link Level#CONFIG}.
* This can be used to debug reasons for custom factor rejection.
* <p>
* @param factor is the factor for which a solver factor is to be created
* @param sgraph is the solver graph that will be the immediate parent of the new solver factor.
* @throws SolverFactorCreationException if no solver factor could be created
* @since 0.08
*/
public SFactor createFactor(Factor factor, SGraph sgraph)
{
final FactorFunction function = factor.getFactorFunction();
final Class<?> functionClass = function.getClass();
// If true, there isn't a real factor function. All we have is the name of the wrapper function.
final boolean isCustomWrapper = functionClass == CustomFactorFunctionWrapper.class;
final ReleasableIterator<CF> customFactorsIter = OptionLookupIterator.create(sgraph, this);
// Outer loop iterates over CustomFactors instances found in option delegate chain.
try
{
final CF firstCustomFactors = customFactorsIter.next();
CF customFactors = firstCustomFactors;
RuntimeException lastFailure = null;
while (true)
{
Class<?> functionSuperclass = functionClass;
String functionName = isCustomWrapper ? function.getName() : functionSuperclass.getName();
// Middle loop iterates over superclasses of functionClass, if not a custom wrapper.
while (true)
{
// Inner loop iterates over factor creators for given key
for (ISolverFactorCreator<? extends SFactor,SGraph> creator : customFactors.get(functionName))
{
try
{
return creator.create(factor, sgraph);
}
catch (RuntimeException ex)
{
lastFailure = ex;
DimpleEnvironment.log(Level.CONFIG, "Custom factor rejected for %s: %s", factor, ex);
}
}
final Class<?> superclass = functionSuperclass.getSuperclass();
if (!isCustomWrapper && FactorFunction.class.isAssignableFrom(superclass))
{
// Try again using superclass as long as it is still a subclass of FactorFunction.
functionSuperclass = superclass.asSubclass(FactorFunction.class);
functionName = functionSuperclass.getName();
}
else
{
break;
}
}
if (!customFactorsIter.hasNext())
{
if (isCustomWrapper)
{
// There isn't a real factor function, so we throw an exception instead of generating a default
if (lastFailure != null)
{
throw new SolverFactorCreationException(lastFailure,
"Cannot find factor function '%s' and could not produce custom factor: %s",
functionName, lastFailure);
}
else
{
// Not a real factor and no custom factor creator found under this name.
throw new SolverFactorCreationException(
"Cannot find factor function or custom factor implementation for '%s'", functionName);
}
}
return firstCustomFactors.createDefault(factor, sgraph);
}
customFactors = customFactorsIter.next();
}
}
finally
{
customFactorsIter.release();
}
}
}