Decompositions

Python implementation of the Overview/Decompositions

Sections

Definition

Conversion to transform

Conversion to fud

Example - a weather forecast

Definition

A functional definition set decomposition is a model that consists of a tree of fuds that are contingent on components.

The set of functional definition set decompositions $\mathcal{D}_{\mathrm{F}}$ is a subset of the trees of pairs of (i) states, $\mathcal{S}$, and (ii) functional definition sets, $\mathcal{F}$, \[ \begin{eqnarray} \mathcal{D}_{\mathrm{F}} &\subset& \mathrm{trees}(\mathcal{S} \times \mathcal{F}) \end{eqnarray} \] The Tree a generic type is described in Notation. The DecompFud type is defined as a Tree of pairs of State and Fud,

newtype DecompFud = DecompFud (Tree (State,Fud))

A fud decomposition is constructed from a tree of pairs of states and fuds,

treePairStateFudsDecompFud :: Tree (State,Fud) -> Maybe DecompFud

Consider the deck of cards example,

def lluu(ll):
    return listsSystem([(v,sset(ww)) for (v,ww) in ll])

[suit,rank] = map(VarStr, ["suit","rank"])
[hearts,clubs,diamonds,spades] = map(ValStr, ["hearts","clubs","diamonds","spades"])
[jack,queen,king,ace] = map(ValStr, ["J","Q","K","A"])

uu = lluu([
      (suit, [hearts,clubs,diamonds,spades]),
      (rank, [jack,queen,king,ace] + list(map(ValInt,range(2,10+1))))])

vv = sset([suit, rank])

uu
# {(rank, {A, J, K, Q, 2, 3, 4, 5, 6, 7, 8, 9, 10}), (suit, {clubs, diamonds, hearts, spades})}

vv
# {rank, suit}

aa = unit(cart(uu,vv))

rpln(aall(aa))
# ({(rank, A), (suit, clubs)}, 1 % 1)
# ({(rank, A), (suit, diamonds)}, 1 % 1)
# ({(rank, A), (suit, hearts)}, 1 % 1)
# ({(rank, A), (suit, spades)}, 1 % 1)
# ({(rank, J), (suit, clubs)}, 1 % 1)
# ({(rank, J), (suit, diamonds)}, 1 % 1)
# ...
# ({(rank, 9), (suit, hearts)}, 1 % 1)
# ({(rank, 9), (suit, spades)}, 1 % 1)
# ({(rank, 10), (suit, clubs)}, 1 % 1)
# ({(rank, 10), (suit, diamonds)}, 1 % 1)
# ({(rank, 10), (suit, hearts)}, 1 % 1)
# ({(rank, 10), (suit, spades)}, 1 % 1)

A transform $T_{\mathrm{c}}$ can be constructed relating the suit to the colour,

def lltt(kk,ww,qq):
    return trans(unit(sset([llss(zip(kk + ww,ll)) for ll in qq])),sset(ww))

colour = VarStr("colour")
red = ValStr("red")
black = ValStr("black")

ttc = lltt([suit],[colour],[
      [hearts, red], 
      [clubs, black], 
      [diamonds, red], 
      [spades, black]])

Another transform $T_{\mathrm{t}}$ can be constructed relating the rank to whether it is a pip card or a face card,

pip_or_face = VarStr("pip_or_face")
pip = ValStr("pip")
face = ValStr("face")

ttt = lltt([rank],[pip_or_face],[
      [ace, pip], 
      [king, face], 
      [queen, face], 
      [jack, face]] +
      [[ValInt(i), pip] for i in range(2,10+1)])

Let another transform $T_{\mathrm{op}}$ with a derived variable odd_pip have value yes for odd pip cards, and value no otherwise,

odd_pip = VarStr("odd_pip")
yes = ValStr("yes")
no = ValStr("no")

ttop = lltt([rank],[odd_pip],[
      [ace, yes], 
      [king, no], 
      [queen, no], 
      [jack, no]] +
      [[ValInt(i), no] for i in [2,4,6,8,10]] +
      [[ValInt(i), yes] for i in [3,5,7,9]])

Let $D$ be a fud decomposition, $D \in \mathcal{D}_{\mathrm{F}}$, be constructed from a list of paths,

zzdf = treePairStateFudsDecompFud

def lldf(zz):
    return zzdf(pathsTree([[(llss(ss),llff(ff)) for (ss,ff) in ll] for ll in zz]))

df = lldf([
      [([], [ttop]), ([(odd_pip, no)],  [ttc, ttt])],
      [([], [ttop]), ([(odd_pip, yes)], [ttc])]])

The set of fuds is $\mathrm{fuds}(D) := \{F : ((\cdot,F),\cdot) \in \mathrm{nodes}(D)\}$,

