
    J-jHH                     6    d Z ddlmZmZ ddlZ G d de      Zy)a\  `RecombinationWeights` is a list of recombination weights for the CMA-ES.

The most delicate part is the correct setting of negative weights depending
on learning rates to prevent negative definite matrices when using the
weights in the covariance matrix update.

The dependency chain is

lambda -> weights -> mueff -> c1, cmu -> negative weights

    )divisionprint_functionNc                       e Zd ZdZddZd Zd ZddZddZd Z	d	 Z
d
 Zd Zed        Zed        Zed        Zed        Zy)RecombinationWeightsa  a list of decreasing (recombination) weight values.

    To be used in the update of the covariance matrix C in CMA-ES as
    ``w_i``::

        C <- (1 - c1 - cmu * sum w_i) C + c1 ... + cmu sum w_i y_i y_i^T

    After calling `finalize_negative_weights`, the weights
    ``w_i`` let ``1 - c1 - cmu * sum w_i = 1`` and guaranty positive
    definiteness of C if ``y_i^T C^-1 y_i <= dimension`` for all
    ``w_i < 0``.

    Class attributes/properties:

    - ``lambda_``: number of weights, alias for ``len(self)``
    - ``mu``: number of strictly positive weights, i.e.
      ``sum([wi > 0 for wi in self])``
    - ``mueff``: variance effective number of positive weights, i.e.
      ``1 / sum([self[i]**2 for i in range(self.mu)])`` where
      ``1 == sum([self[i] for i in range(self.mu)])**2``
    - `mueffminus`: variance effective number of negative weights
    - `positive_weights`: `np.array` of the strictly positive weights
    - ``finalized``: `True` if class instance is ready to use

    Class methods not inherited from `list`:

    - `finalize_negative_weights`: main method
    - `zero_negative_weights`: set negative weights to zero, leads to
      ``finalized`` to be `True`.
    - `set_attributes_from_weights`: useful when weight values are
      "manually" changed, removed or inserted
    - `asarray`: alias for ``np.asarray(self)``
    - `do_asserts`: check consistency of weight values, passes also when
      not yet ``finalized``

    Usage:

    >>> # from recombination_weights import RecombinationWeights
    >>> from cma.recombination_weights import RecombinationWeights
    >>> dimension, popsize = 5, 7
    >>> weights = RecombinationWeights(popsize)
    >>> c1 = 2. / (dimension + 1)**2  # caveat: __future___ division
    >>> cmu = weights.mueff / (weights.mueff + dimension**2)
    >>> weights.finalize_negative_weights(dimension, c1, cmu)
    >>> print('weights = [%s]' % ', '.join("%.2f" % w for w in weights))
    weights = [0.59, 0.29, 0.12, 0.00, -0.31, -0.57, -0.79]
    >>> print("sum=%.2f, c1+cmu*sum=%.2f" % (sum(weights),
    ...                                      c1 + cmu * sum(weights)))
    sum=-0.67, c1+cmu*sum=0.00
    >>> print('mueff=%.1f, mueffminus=%.1f, mueffall=%.1f' % (
    ...       weights.mueff,
    ...       weights.mueffminus,
    ...       sum(abs(w) for w in weights)**2 /
    ...         sum(w**2 for w in weights)))
    mueff=2.3, mueffminus=2.7, mueffall=4.8
    >>> weights = RecombinationWeights(popsize)
    >>> print("sum=%.2f, mu=%d, sumpos=%.2f, sumneg=%.2f" % (
    ...       sum(weights),
    ...       weights.mu,
    ...       sum(weights[:weights.mu]),
    ...       sum(weights[weights.mu:])))
    sum=0.00, mu=3, sumpos=1.00, sumneg=-1.00
    >>> print('weights = [%s]' % ', '.join("%.2f" % w for w in weights))
    weights = [0.59, 0.29, 0.12, 0.00, -0.19, -0.34, -0.47]
    >>> weights = RecombinationWeights(21)
    >>> weights.finalize_negative_weights(3, 0.081, 0.28)
    >>> weights.insert(weights.mu, 0)  # add zero weight in the middle
    >>> weights = weights.set_attributes_from_weights()  # change lambda_
    >>> assert weights.lambda_ == 22
    >>> print("sum=%.2f, mu=%d, sumpos=%.2f" %
    ...       (sum(weights), weights.mu, sum(weights[:weights.mu])))
    sum=0.24, mu=10, sumpos=1.00
    >>> print('weights = [%s]%%' % ', '.join(["%.1f" % (100*weights[i])
    ...                                     for i in range(0, 22, 5)]))
    weights = [27.0, 6.8, 0.0, -6.1, -11.7]%
    >>> weights.zero_negative_weights()  #  doctest:+ELLIPSIS
    [0.270...
    >>> "%.2f, %.2f" % (sum(weights), sum(weights[weights.mu:]))
    '1.00, 0.00'
    >>> mu = int(weights.mu / 2)
    >>> for i in range(len(weights)):
    ...     weights[i] = 1. / mu if i < mu else 0
    >>> weights = weights.set_attributes_from_weights()
    >>> 5 * "%.1f  " % (sum(w for w in weights if w > 0),
    ...                 sum(w for w in weights if w < 0),
    ...                 weights.mu,
    ...                 weights.mueff,
    ...                 weights.mueffminus)
    '1.0  0.0  5.0  5.0  0.0  '

    The optimal weights on the sphere and other functions are closer
    to exponent 0.75:

    >>> for expo, w in [(expo, RecombinationWeights(5, exponent=expo))
    ...                 for expo in [1, 0.9, 0.8, 0.7, 0.6, 0.5]]:
    ...    assert all([len(w(i)) == i for i in range(3, 8)])
    ...    print(7 * "%.2f " % tuple([expo, w.mueff] + w))
    1.00 1.65 0.73 0.27 0.00 -0.36 -0.64 
    0.90 1.70 0.71 0.29 0.00 -0.37 -0.63 
    0.80 1.75 0.69 0.31 0.00 -0.39 -0.61 
    0.70 1.80 0.67 0.33 0.00 -0.40 -0.60 
    0.60 1.84 0.65 0.35 0.00 -0.41 -0.59 
    0.50 1.89 0.62 0.38 0.00 -0.43 -0.57 

    >>> for lam in [8, 8**2, 8**3, 8**4]:
    ...     if lam == 8:
    ...         print(" lam expo mueff        w[i] / w[i](1)")
    ...         print("          /mu(1) 1   2    3    4    5    6    7    8")
    ...     w1 = RecombinationWeights(lam, exponent=1)
    ...     for expo, w in [(expo, RecombinationWeights(lam, exponent=expo))
    ...                     for expo in [1, 0.8, 0.6]]:
    ...         print('%4d ' % lam + 10 * "%.2f " % tuple([expo, w.mueff / w1.mueff] + [w[i] / w1[i] for i in range(8)]))
     lam expo mueff        w[i] / w[i](1)
              /mu(1) 1   2    3    4    5    6    7    8
       8 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 
       8 0.80 1.11 0.90 1.02 1.17 1.50 1.30 1.07 0.98 0.93 
       8 0.60 1.24 0.80 1.02 1.35 2.21 1.68 1.13 0.95 0.85 
      64 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 
      64 0.80 1.17 0.82 0.86 0.88 0.91 0.93 0.95 0.97 0.98 
      64 0.60 1.36 0.65 0.72 0.76 0.80 0.84 0.87 0.91 0.94 
     512 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 
     512 0.80 1.20 0.76 0.78 0.79 0.80 0.81 0.82 0.83 0.83 
     512 0.60 1.42 0.56 0.59 0.61 0.63 0.64 0.65 0.67 0.68 
    4096 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 
    4096 0.80 1.21 0.71 0.73 0.74 0.74 0.75 0.75 0.76 0.76 
    4096 0.60 1.44 0.50 0.52 0.53 0.54 0.55 0.55 0.56 0.56 

    Reference: Hansen 2016, arXiv:1604.00772.
c                    |}|| _         |d| _         	 t        |      }|dk  rt        d|z        d| _        t        j                  | |       | j                  d       t        | | j                  d       }|d	k7  r2t	        | j                  t        |             D ]  }| |xx   | z  cc<    | j                          d| _        y# t        $ r 	 t        t        |            }nt# t        $ rh d }t	        d|dz         D cg c]D  } |t        j                  |dz   dz        t        j                  |      z
  | j                         F nc c}w }}Y nw xY wY ?w xY w)
