"Efficient" least- and greatest fixpoint computations? - isabelle

I am trying to compute two finite sets of some enumerable type (let's say char) using a least- and greatest- fixpoint computation, respectively. I want my definitions to be extractable to SML, and to be "semi-efficient" when executed. What are my options?
From exploring the HOL library and playing around with code generation, I have the following observations:
I could use the complete_lattice.lfp and complete_lattice.gfp constants with a pair of additional monotone functions to compute my sets, which in fact I currently am doing. Code generation does work with these constants, but the code produced is horribly inefficient, and if I understand the generated SML code correctly is performing an exhaustive search over every possible set in the powerset of characters. Any use, no matter how simple, of these two constants at type char therefore causes a divergence when executed.
I could try to make use of the iterative fixpoint described by the Kleene fixpoint theorem in directed complete partial orders. From exploring, there's a ccpo_class.fixp constant in the theory Complete_Partial_Order, but the underlying iterates constant that this is defined in terms of has no associated code equations, and so code cannot be extracted.
Are there any existing fixpoint combinators hiding somewhere, suitable for use with finite sets, that produce semi-efficient code with code generation that I have missed?

None of the general fixpoint combinators in Isabelle's standard library is meant to used directly for code extraction because their construction is too general to be usable in practice. (There is another one in the theory ~~/src/HOL/Library/Bourbaki_Witt_Fixpoint.) But the theory ~~/src/HOL/Library/While_Combinator connects the lfp and gfp fixpoints to the iterative implementation you are looking for, see theorems lfp_while_lattice and gfp_while_lattice. These characterisations have the precondition that the function is monotone, so they cannot be used as code equations directly. So you have two options:
Use the while combinator instead of lfp/gfp in your code equations and/or definitions.
Tell the code preprocessor to use lfp_while_lattice as a [code_unfold] equation. This works if you also add all the rules that the preprocessor needs to prove the assumptions of these equations for the instances at which it should apply. Hence, I recommend that you also add as [code_unfold] the monotonicity statement of your function and the theorem to prove the finiteness of char set, i.e., finite_class.finite.

Related

Is there a prover just for propositional logic

I tried to implement LTL logic syntactically using the axiomatization command, with the purpose of automatically finding proofs for theorems (motivation of proving program properties).
However the automatic provers such as (cvc4, z3, e, etc) all use quantifiers of some sort. For example using FOL one could prove F(p)-->G(p) which is obviously false.
My question is if there exists a prover, just like the ones mentioned, but that is made for propositional logic, i.e. only has access to MP and the propositional logic axioms.
I am rather new to isabelle so there might be an easier way of doing this im not seeing.
EDIT: I am looking for a hilbert style deduction prover and not a SAT as this would defeat the problem of implementing it axiomatically
I think the sat method only uses propositional logic.
However, I would recommend not to use axiomatizations and just define the syntax of LTL using datatypes and the semantics using functions. Maybe you can reuse the formalization from https://www.isa-afp.org/entries/LTL.html
Without axiomatizations you are then free to use any method.
What you want is a SAT solver, such as minisat.
However the automatic provers such as (cvc4, z3, e, etc) all use quantifiers of some sort. For example using FOL one could prove F(p)-->G(p) which is obviously false.
This is not correct. Any first-order theorem prover, like iProver, E, Vampire, will not prove forall X. f(X) => g(x).

What's a cterm?

The Isabelle implementation manual says:
Types ctyp and cterm represent certified types and terms, respectively. These are abstract datatypes that guarantee that its values have passed the full well-formedness (and well-typedness) checks, relative to the declarations of type constructors, constants etc. in the background theory.
My understanding is: when I write cterm t, Isabelle checks that the term is well-built according to the theory where it lives in.
The abstract types ctyp and cterm are part of the same inference kernel that is mainly responsible for thm. Thus syntactic operations on ctyp and cterm are located in the Thm module, even though theorems
are not yet involved at that stage.
My understanding is: if I want to modify a cterm at the ML level I will use operations of the Thm module (where can I find that module?)
Furthermore, it looks like cterm t is an entity that converts a term of the theory level to a term of the ML-level. So I inspect the code of cterm in the declaration:
ML_val ‹
some_simproc #{context} #{cterm "some_term"}
›
and get to ml_antiquotations.ML:
ML_Antiquotation.value \<^binding>‹cterm› (Args.term >> (fn t =>
"Thm.cterm_of ML_context " ^ ML_Syntax.atomic (ML_Syntax.print_term t))) #>
This line of code is unreadable to me with my current knowledge.
I wonder if someone could give a better low-level explanation of cterm. What is the meaning of the code below? Where are located the checks that cterm performs on theory terms? Where are located the manipulations that we can do on cterms (the module Thm above)?
The ‘c’ stands for ‘certified’ (Or ‘checked’? Not sure). A cterm is basically a term that has been undergone checking. The #{cterm …} antiquotation allows you to simply write down terms and directly get a cterm in various contexts (in this case probably the context of ML, i.e. you directly get a cterm value with the intended content). The same works for regular terms, i.e. #{term …}.
You can manipulate cterms directly using the functions from the Thm structure (which, incidentally, can be found in ~~/src/Pure/thm.ML; most of these basic ML files are in the Pure directory). However, in my experience, it is usually easier to just convert the cterm to a regular term (using Thm.term_of – unlike Thm.cterm_of, this is a very cheap operation) and then work with the term instead. Directly manipulating cterms only really makes sense if you need another cterm in the end, because re-certifying terms is fairly expensive (still, unless your code is called very often, it probably isn't really a performance problem).
In most cases, I would say the workflow is like this: If you get a cterm as an input, you turn it into a regular term. This you can easily inspect/take apart/whatever. At some point, you might have to turn it into a cterm again (e.g. because you want to instantiate some theorem with it or use it in some other way that involves the kernel) and then you just use Thm.cterm_of to do that.
I don't know exactly what the #{cterm …} antiquotation does internally, but I would imagine that at the end of the day, it just parses its parameter as an Isabelle term and then certifies it with the current context (i.e. #{context}) using something like Thm.cterm_of.
To gather my findings with cterms, I post an answer.
This is how a cterm looks like in Pure:
abstype cterm =
Cterm of {cert: Context.certificate,
t: term, T: typ,
maxidx: int,
sorts: sort Ord_List.T}
(To be continued)

The Idiomatic Way To Do OOP, Types and Methods in Julia

As I'm learning Julia, I am wondering how to properly do things I might have done in Python, Java or C++ before. For example, previously I might have used an abstract base class (or interface) to define a family of models through classes. Each class might then have a method like calculate. So to call it I might have model.calculate(), where the model is an object from one of the inheriting classes.
I get that Julia uses multiple dispatch to overload functions with different signatures such as calculate(model). The question I have is how to create different models. Do I use the type system for that and create different types like:
abstract type Model end
type BlackScholes <: Model end
type Heston <: Model end
where BlackScholes and Heston are different types of model? If so, then I can overload different calculate methods:
function calculate(model::BlackScholes)
# code
end
function calculate(model::Heston)
# code
end
But I'm not sure if this is a proper and idiomatic use of types in Julia. I will greatly appreciate your guidance!
This is a hard question to answer. Julia offers a wide range of tools to solve any given problem, and it would be hard for even a core developer of the language to assert that one particular approach is "right" or even "idiomatic".
For example, in the realm of simulating and solving stochastic differential equations, you could look at the approach taken by Chris Rackauckas (and many others) in the suite of packages under the JuliaDiffEq umbrella. However, many of these people are extremely experienced Julia coders, and what they do may be somewhat out of reach for less experienced Julia coders who just want to model something in a manner that is reasonably sensible and attainable for a mere mortal.
It is is possible that the only "right" answer to this question is to direct users to the Performance Tips section of the docs, and then assert that as long as you aren't violating any of the recommendations there, then what you are doing is probably okay.
I think the best way I can answer this question from my own personal experience is to provide an example of how I (a mere mortal) would approach the problem of simulating different Ito processes. It is actually not too far off what you have put in the question, although with one additional layer. To be clear, I make no claim that this is the "right" way to do things, merely that it is one approach that utilizes multiple dispatch and Julia's type system in a reasonably sensible fashion.
I start off with an abstract type, for nesting specific subtypes that represent specific models.
abstract type ItoProcess ; end
Now I define some specific model subtypes, e.g.
struct GeometricBrownianMotion <: ItoProcess
mu::Float64
sigma::Float64
end
struct Heston <: ItoProcess
mu::Float64
kappa::Float64
theta::Float64
xi::Float64
end
Note, in this case I don't need to add constructors that convert arguments to Float64, since Julia does this automatically, e.g. GeometricBrownianMotion(1, 2.0) will work out-of-the-box, as Julia will automatically convert 1 to 1.0 when constructing the type.
However, I might want to add some constructors for common parameterizations, e.g.
GeometricBrownianMotion() = GeometricBrownianMotion(0.0, 1.0)
I might also want some functions that return useful information about my models, e.g.
number_parameter(model::GeometricBrownianMotion) = 2
number_parameter(model::Heston) = 4
In fact, given how I've defined the models above, I could actually be a bit sneaky and define a method that works for all subtypes:
number_parameter(model::T) where {T<:ItoProcess} = length(fieldnames(typeof(model)))
Now I want to add some code that allows me to simulate my models:
function simulate(model::T, numobs::Int, stval) where {T<:ItoProcess}
# code here that is common to all subtypes of ItoProcess
simulate_inner(model, somethingelse)
# maybe more code that is common to all subtypes of ItoProcess
end
function simulate_inner(model::GeometricBrownianMotion, somethingelse)
# code here that is specific to GeometricBrownianMotion
end
function simulate_inner(model::Heston, somethingelse)
# code here that is specific to Heston
end
Note that I have used the abstract type to allow me to group all code that is common to all subtypes of ItoProcess in the simulate function. I then use multiple dispatch and simulate_inner to run any code that needs to be specific to a particular subtype of ItoProcess. For the aforementioned reasons, I hesitate to use the phrase "idiomatic", but let me instead say that the above is quite a common pattern in typical Julia code.
The one thing to be careful of in the above code is to ensure that the output type of the simulate function is type-stable, that is, the output type can be uniquely determined by the input types. Type stability is usually an important factor in ensuring performant Julia code. An easy way in this case to ensure type-stability is to always return Matrix{Float64} (if the output type is fixed for all subtypes of ItoProcess then obviously it is uniquely determined). I examine a case where the output type depends on input types below for my estimate example. Anyway, for simulate I might always return Matrix{Float64} since for GeometricBrownianMotion I only need one column, but for Heston I will need two (the first for price of the asset, the second for the volatility process).
In fact, depending on how the code is used, type-stability is not always necessary for performant code (see eg using function barriers to prevent type-instability from flowing through to other parts of your program), but it is a good habit to be in (for Julia code).
I might also want routines to estimate these models. Again, I can follow the same approach (but with a small twist):
function estimate(modeltype::Type{T}, data)::T where {T<:ItoProcess}
# again, code common to all subtypes of ItoProcess
estimate_inner(modeltype, data)
# more common code
return T(some stuff generated from function that can be used to construct T)
end
function estimate_inner(modeltype::Type{GeometricBrownianMotion}, data)
# code specific to GeometricBrownianMotion
end
function estimate_inner(modeltype::Type{Heston}, data)
# code specific to Heston
end
There are a few differences from the simulate case. Instead of inputting an instance of GeometricBrownianMotion or Heston, I instead input the type itself. This is because I don't actually need an instance of the type with defined values for the fields. In fact, the values of those fields is the very thing I am attempting to estimate! But I still want to use multiple dispatch, hence the ::Type{T} construct. Note also I have specified an output type for estimate. This output type is dependent on the ::Type{T} input, and so the function is type-stable (output type can be uniquely determined by input types). But common with the simulate case, I have structured the code so that code that is common to all subtypes of ItoProcess only needs to be written once, and code that is specific to the subtypes is separted out.
This answer is turning into an essay, so I should tie it off here. Hopefully this is useful to the OP, as well as anyone else getting into Julia. I just want to finish by emphasizing that what I have done above is only one approach, there are others that will be just as performant, but I have personally found the above to be useful from a structural perspective, as well as reasonably common across the Julia ecosystem.

Correct way of writing recursive functions in CLP(R) with Prolog

I am very confused in how CLP works in Prolog. Not only do I find it hard to see the benefits (I do see it in specific cases but find it hard to generalise those) but more importantly, I can hardly make up how to correctly write a recursive predicate. Which of the following would be the correct form in a CLP(R) way?
factorial(0, 1).
factorial(N, F):- {
N > 0,
PrevN = N - 1,
factorial(PrevN, NewF),
F = N * NewF}.
or
factorial(0, 1).
factorial(N, F):- {
N > 0,
PrevN = N - 1,
F = N * NewF},
factorial(PrevN, NewF).
In other words, I am not sure when I should write code outside the constraints. To me, the first case would seem more logical, because PrevN and NewF belong to the constraints. But if that's true, I am curious to see in which cases it is useful to use predicates outside the constraints in a recursive function.
There are several overlapping questions and issues in your post, probably too many to coherently address to your complete satisfaction in a single post.
Therefore, I would like to state a few general principles first, and then—based on that—make a few specific comments about the code you posted.
First, I would like to address what I think is most important in your case:
LP &subseteq; CLP
This means simply that CLP can be regarded as a superset of logic programming (LP). Whether it is to be considered a proper superset or if, in fact, it makes even more sense to regard them as denoting the same concept is somewhat debatable. In my personal view, logic programming without constraints is much harder to understand and much less usable than with constraints. Given that also even the very first Prolog systems had a constraint like dif/2 and also that essential built-in predicates like (=)/2 perfectly fit the notion of "constraint", the boundaries, if they exist at all, seem at least somewhat artificial to me, suggesting that:
LP &approx; CLP
Be that as it may, the key concept when working with CLP (of any kind) is that the constraints are available as predicates, and used in Prolog programs like all other predicates.
Therefore, whether you have the goal factorial(N, F) or { N > 0 } is, at least in principle, the same concept: Both mean that something holds.
Note the syntax: The CLP(&Rscr;) constraints have the form { C }, which is {}(C) in prefix notation.
Note that the goal factorial(N, F) is not a CLP(&Rscr;) constraint! Neither is the following:
?- { factorial(N, F) }.
ERROR: Unhandled exception: type_error({factorial(_3958,_3960)},...)
Thus, { factorial(N, F) } is not a CLP(&Rscr;) constraint either!
Your first example therefore cannot work for this reason alone already. (In addition, you have a syntax error in the clause head: factorial (, so it also does not compile at all.)
When you learn working with a constraint solver, check out the predicates it provides. For example, CLP(&Rscr;) provides {}/1 and a few other predicates, and has a dedicated syntax for stating relations that hold about floating point numbers (in this case).
Other constraint solver provide their own predicates for describing the entities of their respective domains. For example, CLP(FD) provides (#=)/2 and a few other predicates to reason about integers. dif/2 lets you reason about any Prolog term. And so on.
From the programmer's perspective, this is exactly the same as using any other predicate of your Prolog system, whether it is built-in or stems from a library. In principle, it's all the same:
A goal like list_length(Ls, L) can be read as: "The length of the list Ls is L."
A goal like { X = A + B } can be read as: The number X is equal to the sum of A and B. For example, if you are using CLP(Q), it is clear that we are talking about rational numbers in this case.
In your second example, the body of the clause is a conjunction of the form (A, B), where A is a CLP(&Rscr;) constraint, and B is a goal of the form factorial(PrevN, NewF).
The point is: The CLP(&Rscr;) constraint is also a goal! Check it out:
?- write_canonical({a,b,c}).
{','(a,','(b,c))}
true.
So, you are simply using {}/1 from library(clpr), which is one of the predicates it exports.
You are right that PrevN and NewF belong to the constraints. However, factorial(PrevN, NewF) is not part of the mini-language that CLP(&Rscr;) implements for reasoning over floating point numbers. Therefore, you cannot pull this goal into the CLP(&Rscr;)-specific part.
From a programmer's perspective, a major attraction of CLP is that it blends in completely seamlessly into "normal" logic programming, to the point that it can in fact hardly be distinguished at all from it: The constraints are simply predicates, and written down like all other goals.
Whether you label a library predicate a "constraint" or not hardly makes any difference: All predicates can be regarded as constraints, since they can only constrain answers, never relax them.
Note that both examples you post are recursive! That's perfectly OK. In fact, recursive predicates will likely be the majority of situations in which you use constraints in the future.
However, for the concrete case of factorial, your Prolog system's CLP(FD) constraints are likely a better fit, since they are completely dedicated to reasoning about integers.

Struggling with the basics

I am trying to self-learn SML. Though I can write some SML code, I have not attained the ah ha.
In:
val x = 5;
how does binding a value of 5 to the name x differ from assigning a value of 5 to the memory location/variable x in imperative programming?
How does the above expression elucidate "a style that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data"?
What do I have to throw away about imperative programming to catch on FP quickly?
Please be gentle with me.
How does binding a value of 5 to the name x differ from assigning a value of 5 to the memory location/variable x in imperative programming?
The key difference between variables in functional programming and variables in imperative programming is that variables in functional programming cannot be modified.
Then why are they called “variables”?
Variables in functional programming languages don't vary in the sense that you can modify their value. However, they do vary in the mathematical sense in that they represent some unknown constant value. For example, consider the following quadratic expression:
This is a quadratic expression in one variable. The variable x varies in the sense that you can select any value for x. However, once you select a certain value for x you cannot change that value.
Now, if you have a quadractic equation then the choice of x is no longer arbitrary. For example, consider the following quadratic equation:
The only choices for x are those which satisfy the equation (i.e. x = -2 or x = -1.5).
A mathematical function on the other hand is a relation between two sets, called the domain (input set) and the codomain (output set). For example, consider the following function:
This function relates every x belonging to the set of real numbers to its corresponding 2x^2 + 7x + 6, also belonging to the set of real numbers. Again, we are free to choose any value for x. However, once we choose some value for x we are not allowed to modify it.
The point is that they are called variables because they vary in the mathematical sense that they can assume different values. Hence, they vary with instance. However, they do not vary with time.
This immutability of variables is important in functional programming because it makes your program referentially transparent (i.e. invoking a function can be thought of as simply replacing the function call with the function body).
If variables were allowed to vary with time then you wouldn't be able to simply substitute the function call with the function body because the substituted variable could change over time. Hence, your substituted function body could become invalid over time.
How does the above expression elucidate "a style that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data"?
The expression val x = 5; could be thought of as a function application (fn x => y) 5 where y is the rest of the program. Functional programming is all about functions, and immutability in the sense that variables only vary with instance and not with time.
In particular, mutability is considered bad because it is implicit. Anything that is implicit could potentially cause unforeseeable errors in your program. Hence, the Zen of Python explicitly states that:
Explicit is better than implicit.
In fact, mutable state is the primary cause of software complexity. Hence, functional programming tries to eschew mutability as much as possible. However, that doesn't mean that we only use immutable structures. We can use mutable structures. We just need to be explicit about it.
What do I have to throw away about imperative programming to catch on FP quickly?
Nothing. Functional programming and imperative programming are two ways of thinking about computation. Computation is a very abstract idea. What exactly is computation? How do we know which problems can be computed? These were some of the questions that mathematicians struggled with in the early nineteen hundreds.
To think about computation we need to have a formal model of computation (i.e. a formal system that captures the notion of computation). There are various models of computation. However, the most famous are the lambda calculus (which gave rise to functional programming) and turing machines (which gave rise to imperative programming).
Both the lambda calculus and the turing machine are equivalent in power. They both capture the notion of computing and every problem that can be computed can be expressed as either a lambda calculus expression or an equivalent turing machine. The only difference is the way in which you express your problem.
You don't have to throw away anything that you learned about imperative programming to understand functional programming. Neither one is better than the other. They are both just different ways of expressing the same problem. However, you do need to start thinking like a functional programmer to write good functional programs.

Resources