decompFudsSetFud :: DecompFud -> Set.Set Fud 

For example,

dfqq = decompFudsSetFud

dfqq(df) == sset([llff([ttop]), llff([ttc]), llff([ttc, ttt])])
# True

The underlying is $\mathrm{und}(D) := \bigcup \{\mathrm{und}(F) : F \in \mathrm{fuds}(D)\}$,

decompFudsUnderlying :: DecompFud -> Set.Set Variable

For example,

dfund = decompFudsUnderlying

dfund(df)
# {rank, suit}

Fud decompositions are constrained such that each of the states in child pairs are states in the derived variables of the parent fud, \[ \forall D \in \mathcal{D}_{\mathrm{F}}~\forall ((\cdot,F),E) \in \mathrm{nodes}(D)~\forall ((S,\cdot),\cdot) \in E~(S \in \mathrm{dom}((F^{\mathrm{T}})^{-1})) \]

fder(llff([ttop])) == sset([odd_pip])
# True

The root nodes have no parent and so their states are constrained to be null, $\forall D \in \mathcal{D}_{\mathrm{F}}~\forall ((S,\cdot),\cdot) \in D~(S = \emptyset)$,

decompFudsTreePairStateFud :: DecompFud -> Tree (State,Fud)

For example,

dfzz = decompFudsTreePairStateFud

(ss,_) = treesRoots(dfzz(df))[-1]

ss == stateEmpty()
# True

The fud decomposition, $D$, can be converted to a list of paths,

def dfll(df):
    return treesPaths(dfzz(df))

rpln([[ss for (ss,ff) in ll] for ll in dfll(df)])
# [{}, {(odd_pip, no)}]
# [{}, {(odd_pip, yes)}]

dfll(df)[0][0] == (llss([]),  llff([ttop]))
# True

dfll(df)[0][1] == (llss([(odd_pip, no)]),  llff([ttc, ttt]))
# True

dfll(df)[1][1] == (llss([(odd_pip, yes)]),  llff([ttc]))
# True

Given a fud decomposition $D \in \mathcal{D}_{\mathrm{F}}$ having underlying variables $V = \mathrm{und}(D)$, each fud $F \in \mathrm{fuds}(D)$ is contingent on the component $C \in \mathrm{B}(V^{\mathrm{C}})$ implied by the union of the ancestor derived states in the derived variables of the union of the ancestor fuds. Let $L$ be a path in the fud decomposition, $L \in \mathrm{paths}(D)$. Then for each child fud $(\cdot,F) = L_i$, where $i \in \{2 \ldots |L|\}$, the union of the ancestor derived states is $R = \bigcup \{S : j \in \{2 \ldots i\},~(S,\cdot) = L_j\}$, the union of the ancestor fuds is $G = \bigcup \{H : j \in \{1 \ldots i-1\},~(\cdot,H) = L_j\}$, and so the contingent component is $(G^{\mathrm{T}})^{-1}(R)$. In the case where the underlying of the ancestor fud, $G$, is the whole substrate, $\mathrm{und}(G)=V$, then the component is $C = (G^{\mathrm{T}})^{-1}(R) \subseteq V^{\mathrm{C}}$. In the deck of cards example, the second path of the decomposition, $D$, is where odd_pip equals yes,

ll = dfll(df)[1]

len(ll)
# 2

[ss for (ss,ff) in ll]
# [{}, {(odd_pip, yes)}]

rr = stateEmpty()
for (ss,_) in ll[:2]:
    rr = sunion(rr,ss)

rr
# {(odd_pip, yes)}

gg = fudEmpty()
for (_,ff) in ll[:1]:
    gg = funion(gg,ff)

gg == llff([ttop])
# True

The expanded component contains all of the odd pip cards,

vvc = unit(cart(uu,vv))

inv = transformsInverse

cc = mul(inv(fftt(gg))[rr],vvc)

rpln(aall(cc))
# ({(rank, A), (suit, clubs)}, 1 % 1)
# ({(rank, A), (suit, diamonds)}, 1 % 1)
# ({(rank, A), (suit, hearts)}, 1 % 1)
# ({(rank, A), (suit, spades)}, 1 % 1)
# ({(rank, 3), (suit, clubs)}, 1 % 1)
# ({(rank, 3), (suit, diamonds)}, 1 % 1)
# ({(rank, 3), (suit, hearts)}, 1 % 1)
# ({(rank, 3), (suit, spades)}, 1 % 1)
# ({(rank, 5), (suit, clubs)}, 1 % 1)
# ({(rank, 5), (suit, diamonds)}, 1 % 1)
# ({(rank, 5), (suit, hearts)}, 1 % 1)
# ({(rank, 5), (suit, spades)}, 1 % 1)
# ({(rank, 7), (suit, clubs)}, 1 % 1)
# ({(rank, 7), (suit, diamonds)}, 1 % 1)
# ({(rank, 7), (suit, hearts)}, 1 % 1)
# ({(rank, 7), (suit, spades)}, 1 % 1)
# ({(rank, 9), (suit, clubs)}, 1 % 1)
# ({(rank, 9), (suit, diamonds)}, 1 % 1)
# ({(rank, 9), (suit, hearts)}, 1 % 1)
# ({(rank, 9), (suit, spades)}, 1 % 1)

