in progress....
[jalview.git] / forester / java / src / org / forester / rio / RIOUtil.java
1
2 package org.forester.rio;
3
4 import java.io.File;
5 import java.io.FilenameFilter;
6 import java.io.IOException;
7 import java.math.RoundingMode;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.SortedMap;
12 import java.util.SortedSet;
13 import java.util.TreeSet;
14
15 import org.forester.datastructures.IntMatrix;
16 import org.forester.io.parsers.IteratingPhylogenyParser;
17 import org.forester.io.parsers.PhylogenyParser;
18 import org.forester.io.parsers.nexus.NexusPhylogeniesParser;
19 import org.forester.io.parsers.nhx.NHXParser;
20 import org.forester.io.parsers.nhx.NHXParser.TAXONOMY_EXTRACTION;
21 import org.forester.io.parsers.phyloxml.PhyloXmlParser;
22 import org.forester.io.parsers.util.ParserUtils;
23 import org.forester.io.writers.PhylogenyWriter;
24 import org.forester.phylogeny.Phylogeny;
25 import org.forester.phylogeny.PhylogenyNode;
26 import org.forester.phylogeny.data.Sequence;
27 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
28 import org.forester.rio.RIO.REROOTING;
29 import org.forester.sdi.SDIException;
30 import org.forester.sdi.SDIutil.ALGORITHM;
31 import org.forester.util.BasicDescriptiveStatistics;
32 import org.forester.util.BasicTable;
33 import org.forester.util.BasicTableParser;
34 import org.forester.util.EasyWriter;
35 import org.forester.util.ForesterUtil;
36
37 public final class RIOUtil {
38
39     public static final void executeAnalysis( final File gene_trees_file,
40                                               final File species_tree_file,
41                                               final File orthology_outtable,
42                                               final File orthology_outtable_with_mappings,
43                                               final File orthology_groups_outfile,
44                                               final File logfile,
45                                               final String outgroup,
46                                               final REROOTING rerooting,
47                                               final int gt_first,
48                                               final int gt_last,
49                                               final File return_species_tree,
50                                               final File return_min_dup_gene_tree,
51                                               final File return_median_dup_gene_tree,
52                                               final boolean transfer_taxonomy,
53                                               final ALGORITHM algorithm,
54                                               final boolean use_gene_trees_dir,
55                                               final EasyWriter log,
56                                               final double ortholog_group_cutoff,
57                                               final boolean perform_id_mapping,
58                                               final File id_mapping_dir,
59                                               final String id_mapping_suffix ) {
60         try {
61             final SortedMap<String, String> id_map;
62             if ( perform_id_mapping ) {
63                 id_map = obtainMapping( id_mapping_dir, gene_trees_file.getName(), id_mapping_suffix );
64             }
65             else {
66                 id_map = null;
67             }
68             final RIO rio;
69             boolean iterating = false;
70             final PhylogenyParser p = ParserUtils.createParserDependingOnFileType( gene_trees_file, true );
71             if ( p instanceof PhyloXmlParser ) {
72                 rio = RIO.executeAnalysis( gene_trees_file,
73                                            species_tree_file,
74                                            algorithm,
75                                            rerooting,
76                                            outgroup,
77                                            gt_first,
78                                            gt_last,
79                                            logfile != null,
80                                            true,
81                                            transfer_taxonomy );
82             }
83             else {
84                 iterating = true;
85                 if ( p instanceof NHXParser ) {
86                     final NHXParser nhx = ( NHXParser ) p;
87                     nhx.setReplaceUnderscores( false );
88                     nhx.setIgnoreQuotes( true );
89                     nhx.setTaxonomyExtraction( TAXONOMY_EXTRACTION.AGGRESSIVE );
90                 }
91                 else if ( p instanceof NexusPhylogeniesParser ) {
92                     final NexusPhylogeniesParser nex = ( NexusPhylogeniesParser ) p;
93                     nex.setReplaceUnderscores( false );
94                     nex.setIgnoreQuotes( true );
95                     nex.setTaxonomyExtraction( TAXONOMY_EXTRACTION.AGGRESSIVE );
96                 }
97                 else {
98                     throw new RuntimeException( "unknown parser type: " + p );
99                 }
100                 final IteratingPhylogenyParser ip = ( IteratingPhylogenyParser ) p;
101                 ip.setSource( gene_trees_file );
102                 rio = RIO.executeAnalysis( ip,
103                                            species_tree_file,
104                                            algorithm,
105                                            rerooting,
106                                            outgroup,
107                                            gt_first,
108                                            gt_last,
109                                            logfile != null,
110                                            !use_gene_trees_dir,
111                                            transfer_taxonomy );
112             }
113             if ( !use_gene_trees_dir ) {
114                 if ( algorithm == ALGORITHM.GSDIR ) {
115                     System.out.println( "Taxonomy linking based on           :\t" + rio.getGSDIRtaxCompBase() );
116                 }
117             }
118             final IntMatrix m;
119             if ( iterating ) {
120                 m = rio.getOrthologTable();
121             }
122             else {
123                 m = RIO.calculateOrthologTable( rio.getAnalyzedGeneTrees(), true );
124             }
125             final BasicDescriptiveStatistics stats = rio.getDuplicationsStatistics();
126             if ( perform_id_mapping ) {
127                 writeOrthologyTable( orthology_outtable, stats.getN(), m, !use_gene_trees_dir, id_map, true );
128                 writeOrthologyTable( orthology_outtable_with_mappings,
129                                      stats.getN(),
130                                      m,
131                                      !use_gene_trees_dir,
132                                      id_map,
133                                      false );
134             }
135             else {
136                 writeOrthologyTable( orthology_outtable, stats.getN(), m, !use_gene_trees_dir, null, false );
137             }
138             final int ortholog_groups = writeOrtologGroups( orthology_groups_outfile,
139                                                             ortholog_group_cutoff,
140                                                             stats.getN(),
141                                                             m,
142                                                             !use_gene_trees_dir,
143                                                             false,
144                                                             id_map );
145             final int ortholog_groups_005 = writeOrtologGroups( null, 0.05, stats.getN(), m, false, true, null );
146             final int ortholog_groups_025 = writeOrtologGroups( null, 0.25, stats.getN(), m, false, true, null );
147             final int ortholog_groups_05 = writeOrtologGroups( null, 0.5, stats.getN(), m, false, true, null );
148             final int ortholog_groups_075 = writeOrtologGroups( null, 0.75, stats.getN(), m, false, true, null );
149             final int ortholog_groups_095 = writeOrtologGroups( null, 0.95, stats.getN(), m, false, true, null );
150             if ( ( algorithm != ALGORITHM.SDIR ) && ( logfile != null ) ) {
151                 writeLogFile( logfile,
152                               rio,
153                               species_tree_file,
154                               gene_trees_file,
155                               orthology_outtable,
156                               org.forester.application.rio.PRG_NAME,
157                               org.forester.application.rio.PRG_VERSION,
158                               org.forester.application.rio.PRG_DATE,
159                               ForesterUtil.getForesterLibraryInformation(),
160                               !use_gene_trees_dir );
161             }
162             if ( return_species_tree != null ) {
163                 writeTree( rio.getSpeciesTree(),
164                            return_species_tree,
165                            use_gene_trees_dir ? null : "Wrote (stripped) species tree to    :\t",
166                            null );
167             }
168             if ( return_min_dup_gene_tree != null && rio.getMinDuplicationsGeneTree() != null ) {
169                 final int min = ( int ) rio.getDuplicationsStatistics().getMin();
170                 writeTree( rio.getMinDuplicationsGeneTree(),
171                            new File( return_min_dup_gene_tree.toString() + min + ".xml" ),
172                            use_gene_trees_dir ? null : "Wrote one min duplication gene tree :\t",
173                            id_map );
174             }
175             if ( return_median_dup_gene_tree != null && rio.getDuplicationsToTreeMap() != null ) {
176                 final int med = ( int ) rio.getDuplicationsStatistics().median();
177                 writeTree( rio.getDuplicationsToTreeMap().get( med ),
178                            new File( return_median_dup_gene_tree.toString() + med + ".xml" ),
179                            use_gene_trees_dir ? null : "Wrote one med duplication gene tree :\t",
180                            id_map );
181             }
182             final java.text.DecimalFormat df = new java.text.DecimalFormat( "0.##" );
183             final int min = ( int ) stats.getMin();
184             final int max = ( int ) stats.getMax();
185             final int median = ( int ) stats.median();
186             int min_count = 0;
187             int max_count = 0;
188             int median_count = 0;
189             for( double d : stats.getData() ) {
190                 if ( ( ( int ) d ) == min ) {
191                     ++min_count;
192                 }
193                 if ( ( ( int ) d ) == max ) {
194                     ++max_count;
195                 }
196                 if ( ( ( int ) d ) == median ) {
197                     ++median_count;
198                 }
199             }
200             final double min_count_percentage = ( 100.0 * min_count ) / stats.getN();
201             final double max_count_percentage = ( 100.0 * max_count ) / stats.getN();
202             final double median_count_percentage = ( 100.0 * median_count ) / stats.getN();
203             if ( use_gene_trees_dir ) {
204                 String name = gene_trees_file.getName();
205                 if ( name.indexOf( "." ) > 0 ) {
206                     name = name.substring( 0, name.lastIndexOf( "." ) );
207                 }
208                 log.print( name );
209                 log.print( "\t" );
210                 log.print( Integer.toString( rio.getExtNodesOfAnalyzedGeneTrees() ) );
211                 log.print( "\t" );
212                 log.print( Integer.toString( ortholog_groups ) );
213                 //
214                 log.print( "\t" );
215                 log.print( Integer.toString( ortholog_groups_005 ) );
216                 log.print( "\t" );
217                 log.print( Integer.toString( ortholog_groups_025 ) );
218                 log.print( "\t" );
219                 log.print( Integer.toString( ortholog_groups_05 ) );
220                 log.print( "\t" );
221                 log.print( Integer.toString( ortholog_groups_075 ) );
222                 log.print( "\t" );
223                 log.print( Integer.toString( ortholog_groups_095 ) );
224                 //
225                 log.print( "\t" );
226                 if ( stats.getN() > 3 ) {
227                     log.print( df.format( median ) );
228                 }
229                 else {
230                     log.print( "" );
231                 }
232                 log.print( "\t" );
233                 log.print( df.format( stats.arithmeticMean() ) );
234                 log.print( "\t" );
235                 if ( stats.getN() > 3 ) {
236                     log.print( df.format( stats.sampleStandardDeviation() ) );
237                 }
238                 else {
239                     log.print( "" );
240                 }
241                 log.print( "\t" );
242                 log.print( Integer.toString( min ) );
243                 log.print( "\t" );
244                 log.print( Integer.toString( max ) );
245                 log.print( "\t" );
246                 log.print( Integer.toString( rio.getRemovedGeneTreeNodes().size() ) );
247                 log.print( "\t" );
248                 log.print( Integer.toString( stats.getN() ) );
249                 log.println();
250             }
251             else {
252                 System.out.println( "Gene tree internal nodes            :\t" + rio.getIntNodesOfAnalyzedGeneTrees() );
253                 System.out.println( "Gene tree external nodes            :\t" + rio.getExtNodesOfAnalyzedGeneTrees() );
254                 System.out.println( "Mean number of duplications         :\t" + df.format( stats.arithmeticMean() )
255                         + "\t" + df.format( ( 100.0 * stats.arithmeticMean() ) / rio.getIntNodesOfAnalyzedGeneTrees() )
256                         + "%\t(sd: " + df.format( stats.sampleStandardDeviation() ) + ")" );
257                 if ( stats.getN() > 3 ) {
258                     System.out.println( "Median number of duplications       :\t" + df.format( median ) + "\t"
259                             + df.format( ( 100.0 * median ) / rio.getIntNodesOfAnalyzedGeneTrees() ) + "%" );
260                 }
261                 System.out.println( "Minimum duplications                :\t" + min + "\t"
262                         + df.format( ( 100.0 * min ) / rio.getIntNodesOfAnalyzedGeneTrees() ) + "%" );
263                 System.out.println( "Maximum duplications                :\t" + ( int ) max + "\t"
264                         + df.format( ( 100.0 * max ) / rio.getIntNodesOfAnalyzedGeneTrees() ) + "%" );
265                 System.out.println( "Gene trees with median duplications :\t" + median_count + "\t"
266                         + df.format( median_count_percentage ) + "%" );
267                 System.out.println( "Gene trees with minimum duplications:\t" + min_count + "\t"
268                         + df.format( min_count_percentage ) + "%" );
269                 System.out.println( "Gene trees with maximum duplications:\t" + max_count + "\t"
270                         + df.format( max_count_percentage ) + "%" );
271                 if ( algorithm == ALGORITHM.GSDIR ) {
272                     System.out.println( "Removed ext gene tree nodes         :\t"
273                             + rio.getRemovedGeneTreeNodes().size() );
274                 }
275             }
276         }
277         catch ( final RIOException e ) {
278             ForesterUtil.fatalError( e.getLocalizedMessage() );
279         }
280         catch ( final SDIException e ) {
281             ForesterUtil.fatalError( e.getLocalizedMessage() );
282         }
283         catch ( final IOException e ) {
284             ForesterUtil.fatalError( e.getLocalizedMessage() );
285         }
286         catch ( final OutOfMemoryError e ) {
287             ForesterUtil.outOfMemoryError( e );
288         }
289         catch ( final Exception e ) {
290             ForesterUtil.unexpectedFatalError( e );
291         }
292         catch ( final Error e ) {
293             ForesterUtil.unexpectedFatalError( e );
294         }
295     }
296
297     private static final void writeOrthologyTable( final File table_outfile,
298                                                    final int gene_trees_analyzed,
299                                                    final IntMatrix m,
300                                                    final boolean verbose,
301                                                    final SortedMap<String, String> id_map,
302                                                    final boolean replace_ids )
303             throws IOException {
304         final EasyWriter w = ForesterUtil.createEasyWriter( table_outfile );
305         final java.text.DecimalFormat df = new java.text.DecimalFormat( "0.####" );
306         df.setDecimalSeparatorAlwaysShown( false );
307         df.setRoundingMode( RoundingMode.HALF_UP );
308         for( int i = 0; i < m.size(); ++i ) {
309             w.print( "\t" );
310             if ( replace_ids ) {
311                 if ( !id_map.containsKey( m.getLabel( i ) ) ) {
312                     throw new IOException( "no id mapping for \"" + m.getLabel( i ) + "\" (attempting to write ["
313                             + table_outfile + "])" );
314                 }
315                 w.print( id_map.get( m.getLabel( i ) ) );
316             }
317             else {
318                 w.print( m.getLabel( i ) );
319             }
320         }
321         w.println();
322         for( int x = 0; x < m.size(); ++x ) {
323             if ( replace_ids ) {
324                 w.print( id_map.get( m.getLabel( x ) ) );
325             }
326             else {
327                 w.print( m.getLabel( x ) );
328             }
329             for( int y = 0; y < m.size(); ++y ) {
330                 w.print( "\t" );
331                 if ( x == y ) {
332                     if ( m.get( x, y ) != gene_trees_analyzed ) {
333                         ForesterUtil.unexpectedFatalError( "diagonal value is off" );
334                     }
335                     w.print( "-" );
336                 }
337                 else {
338                     w.print( df.format( ( ( double ) m.get( x, y ) ) / gene_trees_analyzed ) );
339                 }
340             }
341             w.println();
342         }
343         if ( !replace_ids && id_map != null && id_map.size() > 0 ) {
344             w.println();
345             id_map.forEach( ( k, v ) -> {
346                 try {
347                     w.println( k + "\t" + v );
348                 }
349                 catch ( final IOException e ) {
350                     //ignore
351                 }
352             } );
353         }
354         w.close();
355         if ( verbose ) {
356             System.out.println( "Wrote table to                      :\t" + table_outfile.getCanonicalPath() );
357         }
358     }
359
360     private static final int writeOrtologGroups( final File outfile,
361                                                  final double cutoff,
362                                                  final int gene_trees_analyzed,
363                                                  final IntMatrix m,
364                                                  final boolean verbose,
365                                                  final boolean calc_conly,
366                                                  final SortedMap<String, String> id_map )
367             throws IOException {
368         List<SortedSet<String>> groups = new ArrayList<SortedSet<String>>();
369         BasicDescriptiveStatistics stats = new BasicDescriptiveStatistics();
370         int below_075 = 0;
371         int below_05 = 0;
372         int below_025 = 0;
373         for( int x = 1; x < m.size(); ++x ) {
374             final String a = m.getLabel( x );
375             for( int y = 0; y < x; ++y ) {
376                 final String b = m.getLabel( y );
377                 final double s = ( ( double ) m.get( x, y ) ) / gene_trees_analyzed;
378                 stats.addValue( s );
379                 if ( s < 0.75 ) {
380                     below_075++;
381                     if ( s < 0.5 ) {
382                         below_05++;
383                         if ( s < 0.25 ) {
384                             below_025++;
385                         }
386                     }
387                 }
388                 if ( s >= cutoff ) {
389                     boolean found = false;
390                     for( final SortedSet<String> group : groups ) {
391                         if ( group.contains( a ) ) {
392                             group.add( b );
393                             found = true;
394                         }
395                         if ( group.contains( b ) ) {
396                             group.add( a );
397                             found = true;
398                         }
399                     }
400                     if ( !found ) {
401                         final SortedSet<String> new_group = new TreeSet<String>();
402                         new_group.add( a );
403                         new_group.add( b );
404                         groups.add( new_group );
405                     }
406                 }
407             }
408         }
409         //Deal with singlets:
410         for( int x = 0; x < m.size(); ++x ) {
411             final String a = m.getLabel( x );
412             boolean found = false;
413             for( final SortedSet<String> group : groups ) {
414                 if ( group.contains( a ) ) {
415                     found = true;
416                     break;
417                 }
418             }
419             if ( !found ) {
420                 final SortedSet<String> new_group = new TreeSet<String>();
421                 new_group.add( a );
422                 groups.add( new_group );
423             }
424         }
425         if ( calc_conly ) {
426             return groups.size();
427         }
428         final java.text.DecimalFormat df = new java.text.DecimalFormat( "0.####" );
429         df.setDecimalSeparatorAlwaysShown( false );
430         df.setRoundingMode( RoundingMode.HALF_UP );
431         final EasyWriter w = ForesterUtil.createEasyWriter( outfile );
432         int counter = 1;
433         for( final SortedSet<String> group : groups ) {
434             w.print( Integer.toString( counter++ ) );
435             for( final String s : group ) {
436                 w.print( "\t" );
437                 if ( id_map != null && id_map.size() > 0 ) {
438                     if ( !id_map.containsKey( s ) ) {
439                         throw new IOException( "no id mapping for \"" + s + "\" (attempting to write [" + outfile
440                                 + "])" );
441                     }
442                     w.print( id_map.get( s ) );
443                 }
444                 else {
445                     w.print( s );
446                 }
447             }
448             w.println();
449         }
450         w.println();
451         w.println( "# Cutoff\t" + df.format( cutoff ) );
452         w.println();
453         w.println( "# Orthology support statistics:" );
454         if ( stats.getN() > 3 ) {
455             w.println( "# Median\t" + df.format( stats.median() ) );
456         }
457         w.println( "# Mean\t" + df.format( stats.arithmeticMean() ) );
458         if ( stats.getN() > 3 ) {
459             w.println( "# SD\t" + df.format( stats.sampleStandardDeviation() ) );
460         }
461         w.println( "# Min\t" + df.format( stats.getMin() ) );
462         w.println( "# Max\t" + df.format( stats.getMax() ) );
463         w.println( "# Total\t" + df.format( stats.getN() ) );
464         w.println( "# Below 0.75\t" + below_075 + "\t" + df.format( ( 100.0 * below_075 / stats.getN() ) ) + "%" );
465         w.println( "# Below 0.5\t" + below_05 + "\t" + df.format( ( 100.0 * below_05 / stats.getN() ) ) + "%" );
466         w.println( "# Below 0.25\t" + below_025 + "\t" + df.format( ( 100.0 * below_025 / stats.getN() ) ) + "%" );
467         w.close();
468         if ( verbose ) {
469             System.out.println( "Number of ortholog groups           :\t" + groups.size() );
470             System.out.println( "Wrote orthologs groups table to     :\t" + outfile.getCanonicalPath() );
471         }
472         return groups.size();
473     }
474
475     private static void writeTree( final Phylogeny p,
476                                    final File f,
477                                    final String comment,
478                                    final SortedMap<String, String> id_map )
479             throws IOException {
480         if ( id_map != null && id_map.size() > 0 ) {
481             final PhylogenyNodeIterator it = p.iteratorExternalForward();
482             while ( it.hasNext() ) {
483                 final PhylogenyNode n = it.next();
484                 if ( !id_map.containsKey( n.getName() ) ) {
485                     throw new IOException( "no id mapping for \"" + n.getName() + "\" (attempting to write [" + f
486                             + "])" );
487                 }
488                 final Sequence seq = new Sequence();
489                 seq.setName( id_map.get( n.getName() ) );
490                 n.getNodeData().addSequence( seq );
491             }
492         }
493         final PhylogenyWriter writer = new PhylogenyWriter();
494         writer.toPhyloXML( f, p, 0 );
495         if ( comment != null ) {
496             System.out.println( comment + f.getCanonicalPath() );
497         }
498     }
499
500     private static void writeLogFile( final File logfile,
501                                       final RIO rio,
502                                       final File species_tree_file,
503                                       final File gene_trees_file,
504                                       final File outtable,
505                                       final String prg_name,
506                                       final String prg_v,
507                                       final String prg_date,
508                                       final String f,
509                                       final boolean verbose )
510             throws IOException {
511         final EasyWriter out = ForesterUtil.createEasyWriter( logfile );
512         out.println( "# " + prg_name );
513         out.println( "# version : " + prg_v );
514         out.println( "# date    : " + prg_date );
515         out.println( "# based on: " + f );
516         out.println( "# ----------------------------------" );
517         out.println( "Gene trees                          :\t" + gene_trees_file.getCanonicalPath() );
518         out.println( "Species tree                        :\t" + species_tree_file.getCanonicalPath() );
519         out.println( "All vs all orthology table          :\t" + outtable.getCanonicalPath() );
520         out.flush();
521         out.println( rio.getLog().toString() );
522         out.close();
523         if ( verbose ) {
524             System.out.println( "Wrote log to                        :\t" + logfile.getCanonicalPath() );
525         }
526     }
527
528     private final static SortedMap<String, String> obtainMapping( final File dir,
529                                                                   final String prefix,
530                                                                   final String suffix )
531             throws IOException {
532         if ( !dir.exists() ) {
533             throw new IOException( "[" + dir + "] does not exist" );
534         }
535         if ( !dir.isDirectory() ) {
536             throw new IOException( "[" + dir + "] is not a directory" );
537         }
538         final File mapping_files[] = dir.listFiles( new FilenameFilter() {
539
540             @Override
541             public boolean accept( final File dir, final String name ) {
542                 return ( name.endsWith( suffix ) );
543             }
544         } );
545         if ( mapping_files.length == 1 ) {
546             throw new IOException( "no files ending with \"" + suffix + "\" found in [" + dir + "]" );
547         }
548         String my_prefix = ForesterUtil.removeFileExtension( prefix );
549         boolean done = false;
550         boolean more_than_one = false;
551         File the_one = null;
552         do {
553             int matches = 0;
554             for( File file : mapping_files ) {
555                 if ( file.getName().startsWith( my_prefix ) ) {
556                     matches++;
557                     if ( matches > 1 ) {
558                         the_one = null;
559                         break;
560                     }
561                     the_one = file;
562                 }
563             }
564             if ( matches > 1 ) {
565                 more_than_one = true;
566                 done = true;
567             }
568             if ( matches == 1 ) {
569                 done = true;
570             }
571             else {
572                 if ( my_prefix.length() <= 1 ) {
573                     throw new IOException( "no file matching \"" + ForesterUtil.removeFileExtension( prefix )
574                             + "\" and ending with \"" + suffix + "\" found in [" + dir + "]" );
575                 }
576                 my_prefix = my_prefix.substring( 0, my_prefix.length() - 1 );
577             }
578         } while ( !done );
579         if ( more_than_one ) {
580             throw new IOException( "multiple files matching \"" + ForesterUtil.removeFileExtension( prefix )
581                     + "\" and ending with \"" + suffix + "\" found in [" + dir + "]" );
582         }
583         else if ( the_one != null ) {
584         }
585         else {
586             throw new IOException( "no file matching \"" + ForesterUtil.removeFileExtension( prefix )
587                     + "\" and ending with \"" + suffix + "\" found in [" + dir + "]" );
588         }
589         final BasicTable<String> t = BasicTableParser.parse( the_one, '\t' );
590         return t.getColumnsAsMap( 0, 1 );
591     }
592 }