Download presentation
Presentation is loading. Please wait.
1
Sliding Constraints Toby Walsh National ICT Australia and University of New South Wales www.cse.unsw.edu.au/~tw Joint work with Christian Bessiere, Emmanuel Hebrard, Brahim Hnich, Zeynep Kiziltan
2
SLIDE meta-constraint ● Hot off the press ● Some of this is to appear at ECAI 06 ● Some is even under review for IJCAI 07!
3
SLIDE meta-constraint ● A constructor for generating many sequencing and related global constraints – REGULAR [Pesant 04] – AMONG SEQ [Beldiceanu and Contegjean 94] – CARD PATH [Beldiceanu and Carlsson 01] – VALUE PRECEDENCE [Lee and Law 04] – … ● Slides a constraint down one or more sequences of variables – Ensuring constraint holds at every point – Fixed parameter tractable
4
Basic SLIDE ● SLIDE(C,[X1,..Xn]) holds iff – C(Xi,..Xi+k) holds for every I ● INCREASING([X1,..Xn]) – SLIDE(<=,[X1,..Xn]) – Unfolds into X1 <= X2, X2 <= X3, … Xn-1 <= Xn
5
Basic SLIDE ● SLIDE(C,[X1,..Xn]) holds iff – C(Xi,..Xi+k) holds for every I ● ALWAYS_CHANGE([X1,..Xn]) – SLIDE(=/=,[X1,..Xn]) – Unfolds into X1 =/= X2, X2 =/= X3, … Xn-1 =/= Xn
6
Basic SLIDE ● AMONG SEQ constraint [Beldiceanu and Contegjean 94] – Car sequencing, staff rostering, … ● E.g. at most 1 in 3 cars have a sun roof, at most 3 in 7 night shifts, … – SLIDE(C,[X1,..Xn]) where C(X1..,Xk) holds iff AMONG([X1,..,Xk],l,u,v)
7
Basic SLIDE ● AMONG SEQ constraint [Beldiceanu and Contegjean 94] – Car sequencing, staff rostering, … ● E.g. at most 1 in 3 cars have a sun roof, at most 3 in 7 night shifts, … – SLIDE(C,[X1,..Xn]) where C(X1..,Xk) holds iff AMONG([X1,..,Xk],l,u,v) – E.g. l=u=2, k=3 and v={a} – SLIDE(C,[X1,..X5]) where X1=a, X2,.. X5 in {a,b}
8
Basic SLIDE ● AMONG SEQ constraint [Beldiceanu and Contegjean 94] – Car sequencing, staff rostering, … ● E.g. at most 1 in 3 cars have a sun roof, at most 3 in 7 night shifts, … – SLIDE(C,[X1,..Xn]) where C(X1..,Xk) holds iff AMONG([X1,..,Xk],l,u,v) – E.g. l=u=2, k=3 and v={a} – SLIDE(C,[X1,..X5]) where X1=a, X2,.. X5 in {a,b} – Enforcing GAC sets X4=a since only satisfying tuples are a,a,b,a,a and a,b,a,a,b. Enforcing GAC on decomposition does nothing!
9
GSC ● Global sequence constraint in ILOG Solver – Combines AMONG SEQ with GCC – Regin and Puget have given partial propagator
10
GSC ● Global sequence constraint in ILOG Solver – Combines AMONG SEQ with GCC – Regin and Puget have given partial propagator – We can show why! ● It is NP-hard to enforce GAC on GSC ● Can actually prove this when GSC is AMONG SEQ plus an ALL DIFFERENT ● ALL DIFFERENT is a special case of GCC
11
GSC ● Reduction from 1in3 SAT on positive clauses – The jth block of 2N clauses will ensure jth clause satisfied – Even numbered CSP vars represent truth assignment – Odd numbered CSP vars “junk” to ensure N odd values in each block – X_2jN+2i odd iff xi true – AMONG SEQ([X1,…],N,N,2N,{1,3,..}) ● This ensures truth assignment repeated along variables!
12
GSC ● Reduction from 1in3 SAT on positive clauses – Suppose jth clause is (x or y or z) ● X_2jN+2x, X_2jN+2y, X_2jN+2z in {4NM+4j, 4NM+4j+1, 4NM+4j+2} ● As ALL DIFFERENT, only one of these odd – For i other than x, y or z, X_2jN+2i in {4jN+4i, 4jN+4i+1} and X_2jN+2i+1 in {4jN+4i+2, 4jn+4i+3}
13
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) [Frisch et al CP 02] – Introduce sequence of Boolean vars [B1,..Bn+1] – Play role of alpha in LEX propagator
14
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) holds iff C(Xi,Yi,Bi,Bi+1) holds for each i
15
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) – C(Xi,Yi,Bi,Bi+1) holds iff Bi=Bi+1=1 or (Bi=Bi+1=0 and Xi=Yi) or (Bi=0, Bi+1=1 and Xi<Yi)
16
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) – C(Xi,Yi,Bi,Bi+1) holds iff Bi=Bi+1=1 or (Bi=Bi+1=0 and Xi=Yi) or (Bi=0, Bi+1=1 and Xi<Yi) – {2} {1,3,4} {2,3,4} {1} {3,4,5} – {0,1,2} {1} {1,2,3} {0} {0,1,2}
17
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) – C(Xi,Yi,Bi,Bi+1) holds iff Bi=Bi+1=1 or (Bi=Bi+1=0 and Xi=Yi) or (Bi=0, Bi+1=1 and Xi<Yi) – {2} {1,3,4}{2,3,4} {1} {3,4,5} – {2} {1} {1,2,3} {0} {0,1,2}
18
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) – C(Xi,Yi,Bi,Bi+1) holds iff Bi=Bi+1=1 or (Bi=Bi+1=0 and Xi=Yi) or (Bi=0, Bi+1=1 and Xi<Yi) – {2} {1} {2,3,4} {1} {3,4,5} – {2} {1} {1,2,3} {0} {0,1,2}
19
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) – C(Xi,Yi,Bi,Bi+1) holds iff Bi=Bi+1=1 or (Bi=Bi+1=0 and Xi=Yi) or (Bi=0, Bi+1=1 and Xi<Yi) – {2} {1} {2} {1} {3,4,5} – {2} {1} {3} {0} {0,1,2}
20
SLIDE down multiple sequences ● Can SLIDE down more than one sequence at a time – LEX([X1,..Xn],[Y1,..Yn]) – Set B1=0 – Bn+1=1 (strict lex), Bn+1 in {0,1} (lex) – SLIDE(C,[X1..Xn],[Y1,..Yn],[B1,..Bn+1]) – C(Xi,Yi,Bi,Bi+1) holds iff Bi=Bi+1=1 or (Bi=Bi+1=0 and Xi=Yi) or (Bi=0, Bi+1=1 and Xi<Yi) – Highly efficient, incremental,..
21
SLIDE down multiple sequences ● CONTIGUITY([X1,..Xn]) [Maher 02] – 0…01…10…0 – Two simple SLIDEs
22
SLIDE down multiple sequences ● CONTIGUITY([X1,..Xn]) – 0…01…10…0 – Two simple SLIDEs – Introduce Yi in {0,1,2} – SLIDE(<=,[Y1,..Yn])
23
SLIDE down multiple sequences ● CONTIGUITY([X1,..Xn]) – 0…01…10…0 – Two simple SLIDEs – Introduce Yi in {0,1,2} – SLIDE(<=,[Y1,..Yn]) – SLIDE(C,[X1,..Xn],[Y1,..Yn]) where C(Xi,Yi) holds iff Xi=1 Yi=1
24
SLIDE down multiple sequences ● REGULAR(A,[X1,..Xn]) [Pesant 04] – X1.. Xn is a string accepted by FDA A – Can encode many useful constraints including LEX, AMONG, STRETCH, CONTIGUITY …
25
SLIDE down multiple sequences ● REGULAR(A,[X1,..Xn]) [Pesant 04] – X1.. Xn is a string accepted by FDA A – Encodes into simple SLIDE – Introduce Qi to represent state of the automaton after i symbols
26
SLIDE down multiple sequences ● REGULAR(A,[X1,..Xn]) – X1.. Xn is a string accepted by FDA A – Introduce Qi to represent state of the automaton after i symbols – SLIDE(C,[X1,..Xn],[Q1,..Qn+1]) where ● Q1 is starting state of A ● Qn+1 is limited to accepting states of A ● C(Xi,Qi,Qi+1) holds iff A moves from state Qi to state Qi+1 on seeing Xi
27
SLIDE down multiple sequences ● REGULAR(A,[X1,..Xn]) – X1.. Xn is a string accepted by FDA A – Introduce Qi to represent state of the automaton after i symbols – SLIDE(C,[X1,..Xn],[Q1,..Qn+1]) where ● Q1 is starting state of A ● Qn+1 is limited to accepting states of A ● C(Xi,Qi,Qi+1) holds iff A moves from state Qi to state Qi+1 on seeing Xi – Gives highly efficient and effective propagator! – Introducing Qi also gives modelling access to state variables
28
SLIDE with counters ● AMONG([X1,..Xn],v,N) ● Introduce sequence of counts, Yi ● SLIDE(C,[X1,..Xn],[Y1,..Yn+1]) where ● Y1=0, Yn+1=N ● C(Xi,Yi,Yi+1) holds iff (Xi in v and Yi+1=1+Yi) or (Xi not in v and Yi+1=Yi)
29
SLIDE with counters ● CARD PATH [Beldiceanu and Carlsson 01] ● SLIDE is a special case of CARD PATH – SLIDE(C,[X1,..Xn]) iff CARD PATH(C,[X1,..Xn],n- k+1)
30
SLIDE with counters ● CARD PATH ● SLIDE is a special case of CARD PATH – SLIDE(C,[X1,..Xn]) iff CARD PATH(C,[X1,..Xn],n- k+1) ● CARD PATH is a special case of SLIDE – SLIDE(D,[X1,..Xn],[Y1,..Yn+1]) where Y1=0, Yn+1=N, and D(Xi,..Xi+k,Yi,Yi+1) holds iff (Yi+1=1+Yi and C(Xi,..Xi+k)) or (Yi+1=Yi and not C(Xi,..Xi+k))
31
Global constraints for symmetry breaking ● Decision variables: – Col[Italy], Col[France], Col[Austria]... ● Domain of values: – red, yellow, green,... ● Constraints – binary relations like Col[Italy]=/=Col[France] Col[Italy]=/=Col[Austria] …
32
Value symmetry ● Solution: – Col[Italy]=green Col[France]=red Col[Spain]=green … ● Values (colours) are interchangeable: – Swap red with green everywhere will still give us a solution
33
Value symmetry ● Solution: – Col[Italy]=green Col[France]=red Col[Spain]=green … ● Values (colours) are interchangeable: – Col[Italy]=red Col[France]=green Col[Spain]=red …
34
Value precedence ● Old idea – Used in bin-packing and graph colouring algorithms – Only open the next new bin – Only use one new colour ● Applied now to constraint satisfaction [Law and Lee 04]
35
Value precedence ● Suppose all values from 1 to m are interchangeable – Might as well let X1=1
36
Value precedence ● Suppose all values from 1 to m are interchangeable – Might as well let X1=1 – For X2, we need only consider two choices ● X2=1 or X2=2
37
Value precedence ● Suppose all values from 1 to m are interchangeable – Might as well let X1=1 – For X2, we need only consider two choices – Suppose we try X2=2
38
Value precedence ● Suppose all values from 1 to m are interchangeable – Might as well let X1=1 – For X2, we need only consider two choices – Suppose we try X2=2 – For X3, we need only consider three choices ● X3=1, X3=2, X3=3
39
Value precedence ● Suppose all values from 1 to m are interchangeable – Might as well let X1=1 – For X2, we need only consider two choices – Suppose we try X2=2 – For X3, we need only consider three choices – Suppose we try X3=2
40
Value precedence ● Suppose all values from 1 to m are interchangeable – Might as well let X1=1 – For X2, we need only consider two choices – Suppose we try X2=2 – For X3, we need only consider three choices – Suppose we try X3=2 – For X4, we need only consider three choices ● X4=1, X4=2, X4=3
41
Value precedence ● Global constraint – Precedence([X1,..Xn]) iff min({i | Xi=j or i=n+1}) < min({i | Xi=k or i=n+2}) for all j<k – In other words ● The first time we use j is before the first time we use k
42
Value precedence ● Global constraint – Precedence([X1,..Xn]) iff min({i | Xi=j or i=n+1}) < min({i | Xi=k or i=n+2}) for all j<k – In other words ● The first time we use j is before the first time we use k – E.g ● Precedence([1,1,2,1,3,2,4,2,3]) ● But not Precedence([1,1,2,1,4])
43
Value precedence ● Global constraint – Precedence([X1,..Xn]) iff min({i | Xi=j or i=n+1}) < min({i | Xi=k or i=n+2}) for all j<k – In other words ● The first time we use j is before the first time we use k – E.g ● Precedence([1,1,2,1,3,2,4,2,3]) ● But not Precedence([1,1,2,1,4]) – Proposed by [Law and Lee 2004] ● Pointer based propagator (alpha, beta, gamma) but only for two interchangeable values at a time
44
Value precedence ● Precedence([i,j],[X1,..Xn]) iff min({i | Xi=j or i=n+1}) < min({i | Xi=k or i=n+2}) ● Of course – Precedence([X1,..Xn]) iff Precedence([i,j],[X1,..Xn]) for all i<j
45
Value precedence ● Precedence([i,j],[X1,..Xn]) iff min({i | Xi=j or i=n+1}) < min({i | Xi=k or i=n+2}) ● Of course – Precedence([X1,..Xn]) iff Precedence([i,j],[X1,..Xn]) for all i<j – Precedence([X1,..Xn]) iff Precedence([i,i+1],[X1,..Xn]) for all i
46
Value precedence ● Precedence([i,j],[X1,..Xn]) iff min({i | Xi=j or i=n+1}) < min({i | Xi=k or i=n+2}) ● Of course – Precedence([X1,..Xn]) iff Precedence([i,j],[X1,..Xn]) for all i<j ● But this hinders propagation – GAC(Precedence([X1,..Xn])) does strictly more pruning than GAC(Precedence([i,j],[X1,..Xn])) for all i<j – Consider X1=1, X2 in {1,2}, X3 in {1,3} and X4 in {3,4}
47
Puget’s method ● Introduce Zj to record first time we use j ● Add constraints – Xi=j implies Zj <= i – Zj=i implies Xi=j – Zi < Zi+1
48
Puget’s method ● Introduce Zj to record first time we use j ● Add constraints – Xi=j implies Zj < I – Zj=i implies Xi=j – Zi < Zi+1 ● Binary constraints – easy to implement
49
Puget’s method ● Introduce Zj to record first time we use j ● Add constraints – Xi=j implies Zj < I – Zj=i implies Xi=j – Zi < Zi+1 ● Unfortunately hinders propagation – AC on encoding may not give GAC on Precedence([X1,..Xn]) – Consider X1=1, X2 in {1,2}, X3 in {1,3}, X4 in {3,4}, X5=2, X6=3, X7=4
50
Propagating Precedence ● Simple SLIDE encoding ● Introduce sequence of variables, Yi – Record largest value used so far – Y1=0
51
Propagating Precedence ● Simple SLIDE encoding ● SLIDE(C,[X1,..Xn],[Y1,..Yn+1]) where C(Xi,Yi,Yi+1) holds iff Xi<=1+Yi and Yi+1=max(Yi,Xi)
52
Propagating Precedence ● Simple SLIDE encoding ● SLIDE(C,[X1,..Xn],[Y1,..Yn+1]) where C(Xi,Yi,Yi+1) holds iff Xi<=1+Yi and Yi+1=max(Yi,Xi) – Consider Y1=0, X1 in {1,2,3}, X2 in {1,2,3} and X3=3
53
Precedence and matrix symmetry ● Alternatively, could map into 2d matrix – Xij=1 iff Xi=j ● Value precedence now becomes column symmetry – Can lex order columns to break all such symmetry – Alternatively view value precedence as ordering the columns of a matrix model
54
Precedence and matrix symmetry ● Alternatively, could map into 2d matrix – Xij=1 iff Xi=j ● Value precedence now becomes column symmetry ● However, we get less pruning this way – Additional constraint that rows have sum of 1 – Consider, X1=1, X2 in {1,2,3} and X3=1
55
Precedence for set variables ● Social golfers problem – Each foursome can be represented by a set of cardinality 4 – Values (golfers) within this set are interchangeable ● Value precedence can be applied to such set variables – But idea is now conceptually more complex!
56
Precedence for set variables ● We might as well start with X1={1,2,3}
57
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Now 1, 2, 3 and 4 are still symmetric
58
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Let’s try X2={2,5,6,7}
59
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Let’s try X2={2,5,6,7} ● But if we permute 1 with 2 we get – X1’={1,2,3,4} and X2’={1,5,6,7} – And X2’ is lower than X2 in the multiset ordering
60
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Let’s try X2={1,5,6,7}
61
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Let’s try X2={1,5,6,7} ● Let’s try X3={1,2,4,5}
62
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Let’s try X2={1,5,6,7} ● Let’s try X3={1,2,4,5} ● But if we permute 3 with 4 we get – X1’={1,2,3,4}, X2’={1,5,6,7} and X3’={1,2,3,5} – This is again lower in the multiset ordering
63
Precedence for set variables ● We might as well start with X1={1,2,3,4} ● Let’s try X2={1,5,6,7} ● Let’s try X3={1,2,3,5} ● …
64
Precedence for set variables ● Precedence([S1,..Sn]) iff min(i, {i | j in Si & not(k in Si) or i=n+1}) < min(i, {i | k in Si & not(j in Si) or i=n+2}) for all j<k ● In other words – The first time we distinguish apart j and k (by one appearing on its own), j appears and k does not
65
SLIDE over sets ● Value precedence for set vars ● PRECENDCE([vj,vk],[S1,..Sn]) holds iff ● min(i,{i | vj in Si and vk not in Si or i=n+1}) < ● min(i,{i | vk in Si and vj not in Si or i=n+2}) ● Introduce sequence of Booleans to indicate whether vars have been distinguished apart yet or not
66
SLIDE over sets ● Value precedence for set vars ● PRECENDCE([vj,vk],[S1,..Sn]) holds iff ● min(i,{i | vj in Si and vk not in Si or i=n+1}) < ● min(i,{i | vk in Si and vj not in Si or i=n+2}) ● SLIDE(C,[S1,..Sn],[B1,..Bn+1]) where ● B1=0 and ● C(Si,Bi,Bi+1) holds iff Bi=Bi+1=1, or Bi=Bi+1=0 and (vj, vk in Si or vj, vk not in Si), or Bi=0, Bi+1=1, vj in Si and vk not in Si
67
SLIDE over sets ● Open stacks problem – IJCAI 05 modelling challenge ● Three SLIDEs and one ALL DIFFERENT – First SLIDE: Si+1 = Si u customer(Xi) – Second SLIDE: Ti-1= Ti u customer(Xi) – Third SLIDE: |Si intersect Ti| < OpenStacks
68
Circular SLIDE ● STRETCH used in shift rostering [Hellsten et al 04] ● Given sequence of vars X1,.. Xn – Each stretch of identical values a occurs at least shortest(a) and at most longest(a) time – For example, at least 0 and at most 3 night shifts in a row – Each transition Xi=/=Xi+1 is limited to given patterns – For example, only Xi=night, Xi+1=off is permitted
69
Circular SLIDE ● STRETCH can be efficiently encoded using SLIDE ● SLIDE(C,[X1,..Xn],[Y1,..Yn+1]) where – Y1=1 – C(Xi,Xi+1,Yi,Yi+1) holds iff Xi=Xi+1, Yi+1=1+Yi, Yi+1<=longest(Xi), or Xi=/=Xi+1, Yi>=shortest(Xi) and (Xi,Xi+1) in set of permitted changes
70
Circular SLIDE ● Circular forms of STRETCH are needed for repeating shift patterns ● Circular form of SLIDE useful in such situations ● SLIDEo(C,[X1,..Xn]) holds iff – C(Xi,..X1+(i+k-1)mod n) holds for 1<=i<=n
71
SLIDE algebra ● SLIDEOR(C,[X1..Xn]) holds iff – C(Xi,..Xi+k) holds for some I ● Encodes as CARD PATH (and thus as SLIDE) ● Other more complex combinations – NOT(SLIDE(C,[X1,..Xn])) iff SLIDEOR(NOT(C),[X1,..Xn]) – SLIDE(C1,[X1,..Xn]) and SLIDE(C2,[X1,..Xn]) iff SLIDE(C1 and C2,[X1,..Xn]) –..
72
Propagating SLIDE ● But how do we propagate global constraints expressed using SLIDE?
73
Propagating SLIDE ● SLIDE(C,[X1,..Xn]) – Just post sequence of constraints, C(Xi,..Xi+k) – If constraint graph is Berge acyclic, then we will achieve GAC – Gives efficient GAC propagators for CONTIGUITY, DOMAIN, ELEMENT, LEX, PRECEDENCE, REGULAR, …
74
Propagating SLIDE ● SLIDE(C,[X1,..Xn]) – Just post sequence of constraints, C(Xi,..Xi+k) – If constraint graph is Berge acyclic, then we will achieve GAC – Gives efficient GAC propagators for CONTIGUITY, DOMAIN, ELEMENT, LEX, PRECEDENCE, REGULAR, … – But what about case constraint graph is not Berge- acyclic? ● Slide constraints overlap on more than one variable
75
Propagating SLIDE ● SLIDE(C,[X1,..Xn]) – Slide constraints overlap on more than one variable – Enforce GAC using dynamic programming ● pass support down sequence
76
Propagating SLIDE ● SLIDE(C,[X1,..Xn]) – Equivalently a “dual” encoding – Consider AMONG SEQ(2,2,3,[X1,..X5],{a}) where ● X1=a, X2,X3,X4,X5 in {a,b} ● AMONG([X1,X2,X3],2,{a}) ● AMONG([X2,X3,X4],2,{a}) ● AMONG([X3,X4,X5],2,{a}) ● Enforcing GAC sets X4=a – GAC can be enforced in O(nd^k+1) time and O(nd^k) space where constraints overlap on k variables – Fixed parameter tractable
77
Conclusions ● SLIDE is a very useful meta-constraint – Many global constraints for sequencing and other problems can be encoded as SLIDE ● SLIDE can be propagated easily – Constraints overlap on just one variable => simply post slide constraints – Constraints overlap on more than one variable => use dynamic programming or equivalently a simple dual encoding
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.