The function $\mathrm{cont} \in \mathcal{D}_{\mathrm{F}} \to \mathrm{P}(\mathcal{A} \times \mathcal{F})$ returns the set of component-fud pairs of the fud decomposition. When the fud decomposition, $D$, is applied to a histogram $A \in \mathcal{A}$ in variables $\mathrm{vars}(A) = V$, each fud transform is applied to the contingent slice, $A * C * F^{\mathrm{T}}$ where $(C,F) \in \mathrm{cont}(D)$. Two fuds on the same path $(\cdot,F_1) \in L_j$ and $(\cdot,F_2) \in L_i$ where $L \in \mathrm{paths}(D)$ and $j<i$, are such that the fud $(C_1,F_1) \in \mathrm{cont}(D)$ nearer the root has a component which is a superset of the component of the fud $(C_2,F_2) \in \mathrm{cont}(D)$ nearer the leaves, $C_1 \supset C_2$. So the slice nearer the root is greater than or equal to the slice nearer the leaves, $A * C_1 \geq A * C_2$. That is, the fuds are more and more selectively contingent along the fud decomposition’s paths, and so are applied to smaller and smaller slices. In the case of the deck of cards example, the component, $C$, which consists of the odd pip cards, is a subset of the decomposition root component, which is the cartesian, $V^{\mathrm{C}}$,

leq(cc,vvc)
# True

leq(mul(aa,cc),mul(aa,vvc))
# True

In the case where each of the slice derived are diagonalised, $\forall (C,F) \in \mathrm{cont}(D)~(\mathrm{diagonal}(A * C * F^{\mathrm{T}}))$, the fud decomposition, $D$, is a contingent, layered, redundant model of the sample histogram, $A$.

Conversion to transform

A fud decomposition is a model, so it can be converted to a functional transform, $D^{\mathrm{T}} \in \mathcal{T}_{\mathrm{f}}$. The partition of the fud decomposition transform is equal to the set of components corresponding to those fud derived states that are not parent derived states in the decomposition tree, $\bigcup \{\mathrm{dom}((F^{\mathrm{T}})^{-1}) \setminus \{S : ((S,\cdot),\cdot) \in E\} : ((\cdot,F),E) \in \mathrm{nodes}(D)\}$. The resultant transform has the same underlying variables as the fud decomposition, $\mathrm{und}(D^{\mathrm{T}}) = \mathrm{und}(D)$.

Decompositions are rarely converted to transforms in practice. Useful decompositions generally have large substrates and so the components of a partition of the cartesian are very large. This is because volume varies exponentially with dimension, $v = d^n$. In practice, rather than applying decomposition transforms to sample histograms to obtain the derived histogram, $A * D^{\mathrm{T}}$, decompositions are applied directly to sample histograms to obtain a tree of pairs of derived state $S$ and derived slice histogram $A * C * F^{\mathrm{T}}$,

decompFudsHistogramsApply :: DecompFud -> Histogram -> Tree (State,Histogram)

For example,

def dfapplyll(aa,df):
    return treesPaths(decompFudsHistogramsApply(df,aa))

rpln([[(ss, vars(bb), size(bb)) for (ss,bb) in ll] for ll in dfapplyll(aa,df)])
# [({}, {odd_pip}, 52 % 1), ({(odd_pip, no)}, {colour, pip_or_face}, 32 % 1)]
# [({}, {odd_pip}, 52 % 1), ({(odd_pip, yes)}, {colour}, 20 % 1)]

rpln([[ss for (ss,bb) in ll] for ll in dfapplyll(aa,df)])
# [{}, {(odd_pip, no)}]
# [{}, {(odd_pip, yes)}]

rpln(aall(dfapplyll(aa,df)[0][0][1]))
# ({(odd_pip, no)}, 32 % 1)
# ({(odd_pip, yes)}, 20 % 1)

rpln(aall(dfapplyll(aa,df)[0][1][1]))
# ({(colour, black), (pip_or_face, face)}, 6 % 1)
# ({(colour, black), (pip_or_face, pip)}, 10 % 1)
# ({(colour, red), (pip_or_face, face)}, 6 % 1)
# ({(colour, red), (pip_or_face, pip)}, 10 % 1)

