Skip to content

pgms

pgms ¤

fully_factorized(num_variables, input_layer='categorical', input_params=None, input_layer_kwargs=None) ¤

Construct a circuit encoding a fully-factorized model.

Parameters:

Name Type Description Default
num_variables int

The number of variables.

required
input_layer str

The input layer to use for the factors. It can be 'categorical', 'binomial' or 'gaussian'. Defaults to 'categorical'.

'categorical'
input_params Mapping[str, Parameterization] | None

A dictionary mapping each name of a parameter of the input layer to its parameterization. If it is None, then the default parameterization of the chosen input layer will be chosen.

None
input_layer_kwargs Mapping[str, Any] | list[Mapping[str, Any]] | None

Additional optional arguments to pass when constructing input layers. If it is a dictionary, then it is interpreted as kwargs passed to all input layers constructors. If it is a list of dictionaries, then it must contain as many dictionaries as the number of variables, and each dictionary is interpreted as kwargs passed to each input layer constructor (in the order given by range(num_variables)).

None
Source code in cirkit/templates/pgms.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def fully_factorized(
    num_variables: int,
    input_layer: str = "categorical",
    input_params: Mapping[str, Parameterization] | None = None,
    input_layer_kwargs: Mapping[str, Any] | list[Mapping[str, Any]] | None = None,
) -> Circuit:
    """Construct a circuit encoding a fully-factorized model.

    Args:
        num_variables: The number of variables.
        input_layer: The input layer to use for the factors. It can be 'categorical', 'binomial' or
            'gaussian'. Defaults to 'categorical'.
        input_params: A dictionary mapping each name of a parameter of the input layer to
            its parameterization. If it is None, then the default parameterization of the chosen
            input layer will be chosen.
        input_layer_kwargs: Additional optional arguments to pass when constructing input layers.
            If it is a dictionary, then it is interpreted as kwargs passed to all input layers
            constructors. If it is a list of dictionaries, then it must contain as many
            dictionaries as the number of variables, and each dictionary is interpreted as kwargs
            passed to each input layer constructor (in the order given by range(num_variables)).
    """
    if num_variables <= 0:
        raise ValueError("The number of variables should be a positive integer")
    if input_layer not in ["categorical", "binomial", "gaussian"]:
        raise ValueError(f"Unknown input layer called {input_layer}")
    input_layer_kwargs_ls: list[Mapping[str, Any]]
    if input_layer_kwargs is None:
        input_layer_kwargs_ls = [{}] * num_variables
    elif isinstance(input_layer_kwargs, Mapping):
        input_layer_kwargs_ls = [input_layer_kwargs] * num_variables
    elif isinstance(input_layer_kwargs, list):
        if len(input_layer_kwargs) != num_variables:
            raise ValueError(
                f"The list of input layer kwargs should have length num_variables={num_variables}"
            )
        if not all(isinstance(kwargs, Mapping) for kwargs in input_layer_kwargs):
            raise ValueError("The list of input layer kwargs should be a list of dictionaries")
        input_layer_kwargs_ls = input_layer_kwargs

    # Construct the input layers
    input_param_kwargs: Mapping[str, Any]
    if input_params is None:
        input_param_kwargs = {}
    else:
        input_param_kwargs = named_parameterizations_to_factories(input_params)
    input_factories = [
        name_to_input_layer_factory(input_layer, **kwargs, **input_param_kwargs)
        for kwargs in input_layer_kwargs_ls
    ]
    input_layers = [f(Scope([i]), 1) for i, f in enumerate(input_factories)]

    # Catch the case there is only one variable
    if len(input_layers) == 1:
        return Circuit(input_layers, in_layers={}, outputs=[input_layers[0]])

    # Construct a product layer
    prod_sl = HadamardLayer(1, arity=len(input_layers))

    return Circuit(input_layers + [prod_sl], in_layers={prod_sl: input_layers}, outputs=[prod_sl])

hmm(ordering, input_layer='categorical', num_latent_states=1, input_params=None, input_layer_kwargs=None, weight_param=None) ¤

Construct a symbolic circuit mimicking a hidden markov model (HMM) of a given variable ordering. Product Layers are of type HadamardLayer, and sum layers are of type SumLayer. Note that the HMM constructed is inhomogeneous, i.e., the emission probability tables and the transition probability tables are not shared between time steps.

Parameters:

Name Type Description Default
ordering Sequence[int]

The input order of variables of the HMM.

required
input_layer str

The input layer to use for the factors. It can be 'categorical', 'binomial' or 'gaussian'. Defaults to 'categorical'.

'categorical'
num_latent_states int

The number of states the latent variables can assume or, equivalently, the number of sum units per sum layer.

1
input_params Mapping[str, Parameterization] | None

A dictionary mapping each name of a parameter of the input layer to its parameterization. If it is None, then the default parameterization of the chosen input layer will be chosen.

None
input_layer_kwargs Mapping[str, Any] | list[Mapping[str, Any]] | None

Additional optional arguments to pass when constructing input layers. If it is a dictionary, then it is interpreted as kwargs passed to all input layers constructors. If it is a list of dictionaries, then it must contain as many dictionaries as the number of variables, and each dictionary is interpreted as kwargs passed to each input layer constructor (in the order given by range(num_variables)).

