
    Vhw                         U d dl Z d dlmZ d dlmZmZmZ d dlZd dlm	Z	m
Z
mZmZmZ d dlmZ  edefi       Zeed<    e j&                  e      Zej,                  ej.                  ej0                  ej2                  hZej6                  dej8                  dej:                  d	ej<                  d
ej>                  dej@                  dejB                  diZ"	 ejF                  dejH                  dejJ                  dejL                  diZ'e(eejR                  ef   e*e+e+f   f   ed<   e'jY                  e"       dejR                  de-fdZ.d Z/d Z0d Z1ejd                  jg                  dd      Z4 e1e4      Z5ddddddejl                  fdejn                  dejn                  dede*e+d f   d!ejR                  d"ee+   d#ee+   d$ee8   d%eejR                     d&eejR                     d'e-d(ee   de*ejn                  ejn                  f   fd)Z9e5	 	 	 	 	 	 	 	 	 d9d*eejn                     de:de;e+   d!ejR                  d"eee+e8e-f      d#eee+e8e-f      d$ee8   d%eejR                     d&eejR                     d'e-d(ee:   deejn                     deejn                     de*ejn                  ejn                  f   fd+       Z< ejz                         ddejl                  fd*ejn                  de*e+d f   d,ejn                  d-eejn                     d.ejR                  d"eee+e8f      d#eee+e8f      d(ee   dejn                  fd/       Z>e5ddejl                  j~                  fd*ejn                  de;e+   d,ejn                  d-eejn                     d.ejR                  d"eee+e8e-f      d#eee+e8e-f      d(ee:   dejn                  fd0       Z@ejl                  j~                  fd*ejn                  de;e+   d,ejn                  d-eejn                     d"ee+e8f   d#ee+e8f   d(ee:   dejn                  fd1ZAddejl                  fej                  d2d*ejn                  de*e+d f   d,ejn                  d-eejn                     d3ejR                  d"eee+e8f      d#eee+e8f      d(ed.ejR                  dejn                  fd4ZCe5ddejl                  j~                  ej                  fd*ejn                  de;e+   d,ejn                  d-eejn                     d3ejR                  d"eee+e8e-f      d#eee+e8e-f      d(ee:   d.ejR                  dejn                  fd5       ZDejl                  j~                  ej                  fd*ejn                  de;e+   d,ejn                  d-eejn                     d"ee+e8f   d#ee+e8f   d(ee:   d.ejR                  dejn                  fd6ZE G d7 d8e	      ZFy):    N)ABCMeta)AnyOptionalUnion)AffineQuantizedObserverBaseget_block_sizeMappingTypeTorchAODTypeZeroPointDomain)NodeABC)r      )r      )r      )r      )r      )r   ?   )r      )r      )ir   )i i  )i   i_DTYPE_TO_QVALUE_BOUNDSdtypereturnc                     t         j                  t         j                  t         j                  t         j                  h}| |v S N)torchfloat8_e4m3fnfloat8_e4m3fnuzfloat8_e5m2float8_e5m2fnuz)r   	fp8_typess     _/home/dcms/DCMS/lib/python3.12/site-packages/torch/ao/quantization/pt2e/_affine_quantization.py_is_float8_typer"   4   s:    	I I    c                 4   | t         v r?t        j                  |       j                  t        j                  |       j                  }}n"| t
        vrt        d|        t
        |    \  }}||}||}||k\  sJ d| d|        ||k  sJ d| d|        ||fS )zGet quant_min and quant_max args based on dtype and also
    verify that they are within the range of possible quant_min/quant_max
    for dtype
    zUnsupported dtype: z9quant_min out of bound for dtype, quant_min_lower_bound: z quant_min: z9quant_max out of bound for dtype, quant_max_upper_bound: z quant_max: )	FP8_TYPESr   finfominmaxr   
ValueError)r   	quant_min	quant_maxquant_min_lower_boundquant_max_upper_bounds        r!   _get_and_check_qmin_qmaxr.   ?   s    
 	KK""KK""  5 