rpln(aall(dfapplyll(aa,df)[1][1][1]))
# ({(colour, black)}, 10 % 1)
# ({(colour, red)}, 10 % 1)

A similar function preserves the underlying variables, $A * C * \mathrm{his}(F^{\mathrm{T}})~\%~(\mathrm{und}(F) \cup \mathrm{der}(F))$,

decompFudsHistogramsMultiply :: DecompFud -> Histogram -> Tree (State,Histogram)

For example,

def dfmulll(aa,df):
    return treesPaths(decompFudsHistogramsMultiply(df,aa))

rpln([[(ss, vars(bb), size(bb)) for (ss,bb) in ll] for ll in dfmulll(aa,df)])
# [({}, {odd_pip, rank, suit}, 52 % 1), ({(odd_pip, no)}, {colour, pip_or_face, rank, suit}, 32 % 1)]
# [({}, {odd_pip, rank, suit}, 52 % 1), ({(odd_pip, yes)}, {colour, rank, suit}, 20 % 1)]

rpln([[ss for (ss,bb) in ll] for ll in dfmulll(aa,df)])
# [{}, {(odd_pip, no)}]
# [{}, {(odd_pip, yes)}]

rpln(aall(dfmulll(aa,df)[0][0][1]))
# ({(odd_pip, no), (rank, J), (suit, clubs)}, 1 % 1)
# ({(odd_pip, no), (rank, J), (suit, diamonds)}, 1 % 1)
# ({(odd_pip, no), (rank, J), (suit, hearts)}, 1 % 1)
# ({(odd_pip, no), (rank, J), (suit, spades)}, 1 % 1)
# ({(odd_pip, no), (rank, K), (suit, clubs)}, 1 % 1)
# ({(odd_pip, no), (rank, K), (suit, diamonds)}, 1 % 1)
# ...
# ({(odd_pip, yes), (rank, 7), (suit, hearts)}, 1 % 1)
# ({(odd_pip, yes), (rank, 7), (suit, spades)}, 1 % 1)
# ({(odd_pip, yes), (rank, 9), (suit, clubs)}, 1 % 1)
# ({(odd_pip, yes), (rank, 9), (suit, diamonds)}, 1 % 1)
# ({(odd_pip, yes), (rank, 9), (suit, hearts)}, 1 % 1)
# ({(odd_pip, yes), (rank, 9), (suit, spades)}, 1 % 1)

rpln(aall(dfmulll(aa,df)[0][1][1]))
# ({(colour, black), (pip_or_face, face), (rank, J), (suit, clubs)}, 1 % 1)
# ({(colour, black), (pip_or_face, face), (rank, J), (suit, spades)}, 1 % 1)
# ({(colour, black), (pip_or_face, face), (rank, K), (suit, clubs)}, 1 % 1)
# ({(colour, black), (pip_or_face, face), (rank, K), (suit, spades)}, 1 % 1)
# ({(colour, black), (pip_or_face, face), (rank, Q), (suit, clubs)}, 1 % 1)
# ({(colour, black), (pip_or_face, face), (rank, Q), (suit, spades)}, 1 % 1)
# ...
# ({(colour, red), (pip_or_face, pip), (rank, 6), (suit, diamonds)}, 1 % 1)
# ({(colour, red), (pip_or_face, pip), (rank, 6), (suit, hearts)}, 1 % 1)
# ({(colour, red), (pip_or_face, pip), (rank, 8), (suit, diamonds)}, 1 % 1)
# ({(colour, red), (pip_or_face, pip), (rank, 8), (suit, hearts)}, 1 % 1)
# ({(colour, red), (pip_or_face, pip), (rank, 10), (suit, diamonds)}, 1 % 1)
# ({(colour, red), (pip_or_face, pip), (rank, 10), (suit, hearts)}, 1 % 1)

rpln(aall(dfmulll(aa,df)[1][1][1]))
# ({(colour, black), (rank, A), (suit, clubs)}, 1 % 1)
# ({(colour, black), (rank, A), (suit, spades)}, 1 % 1)
# ({(colour, black), (rank, 3), (suit, clubs)}, 1 % 1)
# ({(colour, black), (rank, 3), (suit, spades)}, 1 % 1)
# ({(colour, black), (rank, 5), (suit, clubs)}, 1 % 1)
# ({(colour, black), (rank, 5), (suit, spades)}, 1 % 1)
# ...
# ({(colour, red), (rank, 5), (suit, diamonds)}, 1 % 1)
# ({(colour, red), (rank, 5), (suit, hearts)}, 1 % 1)
# ({(colour, red), (rank, 7), (suit, diamonds)}, 1 % 1)
# ({(colour, red), (rank, 7), (suit, hearts)}, 1 % 1)
# ({(colour, red), (rank, 9), (suit, diamonds)}, 1 % 1)
# ({(colour, red), (rank, 9), (suit, hearts)}, 1 % 1)

