refactor: use Atom.Vec (a VecI32) for atom vectors

This commit is contained in:
Simon Cruanes 2021-08-31 09:30:05 -04:00
parent 1877c00c02
commit 4a2367b1bd
No known key found for this signature in database
GPG key ID: 4AC01D0849AA62B6
4 changed files with 68 additions and 61 deletions

View file

@ -44,6 +44,7 @@ module Make(Plugin : PLUGIN)
val abs : t -> t val abs : t -> t
val pa : var -> t val pa : var -> t
val na : var -> t val na : var -> t
module AVec : Vec_sig.S with type elt := t
end = struct end = struct
include Int_id.Make() include Int_id.Make()
let[@inline] neg i = (i lxor 1) let[@inline] neg i = (i lxor 1)
@ -54,6 +55,7 @@ module Make(Plugin : PLUGIN)
let[@inline] abs a = a land (lnot 1) let[@inline] abs a = a land (lnot 1)
let[@inline] var a = Var0.of_int_unsafe (a lsr 1) let[@inline] var a = Var0.of_int_unsafe (a lsr 1)
let[@inline] na v = (((v:var:>int) lsl 1) lor 1) let[@inline] na v = (((v:var:>int) lsl 1) lor 1)
module AVec = VecI32
end end
type atom = Atom0.t type atom = Atom0.t
@ -73,8 +75,11 @@ module Make(Plugin : PLUGIN)
| Bcp of clause | Bcp of clause
| Bcp_lazy of clause lazy_t | Bcp_lazy of clause lazy_t
(** Vector of clauses *) module AVec = Atom0.AVec
(** Vector of atoms *)
module CVec = Clause0.CVec module CVec = Clause0.CVec
(** Vector of clauses *)
(* ### stores ### *) (* ### stores ### *)
@ -535,9 +540,11 @@ module Make(Plugin : PLUGIN)
clauses_hyps : CVec.t; clauses_hyps : CVec.t;
(* clauses added by the user, never removed *) (* clauses added by the user, never removed *)
clauses_learnt : CVec.t; clauses_learnt : clause Vec.t;
(* learnt clauses (tautologies true at any time, whatever the user level). (* learnt clauses (tautologies true at any time, whatever the user level).
GC'd regularly. *) GC'd regularly.
Use a [Vec] so we can sort it.
TODO: when we can sort any vec, come back to that. *)
clauses_to_add : CVec.t; clauses_to_add : CVec.t;
(* Clauses either assumed or pushed by the theory, waiting to be added. *) (* Clauses either assumed or pushed by the theory, waiting to be added. *)
@ -558,13 +565,13 @@ module Make(Plugin : PLUGIN)
if some theory wants atoms to be decided on (for theory combination), if some theory wants atoms to be decided on (for theory combination),
store them here. *) store them here. *)
trail : atom Vec.t; trail : AVec.t;
(* decision stack + propagated elements (atoms or assignments). *) (* decision stack + propagated elements (atoms or assignments). *)
var_levels : int Vec.t; var_levels : VecI32.t;
(* decision levels in [trail] *) (* decision levels in [trail] *)
mutable assumptions: atom Vec.t; mutable assumptions: AVec.t;
(* current assumptions *) (* current assumptions *)
mutable th_head : int; mutable th_head : int;
@ -641,7 +648,7 @@ module Make(Plugin : PLUGIN)
next_decisions = []; next_decisions = [];
clauses_hyps = CVec.create(); clauses_hyps = CVec.create();
clauses_learnt = CVec.create(); clauses_learnt = Vec.create();
clauses_to_add = CVec.create (); clauses_to_add = CVec.create ();
clauses_dead = CVec.create(); clauses_dead = CVec.create();
@ -654,9 +661,9 @@ module Make(Plugin : PLUGIN)
th_head = 0; th_head = 0;
elt_head = 0; elt_head = 0;
trail = Vec.create (); trail = AVec.create ();
var_levels = Vec.create(); var_levels = VecI32.create();
assumptions= Vec.create(); assumptions= AVec.create();
order = H.create store; order = H.create store;
@ -746,7 +753,7 @@ module Make(Plugin : PLUGIN)
CVec.iter clauses ~f CVec.iter clauses ~f
end end
let[@inline] decision_level st = Vec.size st.var_levels let[@inline] decision_level st = VecI32.size st.var_levels
let[@inline] nb_clauses st = CVec.size st.clauses_hyps let[@inline] nb_clauses st = CVec.size st.clauses_hyps
let stat self = self.stat let stat self = self.stat
@ -803,7 +810,7 @@ module Make(Plugin : PLUGIN)
(* iterate on all learnt clauses, pool included *) (* iterate on all learnt clauses, pool included *)
let iter_clauses_learnt_ (self:t) ~f : unit = let iter_clauses_learnt_ (self:t) ~f : unit =
CVec.iter self.clauses_learnt ~f; Vec.iter f self.clauses_learnt;
Vec.iter (fun pool -> Clause_pool.iter pool ~f) self.clause_pools; Vec.iter (fun pool -> Clause_pool.iter pool ~f) self.clause_pools;
() ()
@ -915,9 +922,9 @@ module Make(Plugin : PLUGIN)
i.e we have indeed reached a propagation fixpoint before making i.e we have indeed reached a propagation fixpoint before making
a new decision *) a new decision *)
let new_decision_level st = let new_decision_level st =
assert (st.th_head = Vec.size st.trail); assert (st.th_head = AVec.size st.trail);
assert (st.elt_head = Vec.size st.trail); assert (st.elt_head = AVec.size st.trail);
Vec.push st.var_levels (Vec.size st.trail); VecI32.push st.var_levels (AVec.size st.trail);
Plugin.push_level st.th; Plugin.push_level st.th;
() ()
@ -950,13 +957,13 @@ module Make(Plugin : PLUGIN)
) else ( ) else (
Log.debugf 5 (fun k -> k "(@[sat.cancel-until %d@])" lvl); Log.debugf 5 (fun k -> k "(@[sat.cancel-until %d@])" lvl);
(* We set the head of the solver and theory queue to what it was. *) (* We set the head of the solver and theory queue to what it was. *)
let head = ref (Vec.get self.var_levels lvl) in let head = ref (VecI32.get self.var_levels lvl) in
self.elt_head <- !head; self.elt_head <- !head;
self.th_head <- !head; self.th_head <- !head;
(* Now we need to cleanup the vars that are not valid anymore (* Now we need to cleanup the vars that are not valid anymore
(i.e to the right of elt_head in the queue. *) (i.e to the right of elt_head in the queue. *)
for c = self.elt_head to Vec.size self.trail - 1 do for c = self.elt_head to AVec.size self.trail - 1 do
let a = Vec.get self.trail c in let a = AVec.get self.trail c in
(* Atom literal is unassigned, we nedd to add it back to (* Atom literal is unassigned, we nedd to add it back to
the heap of potentially assignable literals, unless it has the heap of potentially assignable literals, unless it has
a level lower than [lvl], in which case we just move it back. *) a level lower than [lvl], in which case we just move it back. *)
@ -965,7 +972,7 @@ module Make(Plugin : PLUGIN)
(* It is a late propagation, which has a level (* It is a late propagation, which has a level
lower than where we backtrack, so we just move it to the head lower than where we backtrack, so we just move it to the head
of the queue, to be propagated again. *) of the queue, to be propagated again. *)
Vec.set self.trail !head a; AVec.set self.trail !head a;
head := !head + 1 head := !head + 1
) else ( ) else (
(* it is a result of bolean propagation, or a semantic propagation (* it is a result of bolean propagation, or a semantic propagation
@ -982,8 +989,8 @@ module Make(Plugin : PLUGIN)
let n = decision_level self - lvl in let n = decision_level self - lvl in
assert (n>0); assert (n>0);
(* Resize the vectors according to their new size. *) (* Resize the vectors according to their new size. *)
Vec.shrink self.trail !head; AVec.shrink self.trail !head;
Vec.shrink self.var_levels lvl; VecI32.shrink self.var_levels lvl;
Plugin.pop_levels self.th n; Plugin.pop_levels self.th n;
(* TODO: for scoped clause pools, backtrack them *) (* TODO: for scoped clause pools, backtrack them *)
self.next_decisions <- []; self.next_decisions <- [];
@ -1070,10 +1077,10 @@ module Make(Plugin : PLUGIN)
Atom.set_is_true store a true; Atom.set_is_true store a true;
Var.set_level store (Atom.var a) lvl; Var.set_level store (Atom.var a) lvl;
Var.set_reason store (Atom.var a) (Some reason); Var.set_reason store (Atom.var a) (Some reason);
Vec.push self.trail a; AVec.push self.trail a;
Log.debugf 20 Log.debugf 20
(fun k->k "(@[sat.enqueue[%d]@ %a@])" (fun k->k "(@[sat.enqueue[%d]@ %a@])"
(Vec.size self.trail) (Atom.debug store) a); (AVec.size self.trail) (Atom.debug store) a);
() ()
(* swap elements of array *) (* swap elements of array *)
@ -1156,7 +1163,7 @@ module Make(Plugin : PLUGIN)
let continue = ref true in let continue = ref true in
let blevel = ref 0 in let blevel = ref 0 in
let c = ref (Some c_clause) in (* current clause to analyze/resolve *) let c = ref (Some c_clause) in (* current clause to analyze/resolve *)
let tr_ind = ref (Vec.size self.trail - 1) in (* pointer in trail *) let tr_ind = ref (AVec.size self.trail - 1) in (* pointer in trail *)
(* conflict level *) (* conflict level *)
assert (decision_level self > 0); assert (decision_level self > 0);
@ -1216,7 +1223,7 @@ module Make(Plugin : PLUGIN)
(* look for the next node to expand *) (* look for the next node to expand *)
while while
let a = Vec.get self.trail !tr_ind in let a = AVec.get self.trail !tr_ind in
Log.debugf 30 Log.debugf 30
(fun k -> k "(@[sat.analyze-conflict.at-trail-elt@ %a@])" (Atom.debug store) a); (fun k -> k "(@[sat.analyze-conflict.at-trail-elt@ %a@])" (Atom.debug store) a);
(not (Var.marked store (Atom.var a))) || (not (Var.marked store (Atom.var a))) ||
@ -1224,7 +1231,7 @@ module Make(Plugin : PLUGIN)
do do
decr tr_ind; decr tr_ind;
done; done;
let p = Vec.get self.trail !tr_ind in let p = AVec.get self.trail !tr_ind in
decr pathC; decr pathC;
decr tr_ind; decr tr_ind;
match !pathC, Atom.reason store p with match !pathC, Atom.reason store p with
@ -1436,7 +1443,7 @@ module Make(Plugin : PLUGIN)
(* no watch lit found *) (* no watch lit found *)
if Atom.is_false store first then ( if Atom.is_false store first then (
(* clause is false *) (* clause is false *)
self.elt_head <- Vec.size self.trail; self.elt_head <- AVec.size self.trail;
raise_notrace (Conflict c) raise_notrace (Conflict c)
) else ( ) else (
Stat.incr self.n_propagations; Stat.incr self.n_propagations;
@ -1475,7 +1482,7 @@ module Make(Plugin : PLUGIN)
exception Th_conflict of Clause.t exception Th_conflict of Clause.t
let[@inline] slice_get st i = Vec.get st.trail i let[@inline] slice_get st i = AVec.get st.trail i
let acts_add_clause self ?(keep=false) (l:lit list) (dp:dproof): unit = let acts_add_clause self ?(keep=false) (l:lit list) (dp:dproof): unit =
let atoms = List.rev_map (make_atom_ self) l in let atoms = List.rev_map (make_atom_ self) l in
@ -1483,7 +1490,7 @@ module Make(Plugin : PLUGIN)
let c = Clause.make_l self.store ~removable atoms in let c = Clause.make_l self.store ~removable atoms in
Proof.with_proof self.proof dp; Proof.with_proof self.proof dp;
Log.debugf 5 (fun k->k "(@[sat.th.add-clause@ %a@])" (Clause.debug self.store) c); Log.debugf 5 (fun k->k "(@[sat.th.add-clause@ %a@])" (Clause.debug self.store) c);
Vec.push self.clauses_to_add c CVec.push self.clauses_to_add c
let acts_add_clause_in_pool self ~pool (l:lit list) (dp:dproof): unit = let acts_add_clause_in_pool self ~pool (l:lit list) (dp:dproof): unit =
let atoms = List.rev_map (make_atom_ self) l in let atoms = List.rev_map (make_atom_ self) l in
@ -1555,8 +1562,8 @@ module Make(Plugin : PLUGIN)
) )
let[@specialise] acts_iter self ~full head f : unit = let[@specialise] acts_iter self ~full head f : unit =
for i = (if full then 0 else head) to Vec.size self.trail-1 do for i = (if full then 0 else head) to AVec.size self.trail-1 do
let a = Vec.get self.trail i in let a = AVec.get self.trail i in
f (Atom.lit self.store a); f (Atom.lit self.store a);
done done
@ -1615,7 +1622,7 @@ module Make(Plugin : PLUGIN)
need to inform the theory of those assumptions, so it can do its job. need to inform the theory of those assumptions, so it can do its job.
@return the conflict clause, if the theory detects unsatisfiability *) @return the conflict clause, if the theory detects unsatisfiability *)
let rec theory_propagate self : clause option = let rec theory_propagate self : clause option =
assert (self.elt_head = Vec.size self.trail); assert (self.elt_head = AVec.size self.trail);
assert (self.th_head <= self.elt_head); assert (self.th_head <= self.elt_head);
if self.th_head = self.elt_head then ( if self.th_head = self.elt_head then (
None (* fixpoint/no propagation *) None (* fixpoint/no propagation *)
@ -1638,13 +1645,13 @@ module Make(Plugin : PLUGIN)
(* First, treat the stack of lemmas added by the theory, if any *) (* First, treat the stack of lemmas added by the theory, if any *)
flush_clauses st; flush_clauses st;
(* Now, check that the situation is sane *) (* Now, check that the situation is sane *)
assert (st.elt_head <= Vec.size st.trail); assert (st.elt_head <= AVec.size st.trail);
if st.elt_head = Vec.size st.trail then ( if st.elt_head = AVec.size st.trail then (
theory_propagate st theory_propagate st
) else ( ) else (
match match
while st.elt_head < Vec.size st.trail do while st.elt_head < AVec.size st.trail do
let a = Vec.get st.trail st.elt_head in let a = AVec.get st.trail st.elt_head in
propagate_atom st a; propagate_atom st a;
st.elt_head <- st.elt_head + 1; st.elt_head <- st.elt_head + 1;
done; done;
@ -1659,11 +1666,11 @@ module Make(Plugin : PLUGIN)
Log.debugf 5 (fun k->k "(@[sat.analyze-final@ :lit %a@])" (Atom.debug store) a); Log.debugf 5 (fun k->k "(@[sat.analyze-final@ :lit %a@])" (Atom.debug store) a);
assert (Atom.is_false store a); assert (Atom.is_false store a);
let core = ref [a] in let core = ref [a] in
let idx = ref (Vec.size self.trail - 1) in let idx = ref (AVec.size self.trail - 1) in
Var.mark store (Atom.var a); Var.mark store (Atom.var a);
let seen = ref [Atom.var a] in let seen = ref [Atom.var a] in
while !idx >= 0 do while !idx >= 0 do
let a' = Vec.get self.trail !idx in let a' = AVec.get self.trail !idx in
if Var.marked store (Atom.var a') then ( if Var.marked store (Atom.var a') then (
match Atom.reason store a' with match Atom.reason store a' with
| Some Decision -> core := a' :: !core | Some Decision -> core := a' :: !core
@ -1715,8 +1722,8 @@ module Make(Plugin : PLUGIN)
(* first, mark clauses used on the trail, we cannot GC them. (* first, mark clauses used on the trail, we cannot GC them.
TODO: once we use DRUP, we can avoid marking level-0 explanations TODO: once we use DRUP, we can avoid marking level-0 explanations
as they will never participate in resolution. *) as they will never participate in resolution. *)
Vec.iter AVec.iter
(fun a -> ~f:(fun a ->
match Atom.reason store a with match Atom.reason store a with
| Some (Bcp c) -> Clause.set_marked store c true | Some (Bcp c) -> Clause.set_marked store c true
| Some (Bcp_lazy lc) when Lazy.is_val lc -> | Some (Bcp_lazy lc) when Lazy.is_val lc ->
@ -1794,9 +1801,9 @@ module Make(Plugin : PLUGIN)
| atom :: tl -> | atom :: tl ->
self.next_decisions <- tl; self.next_decisions <- tl;
pick_branch_aux self atom pick_branch_aux self atom
| [] when decision_level self < Vec.size self.assumptions -> | [] when decision_level self < AVec.size self.assumptions ->
(* use an assumption *) (* use an assumption *)
let a = Vec.get self.assumptions (decision_level self) in let a = AVec.get self.assumptions (decision_level self) in
if Atom.is_true self.store a then ( if Atom.is_true self.store a then (
new_decision_level self; (* pseudo decision level, [a] is already true *) new_decision_level self; (* pseudo decision level, [a] is already true *)
pick_branch_lit self pick_branch_lit self
@ -1838,7 +1845,7 @@ module Make(Plugin : PLUGIN)
(match st.on_conflict with Some f -> f st confl | None -> ()); (match st.on_conflict with Some f -> f st confl | None -> ());
| None -> (* No Conflict *) | None -> (* No Conflict *)
assert (st.elt_head = Vec.size st.trail); assert (st.elt_head = AVec.size st.trail);
assert (st.elt_head = st.th_head); assert (st.elt_head = st.th_head);
if n_of_conflicts > 0 && !conflictC >= n_of_conflicts then ( if n_of_conflicts > 0 && !conflictC >= n_of_conflicts then (
Log.debug 1 "(sat.restarting)"; Log.debug 1 "(sat.restarting)";
@ -1849,7 +1856,7 @@ module Make(Plugin : PLUGIN)
(* if decision_level() = 0 then simplify (); *) (* if decision_level() = 0 then simplify (); *)
if n_of_learnts > 0 && if n_of_learnts > 0 &&
Vec.size st.clauses_learnt - Vec.size st.trail > n_of_learnts then ( Vec.size st.clauses_learnt - AVec.size st.trail > n_of_learnts then (
reduce_clause_db st n_of_learnts; reduce_clause_db st n_of_learnts;
); );
@ -1874,7 +1881,7 @@ module Make(Plugin : PLUGIN)
(* fixpoint of propagation and decisions until a model is found, or a (* fixpoint of propagation and decisions until a model is found, or a
conflict is reached *) conflict is reached *)
let solve_ (self:t) : unit = let solve_ (self:t) : unit =
Log.debugf 5 (fun k->k "(@[sat.solve :assms %d@])" (Vec.size self.assumptions)); Log.debugf 5 (fun k->k "(@[sat.solve :assms %d@])" (AVec.size self.assumptions));
check_unsat_ self; check_unsat_ self;
try try
flush_clauses self; (* add initial clauses *) flush_clauses self; (* add initial clauses *)
@ -1888,13 +1895,13 @@ module Make(Plugin : PLUGIN)
n_of_conflicts := !n_of_conflicts *. restart_inc; n_of_conflicts := !n_of_conflicts *. restart_inc;
n_of_learnts := !n_of_learnts *. learntsize_inc n_of_learnts := !n_of_learnts *. learntsize_inc
| E_sat -> | E_sat ->
assert (self.elt_head = Vec.size self.trail && assert (self.elt_head = AVec.size self.trail &&
Vec.is_empty self.clauses_to_add && CVec.is_empty self.clauses_to_add &&
self.next_decisions=[]); self.next_decisions=[]);
begin match Plugin.final_check self.th (full_slice self) with begin match Plugin.final_check self.th (full_slice self) with
| () -> | () ->
if self.elt_head = Vec.size self.trail && if self.elt_head = AVec.size self.trail &&
Vec.is_empty self.clauses_to_add && CVec.is_empty self.clauses_to_add &&
self.next_decisions = [] self.next_decisions = []
then ( then (
raise_notrace E_sat raise_notrace E_sat
@ -1909,7 +1916,7 @@ module Make(Plugin : PLUGIN)
(Clause.debug self.store) c); (Clause.debug self.store) c);
Stat.incr self.n_conflicts; Stat.incr self.n_conflicts;
(match self.on_conflict with Some f -> f self c | None -> ()); (match self.on_conflict with Some f -> f self c | None -> ());
Vec.push self.clauses_to_add c; CVec.push self.clauses_to_add c;
flush_clauses self; flush_clauses self;
end; end;
end end
@ -1925,7 +1932,7 @@ module Make(Plugin : PLUGIN)
(Proof.emit_input_clause (Iter.of_list l)); (Proof.emit_input_clause (Iter.of_list l));
Log.debugf 10 (fun k -> k "(@[sat.assume-clause@ @[<hov 2>%a@]@])" Log.debugf 10 (fun k -> k "(@[sat.assume-clause@ @[<hov 2>%a@]@])"
(Clause.debug self.store) c); (Clause.debug self.store) c);
Vec.push self.clauses_to_add c) CVec.push self.clauses_to_add c)
cnf cnf
let[@inline] theory st = st.th let[@inline] theory st = st.th
@ -1949,8 +1956,8 @@ module Make(Plugin : PLUGIN)
"(@[<v>sat.full-state :res %s - Full summary:@,@[<hov 2>Trail:@\n%a@]@,\ "(@[<v>sat.full-state :res %s - Full summary:@,@[<hov 2>Trail:@\n%a@]@,\
@[<hov 2>Hyps:@\n%a@]@,@[<hov 2>Lemmas:@\n%a@]@,@]@." @[<hov 2>Hyps:@\n%a@]@,@[<hov 2>Lemmas:@\n%a@]@,@]@."
status status
(Vec.pp ~sep:"" @@ Atom.debug self.store) self.trail (AVec.pp @@ Atom.debug self.store) self.trail
(Vec.pp ~sep:"" @@ Clause.debug self.store) self.clauses_hyps (CVec.pp @@ Clause.debug self.store) self.clauses_hyps
(Vec.pp ~sep:"" @@ Clause.debug self.store) self.clauses_learnt) (Vec.pp ~sep:"" @@ Clause.debug self.store) self.clauses_learnt)
let mk_sat (self:t) : Lit.t Solver_intf.sat_state = let mk_sat (self:t) : Lit.t Solver_intf.sat_state =
@ -1958,7 +1965,7 @@ module Make(Plugin : PLUGIN)
let t = self.trail in let t = self.trail in
let module M = struct let module M = struct
type lit = Lit.t type lit = Lit.t
let iter_trail f = Vec.iter (fun a -> f (Atom.lit self.store a)) t let iter_trail f = AVec.iter ~f:(fun a -> f (Atom.lit self.store a)) t
let[@inline] eval f = eval self (make_atom_ self f) let[@inline] eval f = eval self (make_atom_ self f)
let[@inline] eval_level f = eval_level self (make_atom_ self f) let[@inline] eval_level f = eval_level self (make_atom_ self f)
end in end in
@ -2019,11 +2026,11 @@ module Make(Plugin : PLUGIN)
let solve ?(assumptions=[]) (self:t) : res = let solve ?(assumptions=[]) (self:t) : res =
cancel_until self 0; cancel_until self 0;
Vec.clear self.assumptions; AVec.clear self.assumptions;
List.iter List.iter
(fun lit -> (fun lit ->
let a = make_atom_ self lit in let a = make_atom_ self lit in
Vec.push self.assumptions a) AVec.push self.assumptions a)
assumptions; assumptions;
try try
solve_ self; solve_ self;

View file

@ -93,8 +93,8 @@ let[@inline] iteri ~f self =
let[@inline] to_iter self k = iter ~f:k self let[@inline] to_iter self k = iter ~f:k self
let pp out self = let pp ppx out self =
Format.fprintf out "[@["; Format.fprintf out "[@[";
iteri self ~f:(fun i x -> if i>0 then Format.fprintf out ",@ "; Format.pp_print_int out x); iteri self ~f:(fun i x -> if i>0 then Format.fprintf out ",@ "; ppx out x);
Format.fprintf out "@]]" Format.fprintf out "@]]"

View file

@ -80,9 +80,9 @@ let[@inline] iteri ~f self =
let[@inline] to_iter self k = iter ~f:k self let[@inline] to_iter self k = iter ~f:k self
let pp out self = let pp ppx out self =
Format.fprintf out "[@["; Format.fprintf out "[@[";
iteri self iteri self
~f:(fun i x -> if i>0 then Format.fprintf out ",@ "; Format.fprintf out "%.3f" x); ~f:(fun i x -> if i>0 then Format.fprintf out ",@ "; ppx out x);
Format.fprintf out "@]]" Format.fprintf out "@]]"

View file

@ -32,5 +32,5 @@ module type S = sig
val to_iter : t -> elt Iter.t val to_iter : t -> elt Iter.t
val pp : t CCFormat.printer val pp : elt CCFormat.printer -> t CCFormat.printer
end end