a  return recombination weights `list`, post condition is
        ``sum(self) == 0 and sum(self.positive_weights) == 1``.

        Positive and negative weights sum to 1 and -1, respectively.
        The number of positive weights, ``self.mu``, is about
        ``len_/2``. Weights are strictly decreasing.

        `finalize_negative_weights` (...) or `zero_negative_weights` ()
        should be called to finalize the negative weights.

        :param `len_`: AKA ``lambda`` is the number of weights, see
            attribute `lambda_` which is an alias for ``len(self)``.
            Alternatively, a list of "raw" weights can be provided.

        N   c                 d    |dk(  r| S | dk7  | dk  rdndz  }|t        j                  |       |z  z  S )Nr   r   )mathfabs)xexposs      g/Users/jameslopez/projects/TradingBot25/.venv/lib/python3.12/site-packages/cma/recombination_weights.pysigned_powerz3RecombinationWeights.__init__.<locals>.signed_power   s;    qy(a!a%BQ7Atyy|T111    g       @   z%number of weights must be >=2, was %dF)
do_assertsr   )exponentlen	TypeErrorlistranger   log
ValueErrordebug__init__set_attributes_from_weightssummur   	finalized)selflen_r   weightsr   isum_negs          r   r   zRecombinationWeights.__init__   s\      DM	8w<D !8D $& ' '
 	dG$((E(:d4778n%a<477CI.QG8# /3  		884=) 82
 %*!TAX$68$6q ($(b(ADHHQK(OQUQ^Q^_$68 88		8sB   C 	EC'&E'EA	EEEEEEc                    || j                   k  r| d| S || j                  k  r"| d| j                    | | j                   |z
  d z   S || j                  kD  r3| d| j                    || j                  z
  dgz  z   | | j                   d z   S | S )zCreturn a cut or expanded weight list with similar mueff if possibleNr   )r    lambda_)r"   r(   s     r   __call__zRecombinationWeights.__call__   s    dgg>!T\\!>D7):);$<<<T\\!>Wt||%;s$BBT$''(^SSr   c                     || j                   k\  r | |      S |dk  rt        |      dgz  S t        |dz        }|dkD  sJ | d| |d|z  kD  dgz  z   | | d z   S )aF  return weight list of len `lambda_` from the extreme weights.

        This obeys the constraint ``sign(w[i]) == sign((lambda_-1)/2 - i)``
        if the original weights do.

        >>> import cma
        >>> for lam in [2, 3, 5, 11, 33]:
        ...     w = cma.recombination_weights.RecombinationWeights(lam)
        ...     for i in range(1, len(w) + 2):
        ...         w2 = w.trim_middle(i)
        ...         assert len(w2) == i, (len(w2), i)
        ...         if i > 1:
        ...             assert w2[0] == w[0], (w2, w)
        ...             assert w2[-1] == w[-1], (w2, w)
        ...             i_middle = (i - 1) / 2
        ...             assert i > len(w) or (
        ...                  w2[int(i_middle - .1)] * w2[int(i_middle + 1.1)] < 0), (
        ...                        lam, i, i_middle)

        r   g        r   N)r(   int)r"   r(   mu_s      r   trim_middlez RecombinationWeights.trim_middle   sy    * dll"= Q;w<2$&&'A+QwwDSzWq3w.2$66sdeDDr   Nc                 n   7d   dkD  st        dd   z        d   dkD  rt        dd   z        | dd | t        fdt        t              dz
        D              sJ t	        d D              | _        t	        d| j
                         }|dkD  sJ t        t        |             D ]  }| |xx   |z  cc<    dt	        d	 d| j
                   D              z  | _        t	        | j
                  d       }|t	        d
 D              z
  dz  dk  sJ | xs | j                          | S )a  make the class attribute values consistent with weights, in
        case after (re-)setting the weights from input parameter ``weights``,
        post condition is also ``sum(self.postive_weights) == 1``.

        This method allows to set or change the weight list manually,
        e.g. like ``weights[:] = new_list`` or using the `pop`,
        `insert` etc. generic `list` methods to change the list.
        Currently, weights must be non-increasing and the first weight
        must be strictly positive and the last weight not larger than
        zero. Then all ``weights`` are normalized such that the
        positive weights sum to one.
        Nr   z&the first weight must be >0 but was %fr
   z&the last weight must be <=0 but was %fc              3   :   K   | ]  }|   |d z      k\    ywr   N .0r%   r$   s     r   	<genexpr>zCRecombinationWeights.set_attributes_from_weights.<locals>.<genexpr>  *      :!8A 1:1-!8   r   c              3   &   K   | ]	  }|d kD    ywr   Nr1   r3   ws     r   r4   zCRecombinationWeights.set_attributes_from_weights.<locals>.<genexpr>  s     -Wa!eW   c              3   &   K   | ]	  }|d z    ywr   Nr1   r9   s     r   r4   zCRecombinationWeights.set_attributes_from_weights.<locals>.<genexpr>
  s       6#4 *+1#4r;   c              3   ,   K   | ]  }|d k  s	|  ywr8   r1   r9   s     r   r4   zCRecombinationWeights.set_attributes_from_weights.<locals>.<genexpr>       7gQ1g   