-	-.ug6777Nu7U44)	)	-- 	""7!8YK	Q-
 -- 	""7!8YK	Q- ir#   c                    t        |       t        |      k(  sJ g }g }d}t        t        |             D ]  }| |   ||   k7  rw| |   dkD  ro||   | |   z  dk(  sJ d| d||    d| d| |           |j                  ||   | |   z         |j                  | |          |j                  |dz          |dz  }|j                  ||          | |   dk7  r|j                  |       |dz  } ||fS )a  Given block_size and input size find the parameters for reduction:

    Output:
        shape_for_reduction: the shape we use to `view` input to prepare it for reduction
        reduction_dims: the dims we'll do reduction over

    Example::
        Input:
          block_size: (3, 3, 2, 10)
          input_size: (3, 3, 10, 10)

        Output:
          shape_for_reduction: (3, 3, 5, 2, 10)
          reduction_dim: [0, 1, 3, 4]
    r   r   zExpecting input size at z dimension: z" to be divisible by block_size at    )lenrangeappend)
block_size
input_sizeshape_for_reductionreduction_dimscur_dimis         r!   _get_reduction_paramsr:   ^   s<     z?c*o---NG3z?# a=JqM)jma.?a=:a=0A5 *1#\a=/!CA3lS]^_S`Rac5  &&z!}
1'EF&&z!}5!!'A+.qLG  &&z!}5 !}!%%g.qLG%& ..r#   c                 "     ddl m  fd}|S )a  This decorator is used to preserve some high level operators for torch.export.export
    while still allow them to be decomposed for inductor path

    requirement: make sure `fn.__name__[1:]` is the operator name you want to register

    NOTE: This should be applied at the top, after all other decorators have been applied
    NOTE: We haven't tested the case when `fn` accepts tensor subclass instance as input,
    e.g. uint4 tensor subclass instance, and we'll probably need to figure out what would make
    sense for downstream system (like executorch) to accept as well

    Example:
        lib = torch.library.Library("my_namespace', "FRAGMENT")

        register_custom_op = _register_custom_op(lib)

        @register_custom_op
        def _the_op_that_needs_to_be_preserved(...)
            ...

        # after this, `_the_op_that_needs_to_be_preserved` will be preserved as
        # torch.ops.my_namespace.the_op_that_needs_to_be_preserved operator after
        # torch.export.export / torch._export.export_for_training

    r   )register_decompositionc                     ddl m}  j                  d   dk(  sJ d j                          t         fddD              rJ d j                           j                  dd  }| | i 	      z   }j	                  |       j                  | d
       j                  }t        t        t        j                  |      |      }  |g              |S )Nr   )infer_schema_z-Expecting function name starts with `_`, got c              3   :   K   | ]  }|j                   v   y wr   )__name__).0cfns     r!   	<genexpr>z9_register_custom_op.<locals>.decorator.<locals>.<genexpr>   s      
!"A
s   z.<>zEExpecting op to be defined in normal functions, not lambda or local: r   )mutates_argsCompositeImplicitAutograd)
torch._library.infer_schemar>   rA   anydefineimplnsgetattrr   ops)rD   r>   op_nameschemalib_namespaceoplibr<   s   `     r!   	decoratorz&_register_custom_op.<locals>.decorator   s    <
 KKNc!	I:2;;-H	I! 
&+
 
 	aRSUS^S^R_`	a 
 ++ab/<<<

