/******************************************************************************* * Copyright 2014-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.environment; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.security.SecureRandom; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.collect.ConstructorRegistry; import com.analog.lyric.collect.DoubleArrayCache; import com.analog.lyric.collect.IntArrayCache; import com.analog.lyric.collect.WeakLongHashMap; import com.analog.lyric.dimple.events.DimpleEventListener; import com.analog.lyric.dimple.events.IDimpleEventSource; import com.analog.lyric.dimple.events.IModelEventSource; import com.analog.lyric.dimple.factorfunctions.core.FactorFunction; import com.analog.lyric.dimple.factorfunctions.core.FactorFunctionRegistry; import com.analog.lyric.dimple.model.core.FactorGraph; import com.analog.lyric.dimple.model.core.FactorGraphRegistry; import com.analog.lyric.dimple.model.core.Ids; import com.analog.lyric.dimple.model.core.Node; import com.analog.lyric.dimple.options.DimpleOptionHolder; import com.analog.lyric.dimple.options.DimpleOptionRegistry; import com.analog.lyric.dimple.schedulers.IScheduler; import com.analog.lyric.dimple.schedulers.validator.ScheduleValidator; import com.analog.lyric.dimple.solvers.core.DimpleSolverRegistry; import com.analog.lyric.dimple.solvers.core.proposalKernels.IProposalKernel; import com.analog.lyric.dimple.solvers.gibbs.samplers.generic.IGenericSampler; import com.analog.lyric.dimple.solvers.interfaces.IFactorGraphFactory; import com.analog.lyric.dimple.solvers.sumproduct.SumProductSolver; import com.analog.lyric.math.DimpleRandom; import com.analog.lyric.options.IOptionHolder; import com.analog.lyric.options.IOptionKey; import net.jcip.annotations.GuardedBy; import net.jcip.annotations.ThreadSafe; /** * Shared environment for Dimple * * <h2>Overview</h2> * * This object holds shared state for a Dimple session. Typically * there will only be one instance of this class that is global across * all threads, but it is possible to configure different instances in * different threads in the rare case where multiple segregated Dimple * sessions need to be run at the same time. * * <h2>Accessing the environment</h2> * * When working with an object that implements the {@link IDimpleEnvironmentHolder} interface, * which includes both model and solver factor graph, variable and factor objects, you can * reference the appropriate environment through its {@linkplain IDimpleEnvironmentHolder#getEnvironment()} * method. Root {@link FactorGraph} objects store a pointer to the active environment when they * are constructed, and children of a graph will return the environment of the root graph. * If the environment is not otherwise available, you can access the active environment for the current thread * using the static {@link DimpleEnvironment#active()} method. * * <h2>Options</h2> * * The environment is the root of the option hierarchy for Dimple. It implements * the {@link IOptionHolder} interface, and you can set options on it that will be * inherited by all graphs that share that environment. You can use this to set default * option values without having to explicitly set them on newly created graphs. For instance, * in order to enable multi-threading for all graphs, you could simply write: * <blockquote> * <pre> * DimpleEnvironment env = DimpleEnvironment.active(); * env.setOption(SolverOptions.enableMultithreading, true); * </pre> * </blockquote> * Any options that are set on the graph objects will override the values set on the environment. * <p> * For more details options see the {@link com.analog.lyric.options} and {@link com.analog.lyric.dimple.options} * packages as well as the Dimple User Manual. * * <h2>Event handling</h2> * * Likewise, the environment is also the root of the event source hierarchy for Dimple and holds the * instance of the {@link DimpleEventListener} responsible for dispatching events. Event handling is * activated by {@linkplain #createEventListener() creating an event listener} on the environment and * {@linkplain DimpleEventListener#register(com.analog.lyric.dimple.events.IDimpleEventHandler, Class, IDimpleEventSource) * registering} it to dispatch specified events to designated handlers. For instance, the following * code gets the listener for the environment, creating if necessary, and registers a handler for * variable creation events on the given graph: * <blockquote> * <pre> * env.createEventListener().register(handler, VariableAddEvent.class, factorGraph); * </pre> * </blockquote> * For more details on events, see the {@link com.analog.lyric.dimple.events} package and the Dimple User Manual. * * <h2>Logging</h2> * * The environment also provides a simple logging interface that may be used by Dimple solver implementations to * log various non-fatal conditions. Although intended primarily for use in implementations of solvers and custom * factors, it is also available for use by by users of Dimple. The environment provides an instance of the * standard Java {@link Logger} class that is created using {@link Logger#getLogger} with the string * {@code "com.analog.lyric.dimple"}. This may be configured using Java system properties and config files * as described in the Java {@linkplain java.util.logging.LogManager LogManager} class documentation; relevant * properties are documented in the various handler classes (e.g. * {@linkplain java.util.logging.ConsoleHandler ConsoleHandler}, * {@linkplain java.util.logging.FileHandler FileHandler}). The Java logging system is described in more detail * in the <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html">Java Logging Overview</a> * in the Java SE Documentation. The default configuration usually will just log to {@link System#err}. * <p> * This class provides some simple methods for logging {@linkplain #logError(String, Object...) errors} and * {@linkplain #logWarning(String, Object...) warnings}. The environment's {@link #logger()} instance can * be used directly for cases when these methods are not sufficient. * * @since 0.07 * @author Christopher Barber */ @ThreadSafe public class DimpleEnvironment extends DimpleOptionHolder { /*-------------- * Static state */ @GuardedBy("_allInstances") private static final WeakLongHashMap<DimpleEnvironment> _allInstances = new WeakLongHashMap<>(); @GuardedBy("class") private static @Nullable DimpleEnvironment _globalInstance; private static final ThreadLocal<DimpleEnvironment> _threadInstance = new ThreadLocal<DimpleEnvironment>() { @Override protected DimpleEnvironment initialValue() { return defaultEnvironment(); } }; /** * Cache of double[] for temporary use. */ public static DoubleArrayCache doubleArrayCache = new DoubleArrayCache(); /** * Cache of int[] for temporary use. */ public static IntArrayCache intArrayCache = new IntArrayCache(); // Hack to determine if class was loaded from within the MATLAB environment. We do this by // checking to see if class loader comes from a package whose name starts with "com.mathworks". private static enum LoadedFromMATLAB { INSTANCE; private final boolean _fromMATLAB; private LoadedFromMATLAB() { _fromMATLAB = getClass().getClassLoader().getClass().getPackage().getName().startsWith("com.mathworks"); } } /*---------------- * Instance state */ // NOTE: although the environment is typically accessed through a thread-local // variable, there will typically be only one environment shared across all threads // so care should be taken to ensure that the code is thread safe. private final long _envId; private final UUID _uuid; /** * Logging instance for this environment. */ private final AtomicReference<Logger> _logger = new AtomicReference<>(); private final AtomicReference<DimpleRandom> _random = new AtomicReference<>(new DimpleRandom()); private final FactorFunctionRegistry _factorFunctions = new FactorFunctionRegistry(); private final FactorGraphRegistry _factorGraphs = new FactorGraphRegistry(); private final ConstructorRegistry<IGenericSampler> _genericSamplers = new ConstructorRegistry<IGenericSampler>(IGenericSampler.class); private final ConstructorRegistry<IProposalKernel> _proposalKernels = new ConstructorRegistry<>(IProposalKernel.class); @SuppressWarnings("deprecation") private final DimpleOptionRegistry _optionRegistry = new DimpleOptionRegistry(); private final ConstructorRegistry<IScheduler> _schedulers = new ConstructorRegistry<>(IScheduler.class); private final ConstructorRegistry<ScheduleValidator> _scheduleValidators = new ConstructorRegistry<>(ScheduleValidator.class); private final DimpleSolverRegistry _solverRegistry = new DimpleSolverRegistry(); private final AtomicReference<IFactorGraphFactory<?>> _defaultSolverFactory = new AtomicReference<IFactorGraphFactory<?>> (new SumProductSolver()); @GuardedBy("_eventListenerLock") private volatile @Nullable DimpleEventListener _eventListener = null; private final Object _eventListenerLock = new Object(); /*-------------- * Construction */ /** * Constructs a new instance of a Dimple environment. * <p> * @since 0.07 */ public DimpleEnvironment() { this(new SecureRandom().nextLong() & Ids.ENV_ID_MAX); } public DimpleEnvironment(long envId) { if (envId < Ids.ENV_ID_MIN || envId > Ids.ENV_ID_MAX) { throw new IllegalArgumentException(String.format("%d is not a valid environment id", envId)); } synchronized (_allInstances) { if (_allInstances.containsKey(envId)) { throw new IllegalStateException(String.format("An environment with id %d already exists", envId)); } _allInstances.put(envId, this); } _envId = envId; _uuid = Ids.makeUUID(envId, 0L); Logger logger = getDefaultLogger(); if (loadedFromMATLAB() && logger.getHandlers().length == 0 && (logger.getLevel() == null || logger.getLevel().equals(Level.OFF))) { // HACK: The default configuration on MATLAB does not log anything to the console, so tweak it // to output WARNING and above to console. ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.ALL); logger.addHandler(handler); logger.setLevel(Level.WARNING); logger.setUseParentHandlers(false); } _logger.set(logger); } /*---------------- * Static methods */ /** * The default global instance of the dimple environment. * <p> * This is used as the initial value of the {@link #active} environment * for newly created threads that are not {@link DimpleThread}s. * Most users should use {@link #active} instead of this. * <p> * @see #setDefaultEnvironment(DimpleEnvironment) * @since 0.07 */ public synchronized static DimpleEnvironment defaultEnvironment() { DimpleEnvironment env = _globalInstance; if (env == null) { _globalInstance = env = new DimpleEnvironment(); } return env; } /** * The active instance of the dimple environment. * <p> * This is initialized to the {@link #defaultEnvironment} the * first time this is invoked, but can be overridden on a per-thread baseis. * Users should remember that modifications to this environment will affect * other threads unless the environment has been set to a value unique to the * current thread. * <p> * @see #setActive(DimpleEnvironment) * @since 0.07 */ public static DimpleEnvironment active() { return _threadInstance.get(); } /** * Returns environment with specified environment id, or null if not found. * <p> * @since 0.08 */ public static @Nullable DimpleEnvironment withId(long envId) { synchronized(_allInstances) { return _allInstances.get(envId); } } /** * Sets the default dimple environment to a new instance. * @param env is a non-null environment instance. * @see #defaultEnvironment() * @since 0.07 */ public synchronized static void setDefaultEnvironment(DimpleEnvironment env) { _globalInstance = env; } /** * Sets the active dimple environment to a new instance for the current thread. * <p> * This sets the value of {@link #active()} for the current thread only. * <p> * @param env is a non-null environment instance. * @since 0.07 */ public static void setActive(DimpleEnvironment env) { _threadInstance.set(env); } /*---------------------------- * IDimpleEventSource methods */ @Override public @Nullable FactorGraph getContainingGraph() { return null; } /** * {@inheritDoc} * <p> * @see #setEventListener(DimpleEventListener) * @see #createEventListener() */ @Override public @Nullable DimpleEventListener getEventListener() { // For speed, we do not synchronize reads. return _eventListener; } /** * Sets event listener to specified value. * <p> * If you just want to force creation of a default listener instance, then use * {@link #createEventListener()} instead. * <p> * This will invoke notify all event sources registered in the previous and * new listeners that the listener has changed by invoking their * {@link IDimpleEventSource#notifyListenerChanged()} methods. * <p> * @param listener * @see #getEventListener() * @see #createEventListener() * @since 0.07 */ public synchronized void setEventListener(@Nullable DimpleEventListener listener) { synchronized(_eventListenerLock) { DimpleEventListener prevListener = _eventListener; if (prevListener != listener) { _eventListener = listener; if (prevListener != null) { prevListener.notifyListenerChanged(); } if (listener != null) { listener.notifyListenerChanged(); } notifyListenerChanged(); } } } /** * Returns event listener, creating it if previously null. * @see #setEventListener(DimpleEventListener) * @since 0.07 */ public synchronized DimpleEventListener createEventListener() { synchronized(_eventListenerLock) { DimpleEventListener listener = _eventListener; if (listener == null) { _eventListener = listener = new DimpleEventListener(); } return listener; } } @Override public @Nullable IDimpleEventSource getEventParent() { return null; } /** * {@inheritDoc} * <p> * This implementation returns the string {@code "DimpleEnvironment"}. */ @Override public String getEventSourceName() { return "DimpleEnvironment"; } @Override public @Nullable IModelEventSource getModelEventSource() { return null; } /** * {@inheritDoc} * <p> * <b>Currently this method does not do anything on {@code DimpleEnvironment}.</b> */ @Override public void notifyListenerChanged() { // TODO: this does not do anything right now. But if the environment ever ends up storing // references to the models it contains, we might want to pass this on to them... } /*------------------------ * Identification methods */ /** * Randomly generated unique id for environment instance. * <p> * Identifier will be in range {@link Ids#ENV_ID_MIN} and * {@link Ids#ENV_ID_MAX}. * * @since 0.08 */ public long getEnvId() { return _envId; } /** * Randomly generated UUID for environment instance. * <p> * @since 0.08 * @see #getEnvId() * @see Ids#makeUUID(long, long) */ public UUID getUUID() { return _uuid; } /*---------------------------- * System environment methods */ /** * Indicates whether Dimple is being run from MATLAB. * <p> * This returns true if the {@link ClassLoader} class used to load this class * has a name beginning with "com.mathworks". * <p> * @since 0.07 */ public static boolean loadedFromMATLAB() { return LoadedFromMATLAB.INSTANCE._fromMATLAB; } /** * @return Version string for this release of Dimple * @since 0.08 */ public static String getVersion() { InputStream in = System.class.getResourceAsStream("/VERSION"); if (in == null) { return "UNKNOWN"; } String version = "UNKNOWN"; try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) { version = br.readLine(); } catch (Exception e) { // Ignore errors reading file. } return version; } /** * Registry of known option keys for this environment. * <p> * Supports lookup of Dimple {@linkplain IOptionKey option key} instances by name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see DimpleOptionRegistry * @since 0.07 */ public DimpleOptionRegistry optionRegistry() { return _optionRegistry; } /** * Sets the environment-specific {@link #logger} instance. * <p> * This can be used to replace the logger with special implementation * for test purposes when testing logging behavior. * <p> * @param logger * @return the previous logger instance. * @since 0.07 */ public Logger setLogger(Logger logger) { return _logger.getAndSet(logger); } /*------------ * Randomness */ /** * Random generator belonging to the {@link #active} environment. * @since 0.08 * @see #random() */ public static DimpleRandom activeRandom() { return active().random(); } /** * Random generator belonging to this environment. * @since 0.08 * @see #activeRandom() * @see #setRandom(DimpleRandom) */ public DimpleRandom random() { return _random.get(); } /** * Sets random generator belonging to this environment. * @param newRandom the new {@link DimpleRandom} object to be returned by {@link #random()} * @return the previous random object that was replaced * @since 0.08 */ public DimpleRandom setRandom(DimpleRandom newRandom) { return _random.getAndSet(newRandom); } /*----------------- * Logging methods */ /** * Returns default logger instance. * <p> * This is obtained by invoking * {@linkplain Logger#getLogger(String) Logger.getLogger("com.analog.lyric.dimple")}. * <p> * @since 0.07 */ public static Logger getDefaultLogger() { return Logger.getLogger("com.analog.lyric.dimple"); } /** * Logs a message using the thread-specific Dimple logger. * <p> * This is simply shorthand for: * <blockquote><pre> * DimpleEnvironment.{@link #active}().{@link #logger}().{@link Logger#log log}(level, format, args); * </pre></blockquote> * @param level indicates the relative severity/priority of the message. The underlying logger is typically * configured to ignore lower level messages. You can also use {@link ExtendedLevel} values here. * @since 0.08 * @see #logger */ public static void log(Level level, String format, Object ... args) { active().logger().log(level, format, args); } /** * Logs a warning message using the thread-specific Dimple logger. * <p> * This is simply shorthand for: * <blockquote> * DimpleEnvironment.{@link #active}().{@link #logger}().{@link Logger#warning warning}(String.format(format, args)); * </blockquote> * <p> * @param format a non-null String for use with {@link String#format}. * @param args zero or more format arguments. * @since 0.07 */ public static void logWarning(String format, Object ... args) { logWarning(String.format(format, args)); } /** * Logs a warning message using the thread-specific Dimple logger. * <p> * This is simply shorthand for: * <blockquote> * DimpleEnvironment.{@link #active}().{@link #logger}().{@link Logger#warning warning}(message); * </blockquote> * <p> * @since 0.07 */ public static void logWarning(String message) { active().logger().warning(message); } /** * Logs an error message using the thread-specific Dimple logger. * <p> * This is simply shorthand for: * <blockquote> * DimpleEnvironment.{@link #active}().{@link #logger}().{@link Logger#severe severe}(String.format(format, args)); * </blockquote> * <p> * @param format a non-null String for use with {@link String#format}. * @param args zero or more format arguments. * @since 0.07 */ public static void logError(String format, Object ... args) { logError(String.format(format, args)); } /** * Logs a warning message using the thread-specific Dimple logger. * <p> * This is simply shorthand for: * <blockquote> * DimpleEnvironment.{@link #active}().{@link #logger}().{@link Logger#severe severe}(message); * </blockquote> * <p> * @since 0.07 */ public static void logError(String message) { active().logger().log(ExtendedLevel.ERROR, message); } /** * The environment-specific logger instance. * <p> * By default this is set to {@link #getDefaultLogger()} when the environment is initialized. * <p> * Logging is typically configured using Java system properties, which is described * in the documentation for {@link java.util.logging.LogManager}. You can also configure * the logger object directly. * <p> * @see #setLogger * @see #logWarning * @see #logError * @see java.util.logging.Logger * @see java.util.logging.LogManager * @since 0.07 */ public Logger logger() { return _logger.get(); } /*-------------------- * Various registries */ // TODO add conjugate samplers /** * Registry of factor function classes for this environment. * <p> * Supports lookup of {@link FactorFunction} implementations by class name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see FactorFunctionRegistry * @since 0.07 */ public FactorFunctionRegistry factorFunctions() { return _factorFunctions; } /** * Registry of factor graphs associated with this environment. * <p> * @since 0.08 * @see FactorGraphRegistry */ public FactorGraphRegistry factorGraphs() { return _factorGraphs; } /** * Registry of generic sampler classes for this environment. * <p> * Supports lookup of {@link IGenericSampler} implementations by class name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see ConstructorRegistry * @since 0.07 */ public ConstructorRegistry<IGenericSampler> genericSamplers() { return _genericSamplers; } /** * Registry of proposal kernel classes for this environment. * <p> * Supports lookup of {@link IProposalKernel} implementations by class name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see ConstructorRegistry * @since 0.07 */ public ConstructorRegistry<IProposalKernel> proposalKernels() { return _proposalKernels; } /** * Registry of scheduler classes for this environment. * <p> * Supports lookup of {@link IScheduler} implementations by class name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see ConstructorRegistry * @since 0.08 */ public ConstructorRegistry<IScheduler> schedulers() { return _schedulers; } /** * Registry of schedule validator classes for this environment. * <p> * Supports lookup of {@link ScheduleValidator} implementations by class name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see ConstructorRegistry * @since 0.08 */ public ConstructorRegistry<ScheduleValidator> scheduleValidators() { return _scheduleValidators; } /** * Registry of solver factory classes for this environment. * <p> * Supports lookup of {@link IFactorGraphFactory} implementations by class name. * Primarily for use in dynamic language implementations of Dimple. * <p> * @see DimpleSolverRegistry * @since 0.07 */ public DimpleSolverRegistry solvers() { return _solverRegistry; } /** * The default solver factory used by newly constructed {@link FactorGraph}s * @see #setDefaultSolver(IFactorGraphFactory) * @since 0.08 */ public @Nullable IFactorGraphFactory<?> defaultSolver() { return _defaultSolverFactory.get(); } /** * Restores the system default solver factory for this environment. * <p> * The default solver is currently sum-product. * <p> * @since 0.08 */ public void restoreSystemDefaultSolver() { _defaultSolverFactory.set(new SumProductSolver()); } /** * Set the {@linkplain #defaultSolver default solver factory} used by newly constructed {@link FactorGraph}s * @since 0.08 */ public IFactorGraphFactory<?> setDefaultSolver(@Nullable IFactorGraphFactory<?> solver) { return _defaultSolverFactory.getAndSet(solver); } /*---------------- * Lookup methods */ /** * Looks up node within environment with given global id, or null if not found. * @since 0.08 */ public @Nullable Node getNodeByGlobalId(long globalId) { FactorGraph graph = _factorGraphs.getGraphWithId(Ids.graphIdFromGlobalId(globalId)); if (graph != null) { return graph.getNodeByLocalId(Ids.localIdFromGlobalId(globalId)); } return null; } }