# Computation

Z3 functions are saved as array objects—we know this because when we print out a Z3 function, like say `verify` from the default theory, we see an array-like object. This means Z3 saves every value (that it is forced to for a given countermodel) for every input combination, meaning that the space complexity of functions is proportional to the input space. For example, consider a function with inputs in the space of bitvectors with $N=5$. There are $2^5$ bitvectors in this space, so the space complexity of a function with inputs in this space has space complexity of $O(2^5)$. 

This becomes key when thinking of what primitive functions are in the semantics. For example, the default semantics has three primitive functions: `verify`, `falsify`, and `possible`. These are in $O(A*2^N)$, $O(A*2^N)$, and $O(2^N)$, respectively, where $A$ is the number of atomic sentences in the example. (In practice, the three functions are in $O(2^N)$, since $2^N$ is virtually always larger than $A$ for $N>2$.) Indeed, if we print e.g. "possible" for an example ($\neg A, A \boxright C \vDash (A \wedge B) \boxright C$), we see the "possible" as an array:

IMAGE HERE

As a contrast, consider Fine's imposition semantics, which has the same primitives as the default theory along with a three-place `imposition` function. This has a space complexity of $O(2^{3N})$, which means it will take more space (and thus time) in the worst case to save. 

To give a concrete example with numbers, consider a three-place function with inputs in the space of bitvectors, and assume bitvectors of size $N=7$, which is a rather reasonable number to go up to to look for (interesting) countermodels. The space complexity of this function is $O(2^{3*7})$

Since there are many other differences that may affect performance with respeect to speed between Fine's imposition semantics and the default semantics, we did a comparison of speed between the current version of the default theory and a version with all definitions as Z3 primitives. Each of these new primitives then had a constraint that corresponds to its python function definition. (This method has the advantage that at the moment of evaluation, every function's extension can be found merely by doing `z3_model.evaluate(_____)`, though we will see it comes at a considerable cost). Notably, there were three-place primitive functions, like for example `is_alternative`. We then ran the counterfactual test suite for the default theory on both versions with $N=7$, which has a total runtime of 172 seconds (Z3 run time of 3.3 seconds). The latter version, with all functions as primitives, never terminates, thus showing that the additional space complexity is significant. And even if the limiting fact was the number of primitive functions, these would scale linearly with their complexities, meaning that the problem is only worse for the exponential growth of 2^N. 

This brings up a new methodological consideration for theory building: the arity of our primitive functions now matters from a computational point of view, with less arity being computationally cheaper. 
