Hugin Swift API
The Hugin Swift API is provided as framework modules for macOS and iOS (iPhoneOS and iPhoneSimulator).
This documentation provides brief descriptions of classes, methods, and enumerations. For more detailed descriptions, please consult the full documentation provided in the Hugin API Reference Manual. This manual is included in the Hugin software packages, but it can also be downloaded here.
This document contains the following:
- General Information
- Object-Oriented Specification
- Memory Management
- Error Handling
- Example: Load and Propagate
- Acknowledgements
General Information
The Hugin Swift API contains a high performance inference engine that can be used as the core of knowledge based systems built using Bayesian networks or LIMIDs (Limited Memory Influence Diagrams, a generalization of ordinary influence diagrams). Using probabilistic descriptions of causal relationships in an application domain, a knowledge engineer can build knowledge bases that model the domain. Given this description, the Hugin Decision Engine can perform fast and accurate reasoning.
The Hugin Swift API is organized as a macOS/iOS framework module. Classes and methods are provided for tasks such as construction of networks, performing inference, etc. The Hugin Swift API also provides an exception-based mechanism for handling errors.
In order to access the Hugin API declarations in a Swift program, place the following line at the top of the source file:
import Hugin
The Xcode IDE provides the tools needed for developing applications using the Swift programming language. Xcode uses the following names to refer to specific platforms:
MacOSX
refers to macOS.iPhoneSimulator
refers to the “simulator” platform for iOS apps.iPhoneOS
refers to the “real thing” platform for iOS apps.
Using the Swift compiler from the command line (or in a Makefile
),
some arguments are required (unless the default values are appropriate):
swiftc -target <target> -sdk <sdk> -I<path>/Hugin.framework/Modules -F<path> -Xlinker -rpath -Xlinker <path> <source file> -framework Hugin
where <path>
specifies the directory containing Hugin.framework
.
The <target>
is a triplet of the form <arch>-apple-<sys>
:
MacOSX
platform:<arch>
must bex86_64
, and<sys>
must bemacosx10.14
(or a higher version).iPhoneSimulator
platform:<arch>
must bex86_64
, and<sys>
must beios11.1
(or a higher version).iPhoneOS
platform:<arch>
must bearm64
, and<sys>
must beios11.1
(or a higher version).
The <sdk>
must be:
/Applications/Xcode.app/Contents/Developer/Platforms/<platform>.platform/Developer/SDKs/<platform>.sdk
where <platform>
is one of the choices (listed above) offered by Xcode.
When the Xcode IDE is used, the same information must be specified in
the “Build Settings” and “Build Phases” panes: “Framework Search
Paths” and “Import Paths” must be specified, and the application must
be linked with Hugin.framework
. Also, notice that “Bitcode” must be
disabled.
Object-Oriented Specification
The Hugin Swift API provides a powerful object-orientation mechanism. Object-oriented specification of Bayesian networks and LIMIDs make it very easy to reuse models, to encapsulate submodels (providing a means for hierarchical model specification), and to perform model construction in a top-down fashion, a bottom-up fashion, or a mix of the two (allowing repeated changes of level of abstraction).
The classes Domain
and Class
both extend the abstract class
Network
, which contains constants and methods common to regular
Bayesian networks/LIMIDs and object-oriented Bayesian networks/LIMIDs,
respectively.
In addition to the usual nodes, an object-oriented Bayesian network or LIMID contains instance nodes, representing instances of other networks. In other words, an instance node represents a subnetwork. Of course, the network of which instances exist in other networks can itself contain instance nodes, whereby an object-oriented network can be viewed as a hierarchical description of a problem domain. Describing a network in a hierarchical fashion often makes the network much less cluttered, and thus provides a much better means of communicating ideas among knowledge engineers and users.
As systems often are composed of collections of identical or similar components, models of systems often contain repetitive patterns. The notion of instance nodes makes it very easy to construct multiple identical instances of a network fragment.
Memory Management
The Swift runtime system uses Automatic Reference Counting (ARC) to manage the use of memory: Memory used by unreferenced objects are automatically reclaimed. This means that it is not necessary to do anything special to avoid memory leaks: Explicit deletion of objects are not needed.
Domain
, ClassCollection
, DataSet
, and License
objects are
automatically deleted when the last reference to the object
disappears. When this happens, all objects “owned” by the deleted
object are also deleted. For example, a Node
object is owned by a
Domain
or a Class
object, and a Class
object is owned by a
ClassCollection
object. A Node
object itself can also own objects
(such as Table
objects). In general: When an object is deleted,
objects owned by the object being deleted will also be deleted. This
is a recursive process.
In order to avoid (unintentional) deletion, the application must hold
a reference to the Domain
or ClassCollection
object until it is no
longer needed. (DataSet
and License
objects do not own other
objects.)
When an object has been deleted, it becomes “dead” (all internal data
will be deleted, so that the object becomes essentially an empty
shell). If there are no references to the dead object, then ARC will
reclaim it. Otherwise, all attempts to apply operations to a dead
object will result in HuginError.notAlive(type:)
errors.
The deletions caused by ARC are implicit, but explicit deletions are
also possible: For example, a node can be deleted from a network using
the Node.delete()
method. In this case, the node object also
becomes dead.
Note: Unlike the other Hugin API language interfaces (C, Java, etc.),
tables returned from Node.getDistribution()
and
Domain.getMarginal(_:)
are automatically deleted. This is made
possible by the use of ARC.
Error Handling
Several types of errors can occur when using a class or calling a method from the Hugin Swift API. These errors can be the result of errors in the application program, of running out of memory, of corrupted data files, etc.
As a general principle, the Hugin Swift API will try to recover from any error as well as possible. The API will then inform the application program of the problem and take no further action. It is then up to the application program to take the appropriate action.
When a method fails, the data structures will always be left in a consistent state. Moreover, unless otherwise stated explicitly for a particular method, this state can be assumed identical to the state before the failed API call.
To communicate errors to the user of the Hugin Swift API, the API
throws values of the enumeration type HuginError
.
Example: Load and Propagate
The following program shows how to load a belief network or a LIMID
specified as a (non-OOBN) NET file: A Domain
object is constructed
from the NET file. The domain is then triangulated using the “best
greedy” heuristic, and the compilation process is completed. The
(prior) beliefs and expected utilities (if the network is a LIMID) are
then printed. If a case file is given, the file is loaded, the
evidence is propagated, and the updated results are printed.
import Darwin
import Hugin
/**
* This function parses the given NET file, compiles the network,
* and prints the prior beliefs and expected utilities of all
* nodes. If a case file is given, the function loads the file,
* propagates the evidence, and prints the updated results.
*
* If the network is a LIMID, we assume that we should compute
* policies for all decisions (rather than use the ones specified
* in the NET file). Likewise, we update the policies when new
* evidence arrives.
*/
func LAP (_ netName: String, _ caseName: String? = nil) -> Void
{
do {
let domain = try Domain (netName: "\(netName).net")
try domain.openLogFile ("\(netName).log")
try domain.triangulate (Domain.TriangulationMethod.bestGreedy)
try domain.compile()
try domain.closeLogFile()
let hasUtilities = containsUtilities (try domain.getNodes())
if (!hasUtilities) {
print ("Prior beliefs:")
} else {
try domain.updatePolicies()
print ("Overall expected utility: \(try domain.getExpectedUtility())")
print ("\nPrior beliefs (and expected utilities):")
}
try printBeliefsAndUtilities (domain)
if let caseName = caseName {
try domain.parseCase (caseName)
print ("\n\nPropagating the evidence specified in \"\(caseName)\"")
try domain.propagate ()
print ("\nP(evidence) = \(try domain.getNormalizationConstant())\n")
if !hasUtilities {
print ("Updated beliefs:")
} else {
try domain.updatePolicies()
print ("Overall expected utility: \(try domain.getExpectedUtility())")
print ("\nUpdated beliefs (and expected utilities):")
}
try printBeliefsAndUtilities (domain)
}
} catch let err as HuginError {
print ("An error occurred: \(err.description)")
} catch {
print ("An error occurred: \(error)")
}
}
/**
* Print the beliefs and expected utilities of all nodes in the domain.
*/
func printBeliefsAndUtilities (_ domain: Domain) throws -> Void
{
let nodes = try domain.getNodes()
let hasUtilities = containsUtilities (nodes)
for node in nodes {
if let label = try node.getLabel() {
print ("\n\(label) (\(try node.getName()))")
} else {
print ("\n\(try node.getName())")
}
if node.kind == Node.Kind.discrete {
let n = try node.getNumberOfStates()
for i in 0 ..< n {
print (" - \(try node.getStateLabel (i)) \(try node.getBelief (i))", terminator: "")
if (hasUtilities) {
print (" (\(try node.getExpectedUtility (state: i)))")
} else {
print ()
}
}
} else if node.kind == Node.Kind.continuous {
print (" - Mean : \(try node.getMean())")
print (" - SD : \(sqrt (try node.getVariance()))")
} else if node.category == Node.Category.utility {
print (" - Expected utility: \(try node.getExpectedUtility())")
} else { /* "node" is a (real-valued) function node */
do {
print (" - Value: \(try node.getValue())")
} catch {
print (" - Value: N/A")
}
}
}
}
/**
* Are there utility nodes in the list?
*/
func containsUtilities (_ list: Array<Node>) -> Bool
{
for node in list {
if node.category == Node.Category.utility {
return true
}
}
return false
}
/**
* Load a Hugin NET file, compile the network, and print the
* results. If a case file is specified, load it, propagate the
* evidence, and print the results.
*/
if CommandLine.argc == 2 {
LAP (CommandLine.arguments[1])
} else if CommandLine.argc == 3 {
LAP (CommandLine.arguments[1], CommandLine.arguments[2])
} else {
print ("Usage: \(CommandLine.arguments[0]) <netName> [<caseName>]")
}
Acknowledgements
Hugin Expert A/S has participated in a number of projects funded by the European Union. Some projects have also received funding from Innovation Fund Denmark. See https://www.hugin.com/index.php/acknowledgments/ for the complete list of projects.
The development of the functionality concerning the real-valued function node type (introduced in Hugin 7.3) has been sponsored by Danish mortgage credit institution Nykredit Realkredit (https://www.nykredit.dk).
The development of the functionality concerning the discrete function
node type as well as the aggregate
and
probability
expression operators (introduced in Hugin 7.7) for use
in Model.setExpression(...)
has been sponsored by the research
project “Operational risk in banking and finance.” The project is
dedicated to strengthening management of operational risk in the
banking and finance sector, including developing Basel II compliant
operational risk measurement and management tools in accordance with
the Advanced Measurement Approach (AMA). The project is financed by
the University of Stavanger and a consortium of Norwegian banks
consisting of Sparebank 1 SR-Bank, Sparebank 1 SNN, Sparebank 1 SMN,
Sparebanken Hedmark, and Sparebank 1 Oslo and Akershus.