0
# Selection Language
1
2
MDAnalysis provides a powerful and flexible selection language for identifying atoms, residues, and molecular components. The selection syntax is similar to that used in VMD and PyMOL, making it familiar to molecular visualization users while extending functionality for analysis workflows.
3
4
## Overview
5
6
The selection language allows you to:
7
- Select atoms based on properties (names, types, residues, segments)
8
- Use geometric criteria (distances, shapes, regions)
9
- Combine selections with Boolean logic
10
- Create dynamic selections that update with trajectory changes
11
- Perform complex queries with multiple criteria
12
13
## Basic Selection Syntax
14
15
### Core Selection Function
16
17
```python { .api }
18
def select_atoms(universe_or_atomgroup, *args, **kwargs):
19
"""
20
Select atoms using the MDAnalysis selection language.
21
22
Parameters
23
----------
24
universe_or_atomgroup : Universe or AtomGroup
25
Source for atom selection.
26
*args : str
27
Selection string(s) in MDAnalysis selection language.
28
updating : bool, optional
29
Whether selection should update when topology changes (default False).
30
periodic : bool, optional
31
Whether to account for periodic boundary conditions in
32
geometric selections (default True).
33
rtol : float, optional
34
Relative tolerance for floating point comparisons (default 1e-5).
35
atol : float, optional
36
Absolute tolerance for floating point comparisons (default 1e-8).
37
38
Returns
39
-------
40
AtomGroup
41
Selected atoms matching the criteria.
42
43
Examples
44
--------
45
>>> # Basic property selections
46
>>> ca_atoms = u.select_atoms("name CA")
47
>>> protein = u.select_atoms("protein")
48
>>> waters = u.select_atoms("resname SOL TIP3 WAT")
49
50
>>> # Geometric selections
51
>>> near_protein = u.select_atoms("around 5.0 protein")
52
>>> binding_site = u.select_atoms("resid 23-45 and around 8.0 resname LIG")
53
54
>>> # Complex Boolean logic
55
>>> flexible = u.select_atoms("protein and not backbone and not resname PRO")
56
"""
57
```
58
59
## Property-Based Selections
60
61
### Atom Properties
62
63
```python { .api }
64
# Atom name selections
65
u.select_atoms("name CA") # Alpha carbons
66
u.select_atoms("name CA CB CG") # Multiple atom names
67
u.select_atoms("name C*") # Wildcard: all carbon atoms
68
u.select_atoms("name *A") # Names ending with A
69
70
# Atom type selections
71
u.select_atoms("type CT NH1 OH") # Specific atom types
72
u.select_atoms("type CT*") # Types starting with CT
73
74
# Mass selections
75
u.select_atoms("mass 12.01") # Specific mass (carbon)
76
u.select_atoms("mass > 14.0") # Heavier than nitrogen
77
u.select_atoms("mass 12.0:16.0") # Mass range
78
79
# Charge selections
80
u.select_atoms("charge 0.0") # Neutral atoms
81
u.select_atoms("charge < -0.5") # Highly negative
82
u.select_atoms("charge -0.1:0.1") # Nearly neutral range
83
84
# Element selections (if available)
85
u.select_atoms("element C") # Carbon atoms
86
u.select_atoms("element N O") # Nitrogen or oxygen
87
```
88
89
### Residue Properties
90
91
```python { .api }
92
# Residue name selections
93
u.select_atoms("resname ALA") # Alanine residues
94
u.select_atoms("resname ALA GLY PRO") # Multiple residue types
95
u.select_atoms("resname A*") # Names starting with A
96
97
# Residue ID selections
98
u.select_atoms("resid 1") # Residue 1
99
u.select_atoms("resid 1 5 10") # Specific residues
100
u.select_atoms("resid 1-10") # Range of residues
101
u.select_atoms("resid 1:10:2") # Range with step (1,3,5,7,9)
102
103
# Residue number selections (different from resid)
104
u.select_atoms("resnum 1-100") # Sequential numbering
105
106
# Insertion codes (for PDB files)
107
u.select_atoms("icode A") # Specific insertion code
108
u.select_atoms("icode ''") # Empty insertion code
109
```
110
111
### Segment Properties
112
113
```python { .api }
114
# Segment ID selections
115
u.select_atoms("segid PROA") # Specific segment
116
u.select_atoms("segid PROA PROB") # Multiple segments
117
u.select_atoms("segid PRO*") # Wildcard matching
118
119
# Combining segment and residue selections
120
u.select_atoms("segid PROA and resid 1-100")
121
u.select_atoms("segid PROB and resname ALA")
122
```
123
124
## Predefined Selections
125
126
### Common Macros
127
128
```python { .api }
129
# Protein components
130
u.select_atoms("protein") # All protein atoms
131
u.select_atoms("backbone") # Protein backbone (N, CA, C, O)
132
u.select_atoms("sidechain") # Protein sidechains
133
u.select_atoms("nucleic") # Nucleic acid atoms
134
u.select_atoms("nucleicacid") # Alias for nucleic
135
136
# Water and ions
137
u.select_atoms("water") # Water molecules
138
u.select_atoms("ion") # Ion atoms
139
140
# Secondary structure (if available)
141
u.select_atoms("alpha") # Alpha helix
142
u.select_atoms("beta") # Beta sheet
143
u.select_atoms("coil") # Random coil
144
145
# Atom categories
146
u.select_atoms("hydrogen") # Hydrogen atoms
147
u.select_atoms("heavy") # Heavy (non-hydrogen) atoms
148
u.select_atoms("donor") # Hydrogen bond donors
149
u.select_atoms("acceptor") # Hydrogen bond acceptors
150
```
151
152
### Predefined Residue Groups
153
154
```python { .api }
155
# Amino acid categories
156
u.select_atoms("acidic") # Acidic residues (ASP, GLU)
157
u.select_atoms("basic") # Basic residues (LYS, ARG, HIS)
158
u.select_atoms("polar") # Polar residues
159
u.select_atoms("nonpolar") # Nonpolar residues
160
u.select_atoms("charged") # Charged residues (acidic + basic)
161
u.select_atoms("aromatic") # Aromatic residues (PHE, TYR, TRP)
162
u.select_atoms("hydrophobic") # Hydrophobic residues
163
u.select_atoms("small") # Small residues (ALA, GLY, SER)
164
u.select_atoms("medium") # Medium-sized residues
165
u.select_atoms("large") # Large residues
166
167
# Specific amino acid types
168
u.select_atoms("aliphatic") # Aliphatic residues
169
u.select_atoms("sulfur") # Sulfur-containing (CYS, MET)
170
u.select_atoms("alcoholic") # Alcohol groups (SER, THR, TYR)
171
u.select_atoms("amide") # Amide groups (ASN, GLN)
172
```
173
174
## Geometric Selections
175
176
### Distance-Based Selections
177
178
```python { .api }
179
# Around selections - atoms within distance
180
u.select_atoms("around 5.0 protein") # Within 5Å of protein
181
u.select_atoms("around 3.5 resname LIG") # Near ligand
182
u.select_atoms("around 8.0 (resid 23 and name CA)") # Near specific atom
183
184
# Point selections - around coordinates
185
u.select_atoms("point 10.0 20.0 30.0 5.0") # Within 5Å of point
186
u.select_atoms("point 0 0 0 10.0") # Near origin
187
188
# Spherical layer selections
189
u.select_atoms("sphlayer 2.0 5.0 protein") # Shell around protein
190
u.select_atoms("spherical_layer 1.5 4.0 resname LIG") # Full name
191
192
# Cylindrical selections
193
u.select_atoms("cylayer 2.0 5.0 10.0 -5.0 protein") # Cylindrical shell
194
u.select_atoms("cylinder 5.0 10.0 -5.0 protein") # Inside cylinder
195
196
# Isolation selections - isolated atoms/residues
197
u.select_atoms("isolated") # Atoms with no neighbors
198
u.select_atoms("isolated 5.0") # No neighbors within 5Å
199
```
200
201
### Shape-Based Selections
202
203
```python { .api }
204
# Slab selections - between parallel planes
205
u.select_atoms("slab 10.0 20.0 z") # Z-coordinate slab
206
u.select_atoms("slab -5.0 5.0 x") # X-coordinate slab
207
208
# Box selections - rectangular regions
209
u.select_atoms("prop x > 10.0") # X coordinate > 10
210
u.select_atoms("prop y < -5.0") # Y coordinate < -5
211
u.select_atoms("prop z 0.0:10.0") # Z range
212
213
# Complex geometric combinations
214
u.select_atoms("around 8.0 protein and prop z > 0") # Upper half near protein
215
u.select_atoms("sphlayer 3.0 6.0 protein and not water") # Protein shell, no water
216
```
217
218
## Boolean Logic and Combinations
219
220
### Logical Operators
221
222
```python { .api }
223
# AND operations
224
u.select_atoms("protein and name CA") # Protein alpha carbons
225
u.select_atoms("resname ALA and name CB") # Alanine beta carbons
226
u.select_atoms("resid 1-10 and backbone") # N-terminal backbone
227
228
# OR operations
229
u.select_atoms("resname ALA or resname GLY") # Alanine or glycine
230
u.select_atoms("name CA or name CB") # Alpha or beta carbons
231
u.select_atoms("segid PROA or segid PROB") # Multiple segments
232
233
# NOT operations
234
u.select_atoms("protein and not backbone") # Protein sidechains
235
u.select_atoms("not water and not ion") # Non-solvent atoms
236
u.select_atoms("around 5.0 protein and not protein") # Solvent around protein
237
238
# Complex combinations with parentheses
239
u.select_atoms("(resname ALA or resname GLY) and name CA")
240
u.select_atoms("protein and not (backbone or resname PRO)")
241
u.select_atoms("(around 5.0 resname LIG) and (protein or nucleic)")
242
```
243
244
### Selection Algebra
245
246
```python { .api }
247
# Using AtomGroup operations for complex logic
248
protein = u.select_atoms("protein")
249
ligand = u.select_atoms("resname LIG")
250
waters = u.select_atoms("resname SOL")
251
252
# Intersection
253
binding_site_protein = protein & u.select_atoms("around 5.0 resname LIG")
254
255
# Union
256
protein_and_ligand = protein | ligand
257
258
# Difference
259
sidechain = protein - u.select_atoms("backbone")
260
261
# Symmetric difference
262
mobile_region = (protein | ligand) ^ u.select_atoms("around 3.0 (protein or resname LIG)")
263
```
264
265
## Dynamic and Updating Selections
266
267
### Updating Selections
268
269
```python { .api }
270
# Static selections (default) - fixed atom set
271
static_near = u.select_atoms("around 5.0 resname LIG")
272
273
# Dynamic selections - update each frame
274
dynamic_near = u.select_atoms("around 5.0 resname LIG", updating=True)
275
276
# Example of difference
277
ligand = u.select_atoms("resname LIG")
278
for ts in u.trajectory:
279
# Static selection - same atoms throughout
280
static_atoms = static_near.positions
281
282
# Dynamic selection - atoms change as ligand moves
283
dynamic_atoms = dynamic_near.positions
284
285
print(f"Frame {ts.frame}: Static={len(static_near)}, Dynamic={len(dynamic_near)}")
286
```
287
288
### Periodic Boundary Considerations
289
290
```python { .api }
291
# Selections accounting for periodic boundaries
292
u.select_atoms("around 5.0 protein", periodic=True) # Default behavior
293
u.select_atoms("around 5.0 protein", periodic=False) # Ignore PBC
294
295
# Important for systems where molecules cross box boundaries
296
wrapped_selection = u.select_atoms("sphlayer 2.0 5.0 protein", periodic=True)
297
```
298
299
## Advanced Selection Patterns
300
301
### Property Ranges and Comparisons
302
303
```python { .api }
304
# Numerical comparisons
305
u.select_atoms("mass > 15.0") # Heavy atoms
306
u.select_atoms("charge < -0.5") # Negative atoms
307
u.select_atoms("charge >= -0.1 and charge <= 0.1") # Nearly neutral
308
309
# Range specifications
310
u.select_atoms("resid 1:100") # Residue range
311
u.select_atoms("mass 12.0:16.0") # Mass range
312
u.select_atoms("prop x -10.0:10.0") # Coordinate range
313
314
# Multiple ranges
315
u.select_atoms("resid 1-50 100-150 200-250") # Multiple residue ranges
316
u.select_atoms("name CA CB CG CD CE") # Multiple names
317
```
318
319
### Pattern Matching
320
321
```python { .api }
322
# Wildcard patterns
323
u.select_atoms("name C*") # Names starting with C
324
u.select_atoms("name *A") # Names ending with A
325
u.select_atoms("name H*") # All hydrogens
326
u.select_atoms("resname A*") # Residues starting with A
327
u.select_atoms("segid *A") # Segments ending with A
328
329
# Regular expressions (when supported)
330
u.select_atoms("name ~/^C[0-9]+$/") # C followed by numbers
331
u.select_atoms("resname ~/^(ALA|GLY|PRO)$/") # Specific residues
332
```
333
334
### Index-Based Selections
335
336
```python { .api }
337
# Atom index selections
338
u.select_atoms("index 0") # First atom
339
u.select_atoms("index 0 10 20 30") # Specific indices
340
u.select_atoms("index 0-99") # Index range
341
u.select_atoms("index 0:100:10") # Every 10th atom
342
343
# Combining indices with other criteria
344
u.select_atoms("index 0-999 and protein") # First 1000 protein atoms
345
u.select_atoms("protein and index 0:1000:2") # Every other protein atom
346
```
347
348
## Selection Language Extensions
349
350
### Custom Selection Keywords
351
352
```python { .api }
353
# MDAnalysis allows custom selection keyword definitions
354
from MDAnalysis.core.selection import SelectionParser
355
356
def hydrophobic_residues(group):
357
"""Custom selection for hydrophobic residues."""
358
hydrophobic = ['ALA', 'VAL', 'LEU', 'ILE', 'PHE', 'PRO', 'MET', 'TRP']
359
return group.residues[np.isin(group.residues.resnames, hydrophobic)].atoms
360
361
# Register custom selection
362
SelectionParser.parse_tokens.append(('hydrophobic_residues', hydrophobic_residues))
363
364
# Now can use in selections
365
u.select_atoms("hydrophobic_residues and name CA")
366
```
367
368
### Precomputed Selections
369
370
```python { .api }
371
def create_selection_library(universe):
372
"""
373
Create library of commonly used selections for a system.
374
"""
375
selections = {}
376
377
# Basic components
378
selections['protein'] = universe.select_atoms("protein")
379
selections['backbone'] = universe.select_atoms("backbone")
380
selections['sidechain'] = universe.select_atoms("protein and not backbone")
381
selections['water'] = universe.select_atoms("resname SOL WAT TIP*")
382
selections['ions'] = universe.select_atoms("ion")
383
384
# Secondary structure regions (if available)
385
try:
386
selections['helix'] = universe.select_atoms("alpha")
387
selections['sheet'] = universe.select_atoms("beta")
388
selections['loop'] = universe.select_atoms("coil")
389
except:
390
pass
391
392
# Functional regions
393
selections['ca_atoms'] = universe.select_atoms("name CA")
394
selections['heavy_atoms'] = universe.select_atoms("not name H*")
395
396
# Binding site (example - customize for specific system)
397
if 'LIG' in universe.residues.resnames:
398
selections['ligand'] = universe.select_atoms("resname LIG")
399
selections['binding_site'] = universe.select_atoms("around 5.0 resname LIG and protein")
400
selections['first_shell'] = universe.select_atoms("sphlayer 0.0 5.0 resname LIG")
401
selections['second_shell'] = universe.select_atoms("sphlayer 5.0 10.0 resname LIG")
402
403
return selections
404
405
# Create and use selection library
406
sel_lib = create_selection_library(u)
407
protein_ca = sel_lib['ca_atoms']
408
binding_site = sel_lib.get('binding_site', u.select_atoms("name CA")) # Fallback
409
```
410
411
## Performance Optimization
412
413
### Selection Caching
414
415
```python { .api }
416
class SelectionCache:
417
"""
418
Cache frequently used selections for performance.
419
"""
420
421
def __init__(self, universe):
422
self.universe = universe
423
self._cache = {}
424
425
def get_selection(self, selection_string, **kwargs):
426
"""
427
Get selection with caching.
428
429
Parameters
430
----------
431
selection_string : str
432
Selection string.
433
**kwargs
434
Additional selection arguments.
435
436
Returns
437
-------
438
AtomGroup
439
Cached or newly created selection.
440
"""
441
# Create cache key from selection string and kwargs
442
cache_key = (selection_string, tuple(sorted(kwargs.items())))
443
444
if cache_key not in self._cache:
445
self._cache[cache_key] = self.universe.select_atoms(
446
selection_string, **kwargs
447
)
448
449
return self._cache[cache_key]
450
451
def clear_cache(self):
452
"""Clear selection cache."""
453
self._cache.clear()
454
455
# Usage
456
cache = SelectionCache(u)
457
458
# These will be cached after first call
459
protein = cache.get_selection("protein")
460
ca_atoms = cache.get_selection("name CA")
461
backbone = cache.get_selection("backbone")
462
463
# Fast subsequent access
464
for ts in u.trajectory:
465
# Fast access to cached selections
466
protein_com = protein.center_of_mass()
467
ca_positions = ca_atoms.positions
468
```
469
470
### Efficient Selection Strategies
471
472
```python { .api }
473
def optimize_selection_workflow():
474
"""
475
Demonstrate efficient selection strategies.
476
"""
477
# Strategy 1: Use broad selections first, then narrow
478
# Less efficient:
479
# all_ca = u.select_atoms("name CA")
480
# protein_ca = all_ca.select_atoms("protein") # Re-selection overhead
481
482
# More efficient:
483
protein_ca = u.select_atoms("protein and name CA") # Single selection
484
485
# Strategy 2: Reuse selections when possible
486
protein = u.select_atoms("protein")
487
backbone = protein.select_atoms("backbone") # Subset of existing selection
488
sidechain = protein.select_atoms("not backbone") # Complement
489
490
# Strategy 3: Use static selections for unchanging criteria
491
static_protein = u.select_atoms("protein") # Won't change
492
493
# Strategy 4: Use updating selections only when needed
494
dynamic_contacts = u.select_atoms("around 5.0 resname LIG", updating=True)
495
496
# Strategy 5: Batch similar selections
497
selections = {
498
'ca': u.select_atoms("name CA"),
499
'cb': u.select_atoms("name CB"),
500
'backbone': u.select_atoms("backbone"),
501
'sidechain': u.select_atoms("protein and not backbone")
502
}
503
504
return selections
505
506
# Apply optimization strategies
507
optimized_sels = optimize_selection_workflow()
508
```
509
510
## Usage Examples
511
512
### Complex Selection Workflows
513
514
```python { .api }
515
def analyze_binding_pocket(universe, ligand_resname="LIG"):
516
"""
517
Comprehensive binding pocket analysis using selections.
518
"""
519
# Define key selections
520
ligand = universe.select_atoms(f"resname {ligand_resname}")
521
if len(ligand) == 0:
522
raise ValueError(f"No ligand found with resname {ligand_resname}")
523
524
# Binding pocket definition with multiple criteria
525
pocket_atoms = universe.select_atoms(
526
f"(around 5.0 resname {ligand_resname}) and protein and (not backbone)"
527
)
528
529
# Different shells around ligand
530
first_shell = universe.select_atoms(f"sphlayer 0.0 4.0 resname {ligand_resname}")
531
second_shell = universe.select_atoms(f"sphlayer 4.0 8.0 resname {ligand_resname}")
532
533
# Specific interaction partners
534
hbond_partners = universe.select_atoms(
535
f"(around 3.5 resname {ligand_resname}) and (donor or acceptor)"
536
)
537
538
# Hydrophobic contacts
539
hydrophobic_contacts = universe.select_atoms(
540
f"(around 4.5 resname {ligand_resname}) and "
541
f"(resname ALA VAL LEU ILE PHE PRO MET TRP) and "
542
f"(not backbone)"
543
)
544
545
# Salt bridge partners
546
salt_bridge_partners = universe.select_atoms(
547
f"(around 6.0 resname {ligand_resname}) and "
548
f"(resname LYS ARG ASP GLU) and "
549
f"(name NZ NH1 NH2 OD1 OD2 OE1 OE2)"
550
)
551
552
# Analysis results
553
results = {
554
'ligand_atoms': len(ligand),
555
'pocket_atoms': len(pocket_atoms),
556
'first_shell_atoms': len(first_shell),
557
'second_shell_atoms': len(second_shell),
558
'hbond_partners': len(hbond_partners),
559
'hydrophobic_contacts': len(hydrophobic_contacts),
560
'salt_bridge_partners': len(salt_bridge_partners)
561
}
562
563
# Residue-level analysis
564
pocket_residues = pocket_atoms.residues
565
results['pocket_residues'] = len(pocket_residues)
566
results['pocket_resnames'] = list(set(pocket_residues.resnames))
567
568
return results, {
569
'ligand': ligand,
570
'pocket': pocket_atoms,
571
'hbond_partners': hbond_partners,
572
'hydrophobic': hydrophobic_contacts,
573
'salt_bridges': salt_bridge_partners
574
}
575
576
# Run binding pocket analysis
577
results, selections = analyze_binding_pocket(u, "LIG")
578
print("Binding Pocket Analysis:")
579
for key, value in results.items():
580
print(f" {key}: {value}")
581
```
582
583
### Dynamic Selection Analysis
584
585
```python { .api }
586
def track_selection_changes(universe, selection_string, updating=True):
587
"""
588
Track how selection membership changes over trajectory.
589
"""
590
selection = universe.select_atoms(selection_string, updating=updating)
591
592
results = {
593
'frame': [],
594
'n_atoms': [],
595
'atom_indices': [],
596
'unique_atoms': set()
597
}
598
599
for ts in universe.trajectory:
600
frame = ts.frame
601
n_atoms = len(selection)
602
atom_indices = selection.indices.copy()
603
604
results['frame'].append(frame)
605
results['n_atoms'].append(n_atoms)
606
results['atom_indices'].append(atom_indices)
607
results['unique_atoms'].update(atom_indices)
608
609
# Summary statistics
610
results['mean_atoms'] = np.mean(results['n_atoms'])
611
results['std_atoms'] = np.std(results['n_atoms'])
612
results['min_atoms'] = np.min(results['n_atoms'])
613
results['max_atoms'] = np.max(results['n_atoms'])
614
results['total_unique'] = len(results['unique_atoms'])
615
616
return results
617
618
# Example: Track water molecules near protein
619
water_dynamics = track_selection_changes(
620
u, "resname SOL and around 5.0 protein", updating=True
621
)
622
623
print(f"Water dynamics around protein:")
624
print(f" Average water molecules: {water_dynamics['mean_atoms']:.1f}")
625
print(f" Range: {water_dynamics['min_atoms']}-{water_dynamics['max_atoms']}")
626
print(f" Total unique waters: {water_dynamics['total_unique']}")
627
```
628
629
### Selection Validation
630
631
```python { .api }
632
def validate_selections(universe, selection_dict):
633
"""
634
Validate multiple selections and check for issues.
635
"""
636
validation_results = {}
637
638
for name, selection_string in selection_dict.items():
639
try:
640
# Test selection
641
atoms = universe.select_atoms(selection_string)
642
643
# Basic validation
644
results = {
645
'valid': True,
646
'n_atoms': len(atoms),
647
'n_residues': len(atoms.residues) if len(atoms) > 0 else 0,
648
'n_segments': len(atoms.segments) if len(atoms) > 0 else 0,
649
'warnings': []
650
}
651
652
# Check for empty selection
653
if len(atoms) == 0:
654
results['warnings'].append("Selection returned no atoms")
655
656
# Check for very large selections (might be inefficient)
657
if len(atoms) > len(universe.atoms) * 0.8:
658
results['warnings'].append("Selection includes >80% of system")
659
660
# Check for geometric selections without proper context
661
if any(keyword in selection_string.lower()
662
for keyword in ['around', 'within', 'sphlayer']):
663
if 'updating' not in selection_string:
664
results['warnings'].append(
665
"Geometric selection might benefit from updating=True"
666
)
667
668
validation_results[name] = results
669
670
except Exception as e:
671
validation_results[name] = {
672
'valid': False,
673
'error': str(e),
674
'n_atoms': 0
675
}
676
677
return validation_results
678
679
# Example selection validation
680
test_selections = {
681
'protein': "protein",
682
'ca_atoms': "name CA",
683
'binding_site': "around 5.0 resname LIG and protein",
684
'invalid': "name INVALID_ATOM",
685
'water_shell': "sphlayer 5.0 10.0 protein and resname SOL"
686
}
687
688
validation = validate_selections(u, test_selections)
689
for name, result in validation.items():
690
if result['valid']:
691
print(f"{name}: {result['n_atoms']} atoms")
692
for warning in result.get('warnings', []):
693
print(f" Warning: {warning}")
694
else:
695
print(f"{name}: ERROR - {result['error']}")
696
```