Queries may be made too. In this case, however, the model underlying equals the entire substrate, $\mathrm{und}(D) = K = V$, so there are no label variables. The query is merely classified by the model, $D$,

decompFudsHistogramsHistogramsQuery :: DecompFud -> Histogram -> Histogram -> Tree (State,Histogram)

For example,

def red(aa,ll):
    return setVarsHistogramsReduce(sset(ll),aa)

def queryll(qq,df,aa,ll):
    return [[(ss,norm(red(bb,ll))) for (ss,bb) in pp] for pp in treesPaths(decompFudsHistogramsHistogramsQuery(df,aa,qq))]

qq = single(llss([(rank,ace),(suit,spades)]),1)

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq,df,aa,[])])
# [{}, {(odd_pip, yes)}]

rpln(aarr(queryll(qq,df,aa,[])[0][1][1]))
# ({}, 1.0)

qq = single(llss([(rank,queen),(suit,hearts)]),1)

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq,df,aa,[])])
# [{}, {(odd_pip, no)}]

Conversion to fud

The tree of a fud decomposition is sometimes unwieldy, so consider the fud decomposition fud, $D^{\mathrm{F}} \in \mathcal{F}$, which is the intermediate fud used in the construction of the fud decomposition transform, $D^{\mathrm{T}}$. The decomposition fud is defined as the union of the decomposition fuds and the nullable fud, $D^{\mathrm{F}} := \bigcup \mathrm{fuds}(D) \cup \mathrm{nullable}(U)(D^{\mathrm{D}})$. The nullable fud, $\mathrm{nullable}(U)(D^{\mathrm{D}})$, is defined in section ‘Decompositions’ of the paper. It consists of a layer of transforms which is added on top of the union of the decomposition fuds, $\bigcup \mathrm{fuds}(D)$. Each derived variable in the fud union, $w \in \mathrm{der}(F)$ where $F \in \mathrm{fuds}(D)$, is in the underlying of a corresponding transform, $w \in \mathrm{und}(T_w)$, in the nullable layer. The transform derived consists of a nullable variable $\{w’\} = \mathrm{der}(T_w)$. This nullable variable, $w’$, has the same values as its underlying variable, $w$, but with an additional $\mathrm{null}$ value, $U_{w’} = U_w \cup \{\mathrm{null}\}$. If the fud, $F$, is not the root fud, there is also a contingent variable $c$ with values corresponding to the fud’s in-slice and out-slice states, $U_c = \{\mathrm{in},\mathrm{out}\}$. That is, given contingent state $S \in C^{\mathrm{S}}$, where $(C,F) \in \mathrm{cont}(D)$, the derived state, $R$, is such that $(c,\mathrm{in}) \in R$. Similarly, if $S \in V^{\mathrm{CS}} \setminus C^{\mathrm{S}}$, then $(c,\mathrm{out}) \in R$. The underlying of nullable variable’s transform will also contain the contingent variable, $\{c,w\} = \mathrm{und}(T_w)$. The nullable variable, $w’$, is constrained by the transform, $T_w$, to be in the $\mathrm{null}$ value whenever the contingent variable, $c$, is in the $\mathrm{out}$ value, and to be in the value of the underlying variable, $w$, otherwise. That is, $(c,\mathrm{out}) \in R \implies (w’,\mathrm{null}) \in R$, and $(c,\mathrm{in}) \in R \implies (w’,R_w) \in R$. In this way, there is no need to navigate the slices of the decomposition. The fud decomposition fud, $D^{\mathrm{F}}$, can be analysed by examining the effective states of reductions to its nullable derived variables, $\mathrm{der}(D^{\mathrm{F}})$.

Example - a weather forecast

Some of the concepts above regarding functional definition set decompositions can be demonstrated with the sample of some weather measurements created in States, histories and histograms, Transforms and Functional definition sets. Here we add an additional pressure variable pressureb with the same values as pressure. We also add an additional history of stormy weather,

def lluu(ll):
    return listsSystem([(v,sset(ww)) for (v,ww) in ll])

def llhh(vv,ev):
    return listsHistory([(IdInt(i), llss(zip(vv,ll))) for (i,ll) in ev])

hadd = pairHistoriesAdd

def ared(aa,vv):
    return setVarsHistogramsReduce(vv,aa)

def red(aa,ll):
    return setVarsHistogramsReduce(sset(ll),aa)

def ssplit(ll,aa):
    return setVarsSetStatesSplit(sset(ll),states(aa))

def aarr(aa):
    return [(ss,float(q)) for (ss,q) in aall(aa)]