6"9:WUYY6@$t$R(	r#   )torch._inductor.decompositionr<   )rS   rT   r<   s   ` @r!   _register_custom_oprV      s    2 E* r#   
pt2e_quantFRAGMENTTmin_valmax_valmapping_typer4   .target_dtyper*   r+   epsscale_dtypezero_point_dtypepreserve_zerozero_point_domainc                 l    t        d|j                  |||||||	|
||j                  | |      S d| |      S )al  A variant of :func:`~torchao.quantization.quant_primitives.choose_qparams_affine`
    operator that pass in min_val and max_val directly instead of deriving these from a single input.
    This is used for observers in static quantization where min_val and max_val may be obtained through
    tracking all the data in calibration data set.

    Args:
      Mostly same as :func:`~torchao.quantization.quant_primitives.choose_qparams_affine`. with one
      difference: instead of passing in `input` Tensor and use that to calculate min_val/max_val
      and then scale/zero_point, we pass in min_val/max_val directly
    N)_choose_qparams_affinename)rY   rZ   r[   r4   r\   r*   r+   r]   r^   r_   r`   ra   s               r!   "choose_qparams_affine_with_min_maxre      sa    0 ""3"?  FJ r#   inputc                 &	   t        |||      \  }}|t        j                  j                  t        j                  j                  t        j
                  j                  fv s
J d|        |t        v r'|t        j                  j                  k(  s
J d|        | || j                  }|| j                  }|)t        j                  | j                        j                  }t        |      | j                         k(  sJ d| j                          d|        t        || j                               \  }}| j                  |      } t        j                   | |d      }t        j"                  | |d      }nr||J d       |j                  |j                  k(  sJ d	       ||j                  }||j                  }|)t        j                  |j                        j                  }|	rSt        j$                  |t        j&                  |            }t        j(                  |t        j&                  |            }n|}|}|t        j                  j                  k(  s|t        j                  j                  k(  r|t        j                  j                  k(  r,t        j(                  | |      }|t+        ||z
        d
z  z  }nW|t        j                  j                  k(  sJ |t+        |      z  }|t+        |      z  }||kD  }t        j,                  |||      }|	st/        d      |
(|
t0        j2                  j                  k7  rt/        d      t        j4                  ||      }t        j6                  |t9        ||z   dz   d
z              }n|t        j
                  j                  k(  sJ ||z
  t+        ||z
        z  }t        j4                  ||      }|
t0        j:                  j                  k(  rd}nl|	r3|t        j<                  ||z        z
  }t        j4                  |||      }n7|
t0        j>                  j                  k(  sJ d       ||z   dz   d
z  }|||z  z   }||jA                  |      }|jA                  |      |fS )ao  op definition that has compatible signatures with custom op library

    The op does the following:
    1. figure out the dimension for reduction based on block_size
    2. find min_val/max_val based on the dimension for reduction
    3. calculate quantization parameters based on min_val/max_val based on args like `preserve_zero`
       and `zero_point_domain`
    zUnsupported mapping type: z<Only symmetric quantization is supported for FP8 types, got NGot input dim:, block_size: FdimkeepdimzUNeed to provide `min_val` and `max_val` when `input` is None, got: {min_val, max_val}z]Expecting `min_val` and `max_val` to have the same dtype, got: {min_val.dtype, max_val.dtype}r0   zBpreserve_zero == False is not supported for symmetric quantizationzTzero_point_domain != ZeroPointDomain.INT is not supported for symmetric quantization)r'   r   z8if not preserve_zero, zero_point must be in FLOAT domain)r   )!r.   r	   	SYMMETRICrd   SYMMETRIC_NO_CLIPPING_ERR
ASYMMETRICr%   r   r   r&   r]   r1   rk   r:   sizeviewaminamaxr'   
zeros_liker(   floatwherer)   r   INTclamp	full_likeintNONEroundFLOATto)rf   r[   r4   r\   r*   r+   r]   r^   r_   r`   ra   rY   rZ   r6   r7   min_val_negmax_val_posscalesminsmaxmask
zero_point	mid_points                          r!   rc   rc      sj   0 4L)YWIy""--22##  3 
$L>2	3 
 y K11666	YI,X	Y6 ++K#${{;++ekk*..C 
