1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 2 "http://www.w3.org/TR/html4/loose.dtd"> 3<html> 4<!-- 5 == Copyright 2002 The Trustees of Indiana University. 6 7 == Use, modification and distribution is subject to the Boost Software 8 == License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 9 == http://www.boost.org/LICENSE_1_0.txt) 10 11 == Boost.MultiArray Library 12 == Authors: Ronald Garcia 13 == Jeremy Siek 14 == Andrew Lumsdaine 15 == See http://www.boost.org/libs/multi_array for documentation. 16 --> 17<head> 18 <title>The Boost Multidimensional Array Library (Boost.MultiArray)</title> 19</head> 20 21<body> 22 23<h1> 24 <img src="../../../boost.png" alt="boost logo" 25 width="277" align="middle" height="86"> 26 <br>The Boost Multidimensional Array Library 27 <br>(Boost.MultiArray) 28</h1> 29 30<h2>Synopsis</h2> 31 32<p> 33The Boost Multidimensional Array Library provides a class template for 34multidimensional arrays, as well as semantically equivalent 35adaptors for arrays of contiguous data. The classes in this library 36implement a common interface, formalized as a generic programming 37concept. The interface design is in line with the precedent set by the 38C++ Standard Library containers. Boost MultiArray is a more efficient 39and convenient way to express N-dimensional arrays than existing 40alternatives (especially the 41<tt>std::vector<std::vector<...>></tt> formulation 42of N-dimensional arrays). The arrays provided by the library may be 43accessed using the familiar syntax of native C++ arrays. Additional 44features, such as resizing, reshaping, and creating views are 45available (and described below). 46 47 48<h2>Table of Contents</h2> 49 50<ol> 51 <li><a href="#sec_introduction">Introduction</a> 52 53 <li><a href="#sec_example">Short Example</a> 54 55 <li><a href="#sec_components">MultiArray Components</a> 56 57 <li><a href="#sec_assignment">Construction and Assignment</a> 58 59 <li><a href="#sec_generators">Array View and Subarray Type Generators</a> 60 61 <li><a href="#sec_dimensions">Specifying Array Dimensions</a> 62 63 <li><a href="#sec_access">Accessing Elements</a> 64 65 <li><a href="#sec_views">Creating Views</a> 66 67 <li><a href="#sec_storage">Storage Ordering</a> 68 69 <li><a href="#sec_base">Setting the Array Base</a> 70 71 <li><a href="#sec_reshape">Changing an Array's Shape</a> 72 73 <li><a href="#sec_resize">Resizing an Array</a> 74 75 <li><a href="#sec_concepts">MultiArray Concept</a> 76 77 <li><a href="#sec_testcases">Test Cases</a> 78 79 <li><a href="#sec_related">Related Work</a> 80 <li><a href="#sec_credits">Credits</a> 81</ol> 82 83 84<a name="sec_introduction"></a> 85<h2>Introduction</h2> 86 87<p> 88The C++ standard library provides several generic containers, but it 89does not provide any multidimensional array types. The 90<tt>std::vector</tt> class template can be used to implement 91N-dimensional arrays, for example expressing a 2-dimensional array of 92<tt>double</tt> elements using the type 93<tt>std::vector<std::vector<double>></tt>, but the 94resulting interface is unwieldy and the memory overhead can be quite 95high. Native C++ arrays (i.e. <tt>int arr[2][2][2];</tt>) do not 96immediately interoperate well with the C++ Standard Library, and they 97also lose information at function call boundaries (specifically the 98extent of the last dimension). Finally, a dynamically allocated 99contiguous block of elements can be treated as an array, though this 100method requires manual bookkeeping that is error prone and obfuscates 101the intent of the programmer. 102</p> 103 104<p> 105The Boost MultiArray library enhances the C++ standard containers with 106versatile multi-dimensional array abstractions. It includes a general 107array class template and native array adaptors that support idiomatic 108array operations and interoperate with C++ Standard Library containers 109and algorithms. The arrays share a common interface, expressed as a 110generic programming in terms of which generic array algorithms can be 111implemented. 112</p> 113 114<p> 115This document is meant to provide an introductory tutorial and user's 116guide for the most basic and common usage patterns of MultiArray 117components. The <a href="./reference.html">reference manual</a> 118provides more complete and formal documentation of library features. 119</p> 120 121<a name="sec_example"></a> 122<h2>Short Example</h2> 123What follows is a brief example of the use of <tt>multi_array</tt>: 124 125<blockquote> 126<pre> 127#include "boost/multi_array.hpp" 128#include <cassert> 129 130int 131main () { 132 // Create a 3D array that is 3 x 4 x 2 133 typedef boost::multi_array<double, 3> array_type; 134 typedef array_type::index index; 135 array_type A(boost::extents[3][4][2]); 136 137 // Assign values to the elements 138 int values = 0; 139 for(index i = 0; i != 3; ++i) 140 for(index j = 0; j != 4; ++j) 141 for(index k = 0; k != 2; ++k) 142 A[i][j][k] = values++; 143 144 // Verify values 145 int verify = 0; 146 for(index i = 0; i != 3; ++i) 147 for(index j = 0; j != 4; ++j) 148 for(index k = 0; k != 2; ++k) 149 assert(A[i][j][k] == verify++); 150 151 return 0; 152} 153</pre> 154</blockquote> 155 156<a name="sec_components"></a> 157<h2>MultiArray Components</h2> 158 159Boost.MultiArray's implementation (boost/multi_array.hpp) provides three user-level class templates: 160 161<ol> 162 <li><a href="./reference.html#multi_array"><tt>multi_array</tt></a>, 163 164 <li><a href="./reference.html#multi_array_ref"><tt>multi_array_ref</tt></a>, and 165 166 <li><a href="./reference.html#const_multi_array_ref"><tt>const_multi_array_ref</tt></a> 167</ol> 168 169<tt>multi_array</tt> is a container template. When instantiated, it 170allocates space for the number of elements corresponding to the 171dimensions specified at construction time. A <tt>multi_array</tt> may 172also be default constructed and resized as needed. 173 174<p> 175<tt>multi_array_ref</tt> adapts an existing array of data to provide 176the <tt>multi_array</tt> interface. <tt>multi_array_ref</tt> does not own the 177data passed to it. 178 179<p> 180<tt>const_multi_array_ref</tt> is similar to <tt>multi_array_ref</tt> 181but guarantees that the contents of the array are immutable. It can 182thus wrap pointers of type <i>T const*</i>. 183 184<p> 185The three components exhibit very similar behavior. Aside from 186constructor parameters, <tt>multi_array</tt> and 187<tt>multi_array_ref</tt> export the same interface. 188<tt>const_multi_array_ref</tt> provides only the constness-preserving 189portions of the <tt>multi_array_ref</tt> interface. 190 191<a name="sec_assignment"></a> 192<h2>Construction and Assignment</h2> 193<p>Each of the array types - 194<a href="./reference.html#multi_array"><tt>multi_array</tt></a>, 195<a href="./reference.html#multi_array_ref"><tt>multi_array_ref</tt></a>, and 196<a href="./reference.html#const_multi_array_ref"><tt>const_multi_array_ref</tt></a> - 197provides a specialized set of constructors. For further information, 198consult their reference pages. 199 200<p>All of the non-const array types in this library provide assignment 201operators<tt>operator=()</tt>. Each of the array types <tt>multi_array</tt>, 202 <tt>multi_array_ref</tt>, <tt>subarray</tt>, and 203 <tt>array_view</tt> can be assigned from any 204of the others, so long as their shapes match. The 205 const variants, <tt>const_multi_array_ref</tt>, 206<tt>const_subarray</tt>, and <tt>const_array_view</tt>, can be the 207 source of a copy to an array with matching shape. 208Assignment results in a deep (element by element) copy of the data 209contained within an array. 210 211<a name="sec_generators"></a> 212<h2>Array View and Subarray Type Generators</h2> 213In some situations, the use of nested generators for array_view and 214subarray types is inconvenient. For example, inside a 215function template parameterized upon array type, the extra 216"template" keywords can be obfuscating. More likely though, some 217compilers cannot handle templates nested within template parameters. 218For this reason the type generators, <tt>subarray_gen</tt>, 219<tt>const_subarray_gen</tt>, <tt>array_view_gen</tt>, and 220<tt>const_array_view_gen</tt> are provided. Thus, the two typedefs 221in the following example result in the same type: 222<blockquote> 223<pre> 224template <typename Array> 225void my_function() { 226 typedef typename Array::template array_view<3>::type view1_t; 227 typedef typename boost::array_view_gen<Array,3>::type view2_t; 228 // ... 229} 230</pre> 231</blockquote> 232 233<a name="sec_dimensions"></a> 234<h2>Specifying Array Dimensions</h2> 235When creating most of the Boost.MultiArray components, it is necessary 236to specify both the number of dimensions and the extent of each 237(<tt>boost::multi_array</tt> also provides a default constructor). 238Though the number of dimensions is always specified as a template 239parameter, two separate mechanisms have been provided to specify the 240extent of each. 241<p>The first method involves passing a 242<a href="../../utility/Collection.html"> 243Collection</a> of extents to a 244constructor, most commonly a <tt>boost::array</tt>. The constructor 245will retrieve the beginning iterator from the container and retrieve N 246elements, corresponding to extents for the N dimensions. This is 247useful for writing dimension-independent code. 248<h3>Example</h3> 249<blockquote> 250<pre> 251 typedef boost::multi_array<double, 3> array_type; 252 boost::array<array_type::index, 3> shape = {{ 3, 4, 2 }}; 253 array_type A(shape); 254</pre> 255</blockquote> 256 257<p>The second method involves passing the constructor an <tt>extent_gen</tt> 258object, specifying the matrix dimensions. The <tt>extent_gen</tt> type 259 is defined in the <tt>multi_array_types</tt> namespace and as a 260 member of every array type, but by default, the library constructs a 261global <tt>extent_gen</tt> object <tt>boost::extents</tt>. In case of 262concern about memory used by these objects, defining 263<tt>BOOST_MULTI_ARRAY_NO_GENERATORS</tt> before including the library 264header inhibits its construction. 265 266<h3>Example</h3> 267<blockquote> 268<pre> 269 typedef boost::multi_array<double, 3> array_type; 270 array_type A(boost::extents[3][4][2]); 271</pre> 272</blockquote> 273 274<a name="sec_access"></a> 275<h2>Accessing Elements</h2> 276The Boost.MultiArray components provide two ways of accessing 277specific elements within a container. The first uses the traditional 278C array notation, provided by <tt>operator[]</tt>. 279<h3>Example</h3> 280<blockquote> 281<pre> 282 typedef boost::multi_array<double, 3> array_type; 283 array_type A(boost::extents[3][4][2]); 284 A[0][0][0] = 3.14; 285 assert(A[0][0][0] == 3.14); 286</pre> 287</blockquote> 288 289<p> The second method involves passing a 290<a href="../../utility/Collection.html"> 291Collection</a> of indices to <tt>operator()</tt>. N indices will be retrieved 292from the Collection for the N dimensions of the container. 293<h3>Example</h3> 294<blockquote> 295<pre> 296 typedef boost::multi_array<double, 3> array_type; 297 array_type A(boost::extents[3][4][2]); 298 boost::array<array_type::index,3> idx = {{0,0,0}}; 299 A(idx) = 3.14; 300 assert(A(idx) == 3.14); 301</pre> 302</blockquote> 303This can be useful for writing dimension-independent code, and under 304some compilers may yield higher performance than <tt>operator[].</tt> 305 306<p> 307By default, both of the above element access methods perform range 308checking. If a supplied index is out of the range defined for an 309array, an assertion will abort the program. To disable range 310checking (for performance reasons in production releases), define 311the <tt>BOOST_DISABLE_ASSERTS</tt> preprocessor macro prior to 312including multi_array.hpp in your application. 313 314<a name="sec_views"></a> 315<h2>Creating Views</h2> 316Boost.MultiArray provides the facilities for creating a sub-view of an 317already existing array component. It allows you to create a sub-view that 318retains the same number of dimensions as the original array or one 319that has less dimensions than the original as well. 320 321<p>Sub-view creation occurs by placing a call to operator[], passing 322it an <tt>index_gen</tt> type. The <tt>index_gen</tt> is populated by 323passing <tt>index_range</tt> objects to its <tt>operator[]</tt>. 324The <tt>index_range</tt> and <tt>index_gen</tt> types are defined in 325the <tt>multi_array_types</tt> namespace and as nested members of 326every array type. Similar to <tt>boost::extents</tt>, the library by 327default constructs the object <tt>boost::indices</tt>. You can 328suppress this object by 329defining <tt>BOOST_MULTI_ARRAY_NO_GENERATORS</tt> before including the 330library header. A simple sub-view creation example follows. 331 332<h3>Example</h3> 333<blockquote> 334<pre> 335 // myarray = 2 x 3 x 4 336 337 // 338 // array_view dims: [base,bound) (dimension striding default = 1) 339 // dim 0: [0,2) 340 // dim 1: [1,3) 341 // dim 2: [0,4) (strided by 2), 342 // 343 344 typedef boost::multi_array_types::index_range range; 345 // OR typedef array_type::index_range range; 346 array_type::array_view<3>::type myview = 347 myarray[ boost::indices[range(0,2)][range(1,3)][range(0,4,2)] ]; 348 349 for (array_type::index i = 0; i != 2; ++i) 350 for (array_type::index j = 0; j != 2; ++j) 351 for (array_type::index k = 0; k != 2; ++k) 352 assert(myview[i][j][k] == myarray[i][j+1][k*2]); 353</pre> 354</blockquote> 355 356 357<p>By passing an integral value to the index_gen, one may create a 358subview with fewer dimensions than the original array component (also 359called slicing). 360<h3>Example</h3> 361<blockquote> 362<pre> 363 // myarray = 2 x 3 x 4 364 365 // 366 // array_view dims: 367 // [base,stride,bound) 368 // [0,1,2), 1, [0,2,4) 369 // 370 371 typedef boost::multi_array_types::index_range range; 372 array_type::index_gen indices; 373 array_type::array_view<2>::type myview = 374 myarray[ indices[range(0,2)][1][range(0,4,2)] ]; 375 376 for (array_type::index i = 0; i != 2; ++i) 377 for (array_type::index j = 0; j != 2; ++j) 378 assert(myview[i][j] == myarray[i][1][j*2]); 379</pre> 380</blockquote> 381 382<h3>More on <tt>index_range</tt></h3> 383The <tt>index_range</tt> type provides several methods of specifying 384ranges for subview generation. Here are a few range instantiations 385that specify the same range. 386<h3>Example</h3> 387<blockquote> 388<pre> 389 // [base,stride,bound) 390 // [0,2,4) 391 392 typedef boost::multi_array_types::index_range range; 393 range a_range; 394 a_range = range(0,4,2); 395 a_range = range().start(0).finish(4).stride(2); 396 a_range = range().start(0).stride(2).finish(4); 397 a_range = 0 <= range().stride(2) < 4; 398 a_range = 0 <= range().stride(2) <= 3; 399</pre> 400</blockquote> 401 402An <tt>index_range</tt> object passed to a slicing operation will 403inherit its start and/or finish value from the array being sliced if 404you do not supply one. This conveniently prevents you from having to 405know the bounds of the array dimension in certain cases. For example, 406the default-constructed range will take the full extent of the 407dimension it is used to specify. 408 409<h3>Example</h3> 410<blockquote> 411<pre> 412 typedef boost::multi_array_types::index_range range; 413 range a_range; 414 415 // All elements in this dimension 416 a_range = range(); 417 418 // indices i where 3 <= i 419 a_range = range().start(3) 420 a_range = 3 <= range(); 421 a_range = 2 < range(); 422 423 // indices i where i < 7 424 a_range = range().finish(7) 425 a_range = range() < 7; 426 a_range = range() <= 6; 427</pre> 428</blockquote> 429 430The following example slicing operations exhibit some of the 431alternatives shown above 432<blockquote> 433<pre> 434 // take all of dimension 1 435 // take i < 5 for dimension 2 436 // take 4 <= j <= 7 for dimension 3 with stride 2 437 myarray[ boost::indices[range()][range() < 5 ][4 <= range().stride(2) <= 7] ]; 438</pre> 439</blockquote> 440 441<a name="sec_storage"></a> 442<h2>Storage Ordering</h2> 443Each array class provides constructors that accept a storage ordering 444parameter. This is most 445useful when interfacing with legacy codes that require an ordering 446different from standard C, such as FORTRAN. The possibilities are 447<tt>c_storage_order</tt>, <tt>fortran_storage_order</tt>, and 448<tt>general_storage_order</tt>. 449 450<p><tt>c_storage_order</tt>, which is the default, will store elements 451in memory in the same order as a C array would, that is, the 452dimensions are stored from last to first. 453 454<p><tt>fortran_storage_order</tt> will store elements in memory in the same order 455as FORTRAN would: from the first dimension to 456the last. Note that with use of this parameter, the array 457indices will remain zero-based. 458<h3>Example</h3> 459<blockquote> 460<pre> 461 typedef boost::multi_array<double,3> array_type; 462 array_type A(boost::extents[3][4][2],boost::fortran_storage_order()); 463 call_fortran_function(A.data()); 464</pre> 465</blockquote> 466 467<p><tt>general_storage_order</tt> allows one to customize both the order in 468which dimensions are stored in memory and whether dimensions are 469stored in ascending or descending order. 470<h3>Example</h3> 471<blockquote> 472<pre> 473 typedef boost::general_storage_order<3> storage; 474 typedef boost::multi_array<int,3> array_type; 475 476 // Store last dimension, then first, then middle 477 array_type::size_type ordering[] = {2,0,1}; 478 479 // Store the first dimension(dimension 0) in descending order 480 bool ascending[] = {false,true,true}; 481 482 array_type A(extents[3][4][2],storage(ordering,ascending)); 483</pre> 484</blockquote> 485 486 487<a name="sec_base"></a> 488<h2>Setting The Array Base</h2> 489In some situations, it may be inconvenient or awkward to use an 490array that is zero-based. 491the Boost.MultiArray components provide two facilities for changing the 492bases of an array. One may specify a pair of range values, with 493the <tt>extent_range</tt> type, to 494the <tt>extent_gen</tt> constructor in order to set the base value. 495 496<h3>Example</h3> 497<blockquote> 498<pre> 499 typedef boost::multi_array<double, 3> array_type; 500 typedef boost::multi_array_types::extent_range range; 501 // OR typedef array_type::extent_range range; 502 503 array_type::extent_gen extents; 504 505 // dimension 0: 0-based 506 // dimension 1: 1-based 507 // dimension 2: -1 - based 508 array_type A(extents[2][range(1,4)][range(-1,3)]); 509</pre> 510</blockquote> 511 512<p> 513An alternative is to first construct the array normally then 514reset the bases. To set all bases to the same value, use the 515<tt>reindex</tt> member function, passing it a single new index value. 516<h3>Example</h3> 517<blockquote> 518<pre> 519 typedef boost::multi_array<double, 3> array_type; 520 521 array_type::extent_gen extents; 522 523 array_type A(extents[2][3][4]); 524 // change to 1-based 525 A.reindex(1) 526</pre> 527</blockquote> 528 529<p> 530An alternative is to set each base separately using the 531<tt>reindex</tt> member function, passing it a Collection of index bases. 532<h3>Example</h3> 533<blockquote> 534<pre> 535 typedef boost::multi_array<double, 3> array_type; 536 537 array_type::extent_gen extents; 538 539 // dimension 0: 0-based 540 // dimension 1: 1-based 541 // dimension 2: (-1)-based 542 array_type A(extents[2][3][4]); 543 boost::array<array_type::index,ndims> bases = {{0, 1, -1}}; 544 A.reindex(bases); 545</pre> 546</blockquote> 547 548 549<a name="sec_reshape"></a> 550<h2>Changing an Array's Shape</h2> 551The Boost.MultiArray arrays provide a reshape operation. While the 552number of dimensions must remain the same, the shape of the array may 553change so long as the total number of 554elements contained remains the same. 555<h3>Example</h3> 556<blockquote> 557<pre> 558 typedef boost::multi_array<double, 3> array_type; 559 560 array_type::extent_gen extents; 561 array_type A(extents[2][3][4]); 562 boost::array<array_type::index,ndims> dims = {{4, 3, 2}}; 563 A.reshape(dims); 564</pre> 565</blockquote> 566 567<p> 568Note that reshaping an array does not affect the indexing. 569 570<a name="sec_resize"></a> 571<h2>Resizing an Array</h2> 572 573The <tt>boost::multi_array</tt> class provides an element-preserving 574resize operation. The number of dimensions must remain the same, but 575the extent of each dimension may be increased and decreased as 576desired. When an array is made strictly larger, the existing elements 577will be preserved by copying them into the new underlying memory and 578subsequently destructing the elements in the old underlying memory. 579Any new elements in the array are default constructed. However, if 580the new array size shrinks some of the dimensions, some elements will 581no longer be available. 582 583<h3>Example</h3> 584<blockquote> 585<pre> 586 typedef boost::multi_array<int, 3> array_type; 587 588 array_type::extent_gen extents; 589 array_type A(extents[3][3][3]); 590 A[0][0][0] = 4; 591 A[2][2][2] = 5; 592 A.resize(extents[2][3][4]); 593 assert(A[0][0][0] == 4); 594 // A[2][2][2] is no longer valid. 595</pre> 596</blockquote> 597 598 599<a name="sec_concepts"></a> 600<h2>MultiArray Concept</h2> 601Boost.MultiArray defines and uses the 602<a href="./reference.html#MultiArray">MultiArray</a> 603concept. It specifies an interface for N-dimensional containers. 604 605<a name="sec_testcases"></a> 606<h2>Test Cases</h2> 607Boost.MultiArray comes with a suite of test cases meant to exercise 608the features and semantics of the library. A description of the test 609cases can be found <a href="./test_cases.html">here</a>. 610 611<a name="sec_related"></a> 612<h2>Related Work</h2> 613 614<a href="../../array/index.html">boost::array</a> 615 and <a href="https://www.boost.org/sgi/stl/Vector.html">std::vector</a> are 616 one-dimensional containers of user data. Both manage their own 617 memory. <tt>std::valarray</tt> is a low-level 618 C++ Standard Library component 619 meant to provide portable high performance for numerical applications. 620<a href="http://www.oonumerics.org/blitz/">Blitz++</a> is 621 an array library developed by Todd 622 Veldhuizen. It uses 623 advanced C++ techniques to provide near-Fortran performance for 624 array-based numerical applications. 625 <b>array_traits</b> is a beta library, formerly distributed with 626 Boost, that provides a means to create iterators over native C++ 627 arrays. 628 629This library is analogous to 630<a href="../../array/index.html">boost::array</a> in that it augments C style N-dimensional 631arrays, as <tt>boost::array</tt> does for C one-dimensional arrays. 632 633 634<a name="sec_credits"></a> 635<h2>Credits</h2> 636<ul> 637 638 <li><a href="mailto:garcia@osl.iu.edu">Ronald Garcia</a> 639 is the primary author of the library. 640 641 <li><a href="http://www.boost.org/people/jeremy_siek.htm">Jeremy Siek</a> 642 helped with the library and provided a sounding board for ideas, 643 advice, and assistance porting to Microsoft Visual C++. 644 645 <li><a href="mailto:gbavestrelli@yahoo.com">Giovanni Bavestrelli</a> 646 provided an early implementation of an 647 N-dimensional array which inspired feedback from the 648 <a href="http://www.boost.org/">Boost</a> mailing list 649 members. Some design decisions in this work were based upon this 650 implementation and the comments it elicited. 651 652 <li><a href="mailto:tveldhui@acm.org">Todd Veldhuizen</a> wrote 653 <a href="http://oonumerics.org/blitz/">Blitz++</a>, which 654 inspired some aspects of this design. In addition, he supplied 655 feedback on the design and implementation of the library. 656 657 <li><a href="mailto:jewillco@osl.iu.edu">Jeremiah Willcock</a> 658 provided feedback on the implementation and design of the 659 library and some suggestions for features. 660 661 <li><a href="mailto:bdawes@acm.org">Beman Dawes</a> 662 helped immensely with porting the library to Microsoft Windows 663 compilers. 664 665 <li><a href="mailto:glenjofe@gmail.com">Glen Fernandes</a> 666 implemented support for the C++11 allocator model, including 667 support for stateful allocators, minimal allocators, and 668 optimized storage for stateless allocators. 669</ul> 670 671<hr> 672 673<address> 674<a href="mailto:garcia@.cs.indiana.edu">Ronald Garcia</a> 675</address> 676<!-- Created: Fri Jun 29 10:53:07 EST 2001 --> 677<!-- hhmts start -->Last modified: Tue Feb 7 17:15:50 EST 2006 <!-- hhmts end --> 678 679</body> 680</html> 681