def lltt(kk,ww,qq):
    return trans(unit(sset([llss(zip(kk + ww,ll)) for ll in qq])),sset(ww))

def lldf(zz):
    return zzdf(pathsTree([[(llss(ss),llff(ff)) for (ss,ff) in ll] for ll in zz]))

def query(qq,tt,aa,ll):
    return norm(red(mul(mul(tmul(qq,tt),ttaa(tt)),aa),ll))

ent = histogramsEntropy 

cent = transformsHistogramsEntropyComponent

def rent(aa,bb):
    a = size(aa)
    b = size(bb)
    return (a+b) * ent(add(aa,bb)) - a * ent(aa) - b * ent(bb)

def tlent(tt,aa,ll):
    return setVarsTransformsHistogramsEntropyLabel(vars(aa)-sset(ll),tt,aa)

def tlalgn(tt,aa,ll):
    return algn(ared(mul(aa,ttaa(tt)),der(tt)|sset(ll)))

def layer(ff,v):
    return fudsSetVarsLayer(ff,sset([v]))

def dfmulll(aa,df):
    return treesPaths(decompFudsHistogramsMultiply(df,aa))

def queryll(qq,df,aa,ll):
    return [[(ss,norm(red(bb,ll))) for (ss,bb) in pp] for pp in treesPaths(decompFudsHistogramsHistogramsQuery(df,aa,qq))]


[pressure,pressureb,cloud,wind,rain] = map(VarStr,["pressure","pressureb","cloud","wind","rain"])

[low,medium,high,none,light,heavy,strong] = map(ValStr,["low","medium","high","none","light","heavy","strong"])


uu = lluu([
      (pressure, [low,medium,high]),
      (pressureb, [low,medium,high]),
      (cloud,    [none,light,heavy]),
      (wind,     [none,light,strong]),
      (rain,     [none,light,heavy])])

vv = uvars(uu)

hh1 = llhh([pressure,pressureb,cloud,wind,rain],[
      (1,[high,high,none,none,none]),
      (2,[medium,high,light,none,light]),
      (3,[high,medium,none,light,none]),
      (4,[low,medium,heavy,strong,heavy]),
      (5,[low,low,none,light,light]),
      (6,[medium,medium,none,light,light]),
      (7,[low,medium,heavy,light,heavy]),
      (8,[high,medium,none,light,none]),
      (9,[medium,low,light,strong,heavy]),
      (10,[medium,medium,light,light,light]),
      (11,[high,high,light,light,heavy]),
      (12,[medium,high,none,none,none]),
      (13,[medium,low,light,none,none]),
      (14,[high,medium,light,strong,light]),
      (15,[medium,medium,none,light,light]),
      (16,[low,medium,heavy,strong,heavy]),
      (17,[low,low,heavy,light,heavy]),
      (18,[high,medium,none,none,none]),
      (19,[low,low,light,none,light]),
      (20,[high,high,none,none,none])])

hh2 = llhh([pressure,pressureb,cloud,wind,rain],[
      (1,[high,low,none,none,light]),
      (2,[low,high,light,none,light]),
      (3,[low,high,heavy,strong,heavy]),
      (4,[low,high,heavy,light,heavy]),
      (5,[high,low,light,strong,heavy]),
      (6,[low,high,light,light,light]),
      (7,[low,high,light,light,heavy]),
      (8,[low,high,heavy,strong,heavy]),
      (9,[high,low,heavy,light,heavy]),
      (10,[high,low,light,none,light])])

aa = hhaa(hadd(hh1,hh2))

vvc = unit(cart(uu,vv))

uu
# {(cloud, {heavy, light, none}), (pressure, {high, low, medium}), (pressureb, {high, low, medium}), (rain, {heavy, light, none}), (wind, {light, none, strong})}

vv
# {cloud, pressure, pressureb, rain, wind}