Ouyy{*	DEIIK=zlC	D*.C

/
+^ 

./**UF**UF G$7	cb	c7 MMW]]*	kj	k* !--K#&}};++gmm,00Cii)9)9')BCii)9)9')BC 	--222;@@EEE ;00555))[L+>K5Y)>#?!#CDE;#H#H#M#MMMM y!11Dy!11D$;DKKdD1ET  )!_%8%8%=%==f  Es+__UCY1F1Ja0O,PQ
{55:::::{*eI	4I.JJEs+ 4 4 9 99J&[55H)II
"[[Y	J
 &)>)>)C)CCNMNC&2Q6!;	(59+<<
]])9]:
88+8&
22r#   r   r   output_dtypec           
      J    t        | ||||||||j                        S d      S )a  
    Args:
      input (torch.Tensor): original float32, float16 or bfloat16 Tensor
      block_size: (Tuple[int, ...]): granularity of quantization,
           this means the size of the tensor elements that's sharing the same qparam
           e.g. when size is the same as the input tensor dimension, we are using per tensor quantization
      scale (float): quantization parameter for affine quantization
      zero_point (int): quantization parameter for affine quantization
      output_dtype (torch.dtype): requested dtype (e.g. torch.uint8) for output Tensor
      quant_min (Optional[int]): minimum quantized value for output Tensor, if not specified, it will be derived from dtype
      quant_max (Optional[int]): maximum quantized value for output Tensor, if not specified, it will be derived from dtype
      zero_point_domain (ZeroPointDomain): the domain that zero_point is in, should be either integer or float
        if zero_point is in integer domain, zero point is added to the quantized integer value during
        quantization
        if zero_point is in floating point domain, zero point is subtracted from the floating point (unquantized)
        value during quantization
        default is ZeroPointDomain.INT

    Note:
      How can block_size represent different granularities?
      let's say we have a Tensor of size: (3, 3, 10, 10), here is the table showing how block_size represents different
      granularities:

       granularity type       |     block_size
         per_tensor           |    (3, 3, 10, 10)
         per_axis (axis=0)    |    (1, 3, 10, 10)
         per_axis (axis=1)    |    (3, 1, 10, 10)
     per_group (groupsize=2)  |    (3, 3, 10, 2)
     per_group (groupsize=2) for axis = 3 | (3, 3, 2, 10)


    Output:
      quantized tensor with requested dtype
    N)_quantize_affinerd   rf   r4   r   r   r   r*   r+   ra   s           r!   quantize_affiner   m  sG    Z "3"?	 	 FJ	 	r#   c           	          t        |||      \  }}|t        v rt        j                  }t	        | ||||||      j                  |      S )a,  op definition that has compatible signatures with custom op library

    Note:
        zero_point_domain is optional specifies how we quantize the floating point to quantized data:
        INT: quantized_val = (float_val / scale) (integer) + zero_point (integer)
        FLOAT: quantized_val = (float_val - (zero_point (float) - scale * mid_point)) / scale
        None: quantized_val = (float_val / scale) | this is primarily used for floatx quantization
            Where we do not want to round values to nearest integer and instead scale and cast.
    )r.   _SUB_BYTE_UINT_BOUNDSr   uint8_quantize_affine_no_dtype_castr~   r   s           r!   r   r     sY    ( 4L)YWIy ,,{{) 	br#   c                    | j                   t        j                  t        j                  t        j                  fv sJ d| j                           t        |      | j                         k(  sJ d| j                          d|        t        || j                               \  }}| j                  }	| j                  |      } |}
|D ]  }d|
|<   	 |j                  |
      }||j                  |
      }|t        j                  j                  k(  r4t        j                  t        j                  | d|z  z        |z   ||      }n|t        j                   j                  k(  r:|J d       t        j                  t        j                  | d|z  z        ||      }n|2|J d       t        j                  | |j#                         z  ||      }nb|t        j$                  j                  k(  sJ ||z   dz   dz  }|||z  z
  }t        j                  t        j                  | |z
  |z        ||      }|j                  |	      }|S )	aW  
    The op does the following:
    1. figure out the dimension for reduction based on block_size, also reshape the input to align with
       the shape after reduction
    2. quantize the input based on the quantization parameters scale and zero_point and args like zero_point_domain
    3. reshape the quantized result to origianl shape
    zUnsupported input dtype: rh   ri   r   g      ?8zero_point should be None when zero_point_domain is NONE8zero_point should be None when zero_point_domain is Noner0   )r   r   float32float16bfloat16r1   rk   r:   rp   shaperq   r   rw   rd   rx   r|   r{   
reciprocalr}   )rf   r4   r   r   r*   r+   ra   r6   r7   original_shapeshape_after_reductionr9   quantr   rY   s                  r!   r   r     sF   $ ;;  1 
#5;;-0	1  	J599;&@			}N:,?@&*?EJJL+' [[NJJ*+E/ %#$a %JJ,-E__%:;
O//444KKu-.;Y	
 
o2277	7	FE	FEKKu(=>	9U		" 	FE	FEE$4$4$66	9M O$9$9$>$>>>>*Q.!3	uy00KKE12Iy
 JJ~&ELr#   r   input_dtypec                R    t        | ||||||||j                  |	      S d|	      S )a  
    Args:
      input (torch.Tensor): quantized tensor, should match the dtype `dtype` argument
      block_size: (List[int]): granularity of quantization,
        this means the size of the tensor elements that's sharing the same qparam
        e.g. when size is the same as the input tensor dimension, we are using per tensor quantization
      scale (Tensor): quantization parameter for affine quantization
      zero_point (Tensor): quantization parameter for affine quantization
      input_dtype (torch.dtype): requested dtype (e.g. torch.uint8) for output Tensor
      quant_min (Optional[int]): minimum quantized value for input Tensor
      quant_max (Optional[int]): maximum quantized value for input Tensor
      output_dtype (torch.dtype): dtype for output Tensor, default is fp32
      zero_point_domain (ZeroPointDomain): the domain that zero_point is in, should be either integer or float
        if zero_point is in integer domain, zero point is added to the quantized integer value during
        quantization
        if zero_point is in floating point domain, zero point is subtracted from the floating point (unquantized)
        value during quantization
        default is ZeroPointDomain.INT

    Output:
      dequantized Tensor, with requested dtype or fp32
    Nr   )_dequantize_affinerd   	rf   r4   r   r   r   r*   r+   ra   r   s	            r!   dequantize_affiner     sM    D "3"?!
 
 FJ!
 
r#   c	           
         |t         vr&| j                  |k(  sJ d| d| j                          |t        j                  t        j                  t        j
                  fv s
J d|        t        |||      \  }}t        | |||||||      S )zCop definition that has compatible signatures with custom op libraryz
Expected: z, got: zUnsupported output dtype: )r   r   r   r   r   r   r.   !_dequantize_affine_no_dtype_checkr   s	            r!   r   r   :  s     //KK;&	:}GEKK=9	:&  3 
$L>2	3 
 4KIVIy,	 	r#   c                 2   t        |      | j                         k(  sJ d| j                          d|        t        || j                               \  }}	| j                  }
| j                  |      } |}|	D ]  }d||<   	 |j                  |      }||j                  |      }|t        j                  j                  k(  r\| j                  t        j                  d      }|"||j                  t        j                        z
  }|j                  |      }||z  }n|t        j                  j                  k(  r |J d       | j                  |      }||z  }n|I|J d       t        | j                        sJ d| j                          | j                  |      }||z  }nT|t        j                  j                  k(  s
J d	|        ||z   dz   d
z  }| |z
  }|j                  |      }||z  }|||z  }|j                  |
      j                  |      S )a  This function converts AQT tensors to their high precision floating point representation

    The op does the following:
    1. figure out the dimension for reduction based on block_size, also reshape the input to align with
       the shape after reduction
    2. dequantize the input based on the quantization parameters scale and zero_point and args like zero_point_domain
    3. reshape the quantized result to origianl shape and change dtype to the output_dtype
    rh   ri   r   T)copyr   r   zNdequantiztion with no zero point domain is only supported with FP8 types, got zUnexpected zero point domain: r0   )r1   rk   r:   rp   r   rq   r   rw   rd   r~   r   int32r{   r"   r   r}   )rf   r4   r   r   r*   r+   ra   r   r6   r7   r   r   r9   dequantr   s                  r!   r   r   ^  sU   & 	J599;&@			}N:,?@&*?EJJL+' [[NJJ*+E/ %#$a %JJ,-E__%:;
O//444 ((5;;T(2!
ekk ::G**\*E/	o2277	7	FE	F((<(E/		" 	FE	FKK
 	j[\a\g\g[hi	j 
 ((<(E/ !6!6!;!;;	@+,=+>?	@; *Q.!3	)#**\*5!z!G<<'**<88r#   c                       e Zd Zdej                  fdZdeej                  ej                  f   fdZdej                  j                  de
fdZy)	AffineQuantizedMinMaxObserverrf   c                 &   |j                         dk(  r|S |j                         }|j                  | _        | j                  J d       t        |j                  | j                        | _        t        | j                  |j                               \  }}|j                  |      }t        j                  ||d      }t        j                  ||d      }t        | d      rt        | d      s|| _        || _        |S | j                  j                  |j                  k(  s+J d| j                  j                   d|j                          | j                   j                  |j                  k(  s+J d	| j                   j                   d
|j                          t        j"                  | j                  |      }t        j$                  | j                   |      }| j                  j'                  |       | j                   j'                  |       |S )Nr   zgranularity is NoneFrj   rY   rZ   z=Can't update existing min_val - shape mismatch, self.min_val:z != min_val:z=Can't update existing max_val - shape mismatch, self.max_val z != max_val:)numeldetachr   original_dtypegranularityr   r   r4   r:   rp   rq   r   rr   rs   hasattrrY   rZ   r'   r(   copy_)selfrf   input_detachedr6   r7   rY   rZ   s          r!   forwardz%AffineQuantizedMinMaxObserver.forward  s   ;;=AL,22+B-BB+()=)=t?O?OP.COO^002/
+^ (,,-@A**^O**^OtY'wtY/G"DL"DL  ""gmm3Nt||OaOaNbbnovo|o|n}~3 ""gmm3Nt||OaOaNbbnovo|o|n}~3iig6Giig6GLLw'LLw'r#   r   c                 H   t        | d      rt        | d      sJ d       t        | j                  | j                  | j                  g | j
                  | j                  | j                  | j                  | j                  | j                  | j                  | j                        S )NrY   rZ   zhExpecting the observer has min_val and max_val, please run the observer before calling calculate_qparams)r   re   rY   rZ   r[   r\   r*   r+   r]   r^   r_   r`   ra   )r   s    r!   calculate_qparamsz/AffineQuantizedMinMaxObserver.calculate_qparams  s    tY'G)-
 	vu	v 
 2LLLLNNNNHH!!""
 	
r#   modelobserver_nodec                    t        d       ddlm} | j                         \  }}|j                  j                  |      5  | j                  J d       | j                  J d        |||j                  d|      } |||j                  d|      }|j                  j                  t        j                  j                  j                  |j                  d   | j                  ||| j                  | j                  | j                   | j"                  j$                  fi       }|j                  j                  t        j                  j                  j&                  || j                  ||| j                  | j                  | j                   | j"                  j$                  fd| j                  i      }	|j)                  |	       |j                  j+                  |       d d d        y # 1 sw Y   y xY w)	Nzcalling convertr   )create_getattr_from_valuez$Expecting block_size to be populatedz(Expecting original_dtype to be populated_scale_zero_pointr   )printtorch.ao.quantization.fx.utilsr   r   graphinserting_beforer4   r   call_functionr   rN   rW   r   argsr\   r*   r+   ra   rd   r   replace_all_uses_with
erase_node)
r   r   r   r   r   r   
scale_nodezero_point_nodeq_nodedq_nodes
             r!   convertz%AffineQuantizedMinMaxObserver.convert  s    L 224z[[))-8 &	2??.V0VV.##/:9:/25%++xQVWJ7u{{M:O [[..		$$44!&&q)OO#%%NNNN**//	 F kk//		$$66OO#%%NNNN**//	  !4!45G //8KK""=1M&	2 &	2 &	2s    FG%%G.N)rA   
__module____qualname__r   Tensorr   tupler   fxGraphModuler   r    r#   r!   r   r     sN    U\\ @
5u||)C#D 
&+2UXX11 +2$ +2r#   r   )	NNNNNTrw   NN)Gloggingabcr   typingr   r   r   r   torch.ao.quantization.observerr   r   r	   r
   r   torch.fxr   objectr   __annotations__	getLoggerrA   loggerr   r   r   r   r%   uint1uint2uint3uint4uint5uint6uint7r   r   int8int16r   r   dictr   r   rz   updateboolr"   r.   r:   rV   libraryLibrary	quant_libregister_custom_oprw   r   ru   re   strlistrc   no_gradr   rd   r   r   r   r   r   r   r   r   r#   r!   <module>r      s     ' '    56)R(S (			8	$ 
					 
