# Taking propagators to the next level

## Introduction

In my previous post about propagators, we looked at how to define simple one- and two-way relations and we looked at generic operators, which were used extending the definition of conflicts to include sets. In this post, we will examine how to define mathemtical relations between cells containing not only simple values, but also sets. We will use these relations to build a version of the celcius to fahrenheit relation that works not only between cells containing numbers, but also between cells containing sets.

All the code here uses version 0.2.0 of the propaganda library. The code in it’s full form is available at github.

## Defining generic mathemtical operations

We want generic versions of addition, subtraction, multiplication and division that will accept two numbers, a set and a number, and two sets. To do this, we write a function `generic-set-operator` that takes an operator and returns a generic operator based on the given operator.

``````(defn generic-set-operator
[op]
(doto (go/generic-operator op)
(go/assign-operation
(fn [s v] (into #{} (for [elm s] (op elm v))))
set? any?)
(go/assign-operation
(fn [v s] (into #{} (for [elm s] (op v elm))))
any? set?)
(go/assign-operation
(fn [s1 s2] (into #{} (for [e1 s1 e2 s2] (op e1 e2))))
set? set?)))

(def plus (generic-set-operator +))
(def minus (generic-set-operator -))
(def multiply (generic-set-operator *))
(def divide (generic-set-operator /))

(plus 1 2)
;; => 3
(plus #{1 2 3} 4)
;; => #{5 6 7}
(plus 4 #{1 2 3})
;; => #{5 6 7}
(plus #{1 2 3} #{1 2 3})
;; => #{2 3 4 5 6}``````

We can lift the operators to propagator constructors.

``````(def plusp (function->propagator-constructor plus))
(def minusp (function->propagator-constructor minus))
(def multiplyp (function->propagator-constructor multiply))
(def dividep (function->propagator-constructor divide))``````

Finally, we can create the two-way relations for sums and products.

``````(defn sum
[system a b c]
(-> system
(plusp a b c)
(minusp c a b)
(minusp c b a)))

(defn prod
[system a b c]
(-> system
(multiplyp a b c)
(dividep c a b)
(dividep c b a)))

(-> (make-system)
(prod :a :b :c)
(get-value :c))
;; => 20

(-> (make-system)
(prod :a :b :c)
(get-value :b))
;; => 42``````

## Defining the relation between temperatures

In the previous post, we did not have sum and product relations, so we had to write two function for defining the two-way relation. This time around, we can use these new propagator constructors to define our relation.

``````(defn c-f-relation
[system f c]
(let [minus-32    (gensym "minus-32")
five-ninths (gensym "five-ninths")
f-minus-32  (gensym "f-minus-32")]
(-> system
(sum f minus-32 f-minus-32)
(prod five-ninths f-minus-32 c))))``````

The constants we need to define are stored under three new cells allocated for the purpose. `gensym` is used to come up with unique identifiers for those cells.

The `c-f-relation` function works both ways, so our examples from the previous post will still give us the results we want.

``````(-> (make-system)
(c-f-relation :temp-f :temp-c)
(get-value :temp-c))
;; => 340/9

(-> (make-system)
(c-f-relation :temp-f :temp-c)
(get-value :temp-f))
;; => 100N

(-> (make-system)
(c-f-relation :temp-f :temp-c)
(get-value :temp-c))
;; throws ExceptionInfo Inconsistency``````

Using the `extend-merge` function from the previous post, we can have our relations working on sets, as `c-f-relation` only uses generic operators that work on both sets and numbers.

``````(let [my-merge (doto (default-merge)
extend-merge)
(c-f-relation :temp-f :temp-c)
(get-value :temp-c)))
;; => #{280/3 340/9}``````

As conflicts only occur when the there is an empty intersection between sets, we can have several restrictions on the same cell to refine the value of the temperature in celcius

``````(let [my-merge (doto (default-merge)
extend-merge)
(c-f-relation :temp-f :temp-c)
(get-value :temp-c)))
;; => 280/3``````

And, as data flows both ways, the temperature in fahrenheit will automatically have been restricted.

``````(let [my-merge (doto (default-merge)
extend-merge)
(c-f-relation :temp-f :temp-c)