forked from python/peps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pep-0209.txt
704 lines (529 loc) · 28.2 KB
/
pep-0209.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
PEP: 209
Title: Multi-dimensional Arrays
Author: Paul Barrett <[email protected]>, Travis Oliphant <[email protected]>
Status: Withdrawn
Type: Standards Track
Content-Type: text/x-rst
Created: 03-Jan-2001
Python-Version: 2.2
Post-History:
Abstract
========
This PEP proposes a redesign and re-implementation of the multi-
dimensional array module, Numeric, to make it easier to add new
features and functionality to the module. Aspects of Numeric 2
that will receive special attention are efficient access to arrays
exceeding a gigabyte in size and composed of inhomogeneous data
structures or records. The proposed design uses four Python
classes: ArrayType, UFunc, Array, and ArrayView; and a low-level
C-extension module, _ufunc, to handle the array operations
efficiently. In addition, each array type has its own C-extension
module which defines the coercion rules, operations, and methods
for that type. This design enables new types, features, and
functionality to be added in a modular fashion. The new version
will introduce some incompatibilities with the current Numeric.
Motivation
==========
Multi-dimensional arrays are commonly used to store and manipulate
data in science, engineering, and computing. Python currently has
an extension module, named Numeric (henceforth called Numeric 1),
which provides a satisfactory set of functionality for users
manipulating homogeneous arrays of data of moderate size (of order
10 MB). For access to larger arrays (of order 100 MB or more) of
possibly inhomogeneous data, the implementation of Numeric 1 is
inefficient and cumbersome. In the future, requests by the
Numerical Python community for additional functionality is also
likely as PEPs 211: Adding New Linear Operators to Python, and
225: Elementwise/Objectwise Operators illustrate.
Proposal
========
This proposal recommends a re-design and re-implementation of
Numeric 1, henceforth called Numeric 2, which will enable new
types, features, and functionality to be added in an easy and
modular manner. The initial design of Numeric 2 should focus on
providing a generic framework for manipulating arrays of various
types and should enable a straightforward mechanism for adding new
array types and UFuncs. Functional methods that are more specific
to various disciplines can then be layered on top of this core.
This new module will still be called Numeric and most of the
behavior found in Numeric 1 will be preserved.
The proposed design uses four Python classes: ArrayType, UFunc,
Array, and ArrayView; and a low-level C-extension module to handle
the array operations efficiently. In addition, each array type
has its own C-extension module which defines the coercion rules,
operations, and methods for that type. At a later date, when core
functionality is stable, some Python classes can be converted to
C-extension types.
Some planned features are:
1. Improved memory usage
This feature is particularly important when handling large arrays
and can produce significant improvements in performance as well as
memory usage. We have identified several areas where memory usage
can be improved:
a. Use a local coercion model
Instead of using Python's global coercion model which creates
temporary arrays, Numeric 2, like Numeric 1, will implement a
local coercion model as described in :pep:`208` which defers the
responsibility of coercion to the operator. By using internal
buffers, a coercion operation can be done for each array
(including output arrays), if necessary, at the time of the
operation. Benchmarks [1]_ have shown that performance is at
most degraded only slightly and is improved in cases where the
internal buffers are less than the L2 cache size and the
processor is under load. To avoid array coercion altogether,
C functions having arguments of mixed type are allowed in
Numeric 2.
b. Avoid creation of temporary arrays
In complex array expressions (i.e. having more than one
operation), each operation will create a temporary array which
will be used and then deleted by the succeeding operation. A
better approach would be to identify these temporary arrays
and reuse their data buffers when possible, namely when the
array shape and type are the same as the temporary array being
created. This can be done by checking the temporary array's
reference count. If it is 1, then it will be deleted once the
operation is done and is a candidate for reuse.
c. Optional use of memory-mapped files
Numeric users sometimes need to access data from very large
files or to handle data that is greater than the available
memory. Memory-mapped arrays provide a mechanism to do this
by storing the data on disk while making it appear to be in
memory. Memory- mapped arrays should improve access to all
files by eliminating one of two copy steps during a file
access. Numeric should be able to access in-memory and
memory-mapped arrays transparently.
d. Record access
In some fields of science, data is stored in files as binary
records. For example, in astronomy, photon data is stored as a
1 dimensional list of photons in order of arrival time. These
records or C-like structures contain information about the
detected photon, such as its arrival time, its position on the
detector, and its energy. Each field may be of a different
type, such as char, int, or float. Such arrays introduce new
issues that must be dealt with, in particular byte alignment
or byte swapping may need to be performed for the numeric
values to be properly accessed (though byte swapping is also
an issue for memory mapped data). Numeric 2 is designed to
automatically handle alignment and representational issues
when data is accessed or operated on. There are two
approaches to implementing records; as either a derived array
class or a special array type, depending on your point-of-
view. We defer this discussion to the Open Issues section.
2. Additional array types
Numeric 1 has 11 defined types: char, ubyte, sbyte, short, int,
long, float, double, cfloat, cdouble, and object. There are no
ushort, uint, or ulong types, nor are there more complex types
such as a bit type which is of use to some fields of science and
possibly for implementing masked-arrays. The design of Numeric 1
makes the addition of these and other types a difficult and
error-prone process. To enable the easy addition (and deletion)
of new array types such as a bit type described below, a re-design
of Numeric is necessary.
a. Bit type
The result of a rich comparison between arrays is an array of
boolean values. The result can be stored in an array of type
char, but this is an unnecessary waste of memory. A better
implementation would use a bit or boolean type, compressing
the array size by a factor of eight. This is currently being
implemented for Numeric 1 (by Travis Oliphant) and should be
included in Numeric 2.
3. Enhanced array indexing syntax
The extended slicing syntax was added to Python to provide greater
flexibility when manipulating Numeric arrays by allowing
step-sizes greater than 1. This syntax works well as a shorthand
for a list of regularly spaced indices. For those situations
where a list of irregularly spaced indices are needed, an enhanced
array indexing syntax would allow 1-D arrays to be arguments.
4. Rich comparisons
The implementation of :pep:`207`: Rich Comparisons in Python 2.1
provides additional flexibility when manipulating arrays. We
intend to implement this feature in Numeric 2.
5. Array broadcasting rules
When an operation between a scalar and an array is done, the
implied behavior is to create a new array having the same shape as
the array operand containing the scalar value. This is called
array broadcasting. It also works with arrays of lesser rank,
such as vectors. This implicit behavior is implemented in Numeric
1 and will also be implemented in Numeric 2.
Design and Implementation
=========================
The design of Numeric 2 has four primary classes:
1. ArrayType:
This is a simple class that describes the fundamental properties
of an array-type, e.g. its name, its size in bytes, its coercion
relations with respect to other types, etc., e.g.
::
Int32 = ArrayType('Int32', 4, 'doc-string')
Its relation to the other types is defined when the C-extension
module for that type is imported. The corresponding Python code
is::
Int32.astype[Real64] = Real64
This says that the Real64 array-type has higher priority than the
Int32 array-type.
The following attributes and methods are proposed for the core
implementation. Additional attributes can be added on an
individual basis, e.g. .bitsize or .bitstrides for the bit type.
Attributes::
.name: e.g. "Int32", "Float64", etc.
.typecode: e.g. 'i', 'f', etc.
(for backward compatibility)
.size (in bytes): e.g. 4, 8, etc.
.array_rules (mapping): rules between array types
.pyobj_rules (mapping): rules between array and python types
.doc: documentation string
Methods::
__init__(): initialization
__del__(): destruction
__repr__(): representation
C-API: This still needs to be fleshed-out.
2. UFunc:
This class is the heart of Numeric 2. Its design is similar to
that of ArrayType in that the UFunc creates a singleton callable
object whose attributes are name, total and input number of
arguments, a document string, and an empty CFunc dictionary; e.g.
::
add = UFunc('add', 3, 2, 'doc-string')
When defined the add instance has no C functions associated with
it and therefore can do no work. The CFunc dictionary is
populated or registered later when the C-extension module for an
array-type is imported. The arguments of the register method are:
function name, function descriptor, and the CUFunc object. The
corresponding Python code is
::
add.register('add', (Int32, Int32, Int32), cfunc-add)
In the initialization function of an array type module, e.g.
Int32, there are two C API functions: one to initialize the
coercion rules and the other to register the CFunc objects.
When an operation is applied to some arrays, the ``__call__`` method
is invoked. It gets the type of each array (if the output array
is not given, it is created from the coercion rules) and checks
the CFunc dictionary for a key that matches the argument types.
If it exists the operation is performed immediately, otherwise the
coercion rules are used to search for a related operation and set
of conversion functions. The ``__call__`` method then invokes a
compute method written in C to iterate over slices of each array,
namely::
_ufunc.compute(slice, data, func, swap, conv)
The 'func' argument is a CFuncObject, while the 'swap' and 'conv'
arguments are lists of CFuncObjects for those arrays needing pre-
or post-processing, otherwise None is used. The data argument is
a list of buffer objects, and the slice argument gives the number
of iterations for each dimension along with the buffer offset and
step size for each array and each dimension.
We have predefined several UFuncs for use by the ``__call__`` method:
cast, swap, getobj, and setobj. The cast and swap functions do
coercion and byte-swapping, respectively and the getobj and setobj
functions do coercion between Numeric arrays and Python sequences.
The following attributes and methods are proposed for the core
implementation.
Attributes::
.name: e.g. "add", "subtract", etc.
.nargs: number of total arguments
.iargs: number of input arguments
.cfuncs (mapping): the set C functions
.doc: documentation string
Methods::
__init__(): initialization
__del__(): destruction
__repr__(): representation
__call__(): look-up and dispatch method
initrule(): initialize coercion rule
uninitrule(): uninitialize coercion rule
register(): register a CUFunc
unregister(): unregister a CUFunc
C-API: This still needs to be fleshed-out.
3. Array:
This class contains information about the array, such as shape,
type, endian-ness of the data, etc.. Its operators, '+', '-',
etc. just invoke the corresponding UFunc function, e.g.
::
def __add__(self, other):
return ufunc.add(self, other)
The following attributes, methods, and functions are proposed for
the core implementation.
Attributes::
.shape: shape of the array
.format: type of the array
.real (only complex): real part of a complex array
.imag (only complex): imaginary part of a complex array
Methods::
__init__(): initialization
__del__(): destruction
__repr_(): representation
__str__(): pretty representation
__cmp__(): rich comparison
__len__():
__getitem__():
__setitem__():
__getslice__():
__setslice__():
numeric methods:
copy(): copy of array
aslist(): create list from array
asstring(): create string from array
Functions::
fromlist(): create array from sequence
fromstring(): create array from string
array(): create array with shape and value
concat(): concatenate two arrays
resize(): resize array
C-API: This still needs to be fleshed-out.
4. ArrayView
This class is similar to the Array class except that the reshape
and flat methods will raise exceptions, since non-contiguous
arrays cannot be reshaped or flattened using just pointer and
step-size information.
C-API: This still needs to be fleshed-out.
5. C-extension modules:
Numeric2 will have several C-extension modules.
a. _ufunc:
The primary module of this set is the _ufuncmodule.c. The
intention of this module is to do the bare minimum,
i.e. iterate over arrays using a specified C function. The
interface of these functions is the same as Numeric 1, i.e.
::
int (*CFunc)(char *data, int *steps, int repeat, void *func);
and their functionality is expected to be the same, i.e. they
iterate over the inner-most dimension.
The following attributes and methods are proposed for the core
implementation.
Attributes:
Methods::
compute():
C-API: This still needs to be fleshed-out.
b. _int32, _real64, etc.:
There will also be C-extension modules for each array type,
e.g. _int32module.c, _real64module.c, etc. As mentioned
previously, when these modules are imported by the UFunc
module, they will automatically register their functions and
coercion rules. New or improved versions of these modules can
be easily implemented and used without affecting the rest of
Numeric 2.
Open Issues
===========
1. Does slicing syntax default to copy or view behavior?
The default behavior of Python is to return a copy of a sub-list
or tuple when slicing syntax is used, whereas Numeric 1 returns a
view into the array. The choice made for Numeric 1 is apparently
for reasons of performance: the developers wish to avoid the
penalty of allocating and copying the data buffer during each
array operation and feel that the need for a deep copy of an array
to be rare. Yet, some have argued that Numeric's slice notation
should also have copy behavior to be consistent with Python lists.
In this case the performance penalty associated with copy behavior
can be minimized by implementing copy-on-write. This scheme has
both arrays sharing one data buffer (as in view behavior) until
either array is assigned new data at which point a copy of the
data buffer is made. View behavior would then be implemented by
an ArrayView class, whose behavior be similar to Numeric 1 arrays,
i.e. .shape is not settable for non-contiguous arrays. The use of
an ArrayView class also makes explicit what type of data the array
contains.
2. Does item syntax default to copy or view behavior?
A similar question arises with the item syntax. For example, if
``a = [[0,1,2], [3,4,5]]`` and ``b = a[0]``, then changing ``b[0]`` also changes
``a[0][0]``, because ``a[0]`` is a reference or view of the first row of a.
Therefore, if c is a 2-d array, it would appear that ``c[i]``
should return a 1-d array which is a view into, instead of a copy
of, c for consistency. Yet, ``c[i]`` can be considered just a
shorthand for ``c[i,:]`` which would imply copy behavior assuming
slicing syntax returns a copy. Should Numeric 2 behave the same
way as lists and return a view or should it return a copy.
3. How is scalar coercion implemented?
Python has fewer numeric types than Numeric which can cause
coercion problems. For example, when multiplying a Python scalar
of type float and a Numeric array of type float, the Numeric array
is converted to a double, since the Python float type is actually
a double. This is often not the desired behavior, since the
Numeric array will be doubled in size which is likely to be
annoying, particularly for very large arrays. We prefer that the
array type trumps the python type for the same type class, namely
integer, float, and complex. Therefore, an operation between a
Python integer and an Int16 (short) array will return an Int16
array. Whereas an operation between a Python float and an Int16
array would return a Float64 (double) array. Operations between
two arrays use normal coercion rules.
4. How is integer division handled?
In a future version of Python, the behavior of integer division
will change. The operands will be converted to floats, so the
result will be a float. If we implement the proposed scalar
coercion rules where arrays have precedence over Python scalars,
then dividing an array by an integer will return an integer array
and will not be consistent with a future version of Python which
would return an array of type double. Scientific programmers are
familiar with the distinction between integer and float-point
division, so should Numeric 2 continue with this behavior?
5. How should records be implemented?
There are two approaches to implementing records depending on your
point-of-view. The first is two divide arrays into separate
classes depending on the behavior of their types. For example,
numeric arrays are one class, strings a second, and records a
third, because the range and type of operations of each class
differ. As such, a record array is not a new type, but a
mechanism for a more flexible form of array. To easily access and
manipulate such complex data, the class is comprised of numeric
arrays having different byte offsets into the data buffer. For
example, one might have a table consisting of an array of Int16,
Real32 values. Two numeric arrays, one with an offset of 0 bytes
and a stride of 6 bytes to be interpreted as Int16, and one with an
offset of 2 bytes and a stride of 6 bytes to be interpreted as
Real32 would represent the record array. Both numeric arrays
would refer to the same data buffer, but have different offset and
stride attributes, and a different numeric type.
The second approach is to consider a record as one of many array
types, albeit with fewer, and possibly different, array operations
than for numeric arrays. This approach considers an array type to
be a mapping of a fixed-length string. The mapping can either be
simple, like integer and floating-point numbers, or complex, like
a complex number, a byte string, and a C-structure. The record
type effectively merges the struct and Numeric modules into a
multi-dimensional struct array. This approach implies certain
changes to the array interface. For example, the 'typecode'
keyword argument should probably be changed to the more
descriptive 'format' keyword.
a. How are record semantics defined and implemented?
Which ever implementation approach is taken for records, the
syntax and semantics of how they are to be accessed and
manipulated must be decided, if one wishes to have access to
sub-fields of records. In this case, the record type can
essentially be considered an inhomogeneous list, like a tuple
returned by the unpack method of the struct module; and a 1-d
array of records may be interpreted as a 2-d array with the
second dimension being the index into the list of fields.
This enhanced array semantics makes access to an array of one
or more of the fields easy and straightforward. It also
allows a user to do array operations on a field in a natural
and intuitive way. If we assume that records are implemented
as an array type, then last dimension defaults to 0 and can
therefore be neglected for arrays comprised of simple types,
like numeric.
6. How are masked-arrays implemented?
Masked-arrays in Numeric 1 are implemented as a separate array
class. With the ability to add new array types to Numeric 2, it
is possible that masked-arrays in Numeric 2 could be implemented
as a new array type instead of an array class.
7. How are numerical errors handled (IEEE floating-point errors in
particular)?
It is not clear to the proposers (Paul Barrett and Travis
Oliphant) what is the best or preferred way of handling errors.
Since most of the C functions that do the operation, iterate over
the inner-most (last) dimension of the array. This dimension
could contain a thousand or more items having one or more errors
of differing type, such as divide-by-zero, underflow, and
overflow. Additionally, keeping track of these errors may come at
the expense of performance. Therefore, we suggest several
options:
a. Print a message of the most severe error, leaving it to
the user to locate the errors.
b. Print a message of all errors that occurred and the number
of occurrences, leaving it to the user to locate the errors.
c. Print a message of all errors that occurred and a list of
where they occurred.
d. Or use a hybrid approach, printing only the most severe
error, yet keeping track of what and where the errors
occurred. This would allow the user to locate the errors
while keeping the error message brief.
8. What features are needed to ease the integration of FORTRAN
libraries and code?
It would be a good idea at this stage to consider how to ease the
integration of FORTRAN libraries and user code in Numeric 2.
Implementation Steps
====================
1. Implement basic UFunc capability
a. Minimal Array class:
Necessary class attributes and methods, e.g. .shape, .data,
.type, etc.
b. Minimal ArrayType class:
Int32, Real64, Complex64, Char, Object
c. Minimal UFunc class:
UFunc instantiation, CFunction registration, UFunc call for
1-D arrays including the rules for doing alignment,
byte-swapping, and coercion.
d. Minimal C-extension module:
_UFunc, which does the innermost array loop in C.
This step implements whatever is needed to do: 'c = add(a, b)'
where a, b, and c are 1-D arrays. It teaches us how to add
new UFuncs, to coerce the arrays, to pass the necessary
information to a C iterator method and to do the actually
computation.
2. Continue enhancing the UFunc iterator and Array class
a. Implement some access methods for the Array class:
print, repr, getitem, setitem, etc.
b. Implement multidimensional arrays
c. Implement some of basic Array methods using UFuncs:
+, -, \*, /, etc.
d. Enable UFuncs to use Python sequences.
3. Complete the standard UFunc and Array class behavior
a. Implement getslice and setslice behavior
b. Work on Array broadcasting rules
c. Implement Record type
4. Add additional functionality
a. Add more UFuncs
b. Implement buffer or mmap access
Incompatibilities
=================
The following is a list of incompatibilities in behavior between
Numeric 1 and Numeric 2.
1. Scalar coercion rules
Numeric 1 has single set of coercion rules for array and Python
numeric types. This can cause unexpected and annoying problems
during the calculation of an array expression. Numeric 2 intends
to overcome these problems by having two sets of coercion rules:
one for arrays and Python numeric types, and another just for
arrays.
2. No savespace attribute
The savespace attribute in Numeric 1 makes arrays with this
attribute set take precedence over those that do not have it set.
Numeric 2 will not have such an attribute and therefore normal
array coercion rules will be in effect.
3. Slicing syntax returns a copy
The slicing syntax in Numeric 1 returns a view into the original
array. The slicing behavior for Numeric 2 will be a copy. You
should use the ArrayView class to get a view into an array.
4. Boolean comparisons return a boolean array
A comparison between arrays in Numeric 1 results in a Boolean
scalar, because of current limitations in Python. The advent of
Rich Comparisons in Python 2.1 will allow an array of Booleans to
be returned.
5. Type characters are deprecated
Numeric 2 will have an ArrayType class composed of Type instances,
for example Int8, Int16, Int32, and Int for signed integers. The
typecode scheme in Numeric 1 will be available for backward
compatibility, but will be deprecated.
Appendices
==========
A. Implicit sub-arrays iteration
A computer animation is composed of a number of 2-D images or
frames of identical shape. By stacking these images into a single
block of memory, a 3-D array is created. Yet the operations to be
performed are not meant for the entire 3-D array, but on the set
of 2-D sub-arrays. In most array languages, each frame has to be
extracted, operated on, and then reinserted into the output array
using a for-like loop. The J language allows the programmer to
perform such operations implicitly by having a rank for the frame
and array. By default these ranks will be the same during the
creation of the array. It was the intention of the Numeric 1
developers to implement this feature, since it is based on the
language J. The Numeric 1 code has the required variables for
implementing this behavior, but was never implemented. We intend
to implement implicit sub-array iteration in Numeric 2, if the
array broadcasting rules found in Numeric 1 do not fully support
this behavior.
Copyright
=========
This document is placed in the public domain.
Related PEPs
============
* :pep:`207`: Rich Comparisons
by Guido van Rossum and David Ascher
* :pep:`208`: Reworking the Coercion Model
by Neil Schemenauer and Marc-Andre' Lemburg
* :pep:`211`: Adding New Linear Algebra Operators to Python
by Greg Wilson
* :pep:`225`: Elementwise/Objectwise Operators
by Huaiyu Zhu
* :pep:`228`: Reworking Python's Numeric Model
by Moshe Zadka
References
==========
.. [1] P. Greenfield 2000. private communication.