Transforms and implementations
Transforms and implementations¶
See the Equations and transforms API and Systems and implementations API for concise references.
In the previous chapter, we used an equation to describe a mathematical relationship:
@equation
def kinematic_relation(distance: float, speed: float, time: float) -> bool:
return distance == speed * time
Equations are useful when the relationship itself is important and Bilt may need to use it in different directions.
Not every part of a model works like that.
Sometimes we simply want to say:
Given these values, here is how to calculate another one.
For that, Bilt provides transforms.
Transforms describe calculations¶
Consider a small system:
from bilt import Interface, field, system
@system
class Demo(metaclass=Interface):
base: float = field()
increment: float = field()
total: float = field()
We can define a transform that calculates total from base and increment:
from bilt import transform
@transform
def total(base: float, increment: float) -> float:
return base + increment
This tells Bilt that total can be calculated from base and increment:
Unlike an equation, a transform has a fixed computational direction.
The parameter names identify the values it requires, while the function name identifies the value it provides.
So:
means:
Bilt can use this transform to calculate total from base and increment. It cannot use the same transform to calculate base from increment and total.
Transforms are ordinary calculations¶
A transform contains ordinary Python code.
For example:
Given value, we can calculate its magnitude. But knowing the magnitude does not tell us whether the original value was positive or negative.
There is no unique way to run the calculation backwards.
Transforms can also contain conditionals, lookups, calls to other software, or other computations that cannot sensibly be represented as an algebraic relationship.
This is the main distinction between the two relationship types we have seen:
An equation describes a relationship between values.
A transform describes how to calculate one value from others.
Use an equation when the mathematical relationship is the important thing. Use a transform when the computation is the important thing.
Implementations collect relationships for a system¶
We used an implementation in the previous chapter to connect an equation to a system.
Now we can look at its role more closely.
A system and its implementation have different jobs.
Demo describes the boundary:
while basic supplies relationships that can be used to obtain those values:
An implementation can contain equations, transforms, or a mixture of both.
Keeping the system boundary separate from its implementation also allows the same system to be realised in different ways.
Evaluating with an implementation¶
Create an instance with the values we already know:
Then make the implementation active while requesting total:
Bilt knows:
and has been asked for:
The active implementation contains a transform that can produce total from the available values, so the request can be satisfied.
Conceptually:
The important point is that we did not call the transform ourselves:
Instead, we asked the system for:
Bilt found a relationship that could provide it.
Implementations can contain many relationships¶
A real implementation will usually contain more than one relationship.
Let's extend our system:
@system
class Demo(metaclass=Interface):
base: float = field()
increment: float = field()
total: float = field()
adjusted: float = field()
result: float = field()
and define three transforms:
@transform
def total(base: float, increment: float) -> float:
return base + increment
@transform
def adjusted(base: float, total: float) -> float:
return base + total
@transform
def result(total: float, adjusted: float) -> float:
return total + adjusted
We can collect them into one implementation:
This gives Bilt three available relationships:
Now create an instance with just base and increment known:
and ask for result:
To satisfy that request, Bilt needs to obtain:
or, as a graph:
We never wrote:
total_value = total(base, increment)
adjusted_value = adjusted(base, total_value)
result_value = result(total_value, adjusted_value)
The implementation describes which relationships are available. It does not prescribe the order in which they must be evaluated.
The model is not the execution order¶
Nor does the order in:
mean:
Those relationships describe the model.
The required execution order depends on which values are already known and which value we request.
If we request total, Bilt only needs:
If we request result, it needs a longer route:
So:
The model describes the available relationships. The request determines the computation.
This applies equally when an implementation contains equations, transforms, or a mixture of both.
Equations and transforms can work together¶
An implementation does not have to choose between equations and transforms.
For example, part of a model might be described by an equation:
while another part is naturally a directed calculation:
Bilt can use both kinds of relationship when working out how to obtain a requested value.
Equations give it relationships that may be usable in different directions.
Transforms give it relationships with a fixed direction.
Together, they form the set of possible routes through the model.
The next question is how Bilt chooses a route from the values that are known to the value that has been requested.
That is the subject of the next chapter: from models to computation.
← Equations and acausal modelling · Index · Models to computation →