r   dy=)r   allr   r   r   r    mueffr   )r"   r$   r   sposr%   snegs    `    r   r   z0RecombinationWeights.set_attributes_from_weights   sc    1:> <wqzIK Kr{Q <BK ! ! DG :!&s7|a'7!8: : 	: :-W--78DGG$%axxs4y!AGtOG " C  6#*8DGG#4 6 6 6
74778$%s7g777!;eCCC+$//+r   c                    |dk  rt        dt        |      z         || _        || _        | d   dk  r|dkD  rN|d|z  kD  rt	        d||fz         | j                  d||z  z          |r| j                  d|z
  |z
  |z  |z         | j                  dd| j                  z  | j                  dz   z  z          | j                          d| _
        | j                  rt	        d	t        |       z         y
y
)aA  finalize negative weights using ``dimension`` and learning
        rates ``c1`` and ``cmu``.

        This is a rather intricate method which makes this class
        useful. The negative weights are scaled to achieve
        in this order:

        1. zero decay, i.e. ``c1 + cmu * sum w == 0``,
        2. a learning rate respecting mueff, i.e. ``sum |w|^- / sum |w|^+
           <= 1 + 2 * self.mueffminus / (self.mueff + 2)``,
        3. if `pos_def` guaranty positive definiteness when sum w^+ = 1
           and all negative input vectors used later have at most their
           dimension as squared Mahalanobis norm. This is accomplished by
           guarantying ``(dimension-1) * cmu * sum |w|^- < 1 - c1 - cmu``
           via setting ``sum |w|^- <= (1 - c1 -cmu) / dimension / cmu``.

        The latter two conditions do not change the weights with default
        population size.

        Details:

        - To guaranty 3., the input vectors associated to negative
          weights must obey ||.||^2 <= dimension in Mahalanobis norm.
        - The third argument, ``cmu``, usually depends on the
          (raw) weights, in particular it depends on ``self.mueff``.
          For this reason the calling syntax
          ``weights = RecombinationWeights(...).finalize_negative_weights(...)``
          is not supported.

        r   z(dimension must be larger than zero, was r
   
   zjWARNING: c1/cmu = %f/%f seems to assume a
                    too large value for negative weights settingr   r   Tzsum w = %.2f (final)N)r   str_c1_cmuprint_negative_weights_set_sum_negative_weights_limit_sum