KK	KK	KK	KK	KK	KK	KK 
 
KK	JJ	KK&	KK&	T eEKK$=>c3hOP    4 55;; 4  >'/T0f MM!!,
;	(3   $#)-.23B3F3F&\\&\\& & c3h	&
 ++& }& }& 
%& %++&& u{{+& &  0& 5<<%&&R  4837)-.2',&*&*@3ELL!@3@3 S	@3 ++	@3
 c5$./0@3 c5$./0@3 
%@3 %++&@3 u{{+@3 @3  }@3 ell#@3 ell#@3 5<<%&@3 @3F  .2-13B3F3F5<<5c3h5 <<5 &	5
 ++5 c5j)*5 c5j)*5  05 \\5 5p  4837'6':':'?'? << S	  <<  &	 
 ++  c5$./0  c5$./0   }  \\   T (7':':'?'?><<>S	> <<> &	>
 S%Z > S%Z >  }> \\>N .2-1)8)<)<, !&,<<,c3h, <<, &	,
 , c5j)*, c5j)*, ', ++, \\,^  4837'6':':'?'? % << S	  <<  &	 
   c5$./0  c5$./0   }  ++  \\   T (7':':'?'? %G9<<G9S	G9 <<G9 &	G9
 S%Z G9 S%Z G9  }G9 ++G9 \\G9T_2$? _2r#   