From 28a91d88fff4ac651f4aace90d7082c09e45a1ff Mon Sep 17 00:00:00 2001 From: matitaweb Date: Mon, 3 Oct 2011 08:15:18 +0000 Subject: [PATCH] commit by user andrea --- weblib/tutorial/chapter1.ma | 408 +++++++++++++++++++++--------------- 1 file changed, 240 insertions(+), 168 deletions(-) diff --git a/weblib/tutorial/chapter1.ma b/weblib/tutorial/chapter1.ma index c2523f38c..69466662a 100644 --- a/weblib/tutorial/chapter1.ma +++ b/weblib/tutorial/chapter1.ma @@ -1,23 +1,39 @@ - (* -In a system like Matita's very few things are built-in: not even booleans or -logical connectives. The main philosophy od the system is to let you define your -own data-types and functions using a powerful computational mechanism based on -the declaration of inductive types. - -Let us start this tutorial with a simple example based on the following well known -problem. - -h2 class="section"The goat, the wolf and the cabbage/h2div class="paragraph"/divA farmer need to tranfer a goat, a wolf and a cabbage across a river, but there is -only one place available on his boat. Furthermore, the goat will eat the cabbage if -they are left alone on the same bank, and similarly the wolf will eat the goat. -The problem consists in bringing all three item safely across the river. - -Our first data type defines the two banks of the river, which we will call east and -west. It is a simple example of enumerated type, defined by explicitly declaring all -its elements. The type itself is called "bank". -Before giving its definition we "include" the file "logic.ma" that contains a a few -preliminary notions not worth discussing here. +This is an interactive tutorial. To let you interact on line with the system, +you must first of all register yourself. + +Before starting, let us briefly explain the meaning of the menu buttons. +With the Advance and Retract buttons you respectively perform and undo single +computational steps. Each step consists in reading a user command, and processing +it. The part of the user input file (called script) already executed by the +system will be colored, and will not be editable any more. The advance bottom +will also automatically advance the focus of the window, but you can inspect the +whole file using the scroll bars, as usual. Comments are skipped. +Try to advance and retract a few steps, to get the feeling of the system. You can +also come back here by using the top button, that takes you at the beginning of +a file. The play button is meant to process the script up to a position +previously selected by the user; the bottom button execute the whole script. +That's it: we arespan style="font-family: Verdana,sans-serif;" /spannow ready to start. + +The first thing to say is that in a system like Matita's very few things are +built-in: not even booleans or logical connectives. The main philosophy of the +system is to let you define your own data-types and functions using a powerful +computational mechanism based on the declaration of inductive types. + +Let us start this tutorial with a simple example based on the following well +known problem. + +h2 class="section"The goat, the wolf and the cabbage/h2div class="paragraph"/divA farmer need to transfer a goat, a wolf and a cabbage across a river, but there +is only one place available on his boat. Furthermore, the goat will eat the +cabbage if they are left alone on the same bank, and similarly the wolf will eat +the goat. The problem consists in bringing all three items safely across the +river. + +Our first data type defines the two banks of the river, which will be named east +and west. It is a simple example of enumerated type, defined by explicitly +declaring all its elements. The type itself is called "bank". +Before giving its definition we "include" the file "logic.ma" that contains a +few preliminary notions not worth discussing for the moment. *) include "basics/logic.ma". @@ -26,8 +42,8 @@ inductive bank: Type[0] ≝ | east : bank | west : bank. -(* We can now define a simple function computing, for each bank of the river, -the opposite one. *) +(* We can now define a simple function computing, for each bank of the river, the +opposite one. *) definition opposite ≝ λs. match s with @@ -69,170 +85,226 @@ You may use the retract bottom to undo the step, if you wish. The new goal west = west is trivial: it is just a consequence of reflexivity. Such trivial steps can be just closed in Matita by typing a double slash. We complete the proof by the qed command, that instructs the system to store the -lemma performing some bookkeeping operations. +lemma performing some book-keeping operations. *) // qed. -lemma west_to_east : change_side west = east. +(* In exactly the same way, we can prove that the opposite side of west is east. +In this case, we avoid the unnecessary simplification step: // will take care of +it. *) + +lemma west_to_east : a href="cic:/matita/tutorial/chapter1/opposite.def(1)"opposite/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a title="leibnitz's equality" href="cic:/fakeuri.def(1)"=/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a. // qed. -lemma change_twice : ∀x. change_side (change_side x) = x. -* // -qed. +(* A slightly more complex problem consists in proving that opposite is +idempotent *) -inductive item: Type[0] ≝ -| goat : item -| wolf : item -| cabbage: item -| boat : item. +lemma idempotent_opposite : ∀x. a href="cic:/matita/tutorial/chapter1/opposite.def(1)"opposite/a (a href="cic:/matita/tutorial/chapter1/opposite.def(1)"opposite/a x) a title="leibnitz's equality" href="cic:/fakeuri.def(1)"=/a x. -(* definition state ≝ item → side. *) +(* we start the proof moving x from the conclusion into the context, that is a +(backward) introduction step. Matita syntax for an introduction step is simply +the sharp character followed by the name of the item to be moved into the +context. This also allows us to rename the item, if needed: for instance if we +wish to rename x into b (since it is a bank), we just type #b. *) -record state : Type[0] ≝ - {goat_pos : side; - wolf_pos : side; - cabbage_pos: side; - boat_pos : side}. - -definition state_of_item ≝ λs,i. -match i with -[ goat ⇒ goat_pos s -| wolf ⇒ wolf_pos s -| cabbage ⇒ cabbage_pos s -| boat ⇒ boat_pos s]. +#b -(* -definition is_movable ≝ λs,i. - state_of_item s i = state_of_item s boat. - -record movable_item (s:state) : Type[0] ≝ - {elem : item; - mov : is_movable s elem}. *) - -definition start :state ≝ - mk_state east east east east. - -(* slow -inductive move : state → state → Type[0] ≝ -| move_goat: ∀g,w,c. - move (mk_state (change_side g) w c (change_side g)) (mk_state g w c g) -| move_wolf: ∀g,w,c. - move (mk_state g (change_side w) c (change_side w)) (mk_state g w c w) -| move_cabbage: ∀g,w,c. - move (mk_state g w (change_side c) (change_side c)) (mk_state g w c c) -| move_boat: ∀g,w,c,b. - move (mk_state g w c (change_side b)) (mk_state g w c b). +(* See the effect of the execution in the goal window on the right: b has been +added to the context (replacing x in the conclusion); moreover its implicit type +"bank" has been made explicit. + +But how are we supposed to proceed, now? Simplification cannot help us, since b +is a variable: just try to call normalize and you will see that it has no effect. +The point is that we must proceed by cases according to the possible values of b, +namely east and west. To this aim, you must invoke the cases command, followed by +the name of the hypothesis (more generally, an arbitrary expression) that must be +the object of the case analysis (in our case, b). *) -(* this is working well *) -inductive move : state → state → Type[0] ≝ -| move_goat: ∀g,w,c. - move (mk_state g w c g) (mk_state (change_side g) w c (change_side g)) -| move_wolf: ∀g,w,c. - move (mk_state g w c w) (mk_state g (change_side w) c (change_side w)) -| move_cabbage: ∀g,w,c. - move (mk_state g w c c) (mk_state g w (change_side c) (change_side c)) -| move_boat: ∀g,w,c,b. - move (mk_state g w c b) (mk_state g w c (change_side b)). +cases b - -(* this is working -inductive move : state → state → Type[0] ≝ -| move_goat: ∀g,g1,w,c. change_side g = g1 → - move (mk_state g w c g) (mk_state g1 w c g1) -| move_wolf: ∀g,w,w1,c. change_side w = w1 → - move (mk_state g w c w) (mk_state g w1 c w1) -| move_cabbage: ∀g,w,c,c1. change_side c = c1 → - move (mk_state g w c c) (mk_state g w c1 c1) -| move_boat: ∀g,w,c,b,b1. change_side b = b1 → - move (mk_state g w c b) (mk_state g w c b1) -.*) +(* Executing the previous command has the effect of opening two subgoals, +corresponding to the two cases b=east and b=west: you may switch from one to the +other by using the hyperlinks over the top of the goal window. +Both goals can be closed by trivial computations, so we may use // as usual. +If we had to treat each subgoal in a different way, we should focus on each of +them in turn, in a way that will be described at the end of this section. +*) -(* -inductive move : state →state → Type[0] ≝ -| move_goat: ∀s.∀h:is_movable s goat. move s (mk_state (change_side (goat_pos s)) (wolf_pos s) - (cabbage_pos s) (change_side (boat_pos s))) -| move_wolf: ∀s.∀h:is_movable s wolf. move s (mk_state (goat_pos s) (change_side (wolf_pos s)) - (cabbage_pos s) (change_side (boat_pos s))) -| move_cabbage: ∀s.∀h:is_movable s cabbage. move s (mk_state (goat_pos s) (wolf_pos s) - (change_side (cabbage_pos s)) (change_side (boat_pos s))) -| move_boat: ∀s.move s (mk_state (goat_pos s) (wolf_pos s) - (cabbage_pos s) (change_side (boat_pos s))). +// qed. + +(* Instead of working with functions, it is sometimes convenient to work with +predicates. For instance, instead of defining a function computing the opposite +bank, we could declare a predicate stating when two banks are opposite to each +other. Only two cases are possible, leading naturally two the following +definition: *) -(* -definition legal_state ≝ λs. - goat_pos s = boat_pos s ∨ - (goat_pos s = change_side (cabbage_pos s) ∧ - goat_pos s = change_side (wolf_pos s)). *) - -inductive legal_state : state → Prop ≝ -| with_boat : ∀g,w,c.legal_state (mk_state g w c g) -| opposite_side : ∀g,b. - legal_state (mk_state g (change_side g) (change_side g) b). - -inductive reachable : state → state → Prop ≝ -| one : ∀s1,s2.move s1 s2 → reachable s1 s2 -| more : ∀s1,s2,s3. reachable s1 s2 → legal_state s2 → move s2 s3 → - reachable s1 s3. - -definition end ≝ mk_state west west west west. -definition second ≝ mk_state west east east west. -definition third ≝ mk_state west east east east. -definition fourth ≝ mk_state west west east west. -definition fifth ≝ mk_state east west east east. -definition sixth ≝ mk_state east west west west. -definition seventh ≝ mk_state east west west east. - -lemma problem: reachable start end. -normalize /8/ qed. - -lemma problem: reachable start end. +inductive opp : a href="cic:/matita/tutorial/chapter1/bank.ind(1,0,0)"bank/a → a href="cic:/matita/tutorial/chapter1/bank.ind(1,0,0)"bank/a → Prop ≝ +| east_west : opp a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a +| west_east : opp a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a. + +(* In precisely the same way as "bank" is the smallest type containing east and +west, opp is the smallest predicate containing the two sub-cases east_west and +weast_east. If you have some familiarity with Prolog, you may look at opp as the +predicate definined by the two clauses - in this case, the two facts - +(opp east west) and (opp west east). +At the end of this section we shall prove that forall a and b, + (opp a b) iff a = opposite b. +For the moment, it is time to proceed with our formalization of the farmer's +problem. +A state of the system is defined by the position of four item: the goat, the +wolf, the cabbage, and the boat. The simplest way to declare such a data type +is to use a record. +*) + +record state : Type[0] ≝ + {goat_pos : a href="cic:/matita/tutorial/chapter1/bank.ind(1,0,0)"bank/a; + wolf_pos : a href="cic:/matita/tutorial/chapter1/bank.ind(1,0,0)"bank/a; + cabbage_pos: a href="cic:/matita/tutorial/chapter1/bank.ind(1,0,0)"bank/a; + boat_pos : a href="cic:/matita/tutorial/chapter1/bank.ind(1,0,0)"bank/a}. + +(* When you define a record named foo, the system automatically defines a record +constructor named mk_foo. To construct a new record you pass as arguments to +mk_foo the values of the record fields *) + +definition start ≝ a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a. +definition end ≝ a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a. + +(* We must now define the possible moves. A natural way to do it is in the form +of a relation (a binary predicate) over states. *) + +inductive move : a href="cic:/matita/tutorial/chapter1/state.ind(1,0,0)"state/a → a href="cic:/matita/tutorial/chapter1/state.ind(1,0,0)"state/a → Prop ≝ +| move_goat: ∀g,g1,w,c. a href="cic:/matita/tutorial/chapter1/opp.ind(1,0,0)"opp/a g g1 → move (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c g) (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g1 w c g1) +| move_wolf: ∀g,w,w1,c. a href="cic:/matita/tutorial/chapter1/opp.ind(1,0,0)"opp/a w w1 → move (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c w) (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w1 c w1) +| move_cabbage: ∀g,w,c,c1.a href="cic:/matita/tutorial/chapter1/opp.ind(1,0,0)"opp/a c c1 → move (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c c) (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c1 c1) +| move_boat: ∀g,w,c,b,b1. a href="cic:/matita/tutorial/chapter1/opp.ind(1,0,0)"opp/a b b1 → move (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c b) (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c b1). + +(* A state is safe if either the goat is on the same bank of the boat, or both +the wolf and the cabbage are on the opposite bank of the goat. *) + +inductive safe_state : a href="cic:/matita/tutorial/chapter1/state.ind(1,0,0)"state/a → Prop ≝ +| with_boat : ∀g,w,c.safe_state (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g w c g) +| opposite_side : ∀g,g1,b.a href="cic:/matita/tutorial/chapter1/opp.ind(1,0,0)"opp/a g g1 → safe_state (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a g g1 g1 b). + +(* Finally, a state y is reachable from x if either there is a single move +leading from x to y, or there is a safe state z such that z is reachable from x +and there is a move leading from z to y *) + +inductive reachable : a href="cic:/matita/tutorial/chapter1/state.ind(1,0,0)"state/a → a href="cic:/matita/tutorial/chapter1/state.ind(1,0,0)"state/a → Prop ≝ +| one : ∀x,y.a href="cic:/matita/tutorial/chapter1/move.ind(1,0,0)"move/a x y → reachable x y +| more : ∀x,z,y. span style="text-decoration: underline;"/spanreachable x z → a href="cic:/matita/tutorial/chapter1/safe_state.ind(1,0,0)"safe_state/a z → span style="text-decoration: underline;"/spana href="cic:/matita/tutorial/chapter1/move.ind(1,0,0)"move/a z y → reachable x y. + +(* Remarkably, Matita is now able to solve the problem by itslef, provided +you allow automation to exploit more resources. The command /n/ is similar to +//, where n is a measure of this complexity: in particular it is a bound to +the depth of the expected proof tree (more precisely, to the number of nested +applicative nodes). In this case, there is a solution in six moves, and we +need a few more applications to handle reachability, and side conditions. +The magic number to let automation work is, in this case, 9. *) + +lemma problem: a href="cic:/matita/tutorial/chapter1/reachable.ind(1,0,0)"reachable/a a href="cic:/matita/tutorial/chapter1/start.def(1)"start/a a href="cic:/matita/tutorial/chapter1/end.def(1)"end/a. +normalize /9/ qed. + +(* Let us now try to derive the proof in a more interactive way. Of course, we +expect to need several moves to transfer all items from a bank to the other, so +we should start our proof by applying "more". +*) + +lemma problem1: a href="cic:/matita/tutorial/chapter1/reachable.ind(1,0,0)"reachable/a a href="cic:/matita/tutorial/chapter1/start.def(1)"start/a a href="cic:/matita/tutorial/chapter1/end.def(1)"end/a. normalize -@more [4: @move_goat || 3: //] -@more [4: @move_boat || 3: //] -@more [4: @move_wolf || 3: //] -@more [4: @move_goat || 3: //] -@more [4: @move_cabbage || 3: //] -@more [4: @move_boat || 3: //] -@one // -qed. +@a href="cic:/matita/tutorial/chapter1/reachable.con(0,2,0)"more/a +(* We have now four open subgoals: + + X : STATE + Y : reachable [east,east,east,east] X + W : safe_state X + Z : move X [west,west,west,west] + +That is, we must guess a state X, such that this is reachable from start, it is +safe, and there is a move leading from X to end. All goals are active, that is +emphasized by the fact that they are all red. Any command typed by the user is +normally applied in parallel to all active goals, but clearly we must proceed +here is a different way for each of them. The way to do it, is by structuring +the script using the following syntax: [...|... |...|...] where we typically have +as many cells inside the brackets as the number of the active subgoals. The +interesting point is that we can associate with the three symbol "[", "|" and +"]" a small-step semantics that allow to execute them individually. In particular + +- the operator "[" opens a new focusing section for the currently active goals, + and focus on the first of them +- the operator "|" shift the focus to the next goal +- the operator "]" close the focusing section, falling back to the previous level + and adding to it all remaining goals not yet closed + +Let us see the effect of the "[" on our proof: +*) + + [ + +(* As you see, only the first goal has now the focus on. Moreover, all goals got +a progressive numerical label, to help designating them, if needed. +We can now proceed in several possible ways. The most straightforward way is to +provide the intermediate state, that is [east,west,west,east]. We can do it, by +just applying this term. *) + + @(a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a) + +(* This application closes the goal; at present, no goal has the focus on. +In order to act on the next goal, we must focus on it using the "|" operator. In +this case, we would like to skip the next goal, and focus on the trivial third +subgoal: a simple way to do it, is by retyping "|". The proof that +[east,west,west,east] is safe is trivial and can be done with //.*) + + || // (* -definition move_item ≝ λs.λi:movable_item s. -match (elem ? i) with -[ goat ⇒ mk_state (change_side (goat_pos s)) (wolf_pos s) - (cabbage_pos s) (change_side (boat_pos s)) -| wolf ⇒ mk_state (goat_pos s) (change_side (wolf_pos s)) - (cabbage_pos s) (change_side (boat_pos s)) -| cabbage ⇒ mk_state (goat_pos s) (wolf_pos s) - (change_side (cabbage_pos s)) (change_side (boat_pos s)) -| boat ⇒ mk_state (goat_pos s) (wolf_pos s) - (cabbage_pos s) (change_side (boat_pos s)) -]. - -definition legal_state ≝ λs. - cabbage_pos s = goat_pos s ∨ - goat_pos s = wolf_pos s → goat_pos s = boat_pos s. - -definition legal_step ≝ λs1,s2. - ∃i.move_item s1 i = s2. - -inductive reachable : state → state → Prop ≝ -| nil : ∀s.reachable s s -| step : ∀s1,s2,s3. legal_step s1 s2 → legal_state s2 → reachable s2 s3 → - reachable s1 s3. - -definition second ≝ mk_state west east east west. -definition end ≝ mk_state west west west west. - -lemma goat_move: ∀s.legal_step s (mk_state (change_side (goat_pos s)) (wolf_pos s) - (cabbage_pos s) (change_side (boat_pos s))). - -lemma problem: reachable start second. -@step [| @ex_intro // [| @move_goat ] // | //] - - [|| @move_goat //| // - |normalize \ No newline at end of file +We then advance to the next goal, namely the fact that there is a move from +[east,west,west,east] to [west,west,west,west]; this is trivial too, but it +requires /2/ since move_goat opens an additional subgoal. By applying "]" we +refocus on the skipped goal, going back to a situation similar to the one we +started with. *) + + | /2/ ] + +(* Let us perform the next step, namely moving back the boat, in a sligtly +different way. The more operation expects as second argument the new +intermediate state, hence instead of applying more we can apply its +instantiation (more ? (mk_state east west west west)). The question mark is +an implicit argument to be guessed by the system. *) + +span style="text-decoration: underline;"/span@(a href="cic:/matita/tutorial/chapter1/reachable.con(0,2,0)"more/a ? (a href="cic:/matita/tutorial/chapter1/state.con(0,1,0)"mk_state/a a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a)) + +(* All subgoals are focused (they are all red). Two of them are trivial; we +just call automation at level 2, and it will close both of them, taking +us to the third move.*) + + /2/ + +(* We must now move the wolf. Suppose that, instead of specifying the next state +we prefer to specify the move. In the style of the previous move, this amounts +to apply the term (more … (move_wolf … )) The dots are a convenient syntax to +express an arbitrary number of implicit parameters. *) + +@(a href="cic:/matita/tutorial/chapter1/reachable.con(0,2,0)"more/a … (a href="cic:/matita/tutorial/chapter1/move.con(0,2,0)"move_wolf/a … )) + +(* Unfortunately this is not enough to fully instantiate the intermediate state. +All goals still depend from an unknown bank B. Calling automation in this situation +does not help since it refuses to close subgoals instantiating other - still +open - goals. A way to proceed is to focus on the fourth "bank" subgoal, +instantiating it with the appropriate value, in this case east; then we close +focusing, and call automation. To focus on subgoal i, instead of typing "|" several +times we can just write "i:" as described by the following command. *) + + [4: @a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/aspan style="text-decoration: underline;"/span] /2/ + +(* An alternative, simpler way is to instantiate each move with the destination +bank. Let us complete the proof adopting this style. *) + +@(a href="cic:/matita/tutorial/chapter1/reachable.con(0,2,0)"more/a … (a href="cic:/matita/tutorial/chapter1/move.con(0,1,0)"move_goat/a a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a … )) /2/ +@(a href="cic:/matita/tutorial/chapter1/reachable.con(0,2,0)"more/a … (a href="cic:/matita/tutorial/chapter1/move.con(0,3,0)"move_cabbage/a ?? a href="cic:/matita/tutorial/chapter1/bank.con(0,1,0)"east/a … )) /2/ +@(a href="cic:/matita/tutorial/chapter1/reachable.con(0,2,0)"more/a … (a href="cic:/matita/tutorial/chapter1/move.con(0,4,0)"move_boat/a ??? a href="cic:/matita/tutorial/chapter1/bank.con(0,2,0)"west/a …)) /2/ +@a href="cic:/matita/tutorial/chapter1/reachable.con(0,1,0)"one/a /2/ +qed. \ No newline at end of file -- 2.39.2