/******************************************************************************* * 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(); } } }