None
weight_param Parameterization | None

The parameterization to use for the weight coefficients. If None, then it defaults to using a softmax parameterization for the transition probability tables.

None

Returns:

Name Type Description
Circuit Circuit

A symbolic circuit encoding an HMM.

Raises:

Type Description
ValueError

order must consists of consistent numbers, starting from 0.

Source code in cirkit/templates/pgms.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def hmm(
    ordering: Sequence[int],
    input_layer: str = "categorical",
    num_latent_states: int = 1,
    input_params: Mapping[str, Parameterization] | None = None,
    input_layer_kwargs: Mapping[str, Any] | list[Mapping[str, Any]] | None = None,
    weight_param: Parameterization | None = None,
) -> Circuit:
    """Construct a symbolic circuit mimicking a hidden markov model (HMM) of a given variable
    ordering. Product Layers are of type [HadamardLayer][cirkit.symbolic.layers.HadamardLayer],
    and sum layers are of type [SumLayer][cirkit.symbolic.layers.SumLayer]. Note that the HMM
    constructed is inhomogeneous, i.e., the emission probability tables and the transition
    probability tables are not shared between time steps.

    Args:
        ordering: The input order of variables of the HMM.
        input_layer: The input layer to use for the factors. It can be 'categorical', 'binomial' or
            'gaussian'. Defaults to 'categorical'.
        num_latent_states: The number of states the latent variables can assume or, equivalently,
            the number of sum units per sum layer.
        input_params: A dictionary mapping each name of a parameter of the input layer to
            its parameterization. If it is None, then the default parameterization of the chosen
            input layer will be chosen.
        input_layer_kwargs: Additional optional arguments to pass when constructing input layers.
            If it is a dictionary, then it is interpreted as kwargs passed to all input layers
            constructors. If it is a list of dictionaries, then it must contain as many
            dictionaries as the number of variables, and each dictionary is interpreted as kwargs
            passed to each input layer constructor (in the order given by range(num_variables)).
        weight_param: The parameterization to use for the weight coefficients.
            If None, then it defaults to using a softmax parameterization for the transition
            probability tables.

    Returns:
        Circuit: A symbolic circuit encoding an HMM.

    Raises:
        ValueError: order must consists of consistent numbers, starting from 0.
    """
    if not ordering:
        raise ValueError("The ordering should be non-empty")
    num_variables = len(ordering)
    if set(ordering) != set(range(num_variables)):
        raise ValueError("The 'ordering' of variables is not valid")
    if input_layer not in ["categorical", "binomial", "gaussian"]:
        raise ValueError(f"Unknown input layer called {input_layer}")
    input_layer_kwargs_ls: list[Mapping[str, Any]]
    if input_layer_kwargs is None:
        input_layer_kwargs_ls = [{}] * num_variables
    elif isinstance(input_layer_kwargs, Mapping):
        input_layer_kwargs_ls = [input_layer_kwargs] * num_variables
    elif isinstance(input_layer_kwargs, list):
        if len(input_layer_kwargs) != num_variables:
            raise ValueError(
                f"The list of input layer kwargs should have length num_variables={num_variables}"
            )
        if not all(isinstance(kwargs, Mapping) for kwargs in input_layer_kwargs):
            raise ValueError("The list of input layer kwargs should be a list of dictionaries")
        input_layer_kwargs_ls = input_layer_kwargs

    # Get the input layer factories
    input_param_kwargs: Mapping[str, Any]
    if input_params is None:
        input_param_kwargs = {}
    else:
        input_param_kwargs = named_parameterizations_to_factories(input_params)
    input_factories = [
        name_to_input_layer_factory(input_layer, **kwargs, **input_param_kwargs)
        for kwargs in input_layer_kwargs_ls
    ]

    layers: list[Layer] = []
    in_layers: dict[Layer, list[Layer]] = {}
    input_sl = input_factories[-1](Scope([ordering[-1]]), num_latent_states)
    layers.append(input_sl)

    # Set the sum weight factory
    if weight_param is None:
        weight_param = Parameterization(activation="softmax", initialization="normal")
    weight_factory = parameterization_to_factory(weight_param)

    num_units_out = 1 if num_variables == 1 else num_latent_states
    sum_sl = SumLayer(num_latent_states, num_units_out, weight_factory=weight_factory)
    layers.append(sum_sl)
    in_layers[sum_sl] = [input_sl]

    # Loop over the number of variables
    for i in reversed(range(num_variables - 1)):
        last_dense = layers[-1]

        input_sl = input_factories[i](Scope([ordering[i]]), num_latent_states)
        layers.append(input_sl)
        prod_sl = HadamardLayer(num_latent_states, 2)
        layers.append(prod_sl)
        in_layers[prod_sl] = [last_dense, input_sl]

        num_units_out = 1 if i == 0 else num_latent_states
        sum_sl = SumLayer(
            num_latent_states,
            num_units_out,
            weight_factory=weight_factory,
        )
        layers.append(sum_sl)
        in_layers[sum_sl] = [prod_sl]

    return Circuit(layers, in_layers, [layers[-1]])