rpln(aall(aa))
# ({(cloud, heavy), (pressure, high), (pressureb, low), (rain, heavy), (wind, light)}, 1 % 1)
# ({(cloud, heavy), (pressure, low), (pressureb, high), (rain, heavy), (wind, light)}, 1 % 1)
# ({(cloud, heavy), (pressure, low), (pressureb, high), (rain, heavy), (wind, strong)}, 2 % 1)
# ({(cloud, heavy), (pressure, low), (pressureb, low), (rain, heavy), (wind, light)}, 1 % 1)
# ({(cloud, heavy), (pressure, low), (pressureb, medium), (rain, heavy), (wind, light)}, 1 % 1)
# ({(cloud, heavy), (pressure, low), (pressureb, medium), (rain, heavy), (wind, strong)}, 2 % 1)
# ({(cloud, light), (pressure, high), (pressureb, high), (rain, heavy), (wind, light)}, 1 % 1)
# ({(cloud, light), (pressure, high), (pressureb, low), (rain, heavy), (wind, strong)}, 1 % 1)
# ({(cloud, light), (pressure, high), (pressureb, low), (rain, light), (wind, none)}, 1 % 1)
# ({(cloud, light), (pressure, high), (pressureb, medium), (rain, light), (wind, strong)}, 1 % 1)
# ({(cloud, light), (pressure, low), (pressureb, high), (rain, heavy), (wind, light)}, 1 % 1)
# ({(cloud, light), (pressure, low), (pressureb, high), (rain, light), (wind, light)}, 1 % 1)
# ({(cloud, light), (pressure, low), (pressureb, high), (rain, light), (wind, none)}, 1 % 1)
# ({(cloud, light), (pressure, low), (pressureb, low), (rain, light), (wind, none)}, 1 % 1)
# ({(cloud, light), (pressure, medium), (pressureb, high), (rain, light), (wind, none)}, 1 % 1)
# ({(cloud, light), (pressure, medium), (pressureb, low), (rain, heavy), (wind, strong)}, 1 % 1)
# ({(cloud, light), (pressure, medium), (pressureb, low), (rain, none), (wind, none)}, 1 % 1)
# ({(cloud, light), (pressure, medium), (pressureb, medium), (rain, light), (wind, light)}, 1 % 1)
# ({(cloud, none), (pressure, high), (pressureb, high), (rain, none), (wind, none)}, 2 % 1)
# ({(cloud, none), (pressure, high), (pressureb, low), (rain, light), (wind, none)}, 1 % 1)
# ({(cloud, none), (pressure, high), (pressureb, medium), (rain, none), (wind, light)}, 2 % 1)
# ({(cloud, none), (pressure, high), (pressureb, medium), (rain, none), (wind, none)}, 1 % 1)
# ({(cloud, none), (pressure, low), (pressureb, low), (rain, light), (wind, light)}, 1 % 1)
# ({(cloud, none), (pressure, medium), (pressureb, high), (rain, none), (wind, none)}, 1 % 1)
# ({(cloud, none), (pressure, medium), (pressureb, medium), (rain, light), (wind, light)}, 2 % 1)

size(aa)
# 30 % 1

Create a transform $T_{\mathrm{cw}}$ which relates cloud and wind,

cloud_and_wind = VarStr("cloud_and_wind")

ttcw = lltt([cloud,wind],[cloud_and_wind],[
      [none, none, none],
      [none, light, light],
      [none, strong, light],
      [light, none, light],
      [light, light, light],
      [light, strong, light],
      [heavy, none, strong],
      [heavy, light, strong],
      [heavy, strong, strong]])

Create a transform $T_{\mathrm{cp}}$ that relates cloud and pressure,

cloud_and_pressure = VarStr("cloud_and_pressure")

ttcp = lltt([cloud,pressure],[cloud_and_pressure],[
      [none, high, none],
      [none, medium, light],
      [none, low, light],
      [light, high, light],
      [light, medium, light],
      [light, low, light],
      [heavy, high, strong],
      [heavy, medium, strong],
      [heavy, low, strong]])

Create a transform $T_{\mathrm{sc}}$ which rolls together the none and light values of the cloud variable,

storm_cloud = VarStr("storm_cloud")

ttsc = lltt([cloud],[storm_cloud],[
      [none, light],
      [light, light],
      [heavy, heavy]])

Now create a transform $T_{\mathrm{s}}$ that relates pressure and pressureb to detect stormy weather,

storm = VarStr("storm")
yes = ValStr("yes")
no = ValStr("no")

tts = lltt([pressure,pressureb],[storm],[
      [low, low, no],
      [low, medium, no],
      [low, high, yes],
      [medium, low, no],
      [medium, medium, no],
      [medium, high, no],
      [high, low, yes],
      [high, medium, no],
      [high, high, no]])

That is, a storm is where the two pressure variables are at opposite extremes.

Now we create a functional definition set decomposition $D$ which partitions the cartesian into stormy and normal weather. When it is stormy, the cloud is highly aligned with the rain,

df = lldf([
      [([], [tts]), ([(storm, no)],  [ttcw, ttcp])],
      [([], [tts]), ([(storm, yes)], [ttsc])]])

Consider the multiplication of the sample histogram and the decomposition,

rpln([[(ss, vars(bb), size(bb)) for (ss,bb) in ll] for ll in dfmulll(aa,df)])
# [({}, {cloud, pressure, pressureb, rain, storm, wind}, 30 % 1), ({(storm, no)}, {cloud, cloud_and_pressure, cloud_and_wind, pressure, pressureb, rain, wind}, 20 % 1)]
# [({}, {cloud, pressure, pressureb, rain, storm, wind}, 30 % 1), ({(storm, yes)}, {cloud, pressure, pressureb, rain, storm_cloud, wind}, 10 % 1)]

