aGrUM
0.20.2
a C++ library for (probabilistic) graphical models
multiDimBucket_tpl.h
Go to the documentation of this file.
1
/**
2
*
3
* Copyright 2005-2020 Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4
* info_at_agrum_dot_org
5
*
6
* This library is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU Lesser General Public License as published by
8
* the Free Software Foundation, either version 3 of the License, or
9
* (at your option) any later version.
10
*
11
* This library is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU Lesser General Public License for more details.
15
*
16
* You should have received a copy of the GNU Lesser General Public License
17
* along with this library. If not, see <http://www.gnu.org/licenses/>.
18
*
19
*/
20
21
22
/**
23
* @file
24
* @brief Implementation of the MultiDimBucket class.
25
*
26
* @author Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
27
* @author Lionel TORTI
28
*/
29
30
#
include
<
agrum
/
tools
/
multidim
/
implementations
/
multiDimBucket
.
h
>
31
32
namespace
gum
{
33
34
template
<
typename
GUM_SCALAR >
35
MultiDimBucket< GUM_SCALAR >::MultiDimBucket(Size bufferSize) :
36
MultiDimReadOnly< GUM_SCALAR >(), bufferSize__(bufferSize), bucket__(0),
37
changed__(
false
), name__(
"MultiDimBucket"
) {
38
GUM_CONSTRUCTOR(MultiDimBucket);
39
}
40
41
template
<
typename
GUM_SCALAR
>
42
MultiDimBucket
<
GUM_SCALAR
>::
MultiDimBucket
(
43
const
MultiDimBucket
<
GUM_SCALAR
>&
source
) :
44
MultiDimReadOnly
<
GUM_SCALAR
>(
source
),
45
bufferSize__
(
source
.
bufferSize__
),
bucket__
(0),
46
multiDims__
(
source
.
multiDims__
),
allVariables__
(
source
.
allVariables__
),
47
allVarsInst__
(
source
.
allVarsInst__
),
changed__
(
source
.
changed__
),
48
name__
(
"MultiDimBucket"
) {
49
GUM_CONS_CPY
(
MultiDimBucket
);
50
}
51
52
template
<
typename
GUM_SCALAR
>
53
MultiDimBucket
<
GUM_SCALAR
>::~
MultiDimBucket
() {
54
GUM_DESTRUCTOR
(
MultiDimBucket
);
55
typedef
Bijection
<
Instantiation
*,
Instantiation
* >::
iterator_safe
BiIter
;
56
57
for
(
BiIter
iter
=
instantiations__
.
beginSafe
();
58
iter
!=
instantiations__
.
endSafe
();
59
++
iter
) {
60
delete
iter
.
second
();
61
}
62
63
if
(
bucket__
) {
delete
bucket__
; }
64
65
for
(
HashTableIteratorSafe
<
const
MultiDimContainer
<
GUM_SCALAR
>*,
66
Instantiation
* >
iter
67
=
multiDims__
.
beginSafe
();
68
iter
!=
multiDims__
.
endSafe
();
69
++
iter
) {
70
delete
iter
.
val
();
71
}
72
}
73
74
template
<
typename
GUM_SCALAR
>
75
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
add
(
76
const
MultiDimContainer
<
GUM_SCALAR
>&
impl
) {
77
this
->
add
(&
impl
);
78
}
79
80
template
<
typename
GUM_SCALAR
>
81
void
MultiDimBucket
<
GUM_SCALAR
>::
add
(
82
const
MultiDimContainer
<
GUM_SCALAR
>*
impl
) {
83
multiDims__
.
insert
(
impl
,
new
Instantiation
(*
impl
));
84
85
if
(!
MultiDimImplementation
<
GUM_SCALAR
>::
isInMultipleChangeMethod_
()) {
86
for
(
const
auto
var
:
impl
->
variablesSequence
()) {
87
addVariable__
(
var
);
88
}
89
}
90
91
changed__
=
true
;
92
}
93
94
template
<
typename
GUM_SCALAR
>
95
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
erase
(
96
const
MultiDimContainer
<
GUM_SCALAR
>&
impl
) {
97
this
->
erase
(&
impl
);
98
}
99
100
template
<
typename
GUM_SCALAR
>
101
void
MultiDimBucket
<
GUM_SCALAR
>::
erase
(
102
const
MultiDimContainer
<
GUM_SCALAR
>*
impl
) {
103
try
{
104
delete
multiDims__
[
impl
];
105
multiDims__
.
erase
(
impl
);
106
107
if
(!
MultiDimImplementation
<
GUM_SCALAR
>::
isInMultipleChangeMethod_
()) {
108
for
(
auto
var
:
impl
->
variablesSequence
()) {
109
eraseVariable__
(
var
);
110
}
111
}
112
113
changed__
=
true
;
114
}
catch
(
NotFound
&) {
115
// Do nothing
116
}
117
}
118
119
template
<
typename
GUM_SCALAR
>
120
INLINE
bool
MultiDimBucket
<
GUM_SCALAR
>::
contains
(
121
const
MultiDimContainer
<
GUM_SCALAR
>&
impl
)
const
{
122
return
multiDims__
.
exists
(&
impl
);
123
}
124
125
template
<
typename
GUM_SCALAR
>
126
INLINE
const
Set
<
const
DiscreteVariable
* >&
127
MultiDimBucket
<
GUM_SCALAR
>::
allVariables
()
const
{
128
return
allVariables__
;
129
}
130
131
template
<
typename
GUM_SCALAR
>
132
INLINE
Size
MultiDimBucket
<
GUM_SCALAR
>::
bucketSize
()
const
{
133
return
multiDims__
.
size
();
134
}
135
136
template
<
typename
GUM_SCALAR
>
137
INLINE
bool
MultiDimBucket
<
GUM_SCALAR
>::
isBucketEmpty
()
const
{
138
return
multiDims__
.
empty
();
139
}
140
141
template
<
typename
GUM_SCALAR
>
142
INLINE
bool
MultiDimBucket
<
GUM_SCALAR
>::
bucketChanged
()
const
{
143
return
changed__
;
144
}
145
146
template
<
typename
GUM_SCALAR
>
147
INLINE
Size
MultiDimBucket
<
GUM_SCALAR
>::
bufferSize
()
const
{
148
return
bufferSize__
;
149
}
150
151
template
<
typename
GUM_SCALAR
>
152
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
setBufferSize
(
Size
ammount
) {
153
bufferSize__
=
ammount
;
154
155
if
((
this
->
domainSize
() >
bufferSize__
) && (
bucket__
!= 0)) {
156
eraseBuffer__
();
157
}
else
if
(
bucket__
== 0) {
158
initializeBuffer__
();
159
}
160
}
161
162
template
<
typename
GUM_SCALAR
>
163
void
MultiDimBucket
<
GUM_SCALAR
>::
compute
(
bool
force
)
const
{
164
if
((
bucket__
) && (
changed__
||
force
)) {
165
Instantiation
values
(*
bucket__
);
166
167
for
(
values
.
setFirst
(); !
values
.
end
();
values
.
inc
()) {
168
bucket__
->
set
(
values
,
computeValue__
(
values
));
169
}
170
}
else
if
((
bucket__
== 0) &&
changed__
) {
171
slavesValue__
.
clear
();
172
changed__
=
false
;
173
}
174
175
changed__
=
false
;
176
}
177
178
template
<
typename
GUM_SCALAR
>
179
const
std
::
string
&
MultiDimBucket
<
GUM_SCALAR
>::
name
()
const
{
180
return
name__
;
181
}
182
183
template
<
typename
GUM_SCALAR
>
184
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
add
(
const
DiscreteVariable
&
v
) {
185
MultiDimImplementation
<
GUM_SCALAR
>::
add
(
v
);
186
187
if
(!
MultiDimImplementation
<
GUM_SCALAR
>::
isInMultipleChangeMethod_
()) {
188
if
(
this
->
domainSize
() <=
bufferSize__
) {
189
if
(
bucket__
)
190
bucket__
->
add
(
v
);
191
else
192
initializeBuffer__
();
193
}
else
if
(
bucket__
) {
194
eraseBuffer__
();
195
}
196
}
197
}
198
199
template
<
typename
GUM_SCALAR
>
200
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
erase
(
const
DiscreteVariable
&
v
) {
201
MultiDimImplementation
<
GUM_SCALAR
>::
erase
(
v
);
202
203
if
((!
MultiDimImplementation
<
GUM_SCALAR
>::
isInMultipleChangeMethod_
())
204
&& (
this
->
domainSize
() <=
bufferSize__
)) {
205
if
(
bucket__
) {
206
bucket__
->
erase
(
v
);
207
}
else
{
208
initializeBuffer__
();
209
}
210
}
211
}
212
213
template
<
typename
GUM_SCALAR
>
214
INLINE
Size
MultiDimBucket
<
GUM_SCALAR
>::
realSize
()
const
{
215
return
(
bucket__
) ?
bucket__
->
realSize
() : (
Size
)0;
216
}
217
218
template
<
typename
GUM_SCALAR
>
219
INLINE
bool
220
MultiDimBucket
<
GUM_SCALAR
>::
contains
(
const
DiscreteVariable
&
v
)
const
{
221
return
MultiDimImplementation
<
GUM_SCALAR
>::
contains
(
v
);
222
}
223
224
template
<
typename
GUM_SCALAR
>
225
INLINE
GUM_SCALAR
226
MultiDimBucket
<
GUM_SCALAR
>::
get
(
const
Instantiation
&
i
)
const
{
227
compute
();
228
229
if
(
bucket__
) {
230
try
{
231
return
bucket__
->
get
(
232
*(
instantiations__
.
second
(
const_cast
<
Instantiation
* >(&
i
))));
233
}
catch
(
NotFound
&) {
return
bucket__
->
get
(
i
); }
234
}
else
if
(
i
.
isMaster
(
this
)) {
235
if
(!
slavesValue__
.
exists
(&
i
)) {
236
slavesValue__
.
insert
(&
i
,
computeValue__
(
i
));
237
}
238
239
return
slavesValue__
[&
i
];
240
}
else
{
241
return
computeValue__
(
i
);
242
}
243
}
244
245
template
<
typename
GUM_SCALAR
>
246
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
changeNotification
(
247
const
Instantiation
&
i
,
248
const
DiscreteVariable
*
const
var
,
249
Idx
oldval
,
250
Idx
newval
) {
251
if
(
bucket__
) {
252
try
{
253
bucket__
->
changeNotification
(
254
*(
instantiations__
).
second
(
const_cast
<
Instantiation
* >(&
i
)),
255
var
,
256
oldval
,
257
newval
);
258
}
catch
(
NotFound
&) {
259
// Then i is not a slave of this
260
}
261
}
else
{
262
slavesValue__
.
erase
(&
i
);
263
}
264
}
265
266
template
<
typename
GUM_SCALAR
>
267
INLINE
void
268
MultiDimBucket
<
GUM_SCALAR
>::
setFirstNotification
(
const
Instantiation
&
i
) {
269
if
(
bucket__
) {
270
try
{
271
bucket__
->
setFirstNotification
(
272
*(
instantiations__
).
second
(
const_cast
<
Instantiation
* >(&
i
)));
273
}
catch
(
NotFound
&) {
274
// Then i is not a slave of this
275
}
276
}
else
{
277
slavesValue__
.
erase
(&
i
);
278
}
279
}
280
281
template
<
typename
GUM_SCALAR
>
282
INLINE
void
283
MultiDimBucket
<
GUM_SCALAR
>::
setLastNotification
(
const
Instantiation
&
i
) {
284
if
(
bucket__
) {
285
try
{
286
bucket__
->
setLastNotification
(
287
*(
instantiations__
).
second
(
const_cast
<
Instantiation
* >(&
i
)));
288
}
catch
(
NotFound
&) {
289
// Then i is not a slave of this
290
}
291
}
else
{
292
slavesValue__
.
erase
(&
i
);
293
}
294
}
295
296
template
<
typename
GUM_SCALAR
>
297
INLINE
void
298
MultiDimBucket
<
GUM_SCALAR
>::
setIncNotification
(
const
Instantiation
&
i
) {
299
if
(
bucket__
) {
300
try
{
301
bucket__
->
setIncNotification
(
302
*(
instantiations__
.
second
(
const_cast
<
Instantiation
* >(&
i
))));
303
}
catch
(
NotFound
&) {
304
// Then i is not a slave of this
305
}
306
}
else
{
307
slavesValue__
.
erase
(&
i
);
308
}
309
}
310
311
template
<
typename
GUM_SCALAR
>
312
INLINE
void
313
MultiDimBucket
<
GUM_SCALAR
>::
setDecNotification
(
const
Instantiation
&
i
) {
314
if
(
bucket__
) {
315
try
{
316
bucket__
->
setDecNotification
(
317
*(
instantiations__
.
second
(
const_cast
<
Instantiation
* >(&
i
))));
318
}
catch
(
NotFound
&) {
319
// Then i is not a slave of this
320
}
321
}
else
{
322
slavesValue__
.
erase
(&
i
);
323
}
324
}
325
326
template
<
typename
GUM_SCALAR
>
327
INLINE
void
328
MultiDimBucket
<
GUM_SCALAR
>::
setChangeNotification
(
const
Instantiation
&
i
) {
329
if
(
bucket__
) {
330
try
{
331
bucket__
->
setChangeNotification
(
332
*(
instantiations__
.
second
(
const_cast
<
Instantiation
* >(&
i
))));
333
}
catch
(
NotFound
&) {
334
// Then i is not a slave of this
335
}
336
}
else
{
337
slavesValue__
.
erase
(&
i
);
338
}
339
}
340
341
template
<
typename
GUM_SCALAR
>
342
INLINE
bool
MultiDimBucket
<
GUM_SCALAR
>::
registerSlave
(
Instantiation
&
i
) {
343
if
(
bucket__
) {
344
try
{
345
instantiations__
.
insert
(&
i
,
new
Instantiation
(*
bucket__
));
346
}
catch
(
DuplicateElement
&) {
return
false
; }
347
}
348
349
return
MultiDimImplementation
<
GUM_SCALAR
>::
registerSlave
(
i
);
350
}
351
352
template
<
typename
GUM_SCALAR
>
353
INLINE
bool
MultiDimBucket
<
GUM_SCALAR
>::
unregisterSlave
(
Instantiation
&
i
) {
354
MultiDimReadOnly
<
GUM_SCALAR
>::
unregisterSlave
(
i
);
355
356
if
(
bucket__
) {
357
try
{
358
delete
instantiations__
.
second
(&
i
);
359
instantiations__
.
eraseFirst
(&
i
);
360
return
true
;
361
}
catch
(
NotFound
&) {
return
false
; }
362
}
else
{
363
if
(
slavesValue__
.
exists
(&
i
)) {
364
slavesValue__
.
erase
(&
i
);
365
return
true
;
366
}
else
{
367
return
false
;
368
}
369
}
370
}
371
372
template
<
typename
GUM_SCALAR
>
373
INLINE
MultiDimAdressable
&
MultiDimBucket
<
GUM_SCALAR
>::
getMasterRef
() {
374
if
(
bucket__
) {
375
return
*
bucket__
;
376
}
else
{
377
return
*
this
;
378
}
379
}
380
381
template
<
typename
GUM_SCALAR
>
382
INLINE
const
MultiDimAdressable
&
383
MultiDimBucket
<
GUM_SCALAR
>::
getMasterRef
()
const
{
384
if
(
bucket__
) {
385
return
*
bucket__
;
386
}
else
{
387
return
*
this
;
388
}
389
}
390
391
template
<
typename
GUM_SCALAR
>
392
INLINE
std
::
string
393
MultiDimBucket
<
GUM_SCALAR
>::
toString
(
const
Instantiation
*
i
)
const
{
394
std
::
stringstream
sBuff
;
395
sBuff
<< (*
i
) <<
" = "
<<
get
(*
i
);
396
return
sBuff
.
str
();
397
}
398
399
template
<
typename
GUM_SCALAR
>
400
void
MultiDimBucket
<
GUM_SCALAR
>::
commitMultipleChanges_
() {
401
MultiDimImplementation
<
GUM_SCALAR
>::
commitMultipleChanges_
();
402
403
if
(
this
->
domainSize
() <=
bufferSize__
) {
404
initializeBuffer__
();
405
}
else
{
406
eraseBuffer__
();
407
}
408
409
allVariables__
.
clear
();
410
411
while
(!
allVarsInst__
.
empty
()) {
412
allVarsInst__
.
erase
(**(
allVarsInst__
.
variablesSequence
().
beginSafe
()));
413
}
414
415
for
(
// HashTableIteratorSafe<const MultiDimContainer<GUM_SCALAR>*,
416
// Instantiation*>
417
auto
iter
=
multiDims__
.
beginSafe
();
iter
!=
multiDims__
.
endSafe
();
418
++
iter
) {
419
for
(
auto
var
:
iter
.
key
()->
variablesSequence
()) {
420
addVariable__
(
var
);
421
}
422
}
423
424
changed__
=
true
;
425
}
426
427
template
<
typename
GUM_SCALAR
>
428
INLINE
GUM_SCALAR
&
429
MultiDimBucket
<
GUM_SCALAR
>::
get_
(
const
Instantiation
&
i
)
const
{
430
GUM_ERROR
(
OperationNotAllowed
,
"a MultiDimBucket is a read only MultiDim"
);
431
}
432
433
template
<
typename
GUM_SCALAR
>
434
INLINE
void
435
MultiDimBucket
<
GUM_SCALAR
>::
addVariable__
(
const
DiscreteVariable
*
var
) {
436
try
{
437
allVariables__
.
insert
(
var
);
438
allVarsInst__
.
add
(*
var
);
439
}
catch
(
DuplicateElement
&) {
440
// Nothing to do then!
441
}
442
}
443
444
template
<
typename
GUM_SCALAR
>
445
void
MultiDimBucket
<
GUM_SCALAR
>::
eraseVariable__
(
const
DiscreteVariable
*
var
) {
446
bool
found
=
false
;
447
448
for
(
HashTableIteratorSafe
<
const
MultiDimContainer
<
GUM_SCALAR
>*,
449
Instantiation
* >
iter
450
=
multiDims__
.
beginSafe
();
451
iter
!=
multiDims__
.
endSafe
();
452
++
iter
) {
453
if
(
iter
.
key
()->
contains
(*
var
)) {
454
found
=
true
;
455
break
;
456
}
457
}
458
459
// No one use it, we can safely remove it
460
if
(!
found
) {
461
allVariables__
.
erase
(
var
);
462
allVarsInst__
.
erase
(*
var
);
463
}
464
}
465
466
template
<
typename
GUM_SCALAR
>
467
void
MultiDimBucket
<
GUM_SCALAR
>::
initializeBuffer__
() {
468
if
(
bucket__
) {
469
typedef
Bijection
<
Instantiation
*,
Instantiation
* >::
iterator_safe
BiIter
;
470
471
for
(
BiIter
iter
=
instantiations__
.
beginSafe
();
472
iter
!=
instantiations__
.
endSafe
();
473
++
iter
) {
474
delete
iter
.
second
();
475
}
476
477
instantiations__
.
clear
();
478
delete
bucket__
;
479
bucket__
= 0;
480
}
481
482
// Creating the table.
483
bucket__
=
new
MultiDimArray
<
GUM_SCALAR
>();
484
485
for
(
auto
var
:
this
->
variablesSequence
()) {
486
bucket__
->
add
(*
var
);
487
}
488
489
if
(!
this
->
slaves_
().
empty
()) {
490
for
(
List
<
Instantiation
* >::
const_iterator_safe
iter
491
=
this
->
slaves_
().
cbeginSafe
();
492
iter
!=
this
->
slaves_
().
cendSafe
();
493
++
iter
) {
494
instantiations__
.
insert
(*
iter
,
new
Instantiation
(*
bucket__
));
495
}
496
}
497
498
changed__
=
true
;
499
}
500
501
template
<
typename
GUM_SCALAR
>
502
void
MultiDimBucket
<
GUM_SCALAR
>::
eraseBuffer__
() {
503
if
(
bucket__
) {
504
typedef
Bijection
<
Instantiation
*,
Instantiation
* >::
iterator_safe
BiIter
;
505
506
for
(
BiIter
iter
=
instantiations__
.
beginSafe
();
507
iter
!=
instantiations__
.
endSafe
();
508
++
iter
) {
509
delete
iter
.
second
();
510
}
511
512
instantiations__
.
clear
();
513
delete
bucket__
;
514
bucket__
= 0;
515
}
516
}
517
518
template
<
typename
GUM_SCALAR
>
519
GUM_SCALAR
MultiDimBucket
<
GUM_SCALAR
>::
computeValue__
(
520
const
Instantiation
&
value
)
const
{
521
try
{
522
GUM_SCALAR
sum
= (
GUM_SCALAR
)0;
523
GUM_SCALAR
current
;
524
allVarsInst__
.
setVals
(
value
);
525
526
for
(
allVarsInst__
.
setFirstOut
(
value
); !
allVarsInst__
.
end
();
527
allVarsInst__
.
incOut
(
value
)) {
528
current
= (
GUM_SCALAR
)1;
529
530
for
(
HashTableIteratorSafe
<
const
MultiDimContainer
<
GUM_SCALAR
>*,
531
Instantiation
* >
iter
532
=
multiDims__
.
beginSafe
();
533
iter
!=
multiDims__
.
endSafe
();
534
++
iter
) {
535
(
iter
.
val
())->
setVals
(
allVarsInst__
);
536
current
*=
iter
.
key
()->
get
(*(
iter
.
val
()));
537
}
538
539
sum
+=
current
;
540
}
541
542
return
sum
;
543
}
catch
(
NotFound
&
e
) {
544
std
::
cerr
<<
std
::
endl
<<
e
.
errorContent
() <<
std
::
endl
;
545
// This happens if the bucket is empty.
546
GUM_ERROR
(
SizeError
,
"This MultiDimBucket is empty."
);
547
}
548
}
549
550
template
<
typename
GUM_SCALAR
>
551
INLINE
MultiDimContainer
<
GUM_SCALAR
>*
552
MultiDimBucket
<
GUM_SCALAR
>::
newFactory
()
const
{
553
return
new
MultiDimBucket
<
GUM_SCALAR
>;
554
}
555
556
template
<
typename
GUM_SCALAR
>
557
INLINE
const
MultiDimArray
<
GUM_SCALAR
>&
558
MultiDimBucket
<
GUM_SCALAR
>::
bucket
()
const
{
559
if
(
bucket__
) {
560
return
*
bucket__
;
561
}
else
{
562
GUM_ERROR
(
OperationNotAllowed
,
"bucket not used."
);
563
}
564
}
565
566
template
<
typename
GUM_SCALAR
>
567
INLINE
void
MultiDimBucket
<
GUM_SCALAR
>::
replace_
(
const
DiscreteVariable
*
x
,
568
const
DiscreteVariable
*
y
) {
569
MultiDimImplementation
<
GUM_SCALAR
>::
replace_
(
x
,
y
);
570
typedef
Bijection
<
Instantiation
*,
Instantiation
* >::
iterator_safe
Iter
;
571
572
for
(
Iter
iter
=
instantiations__
.
beginSafe
();
573
iter
!=
instantiations__
.
endSafe
();
574
++
iter
) {
575
iter
.
first
()->
replace
(*
x
, *
y
);
576
iter
.
second
()->
replace
(*
x
, *
y
);
577
}
578
579
if
(
bucket__
)
bucket__
->
replace
(*
x
, *
y
);
580
581
allVariables__
.
erase
(
x
);
582
allVariables__
.
insert
(
y
);
583
allVarsInst__
.
replace
(*
x
, *
y
);
584
}
585
586
template
<
typename
GUM_SCALAR
>
587
INLINE
const
HashTable
<
const
MultiDimContainer
<
GUM_SCALAR
>*,
Instantiation
* >&
588
MultiDimBucket
<
GUM_SCALAR
>::
multidims
()
const
{
589
return
multiDims__
;
590
}
591
592
}
/* namespace gum */
gum::Set::emplace
INLINE void emplace(Args &&... args)
Definition:
set_tpl.h:669