- •Contents
- •List of Figures
- •List of Tables
- •Acknowledgments
- •Introduction to MPI
- •Overview and Goals
- •Background of MPI-1.0
- •Background of MPI-1.1, MPI-1.2, and MPI-2.0
- •Background of MPI-1.3 and MPI-2.1
- •Background of MPI-2.2
- •Who Should Use This Standard?
- •What Platforms Are Targets For Implementation?
- •What Is Included In The Standard?
- •What Is Not Included In The Standard?
- •Organization of this Document
- •MPI Terms and Conventions
- •Document Notation
- •Naming Conventions
- •Semantic Terms
- •Data Types
- •Opaque Objects
- •Array Arguments
- •State
- •Named Constants
- •Choice
- •Addresses
- •Language Binding
- •Deprecated Names and Functions
- •Fortran Binding Issues
- •C Binding Issues
- •C++ Binding Issues
- •Functions and Macros
- •Processes
- •Error Handling
- •Implementation Issues
- •Independence of Basic Runtime Routines
- •Interaction with Signals
- •Examples
- •Point-to-Point Communication
- •Introduction
- •Blocking Send and Receive Operations
- •Blocking Send
- •Message Data
- •Message Envelope
- •Blocking Receive
- •Return Status
- •Passing MPI_STATUS_IGNORE for Status
- •Data Type Matching and Data Conversion
- •Type Matching Rules
- •Type MPI_CHARACTER
- •Data Conversion
- •Communication Modes
- •Semantics of Point-to-Point Communication
- •Buffer Allocation and Usage
- •Nonblocking Communication
- •Communication Request Objects
- •Communication Initiation
- •Communication Completion
- •Semantics of Nonblocking Communications
- •Multiple Completions
- •Non-destructive Test of status
- •Probe and Cancel
- •Persistent Communication Requests
- •Send-Receive
- •Null Processes
- •Datatypes
- •Derived Datatypes
- •Type Constructors with Explicit Addresses
- •Datatype Constructors
- •Subarray Datatype Constructor
- •Distributed Array Datatype Constructor
- •Address and Size Functions
- •Lower-Bound and Upper-Bound Markers
- •Extent and Bounds of Datatypes
- •True Extent of Datatypes
- •Commit and Free
- •Duplicating a Datatype
- •Use of General Datatypes in Communication
- •Correct Use of Addresses
- •Decoding a Datatype
- •Examples
- •Pack and Unpack
- •Canonical MPI_PACK and MPI_UNPACK
- •Collective Communication
- •Introduction and Overview
- •Communicator Argument
- •Applying Collective Operations to Intercommunicators
- •Barrier Synchronization
- •Broadcast
- •Example using MPI_BCAST
- •Gather
- •Examples using MPI_GATHER, MPI_GATHERV
- •Scatter
- •Examples using MPI_SCATTER, MPI_SCATTERV
- •Example using MPI_ALLGATHER
- •All-to-All Scatter/Gather
- •Global Reduction Operations
- •Reduce
- •Signed Characters and Reductions
- •MINLOC and MAXLOC
- •All-Reduce
- •Process-local reduction
- •Reduce-Scatter
- •MPI_REDUCE_SCATTER_BLOCK
- •MPI_REDUCE_SCATTER
- •Scan
- •Inclusive Scan
- •Exclusive Scan
- •Example using MPI_SCAN
- •Correctness
- •Introduction
- •Features Needed to Support Libraries
- •MPI's Support for Libraries
- •Basic Concepts
- •Groups
- •Contexts
- •Intra-Communicators
- •Group Management
- •Group Accessors
- •Group Constructors
- •Group Destructors
- •Communicator Management
- •Communicator Accessors
- •Communicator Constructors
- •Communicator Destructors
- •Motivating Examples
- •Current Practice #1
- •Current Practice #2
- •(Approximate) Current Practice #3
- •Example #4
- •Library Example #1
- •Library Example #2
- •Inter-Communication
- •Inter-communicator Accessors
- •Inter-communicator Operations
- •Inter-Communication Examples
- •Caching
- •Functionality
- •Communicators
- •Windows
- •Datatypes
- •Error Class for Invalid Keyval
- •Attributes Example
- •Naming Objects
- •Formalizing the Loosely Synchronous Model
- •Basic Statements
- •Models of Execution
- •Static communicator allocation
- •Dynamic communicator allocation
- •The General case
- •Process Topologies
- •Introduction
- •Virtual Topologies
- •Embedding in MPI
- •Overview of the Functions
- •Topology Constructors
- •Cartesian Constructor
- •Cartesian Convenience Function: MPI_DIMS_CREATE
- •General (Graph) Constructor
- •Distributed (Graph) Constructor
- •Topology Inquiry Functions
- •Cartesian Shift Coordinates
- •Partitioning of Cartesian structures
- •Low-Level Topology Functions
- •An Application Example
- •MPI Environmental Management
- •Implementation Information
- •Version Inquiries
- •Environmental Inquiries
- •Tag Values
- •Host Rank
- •IO Rank
- •Clock Synchronization
- •Memory Allocation
- •Error Handling
- •Error Handlers for Communicators
- •Error Handlers for Windows
- •Error Handlers for Files
- •Freeing Errorhandlers and Retrieving Error Strings
- •Error Codes and Classes
- •Error Classes, Error Codes, and Error Handlers
- •Timers and Synchronization
- •Startup
- •Allowing User Functions at Process Termination
- •Determining Whether MPI Has Finished
- •Portable MPI Process Startup
- •The Info Object
- •Process Creation and Management
- •Introduction
- •The Dynamic Process Model
- •Starting Processes
- •The Runtime Environment
- •Process Manager Interface
- •Processes in MPI
- •Starting Processes and Establishing Communication
- •Reserved Keys
- •Spawn Example
- •Manager-worker Example, Using MPI_COMM_SPAWN.
- •Establishing Communication
- •Names, Addresses, Ports, and All That
- •Server Routines
- •Client Routines
- •Name Publishing
- •Reserved Key Values
- •Client/Server Examples
- •Ocean/Atmosphere - Relies on Name Publishing
- •Simple Client-Server Example.
- •Other Functionality
- •Universe Size
- •Singleton MPI_INIT
- •MPI_APPNUM
- •Releasing Connections
- •Another Way to Establish MPI Communication
- •One-Sided Communications
- •Introduction
- •Initialization
- •Window Creation
- •Window Attributes
- •Communication Calls
- •Examples
- •Accumulate Functions
- •Synchronization Calls
- •Fence
- •General Active Target Synchronization
- •Lock
- •Assertions
- •Examples
- •Error Handling
- •Error Handlers
- •Error Classes
- •Semantics and Correctness
- •Atomicity
- •Progress
- •Registers and Compiler Optimizations
- •External Interfaces
- •Introduction
- •Generalized Requests
- •Examples
- •Associating Information with Status
- •MPI and Threads
- •General
- •Initialization
- •Introduction
- •File Manipulation
- •Opening a File
- •Closing a File
- •Deleting a File
- •Resizing a File
- •Preallocating Space for a File
- •Querying the Size of a File
- •Querying File Parameters
- •File Info
- •Reserved File Hints
- •File Views
- •Data Access
- •Data Access Routines
- •Positioning
- •Synchronism
- •Coordination
- •Data Access Conventions
- •Data Access with Individual File Pointers
- •Data Access with Shared File Pointers
- •Noncollective Operations
- •Collective Operations
- •Seek
- •Split Collective Data Access Routines
- •File Interoperability
- •Datatypes for File Interoperability
- •Extent Callback
- •Datarep Conversion Functions
- •Matching Data Representations
- •Consistency and Semantics
- •File Consistency
- •Random Access vs. Sequential Files
- •Progress
- •Collective File Operations
- •Type Matching
- •Logical vs. Physical File Layout
- •File Size
- •Examples
- •Asynchronous I/O
- •I/O Error Handling
- •I/O Error Classes
- •Examples
- •Subarray Filetype Constructor
- •Requirements
- •Discussion
- •Logic of the Design
- •Examples
- •MPI Library Implementation
- •Systems with Weak Symbols
- •Systems Without Weak Symbols
- •Complications
- •Multiple Counting
- •Linker Oddities
- •Multiple Levels of Interception
- •Deprecated Functions
- •Deprecated since MPI-2.0
- •Deprecated since MPI-2.2
- •Language Bindings
- •Overview
- •Design
- •C++ Classes for MPI
- •Class Member Functions for MPI
- •Semantics
- •C++ Datatypes
- •Communicators
- •Exceptions
- •Mixed-Language Operability
- •Problems With Fortran Bindings for MPI
- •Problems Due to Strong Typing
- •Problems Due to Data Copying and Sequence Association
- •Special Constants
- •Fortran 90 Derived Types
- •A Problem with Register Optimization
- •Basic Fortran Support
- •Extended Fortran Support
- •The mpi Module
- •No Type Mismatch Problems for Subroutines with Choice Arguments
- •Additional Support for Fortran Numeric Intrinsic Types
- •Language Interoperability
- •Introduction
- •Assumptions
- •Initialization
- •Transfer of Handles
- •Status
- •MPI Opaque Objects
- •Datatypes
- •Callback Functions
- •Error Handlers
- •Reduce Operations
- •Addresses
- •Attributes
- •Extra State
- •Constants
- •Interlanguage Communication
- •Language Bindings Summary
- •Groups, Contexts, Communicators, and Caching Fortran Bindings
- •External Interfaces C++ Bindings
- •Change-Log
- •Bibliography
- •Examples Index
- •MPI Declarations Index
- •MPI Function Index
428 |
CHAPTER 13. I/O |
1fvoid MPI::File::Write_ordered_end(const void* buf, MPI::Status& status)
2
3
(binding deprecated, see Section 15.2) g
4
5
6
7
8
9
fvoid MPI::File::Write_ordered_end(const void* buf) (binding deprecated, see Section 15.2) g
13.5 File Interoperability
At the most basic level, le interoperability is the ability to read the information previously
10
written to a le|not just the bits of data, but the actual information the bits represent.
11
MPI guarantees full interoperability within a single MPI environment, and supports in-
12
creased interoperability outside that environment through the external data representation
13
(Section 13.5.2, page 431) as well as the data conversion functions (Section 13.5.3, page 432).
14
Interoperability within a single MPI environment (which could be considered \oper-
15
ability") ensures that le data written by one MPI process can be read by any other MPI
16
process, subject to the consistency constraints (see Section 13.6.1, page 437), provided that
17
it would have been possible to start the two processes simultaneously and have them reside
18
in a single MPI_COMM_WORLD. Furthermore, both processes must see the same data values
19
at every absolute byte o set in the le for which data was written.
20
This single environment le interoperability implies that le data is accessible regardless
21
of the number of processes.
22
There are three aspects to le interoperability:
23
24
25
26
transferring the bits,
converting between di erent le structures, and
27converting between di erent machine representations.
28The rst two aspects of le interoperability are beyond the scope of this standard,
29as both are highly machine dependent. However, transferring the bits of a le into and
30out of the MPI environment (e.g., by writing a le to tape) is required to be supported
31by all MPI implementations. In particular, an implementation must specify how familiar
32operations similar to POSIX cp, rm, and mv can be performed on the le. Furthermore, it
33is expected that the facility provided maintains the correspondence between absolute byte
34o sets (e.g., after possible le structure conversion, the data bits at byte o set 102 in the
35MPI environment are at byte o set 102 outside the MPI environment). As an example,
36a simple o -line conversion utility that transfers and converts les between the native le
37system and the MPI environment would su ce, provided it maintained the o set coherence
38mentioned above. In a high-quality implementation of MPI, users will be able to manipulate
39MPI les using the same or similar tools that the native le system o ers for manipulating
40its les.
41The remaining aspect of le interoperability, converting between di erent machine
42representations, is supported by the typing information speci ed in the etype and letype.
43This facility allows the information in les to be shared between any two applications,
44regardless of whether they use MPI, and regardless of the machine architectures on which
45they run.
46MPI supports multiple data representations: \native," \internal," and \external32."
47An implementation may support additional data representations. MPI also supports user-
48de ned data representations (see Section 13.5.3, page 432). The \native" and \internal"
13.5. FILE INTEROPERABILITY |
429 |
data representations are implementation dependent, while the \external32" representation is common to all MPI implementations and facilitates le interoperability. The data representation is speci ed in the datarep argument to MPI_FILE_SET_VIEW.
Advice to users. MPI is not guaranteed to retain knowledge of what data representation was used when a le is written. Therefore, to correctly retrieve le data, an MPI application is responsible for specifying the same data representation as was used to create the le. (End of advice to users.)
\native" Data in this representation is stored in a le exactly as it is in memory. The advantage of this data representation is that data precision and I/O performance are not lost in type conversions with a purely homogeneous environment. The disadvantage is the loss of transparent interoperability within a heterogeneous MPI environment.
Advice to users. This data representation should only be used in a homogeneous MPI environment, or when the MPI application is capable of performing the data type conversions itself. (End of advice to users.)
Advice to implementors. When implementing read and write operations on top of MPI message-passing, the message data should be typed as MPI_BYTE to ensure that the message routines do not perform any type conversions on the data. (End of advice to implementors.)
\internal" This data representation can be used for I/O operations in a homogeneous or heterogeneous environment; the implementation will perform type conversions if necessary. The implementation is free to store data in any format of its choice, with the restriction that it will maintain constant extents for all prede ned datatypes in any one le. The environment in which the resulting le can be reused is implementationde ned and must be documented by the implementation.
Rationale. This data representation allows the implementation to perform I/O e ciently in a heterogeneous environment, though with implementation-de ned restrictions on how the le can be reused. (End of rationale.)
Advice to implementors. Since \external32" is a superset of the functionality provided by \internal," an implementation may choose to implement \internal" as \external32." (End of advice to implementors.)
\external32" This data representation states that read and write operations convert all data from and to the \external32" representation de ned in Section 13.5.2, page 431. The data conversion rules for communication also apply to these conversions (see Section 3.3.2, page 25-27, of the MPI-1 document). The data on the storage medium is always in this canonical representation, and the data in memory is always in the local process's native representation.
This data representation has several advantages. First, all processes reading the le in a heterogeneous MPI environment will automatically have the data converted to their respective native representations. Second, the le can be exported from one MPI environment and imported into any other MPI environment with the guarantee that the second environment will be able to read all the data in the le.
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
430 |
CHAPTER 13. I/O |
1The disadvantage of this data representation is that data precision and I/O perfor-
2
3
mance may be lost in data type conversions.
4
5
6
7
8
9
Advice to implementors. When implementing read and write operations on top of MPI message-passing, the message data should be converted to and from the \external32" representation in the client, and sent as type MPI_BYTE. This will avoid possible double data type conversions and the associated further loss of precision and performance. (End of advice to implementors.)
10 |
13.5.1 Datatypes for File Interoperability |
|
11
12If the le data representation is other than \native," care must be taken in constructing
13etypes and letypes. Any of the datatype constructor functions may be used; however,
14for those functions that accept displacements in bytes, the displacements must be speci ed
15in terms of their values in the le for the le data representation being used. MPI will
16interpret these byte displacements as is; no scaling will be done. The function
17MPI_FILE_GET_TYPE_EXTENT can be used to calculate the extents of datatypes in the
18le. For etypes and letypes that are portable datatypes (see Section 2.4, page 11), MPI will
19scale any displacements in the datatypes to match the le data representation. Datatypes
20passed as arguments to read/write routines specify the data layout in memory; therefore,
21they must always be constructed using displacements corresponding to displacements in
22memory.
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Advice to users. One can logically think of the le as if it were stored in the memory of a le server. The etype and letype are interpreted as if they were de ned at thisle server, by the same sequence of calls used to de ne them at the calling process. If the data representation is \native", then this logical le server runs on the same architecture as the calling process, so that these types de ne the same data layout on the le as they would de ne in the memory of the calling process. If the etype and letype are portable datatypes, then the data layout de ned in the le is the same as would be de ned in the calling process memory, up to a scaling factor. The routine MPI_FILE_GET_FILE_EXTENT can be used to calculate this scaling factor. Thus, two equivalent, portable datatypes will de ne the same data layout in the le, even in a heterogeneous environment with \internal", \external32", or user de ned data representations. Otherwise, the etype and letype must be constructed so that their typemap and extent are the same on any architecture. This can be achieved if they have an explicit upper bound and lower bound (de ned either using MPI_LB and MPI_UB markers, or using MPI_TYPE_CREATE_RESIZED). This condition must also be ful lled by any datatype that is used in the construction of the etype and letype, if this datatype is replicated contiguously, either explicitly, by a call to MPI_TYPE_CONTIGUOUS, or implictly, by a blocklength argument that is greater than one. If an etype or letype is not portable, and has a typemap or extent that is architecture dependent, then the data layout speci ed by it on a le is implementation dependent.
45File data representations other than \native" may be di erent from corresponding
46data representations in memory. Therefore, for these le data representations, it is
47important not to use hardwired byte o sets for le positioning, including the initial
48displacement that speci es the view. When a portable datatype (see Section 2.4,
13.5. FILE INTEROPERABILITY |
431 |
page 11) is used in a data access operation, any holes in the datatype are scaled to match the data representation. However, note that this technique only works when all the processes that created the le view build their etypes from the same prede ned datatypes. For example, if one process uses an etype built from MPI_INT and another uses an etype built from MPI_FLOAT, the resulting views may be nonportable because the relative sizes of these types may di er from one data representation to another. (End of advice to users.)
MPI_FILE_GET_TYPE_EXTENT(fh, datatype, extent)
IN |
fh |
le handle (handle) |
IN |
datatype |
datatype (handle) |
OUT |
extent |
datatype extent (integer) |
int MPI_File_get_type_extent(MPI_File fh, MPI_Datatype datatype, MPI_Aint *extent)
MPI_FILE_GET_TYPE_EXTENT(FH, DATATYPE, EXTENT, IERROR)
INTEGER FH, DATATYPE, IERROR
INTEGER(KIND=MPI_ADDRESS_KIND) EXTENT
fMPI::Aint MPI::File::Get_type_extent(const MPI::Datatype& datatype) const
(binding deprecated, see Section 15.2) g
Returns the extent of datatype in the le fh. This extent will be the same for all processes accessing the le fh. If the current view uses a user-de ned data representation (see Section 13.5.3, page 432), MPI uses the dtype_ le_extent_fn callback to calculate the extent.
Advice to implementors. In the case of user-de ned data representations, the extent of a derived datatype can be calculated by rst determining the extents of the prede-ned datatypes in this derived datatype using dtype_ le_extent_fn (see Section 13.5.3, page 432). (End of advice to implementors.)
13.5.2 External Data Representation: \external32"
All MPI implementations are required to support the data representation de ned in this section. Support of optional datatypes (e.g., MPI_INTEGER2) is not required.
All oating point values are in big-endian IEEE format [27] of the appropriate size. Floating point values are represented by one of three IEEE formats. These are the IEEE \Single," \Double," and \Double Extended" formats, requiring 4, 8 and 16 bytes of storage, respectively. For the IEEE \Double Extended" formats, MPI speci es a Format Width of 16 bytes, with 15 exponent bits, bias = +16383, 112 fraction bits, and an encoding analogous to the \Double" format. All integral values are in two's complement big-endian format. Bigendian means most signi cant byte at lowest address byte. For C _Bool, Fortran LOGICAL and C++ bool, 0 implies false and nonzero implies true. C float _Complex, double _Complex and long double _Complex as well as Fortran COMPLEX and DOUBLE COMPLEX are represented by a pair of oating point format values for the real and imaginary components.
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
432 |
CHAPTER 13. I/O |
1Characters are in ISO 8859-1 format [28]. Wide characters (of type MPI_WCHAR) are in
2Unicode format [47].
3All signed numerals (e.g., MPI_INT, MPI_REAL) have the sign bit at the most signi cant
4bit. MPI_COMPLEX and MPI_DOUBLE_COMPLEX have the sign bit of the real and imaginary
5parts at the most signi cant bit of each part.
6According to IEEE speci cations [27], the \NaN" (not a number) is system dependent.
7It should not be interpreted within MPI as anything other than \NaN."
8
9Advice to implementors. The MPI treatment of \NaN" is similar to the approach used
10
11
in XDR (see ftp://ds.internic.net/rfc/rfc1832.txt). (End of advice to implementors.)
12All data is byte aligned, regardless of type. All data items are stored contiguously in
13the le (if the le view is contiguous).
14
15Advice to implementors. All bytes of LOGICAL and bool must be checked to determine
16the value. (End of advice to implementors.)
17
18Advice to users. The type MPI_PACKED is treated as bytes and is not converted.
19The user should be aware that MPI_PACK has the option of placing a header in the
20beginning of the pack bu er. (End of advice to users.)
21
The size of the prede ned datatypes returned from MPI_TYPE_CREATE_F90_REAL,
22
MPI_TYPE_CREATE_F90_COMPLEX, and MPI_TYPE_CREATE_F90_INTEGER are de ned
23
in Section 16.2.5, page 493.
24
25
Advice to implementors. When converting a larger size integer to a smaller size
26
integer, only the less signi cant bytes are moved. Care must be taken to preserve the
27
sign bit value. This allows no conversion errors if the data range is within the range
28
of the smaller size integer. (End of advice to implementors.)
29
30
Table 13.2 speci es the sizes of prede ned datatypes in \external32" format.
31 |
|
|
32 |
13.5.3 User-De ned Data Representations |
|
33 |
||
|
||
34 |
There are two situations that cannot be handled by the required representations: |
35
36
37
38
1.a user wants to write a le in a representation unknown to the implementation, and
2.a user wants to read a le written in a representation unknown to the implementation.
39User-de ned data representations allow the user to insert a third party converter into
40the I/O stream to do the data representation conversion.
41
42
43
44
45
46
47
48
13.5. FILE INTEROPERABILITY
Type |
Length |
------------------ |
------ |
MPI_PACKED |
1 |
MPI_BYTE |
1 |
MPI_CHAR |
1 |
MPI_UNSIGNED_CHAR |
1 |
MPI_SIGNED_CHAR |
1 |
MPI_WCHAR |
2 |
MPI_SHORT |
2 |
MPI_UNSIGNED_SHORT |
2 |
MPI_INT |
4 |
MPI_UNSIGNED |
4 |
MPI_LONG |
4 |
MPI_UNSIGNED_LONG |
4 |
MPI_LONG_LONG_INT |
8 |
MPI_UNSIGNED_LONG_LONG |
8 |
MPI_FLOAT |
4 |
MPI_DOUBLE |
8 |
MPI_LONG_DOUBLE |
16 |
MPI_C_BOOL |
4 |
MPI_INT8_T |
1 |
MPI_INT16_T |
2 |
MPI_INT32_T |
4 |
MPI_INT64_T |
8 |
MPI_UINT8_T |
1 |
MPI_UINT16_T |
2 |
MPI_UINT32_T |
4 |
MPI_UINT64_T |
8 |
MPI_AINT |
8 |
MPI_OFFSET |
8 |
MPI_C_COMPLEX |
2*4 |
MPI_C_FLOAT_COMPLEX |
2*4 |
MPI_C_DOUBLE_COMPLEX |
2*8 |
MPI_C_LONG_DOUBLE_COMPLEX 2*16 |
|
MPI_CHARACTER |
1 |
MPI_LOGICAL |
4 |
MPI_INTEGER |
4 |
MPI_REAL |
4 |
MPI_DOUBLE_PRECISION |
8 |
MPI_COMPLEX |
2*4 |
MPI_DOUBLE_COMPLEX |
2*8 |
|
433 |
Optional Type |
Length |
------------------ |
------ |
MPI_INTEGER1 |
1 |
MPI_INTEGER2 |
2 |
MPI_INTEGER4 |
4 |
MPI_INTEGER8 |
8 |
MPI_INTEGER16 |
16 |
MPI_REAL2 |
2 |
MPI_REAL4 |
4 |
MPI_REAL8 |
8 |
MPI_REAL16 |
16 |
MPI_COMPLEX4 |
2*2 |
MPI_COMPLEX8 |
2*4 |
MPI_COMPLEX16 |
2*8 |
MPI_COMPLEX32 |
2*16 |
Table 13.2: \external32" sizes of prede ned datatypes
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