The first path is the normal weather slice,

(_,aa1) = dfmulll(aa,df)[0][1]

ared(aa1,vv) == hhaa(hh1)
# True

If a query is in this slice the fud is just the fud $F = \{T_{\mathrm{cw}},T_{\mathrm{cp}}\}$ discussed in Functional definition sets. The relative entropy and label alignment are different now because the substrate now contains the pressure2 variable,

rent(tmul(aa1,fftt(llff([ttcw, ttcp]))),tmul(vvc,fftt(llff([ttcw, ttcp]))))
# 2.4392475869399846

tlalgn(fftt(llff([ttcw, ttcp])),aa1,[rain]) - tlalgn(fftt(llff([ttcw, ttcp])),ind(aa1),[rain])
# 14.058773820098937

algn(aa1)
# 13.993685672415081

but the the label entropy is unchanged,

tlent(fftt(llff([ttcw, ttcp])),aa1,[rain])
# 8.018185525433372

The second path is the stormy weather slice,

(_,aa2) = dfmulll(aa,df)[1][1]

ared(aa2,vv) == hhaa(hh2)
# True

If a query is in this slice the fud is just the fud of the transform of a rolled cloud variable, $\{T_{\mathrm{sc}}\}$. The relative entropy is

rent(tmul(aa2,fftt(llff([ttsc]))),tmul(vvc,fftt(llff([ttsc]))))
# 0.09317574090613334

tlalgn(fftt(llff([ttsc])),aa2,[rain]) - tlalgn(fftt(llff([ttsc])),ind(aa2),[rain])
# 1.9133297040628943

algn(aa2)
# 5.656670558723758

The label entropy is

tlent(fftt(llff([ttsc])),aa2,[rain])
# 3.8190850097688767

In the case of normal weather with medium pressure, heavy cloud and light winds, the forecast for rain is heavy,

qq1 = hhaa(llhh([pressure,pressureb,cloud,wind],[(1,[medium,medium,heavy,light])]))

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq1,df,aa,[rain])])
# [{}, {(storm, no)}]

rpln(aarr(queryll(qq1,df,aa,[rain])[0][1][1]))
# ({(rain, heavy)}, 1.0)

In the case of stormy weather with low pressure, heavy cloud and light winds, the forecast for rain is also heavy,

qq1s = hhaa(llhh([pressure,pressureb,cloud,wind],[(1,[low,high,heavy,light])]))

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq1s,df,aa,[rain])])
# [{}, {(storm, yes)}]

rpln(aarr(queryll(qq1s,df,aa,[rain])[0][1][1]))
# ({(rain, heavy)}, 1.0)

In the case of normal weather with low pressure, but no cloud and light winds, the prediction is ambiguous,

qq2 = hhaa(llhh([pressure,pressureb,cloud,wind],[(1,[low,medium,none,light])]))

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq2,df,aa,[rain])])
# [{}, {(storm, no)}]

rpln(aarr(queryll(qq2,df,aa,[rain])[0][1][1]))
# ({(rain, heavy)}, 0.2)
# ({(rain, light)}, 0.7)
# ({(rain, none)}, 0.1)

In the case of stormy weather with low pressure, but no cloud and light winds, the prediction is ambiguous but wet,

qq2s = hhaa(llhh([pressure,pressureb,cloud,wind],[(1,[low,high,none,light])]))

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq2s,df,aa,[rain])])
# [{}, {(storm, yes)}]

rpln(aarr(queryll(qq2s,df,aa,[rain])[0][1][1]))
# ({(rain, heavy)}, 0.3333333333333333)
# ({(rain, light)}, 0.6666666666666666)

In the case of normal weather with high pressure, no cloud and strong winds, the prediction is for dry,

qq3 = hhaa(llhh([pressure,pressureb,cloud,wind],[(1,[high,medium,none,strong])]))

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq3,df,aa,[rain])])
# [{}, {(storm, no)}]

rpln(aarr(queryll(qq3,df,aa,[rain])[0][1][1]))
# ({(rain, none)}, 1.0)

In the case of stormy weather with high pressure, no cloud and strong winds, the prediction is for wet,

qq3s = hhaa(llhh([pressure,pressureb,cloud,wind],[(1,[high,low,none,strong])]))

rpln([[ss for (ss,bb) in ll] for ll in queryll(qq3s,df,aa,[rain])])
# [{}, {(storm, yes)}]

rpln(aarr(queryll(qq3s,df,aa,[rain])[0][1][1]))
# ({(rain, heavy)}, 0.3333333333333333)
# ({(rain, light)}, 0.6666666666666666)

top