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

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 be x86_64, and <sys> must be macosx10.14 (or a higher version).
  • iPhoneSimulator platform: <arch> must be x86_64, and <sys> must be ios11.1 (or a higher version).
  • iPhoneOS platform: <arch> must be arm64, and <sys> must be ios11.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

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.