mueffminusrC   r   r!   r   r   )r"   	dimensionc1cmupos_defs        r   finalize_negative_weightsz.RecombinationWeights.finalize_negative_weights  s   > >G ^, - -	8a<QwS= DI& ' ..q28|<44a"fslc5I7@6A B,,QT__1D04

Q2@ .@ A::(3t945 r   c                 t    t        t        |             D ]  }| |xx   | |   dk  rdndz  cc<    d| _        | S )z0finalize by setting all negative weights to zeror   r   T)r   r   r!   )r"   ks     r   zero_negative_weightsz*RecombinationWeights.zero_negative_weightsH  s;    s4y!AGDGaKqQ.G "r   c           	         | }t        |      }|| j                     dk  sJ |d   dk  s^t        | j                  t        | j                  dz        f      }t        || j                        D ]  }| | j                  |z
  z  ||<    t        |t        || j                  d       z        }t        | j                  | j                        D ]  }||xx   |z  cc<    d|z
  dz
  t        |      cxk  rd|z
  dz   k  sJ  J | j                  r1t        dt        |      t        || j                  d        fz         yy)a  set sum of negative weights to ``-abs(value)``

        Precondition: the last weight must no be greater than zero.

        Details: if no negative weight exists, all zero weights with index
        lambda / 2 or greater become uniformely negative.
        r   r
   r   Nr   h㈵>zsum w = %.2f, sum w^- = %.2f)	absr    maxr+   r(   r   r   r   rK   )r"   valuer$   istartr%   factors         r   rL   z.RecombinationWeights._negative_weights_set_sumO  s5    E
