A comparison with LP (I) Example (Prolog): q(X, Y, Z) :- Z = f(X, - - PowerPoint PPT Presentation

a comparison with lp i
SMART_READER_LITE
LIVE PREVIEW

A comparison with LP (I) Example (Prolog): q(X, Y, Z) :- Z = f(X, - - PowerPoint PPT Presentation

A comparison with LP (I) Example (Prolog): q(X, Y, Z) :- Z = f(X, Y). | ?- q(3, 4, Z). Z = f(3,4) | ?- q(X, Y, f(3,4)). X = 3, Y = 4 | ?- q(X, Y, Z). Z = f(X,Y) Example (Prolog): p(X, Y, Z) :- Z is X + Y. | ?- p(3, 4, Z). Z = 7 | ?-


slide-1
SLIDE 1

A comparison with LP (I)

  • Example (Prolog): q(X, Y, Z) :- Z = f(X, Y).

| ?- q(3, 4, Z). Z = f(3,4) | ?- q(X, Y, f(3,4)). X = 3, Y = 4 | ?- q(X, Y, Z). Z = f(X,Y)

  • Example (Prolog): p(X, Y, Z) :- Z is X + Y.

| ?- p(3, 4, Z). Z = 7 | ?- p(X, 4, 7). {INSTANTIATION ERROR: in expression}

1

slide-2
SLIDE 2

A Comparison with LP (II)

  • Example (CLP(ℜ)): p(X, Y, Z) :- Z = X + Y.

2 ?- p(3, 4, Z). Z = 7 *** Yes 3 ?- p(X, 4, 7). X = 3 *** Yes 4 ?- p(X, Y, 7). X = 7 - Y *** Yes

2

slide-3
SLIDE 3

A Comparison with LP (III)

  • Features in CLP:

⋄ Domain of computation (reals, integers, booleans, etc). Have to meet some conditions. ⋄ Type of constraints allowed for each domain: e.g. arithmetic constraints (+, ∗, =, ≤, ≥, <, >) ⋄ Constraint solving algorithms: simplex, gauss, etc.

  • LP can be viewed as a constraint logic language over Herbrand terms with a

single constraint predicate symbol: “=”

3

slide-4
SLIDE 4

A Comparison with LP (IV)

  • Advantages:

⋄ Helps making programs expressive and flexible. ⋄ May save much coding. ⋄ In some cases, more efficient than traditional LP programs due to solvers typically being very efficiently implemented. ⋄ Also, efficiency due to search space reduction: * LP: generate-and-test. * CLP: constrain-and-generate.

  • Disadvantages:

⋄ Complexity of solver algorithms (simplex, gauss, etc) for system developer (and can affect performance).

4

slide-5
SLIDE 5

Example of Search Space Reduction

  • Prolog (generate–and–test):

solution(X, Y, Z) :- p(X), p(Y), p(Z), test(X, Y, Z). p(14). p(15). p(16). p(7). p(3). p(11). test(X, Y, Z) :- Y is X + 1, Z is Y + 1.

  • Query:

| ?- solution(X, Y, Z). X = 14 Y = 15 Z = 16 ? ; no

  • 458 steps (all solutions: 465 steps).

5

slide-6
SLIDE 6

Example of Search Space Reduction

  • CLP(ℜ) (using generate–and–test):

solution(X, Y, Z) :- p(X), p(Y), p(Z), test(X, Y, Z). p(14). p(15). p(16). p(7). p(3). p(11). test(X, Y, Z) :- Y = X + 1, Z = Y + 1.

  • Query:

?- solution(X, Y, Z). Z = 16 Y = 15 X = 14 *** Retry? y *** No

  • 458 steps (all solutions: 465 steps).

6

slide-7
SLIDE 7

Generate–and–test Search Tree

A5 Y=14 Y=15 A4 A3 A2 A1 X=15 X=16 X=7 X=3 X=11 X=14 Z=14 Z=15 Z=16 Z=7 Z=3 Z=11 g B5 Y=11 Y=3 Y=7 Y=16 B4 B3 B2 B1 B A

7

slide-8
SLIDE 8

Example of Search Space Reduction

  • Move test(X, Y, Z) at the beginning (constrain–and–generate):

solution(X, Y, Z) :- test(X, Y, Z), p(X), p(Y), p(Z). p(14). p(15). p(16). p(7). p(3). p(11).

  • Prolog: test(X, Y, Z) :- Y is X + 1, Z is Y + 1.

| ?- solution(X, Y, Z). {INSTANTIATION ERROR: in expression}

  • CLP(ℜ): test(X, Y, Z) :- Y = X + 1, Z = Y + 1.

?- solution(X, Y, Z). Z = 16 Y = 15 X = 14 *** Retry? y *** No

  • 11 steps (all solutions: 11 steps).

8

slide-9
SLIDE 9

Constrain–and–generate Search Tree

Y=15 X=15 X=16 X=7 X=3 X=11 X=14 Z=16 g Y=16

9

slide-10
SLIDE 10

Fibonaci Revisited (Prolog)

  • Fibonaci numbers:

F0 = 0 F1 = 1 Fn+2 = Fn+1 + Fn

  • (The good old) Prolog version:

fib(0, 0). fib(1, 1). fib(N, F) :- N > 1, N1 is N - 1, N2 is N - 2, fib(N1, F1), fib(N2, F2), F is F1 + F2.

  • Can only be used with the first argument instantiated to a number

10

slide-11
SLIDE 11

Fibonaci Revisited (CLP(ℜ))

  • CLP(ℜ) version: syntactically similar to the previous one

fib(0, 0). fib(1, 1). fib(N, F1 + F2) :- N > 1, F1 >= 0, F2 >= 0, fib(N - 1, F1), fib(N - 2, F2).

  • Note all constraints included in program (F1 >= 0, F2 >= 0) – good practice!
  • Only real numbers and equations used (no data structures, no other constraint

system): “pure CLP(ℜ)”

  • Semantics greatly enhanced! E.g.

?- fib(N, F). F = 0, N = 0 ; F = 1, N = 1 ; F = 1, N = 2 ; F = 2, N = 3 ; F = 3, N = 4 ;

11

slide-12
SLIDE 12

Analog RLC circuits (CLP(ℜ))

  • Analysis and synthesis of analog circuits
  • RLC network in steady state
  • Each circuit is composed either of:

⋄ A simple component, or ⋄ A connection of simpler circuits

  • For simplicity, we will suppose subnetworks connected only in parallel and series

− → Ohm’s laws will suffice (other networks need global, i.e., Kirchoff’s laws)

  • We want to relate the current (I), voltage (V) and frequency (W) in steady state
  • Entry point: circuit(C, V, I, W) states that:

across the network C, the voltage is V, the current is I and the frequency is W

  • V and I must be modeled as complex numbers (the imaginary part takes into

account the angular frequency)

  • Note that Herbrand terms are used to provide data structures

12

slide-13
SLIDE 13

Analog RLC circuits (CLP(ℜ))

  • Complex number X + Y i modeled as c(X, Y)
  • Basic operations:

c_add(c(Re1,Im1), c(Re2,Im2), c(Re1+Re2,Im1+Im2)). c_mult(c(Re1, Im1), c(Re2, Im2), c(Re3, Im3)) :- Re3 = Re1 * Re2 - Im1 * Im2, Im3 = Re1 * Im2 + Re2 * Im1. (equality is c equal(c(R, I), c(R, I)), can be left to [extended] unification)

13

slide-14
SLIDE 14

Analog RLC circuits (CLP(ℜ))

  • Circuits in series:

circuit(series(N1, N2), V, I, W) :- c_add(V1, V2, V), circuit(N1, V1, I, W), circuit(N2, V2, I, W).

  • Circuits in parallel:

circuit(parallel(N1, N2), V, I, W) :- c_add(I1, I2, I), circuit(N1, V, I1, W), circuit(N2, V, I2, W).

14

slide-15
SLIDE 15

Analog RLC circuits (CLP(ℜ))

Each basic component can be modeled as a separate unit:

  • Resistor: V = I ∗ (R + 0i)

circuit(resistor(R), V, I, _W) :- c_mult(I, c(R, 0), V).

  • Inductor: V = I ∗ (0 + WLi)

circuit(inductor(L), V, I, W) :- c_mult(I, c(0, W * L), V).

  • Capacitor: V = I ∗ (0 −

1 WCi)

circuit(capacitor(C), V, I, W) :- c_mult(I, c(0, -1 / (W * C)), V).

15

slide-16
SLIDE 16

Analog RLC circuits (CLP(ℜ))

  • Example:

I = 0.65 L = 0.073 C = ? R = ? V = 4.5 ω = 2400

?- circuit(parallel(inductor(0.073), series(capacitor(C), resistor(R))), c(4.5, 0), c(0.65, 0), 2400). R = 6.91229, C = 0.00152546 ?- circuit(C, c(4.5, 0), c(0.65, 0), 2400).

16

slide-17
SLIDE 17

The N Queens Problem

  • Problem:

place N chess queens in a N × N board such that they do not attack each other

  • Data structure: a list holding the column position for each row
  • The final solution is a permutation of the list [1, 2, ..., N]
  • E.g.: the solution

is represented as [2, 4, 1, 3]

  • General idea:

⋄ Start with partial solution ⋄ Nondeterministically select new queen ⋄ Check safety of new queen against those already placed ⋄ Add new queen to partial solution if compatible; start again with new partial solution

17

slide-18
SLIDE 18

The N Queens Problem (Prolog)

queens(N, Qs) :- queens_list(N, Ns), queens(Ns, [], Qs). queens([], Qs, Qs). queens(Unplaced, Placed, Qs) :- select(Unplaced, Q, NewUnplaced), no_attack(Placed, Q, 1), queens(NewUnplaced, [Q|Placed], Qs). no_attack([], _Queen, _Nb). no_attack([Y|Ys], Queen, Nb) :- Queen =\= Y + Nb, Queen =\= Y - Nb, Nb1 is Nb + 1, no_attack(Ys, Queen, Nb1). select([X|Ys], X, Ys). select([Y|Ys], X, [Y|Zs]) :- select(Ys, X, Zs). queens_list(0, []). queens_list(N, [N|Ns]) :- N > 0, N1 is N - 1, queens_list(N1, Ns).

18

slide-19
SLIDE 19

The N Queens Problem (Prolog)

19

slide-20
SLIDE 20

The N Queens Problem (CLP(ℜ))

queens(N, Qs) :- constrain_values(N, N, Qs), place_queens(N, Qs). constrain_values(0, _N, []). constrain_values(N, Range, [X|Xs]) :- N > 0, X > 0, X <= Range, constrain_values(N - 1, Range, Xs), no_attack(Xs, X, 1). no_attack([], _Queen, _Nb). no_attack([Y|Ys], Queen, Nb) :- abs(Queen - (Y + Nb)) > 0, % Queen =\= Y + Nb abs(Queen - (Y - Nb)) > 0, % Queen =\= Y - Nb no_attack(Ys, Queen, Nb + 1). place_queens(0, _). place_queens(N, Q) :- N > 0, member(N, Q), place_queens(N - 1, Q). member(X, [X|_]). member(X, [_|Xs]) :- member(X, Xs).

20

slide-21
SLIDE 21

The N Queens Problem (CLP(ℜ))

  • This last program can attack the problem in its most general instance:

?- queens(M,N). N = [], M = 0 ; M = [1], M = 1 ; N = [2, 4, 1, 3], M = 4 ; N = [3, 1, 4, 2], M = 4 ; N = [5, 2, 4, 1, 3], M = 5 ; N = [5, 3, 1, 4, 2], M = 5 ; N = [3, 5, 2, 4, 1], M = 5 ; N = [2, 5, 3, 1, 4], M = 5 ...

  • Remark: Herbrand terms used to build the data structures
  • But also used as constraints (e.g., length of already built list Xs in no attack(Xs,

X, 1))

  • Note that in fact we are using both ℜ and FT

21

slide-22
SLIDE 22

The N Queens Problem (CLP(ℜ))

22

slide-23
SLIDE 23

The N Queens Problem (CLP(ℜ))

  • CLP(ℜ) generates internally a set of equations for each board size
  • They are non–linear and are thus delayed until instantiation wakes them up

?- constrain_values(4, 4, Q). Q = [_t3, _t5, _t13, _t21] _t3 <= 4 0 < abs(-_t13 + _t3 - 2) _t5 <= 4 0 < abs(-_t13 + _t3 + 2) _t13 <= 4 0 < abs(-_t21 + _t3 - 3) _t21 <= 4 0 < abs(-_t21 + _t3 + 3) 0 < _t3 0 < abs(-_t13 + _t5 - 1) 0 < _t5 0 < abs(-_t13 + _t5 + 1) 0 < _t13 0 < abs(-_t21 + _t5 - 2) 0 < _t21 0 < abs(-_t21 + _t5 + 2) 0 < abs(-_t5 + _t3 - 1) 0 < abs(-_t21 + _t13 - 1) 0 < abs(-_t5 + _t3 + 1) 0 < abs(-_t21 + _t13 + 1)

23

slide-24
SLIDE 24

The N Queens Problem (CLP(ℜ))

  • Constraints are (incrementally) simplified as new queens are added

?- constrain_values(4, 4, Qs), Qs = [3,1|OQs]. OQs = [_t16, _t24] 0 < abs(-_t24) Qs = [3, 1, _t16, _t24] 0 < abs(-_t24 + 6) _t16 <= 4 0 < abs(-_t16) _t24 <= 4 0 < abs(-_t16 + 2) 0 < _t16 0 < abs(-_t24 - 1) 0 < _t24 0 < abs(-_t24 + 3) 0 < abs(-_t16 + 1) 0 < abs(-_t24 + _t16 - 1) 0 < abs(-_t16 + 5) 0 < abs(-_t24 + _t16 + 1)

  • Bad choices are rejected using constraint consistency:

?- constrain_values(4, 4, Qs), Qs = [3,2|OQs]. *** No

24

slide-25
SLIDE 25

Some Real Systems (I)

  • CLP defines a class of languages obtained by

⋄ Specifying the particular constraint system(s) ⋄ Specifying Computation and Selection rules

  • Most share the Herbrand domain with “=”, but add different domains and/or solver

algorithms

  • Most use Computation and Selection rules of Prolog
  • CLP(ℜ):

⋄ Linear arithmetic over reals (=, ≤, >) ⋄ Gauss elimination and an adaptation of Simplex

  • PrologIII:

⋄ Linear arithmetic over rationals (=, ≤, >, =), Simplex ⋄ Boolean (=), 2-valued Boolean Algebra ⋄ Infinite (rational) trees (=, =) ⋄ Equations over finite strings

25

slide-26
SLIDE 26

Some Real Systems (II)

  • CHIP:

⋄ Linear arithmetic over rationals (=, ≤, >, =), Simplex ⋄ Boolean (=), larger Boolean algebra (symbolic values) ⋄ Finite domains ⋄ User–defined constraints and solver algorithms

  • BNR-Prolog:

⋄ Arithmetic over reals (closed intervals) (=, ≤, >, =), Simplex, propagation techniques ⋄ Boolean (=), 2-valued Boolean algebra ⋄ Finite domains, consistency techniques under user–defined strategy

  • SICStus 3:

⋄ Linear arithmetic over reals (=, ≤, >, =) ⋄ Linear arithmetic over rationals (=, ≤, >, =) ⋄ Finite domains (in recent versions)

26

slide-27
SLIDE 27

Some Real Systems (III)

  • ECLiPSe:

⋄ Finite domains ⋄ Linear arithmetic over reals (=, ≤, >, =) ⋄ Linear arithmetic over rationals (=, ≤, >, =)

  • clp(FD)/gprolog:

⋄ Finite domains

  • RISC–CLP:

⋄ Real arithmetic terms: any arithmetic constraint over reals ⋄ Improved version of Tarski’s quantifier elimination

  • Ciao:

⋄ Linear arithmetic over reals (=, ≤, >, =) ⋄ Linear arithmetic over rationals (=, ≤, >, =) ⋄ Finite Domains (currently interpreted) (can be selected on a per-module basis)

27

slide-28
SLIDE 28

Other Extensions: Functional Notation (functions)

  • Functional notation: defined via (local) ops/expansions.

Provides simple syntactic translation to predicate definitions/calls: ⋄ Defining function F/N implies defining predicate F/(N+1). ⋄ Any predicate P/(N+1) can be used as a function P/N. ⋄ The last argument of the predicate holds the result of the function.

  • Using functions does not incur in run-time slowdowns.
  • Function applications are recognized by:

⋄ Marking them with the prefix operator ˜ ⋄ A :- function(F/N) declaration makes all occurrences of a F/N functor to be regarded as a function call. ⋄ Arithmetic operators are translated to is/2 calls (optional).

  • Example: defining fact/1 factorial function (and fact/2 predicate!)

:- use_package(functions). fact(0) := 1. fact(N) := N * ˜fact(--N) :- N > 0.

28

slide-29
SLIDE 29

Functions Package: Examples – Array Access Syntax

  • Assume multi-dimensional arrays such as:

:- use_package(functions). array([N],A) :- functor(A,a,N). array([N|Ms],A) :- functor(A,a,N), rows(N,Ms,A). rows(0,_Ms,_A). rows(N,Ms,A) :- N>0, arg(N,A,Arg), array(Ms,Arg), rows(N-1,Ms,A).

29

slide-30
SLIDE 30

Functions Package: Examples – Array Access Syntax

  • We can now define the array access function with some syntactic sugar:

:- op(45, xfx, [@]). :- function ’@’/2 . @(V,[I]) := ˜arg(I,V). @(V,[I|Js]) := @(˜arg(I,V),Js).

  • And use it:

?- array([2,2],M), M@[2,1] = 3, display(M).

  • E.g., in a vector addition:

vecplus(V1,V2,V3) :- array([N],V1), array([N],V2), array([N],V3), vecplus_(N,V1,V2,V3). vecplus_(0,_,_,_). vecplus_(N,V1,V2,V3) :- N>0, V3@[N] = V1@[N] + V2@[N], vecplus_(N-1,V1,V2,V3).

30

slide-31
SLIDE 31

Functions Package: Examples – Sugar for Append

  • Some syntactic sugar for append:

:- function append/2. test1 :- set_prolog_flag(write_strings,on), X = " ", write(append("Hello",append(X,"world!"))).

  • Even more:

:- function append/2. :- op(200,xfy,[::]). :- function :: /2. A :: B := append(A,B). test2 :- set_prolog_flag(write_strings,on), X = " ", write("Hello" :: X :: "world!").

31

slide-32
SLIDE 32

Support for Higher-Order Programming

  • Ciao provides a number of facilities for easing HO programming [?]

(see the hiord package).

  • The family of call/N builtins allows the first argument of a call to call/N to be

instantiated to: ⋄ A “predicate abstraction”: (’’(X,Y) :- Y is X+10) (read ’’ as λ). ⋄ A higher-order term (supporting currying), e.g.: member([1,2])

  • Special syntax supported:

P(X,. . . ) is read as call(P,X,. . . ), (X,. . . ) is read as ’’(X,. . . )

  • meta predicate/1 declarations extended to reflect higher-order predicates

(pred(N)).

32

slide-33
SLIDE 33

Simple Higher-Order Programming Examples

?- use_package(hiord). ?- P = >(0), P(3). P = >(0) ? yes ?- P = member([1,2]), P(X). P = member([1,2]), X = 1 ? ; P = member([1,2]), X = 2 ? ; no ?- call(member([1,2]),X). X = 1 ? ; X = 2 ? ; no

33

slide-34
SLIDE 34

Simple Higher-Order Programming Examples (Contd.)

?- P = ( _(X,Y):- Y is X+10 ), P(2,R). P = (’’(X,Y):-Y is X+10), R = 12 ? yes ?- call(( _(X,Y):- Y is X+10 ), 2, R). R = 12 ? yes ?-

34

slide-35
SLIDE 35

Simple Higher-Order Programming Examples (Contd.)

  • Example, parametric list regular type:

:- use_package(hiord). :- prop list(L,T) + regtype # "@var{L} is a list of @var{T}s.". :- meta_predicate list(?, pred(1)). list([],_). list([X|Xs], T) :- T(X), list(Xs, T).

  • Examples of use:

⋄ list(L, atom) checks that a term L is a list of atoms. ⋄ list(L, list(atom)) checks that a term L is a list of lists of atoms. ⋄ list(L, ( (X) :- write(X), nl)) writes all the elements of L !

35

slide-36
SLIDE 36

Simple Higher-Order Programming Examples (Contd.)

  • Example, map/3:

:- use_package(hiord). :- meta_predicate map(_,pred(2),_). map([], _, []). map([X|Xs], P, [Y|Ys]) :- P(X,Y), map(Xs,P,Ys).

  • Example use of map/3: translating a list of indices to a list of terms.

?- map([1,3,2], arg(f(a,b,c,d)), R). R = [a,c,b] ? yes ?- map([1,3,2], ( ’’(X,Y) :- Y is X +1 ), R). R = [2,4,3] ? yes

36

slide-37
SLIDE 37

Combining Higher-Order with Functional Notation

  • They can be combined, resulting in essentially functional-style code

(but sometimes with additional modes of use!).

  • Some common examples (in library(hiordlib)):

:- meta_predicate map(_,pred(2),_). map([], _) := []. map([X|Xs], P) := [˜P(X)|˜map(Xs,P)]. :- meta_predicate foldl(_,_,pred(3),_). foldl([], Seed, _Op) := Seed. foldl([X|Xs], Seed, Op) := ˜Op(X,˜foldl(Xs,Seed,Op)).

  • Example use of map/3: translating a list of indices to a list of terms.

?- map([1,3,2], arg(f(a,b,c,d)), R). R = [a,c,b] ? yes

37

slide-38
SLIDE 38

A Currying Model

  • A form of currying is provided which is adapted to familiar argument order

conventions in logic programs: ⋄ For unary higher-order predicates, the argument is added first: >(0) checks if a given number is greater than 0 member(L) is true if the argument is member of the list L ⋄ For binary higher-order predicates, the first argument is added first, the second one is added last: append(".") appends to the first argument the string ".", returning the result in the second one /(2) divides by 2 the first argument, returning the result in the second one (given the definition /(X,Y,Z) :- Z is X/Y)

38

slide-39
SLIDE 39

Higher-Order Programming (Contd.): Currying Rules in Action

:- set_prolog_flag(multi_arity_warnings,off). p(X,Y,Z,W,K,L) :- display(p(X,Y,Z,W,K,L)),nl. ?- use_package(hiord). ?- call(p,1,2,3,4,5,6).

  • ->

p(1,2,3,4,5,6) ?- call(p(1),2,3,4,5,6).

  • ->

p(2,1,3,4,5,6) ?- call(p(1,2),3,4,5,6).

  • ->

p(3,1,2,4,5,6) ?- call(p(1,2,3),4,5,6).

  • ->

p(4,1,2,3,5,6) ?- call(p(1,2,3,4),5,6).

  • ->

p(5,1,2,3,4,6) ?- call(p(1,2,3,4,5),6).

  • ->

p(6,1,2,3,4,5) ?- call(p(1,2,3,4,5,6)).

  • ->

p(1,2,3,4,5,6)

39

slide-40
SLIDE 40

Domians for (Constraint) Logic Programs (Set Sharing)

  • Even more accurate sharing – set sharing [Jacobs et al.] [Muthukumar et al.]:

sets of sets of variables. θ = {W/a, X/f(A1, A2, A3), Y/g(A2), Z/A3} θα = {∅, {X}, {X, Y }, {X, Z}}

  • A bit tricky to understand. Try:

{X} {X, Y } {X, Z} A1 A2 A3 θ = {W/a, X/f(A1, A2, A3, B1), Y/g(h(A2, B1)), Z/A3} θα = {∅, {X}, {X, Y }, {X, Z}} {X} {X, Y } {X, Z} A1 A2 + B1 A3

  • Encodes grounding and independence

⋄ W has no ocurrence in any set: it is ground ⋄ {Y, Z} has no ocurrence in any set: they are independent

40

slide-41
SLIDE 41

Domians for Constraint Logic Programs (Def)

  • Example: Definiteness analysis (Def) [G. de la Banda et al.]

Domain: Def = {d, ℘(℘(Pvar)), ⊤}) X = Y + Z ⇒ [(X, [[Y, Z]]), (Y, [[X, Z]]), (Z, [[X, Y ]])] X = f(Y, Z) ⇒ [(X, [[Y, Z]]), (Y, [[X]]), (Z, [[X]])] X :: N ⇒ [(X, ⊤), (N, [[X]])] X > Y ⇒ [(X, ⊤), (Y, ⊤)] X = 3 ⇒ [(X, d)]

  • Other analyses:

⋄ Freeness analysis [Dumortier et al.] and combinations. ⋄ LSign [Marriott, Sondergaard and Stuckey, ILPS’94]

  • Applications:

⋄ optimization [Keely et al., CP’96] ⋄ parallelization [Bueno et al., PLILP’96] ⋄ ...

41