tww1$$$r{Q $''3t||a'7#89:F64<<0#Vt||f'<=
 1US!2334tww-AAJ& J .5y4#g,AUT1AAAAAA::0w<#gdggh&7"8!89: ; r   c                    | }t        |      }t        || j                  d       | k\  ry|d   dk  r|| j                     dk  sJ t        |t        || j                  d       z        }|dk  rWt        | j                  | j                        D ]  }||xx   |z  cc<    | j
                  rt        dt        |      |fz         t        |      dz   d|z
  k\  sJ y)zDlower bound the sum of negative weights to ``-abs(value)``.
        Nr
   r   r   z#sum w = %.2f (with correction %.2f)rX   )rY   r   r    r   r(   r   rK   )r"   r[   r$   r]   r%   s        r   rM   z0RecombinationWeights._negative_weights_limit_sumi  s     E
wtwwx !eV+r{Q7477#3q#888US!2334A:477DLL1
f$
 2zz;7|U+, -7|d"a%i///r   c                    | dd   cxk\  rdkD  sJ  J d   dk  sJ t              | j                  k(  sJ t        fdt        t              dz
        D              sJ | j                  dkD  sJ | j                  dz
     dcxkD  r| j                     k\  sJ  J dt        d d| j                   D              cxk  rdk  sJ  J | j                  dz  t        d| j                         d	z  t        d
 d| j                   D              z  cxk  rd| j                  z  k  sJ  J | j                  dcxk(  rt        | j                  d       k(  sfn | j                  dz  t        | j                  d       d	z  t        d | j                  d D              z  cxk  rd| j                  z  k  sJ  J yy)zassert consistency.

        Assert:

        - attribute values of ``lambda_, mu, mueff, mueffminus``
        - value of first and last weight
        - monotonicity of weights
        - sum of positive weights to be one

        r   r   r
   c              3   :   K   | ]  }|   |d z      k\    ywr0   r1   r2   s     r   r4   z2RecombinationWeights.do_asserts.<locals>.<genexpr>  r5   r6   g+?c              3       K   | ]  }|  y w)Nr1   r9   s     r   r4   z2RecombinationWeights.do_asserts.<locals>.<genexpr>  s     8&71&7s   Ngjt?r   c              3   &   K   | ]	  }|d z    ywr=   r1   r9   s     r   r4   z2RecombinationWeights.do_asserts.<locals>.<genexpr>       /P>O1>Or;   c              3   &   K   | ]	  }|d z    ywr=   r1   r9   s     r   r4   z2RecombinationWeights.do_asserts.<locals>.<genexpr>  rc   r;   )r   r(   rB   r   r    r   rC   rN   )r"   r$   s    @r   r   zRecombinationWeights.do_assertsz  s    GAJ""""""r{a7|t||+++ :!&s7|a'7!8: : 	: :ww{{twwqy!A9)999999s8ghtww&788@5@@@@@

U"GHTWW%&)C/Pghtww>O/P,PP#

"# 	$ # 	$ # 1>GDGGH,=(>>%'GDGGH%&)C/Pgdggh>O/P,PP('(	) ((	) (( ?r   c                     t        |       S )zalias for ``len(self)``)r   )r"   s    r   r(   zRecombinationWeights.lambda_  s     4yr   c                     | }t        || j                  d        }|t        d |D              z
  dz  dk  sJ |dk(  rdS |dz  t        d || j                  d  D              z  S )Nc              3   ,   K   | ]  }|d k  s	|  ywr8   r1   r9   s     r   r4   z2RecombinationWeights.mueffminus.<locals>.<genexpr>  r?   r@   r   rA   r   c              3   &   K   | ]	  }|d z    ywr=   r1   r9   s     r   r4   z2RecombinationWeights.mueffminus.<locals>.<genexpr>  s     >,=qad,=r;   )r   r    )r"   r$   rE   s      r   rN   zRecombinationWeights.mueffminus  sv    74778$%s7g777!;eCCCQY 	@a#>GDGGH,=>>>	@r   c                 f    	 ddl m}  || d| j                         S #  | d| j                   cY S xY w)z}all (strictly) positive weights as ``np.array``.

        Useful to implement recombination for the new mean vector.
        r   asarrayN)numpyrk   r    r"   rk   s     r   positive_weightsz%RecombinationWeights.positive_weights  s6    	"%4>**	">!s    0c                     ddl m}  ||       S )zreturn weights as numpy arrayr   rj   )rl   rk   rm   s     r   rk   zRecombinationWeights.asarray  s     	"t}r   )r   )NT)T)__name__
__module____qualname____doc__r   r)   r-   r   rS   rV   rL   rM   r   propertyr(   rN   rn   rk   r1   r   r   r   r      s    @B/bE:$L56n;40")8   @ @ 	" 	"  r   r   )rs   
__future__r   r   r   r   r   r1   r   r   <module>rv      s    
 0 _4 _r   