Poor result in log1p(complex)

A case where lop1p of a complex number gets a poor real part:
>>> import mpmath
>>> mpmath.__version__
'1.3.0'
>>> from mpmath import mp, mpc, log, log1p, workprec
>>> c = 1.8370676479640493e-39 - 4.6885882517313053e-20j
>>> complex(log1p(c))
(1.0991429897136409e-39-4.6885882517313053e-20j)
>>> with workprec(500): complex(log1p(c))
(2.93621063767769e-39-4.6885882517313053e-20j)

With the higher precision, the real part of the result is entirely different. I don't understand the code:
>>> mp.mag(c), mp.prec
(-63, 53)

Because -63 < -53, I expected the code to take the branch that uses a 2-term expansion, which works fine:
>>> c - 0.5*c**2 # matches the high-precision result
(2.93621063767769e-39-4.6885882517313053e-20j)

But I must not have found the correct source code, since editing it myself has no visible effect.
The other branch in the code calls log() instead after adding 1 to c with double precision:
>>> complex(log(mp.fadd(c, 1, prec=106)))
(1.0991429897136409e-39-4.6885882517313053e-20j) # reproduces the "bad" result

It's not enough extra precision to help. In this case,
>>> mp.mag((2*c.real + c.real**2 + c.imag**2))
-127
>>> complex(log(mp.fadd(c, 1, prec=127+53))) # so this is enough
(2.93621063767769e-39-4.6885882517313053e-20j)
>>> complex(log(mp.fadd(c, 1, prec=127+53-2))) # but 2 bits less not quite enough
(2.9362106376776874e-39-4.6885882517313053e-20j)

To be clear, "the code" I'm talking about is this, from functions/functions.py:
@defun_wrapped
def log1p(ctx, x):
    if not x:
        return ctx.zero
    if ctx.mag(x) < -ctx.prec:
        return x - 0.5*x**2
    return ctx.log(ctx.fadd(1, x, prec=2*ctx.prec))

LATER: found the right source code. ctx.mag(x) and -ctx.prec in the above both return -63 in this case, so that's why the "wrong" branch is taken. I have no idea how ctx.prec ended up at 63. As shown above, I never changed it from the default 53.

Thanks for a bugreport.

I have no idea how ctx.prec ended up at 63.

It was temporary increased in the _wrap_specfun() helper:
https://github.com/mpmath/mpmath/blob/b5c04506ef0cd4a1f1213f8389ee21c9c3551582/mpmath/ctx_mp_python.py#L1017-L1026
BTW, with
diff --git a/mpmath/functions/functions.py b/mpmath/functions/functions.py
index 2bbb63d..178b375 100644
--- a/mpmath/functions/functions.py
+++ b/mpmath/functions/functions.py
@@ -168,7 +168,8 @@ def log1p(ctx, x):
         return ctx.zero
     if ctx.mag(x) < -ctx.prec:
         return x - 0.5*x**2
-    return ctx.log(ctx.fadd(1, x, prec=2*ctx.prec))
+    u = ctx.fadd(1, x, prec=2*ctx.prec)
+    return ctx.log(u)*(x/(u-1))

 @defun_wrapped
 def powm1(ctx, x, y):

I got:
>>> complex(log1p(c))
(2.93621063767769e-39-4.6885882517313053e-20j)

PS: There is another issue, that could be more trivially fixed.  Above function code is used in the fp context as well.  We could change that to math.log1p() in real case (BTW, why there is no cmath.log1p()?  E.g. there is log10(), missing in complex.h.).

Thanks! It's at least cute that a well-intentioned small boost in precision to absorb rounding errors unintentionally convinced later code to do an entirely wrong thing :wink:.
Can't make time for it now, but suggest caution about:
u = ctx.fadd(1, x, prec=2*ctx.prec)
return ctx.log(u)*(x/(u-1))

That trick works great for real-valued log1p(), but complex numbers are more complicated beasts. For example, there are no particular numeric challenges in computing the imaginary part of the result, but dividing through by u-1 may well introduce multiple ULPs of needless error in the imaginary part (complex divide in Cartesian coordinates sucks in ... what? ... 6 multiplies, 2 divides, and 3 additions 2 of which are subject to potential massive cancellation - but in the real-valued case, it's just the single division it "looks like" it is).
For example, this code isn't exactly the same, but does something similar in Python:
def test(c):
    c2 = mp.fadd(c, 1.0, prec=2*mp.prec)
    L = mpmath.log(c2)
    return c * (L / (c2 - 1.0))

and then
>>> c = 7.395821761854832e+86-3.4983116269888314e+21j
>>> complex(mpmath.log1p(c))
(200.02323321145997-4.730118896363822e-66j)
>>> with workprec(500): complex(mpmath.log1p(c)) # that was _already_ as good as it gets
(200.02323321145997-4.730118896363822e-66j)       # same result
>>> complex(test(c))  # but the imaginary part is damaged by the "-1 trick"
(200.02323321146-4.7301188963640004e-66j)
>>> import math  # by about 399 ULP :-(
>>> (4.7301188963640004e-66 - 4.730118896363822e-66)/math.ulp(4.730118896363822e-66)
339.0

Note here that 1 is insignificantly tiny compared to either component of c. log1p(c) and log(c) should be the same (1 + c.real loses the 1 to rounding even in 106-bit arithmetic):
>>> c = 7.395821761854832e+86-3.4983116269888314e+21j
>>> r = mpmath.log1p(c)
>>> complex(r)
(200.02323321145997-4.730118896363822e-66j)
>>> c2 = mp.fadd(c, 1, prec=2*mp.prec)
>>> c == c2  # adding 1 is irrelevant for this `c`
True
>>> complex(c/c*r)
(200.02323321145997-4.730118896363822e-66j)  # exactly the same
>>> _ == r
True
>>> complex(c*(r/c)) # but just reordering the operations introduces the 339 ULP error
(200.02323321146-4.7301188963640004e-66j)

The surprisingly high ULP error in the last example seems primarily due to that the Python code rounds r/c back to 53 bits before the multiply by c is done. Turns out it's correctly rounded to 53 bits, but for these specific values turns out that bits beyond the 53rd make a real difference to the result. In the context of an mpmath implementation, though, you're working with 10 "extra" bits throughout, which would act to hide that..
FYI, the function following takes a very different approach. Across about 10 million randomized inputs so far, it hasn't yet reached 2 1/2 total ULP error (summing the absolute values of the ULP errors in the .real and .imag parts), and total ULP error is < 1 over 99.2% of the time. I took the result of mpmath.log1p(c) with prec=500 as being "correct". However, the error stats appear to look better if I raise that to 1000 bits, so "correct" looks arguable.
def test(c):
    if mp.mag(c) >= 3:
        return mpmath.log(1.0 + c)
    R = mpf(c.real)
    C = mpf(c.imag)
    imag_part = mpmath.atan2(C, R + 1.0)
    # compute res = (R+1)**2 + C**2 - 1 exactly,
    # = R**2 + 2*R + C**2.
    Rsq = mp.fmul(R, R, exact=True)
    twoR = mp.ldexp(R, 1)
    res = mp.fadd(Rsq, twoR, exact=True)
    Csq = mp.fmul(C, C, exact=True)
    res = mp.fadd(res, Csq, exact=True)
    # Now the real part is log(sqrt(1 + res)) = 0.5 * log(1 + res).
    real_part = mpmath.log1p(res)
    return mpc(real_part * 0.5, imag_part)

Maybe it's safe to use just
diff --git a/mpmath/functions/functions.py b/mpmath/functions/functions.py
index 2bbb63d..b770e05 100644
--- a/mpmath/functions/functions.py
+++ b/mpmath/functions/functions.py
@@ -168,7 +168,7 @@ def log1p(ctx, x):
         return ctx.zero
     if ctx.mag(x) < -ctx.prec:
         return x - 0.5*x**2
-    return ctx.log(ctx.fadd(1, x, prec=2*ctx.prec))
+    return ctx.log(ctx.fadd(1, x, exact=True))

 @defun_wrapped
 def powm1(ctx, x, y):

One case, when mantissa of 1+x could blow up is handled right above.  The other is when |x|>>1.

I think you're on to something there. I'm seeing excellent numerical results from the following Python function, which intends to do much the same. Haven't seen total 2 ULP error yet, and 99.99% of the time under 1 total, but that's after only 7 million trials. Alas, it seems to run significantly slower ovreall.
Two tweaks:

The range over which the cheap series expansion works fine is widened.

Doing exact addition is skipped if adding 1 to the real part is lost in working precision. So the imaginary part is irrelevant to this decision, and exact addition is also skipped if |c.real| >> 1.

def test(c):
    c = mpc(c)
    with extraprec(10):
        if mp.mag(c) * 1.5 < -mp.prec:
            result = c - 0.5 * c * c
        else:
            arg = c
            if c.real + 1.0 != c.real:
                arg = mp.fadd(1.0, c, exact=True)
            result = mp.log(arg)
    return result

Surprise! At about the 11 millionth try, it hit a case pushing 4 ulp error:
>>> c = mpc(-2.0476815825463086e-80 - 2.0235857941734692e-40j)
>>> with workprec(1000): right = mp.log1p(c)
>>> got = test(c)
>>> complex(right)
(-2.3184935597344513e-84-2.0235857941734692e-40j)
>>> complex(got) # small difference in real part
(-2.318493559734453e-84-2.0235857941734692e-40j)
>>> import math # compute ULP difference
>>> rgot = float(got.real)
>>> d = float(abs(rgot - right.real))
>>> d / math.ulp(abs(rgot))
3.7405120134353638

Don't know why. This would have taken the cheap 2-term series branch, and mp.mag(c) is -130 so the teeak I made to its input range didn't matter to that.
WOW! It's due to working precision being 1 bit too small for this case - sheesh.
>>> with workprec(63): complex(c - c*c/2) # what we got
(-2.318493559734453e-84-2.0235857941734692e-40j)
>>> with workprec(64): complex(c - c*c/2) # increase by 1 bit and it's "perfect"
(-2.3184935597344513e-84-2.0235857941734692e-40j)

And here's another stab, which has so far delivered a maximum total ULP error of 1.000254. Those cases are rare enough to check by eyeball, and they're "balanced". Meaning that there's an error very close to 0.5 ULP in each of the components. Deeper digging on half a dozen showed they were all cases where the infinitely precise result was very close to being halfway between representable floats.
The twists here:

Widened the "fast path" (series expansion) input range again.

But ask for another 5 bits in the fast path to combat the 1-in-10-million (or so) cases that were getting total ULP errors over 5 before. Haven't seen another of those since.

Took a trick from my long-winded code to vastly cut the number of cases where an exact add of 1.0 is used. Now that happens only when mp.mag(c) is between -mp.prec/2 and 3.

I suppose in the context of an mpmath implementation, where this code's extraprec(10) is already supplied by magic, the calls to mp.log() would go on to add another 10 bits of their own. And I suppose this code does too. I don't know whether that's important to getting such good results.
My latest run just crossed 24 million cases. No change to mention: max total ULP error still 1.000254, and the number of cases with total error < 1 ULP is well over 99.99%; the most recent was over 7 million cases ago.
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    with extraprec(10):
        if cmag >= 3:
            result = mp.log(1.0 + c)
        elif cmag * 2 < -mp.prec:
            with extraprec(5):
                result = c - c * c * 0.5
        else:
            arg = mp.fadd(1.0, c, exact=True)
            result = mp.log(arg)
    return result

Alas, it seems to run significantly slower overall.

That was mostly an illusion. I had forgotten I boosted the reference calculation to use 1000 bits instead of 500. That made it significantly slower.

Ack. After about 71 million trials, a nearly 2-ULP error popped up, at c=-4.382549590908223e-54-2.9606011943930447e-27j. Another "fast path" case that's always been there (mag is -87). And the error would go away if I used extraprec(6) instead of extraprec(5) - another case where just 1 bit makes all the difference.
I seem lost as to how to out-think this. I always figured that since c - c**2/2 has root 2, and mag < -prec ruled out anything near that large, catastrophic cancellation couldn't occur. But I was wrong about that: it also has root 0, and bad cancellation remains a potential problem for very tiny inputs:
>>> c = mpc(-4.382549590908223e-54-2.9606011943930447e-27j)
>>> complex(c)
(-4.382549590908223e-54-2.9606011943930447e-27j)
>>> complex(c**2/2)
(-4.3825797161207615e-54+1.2974981553329635e-80j)

So the leading 5+ decimal digits of the real part cancel out in the subtraction. That's about 15 bits, which is the total extra precision (10 + 5) I'm using.
How bad can it get? Don't know. But if I ask for more & more precision on each ever-rarer bad case at a time,. it won't be long before the name "fast path" will be a joke :wink:.
BTW, I don't believe anything analogous can go wrong with this series for real-valued inputs.  Despite that these have very different norms:
>>> abs(c)
mpf('2.9606011943930447e-27')
>>> abs(c*c/2)
mpf('4.3825797161207615e-54')

their real components are nevertheless nearly equal. That can't happen with abs(real). In the complex case at hand, it's c.imag that dominates the norm of c, but the norm of c*c/2 is dominated by its real part. That the norms are very different doesn't imply that the components are both far apart ☹

Wouldn't be better using a Horner scheme c*(1. - 0.5*c)?

In this specific case, yes. But cancellation brings it very close to where the 3rd-order term can matter, and there's no reason at all to hope that worse cancellation can't occur. So what I'm testing now does away with the extraprec(5) and uses a 3rd-order "Hornerized" series instead:
            elif cmag * 2 < -mp.prec:
                result = c*c * (c/3.0 - 0.5) + c

No surprises.
After 300 million trials, max total ULP error seen is 1.0009, reached at 2.3352642762712193e+104-0.0377801491250023j. All but 47 values had total error less than 1 ULP.
All inputs were built from native Python floats with input context prec 53, The real and imag components were generated in the same way: pick one of the 2**52 representable floats in [1.0, 2.0) uniformly at random, apply ldexp with a shift picked from [-400, 400] at random, and randomly negate it half the time.

Continued testing continued to deliver similar stellar results. Now that I'm (more than just) happy with accuracy, I can think about speed. For a start, the 2-term Taylor series is obviously much better-behaved in this context via Hornerizing, but I haven't tested that. So that's next.

LOL! Testing with the 2-term Horner series quickly delivered the worst case of the last few days: total ULP error of 1.00224, at -3.8445809036335997e-16+1.754102014431826e-63j) (mag -50, so takes the fast path due to my widening of it).
However, it delivers exactly the same (after rounding back to Python's 53-bit floats) with the 3-term series.
To speed this up, I fiddled the random generator to heavily favor producing smaller outputs, to make it much more likely that the fast path will be taken.

Damn. I added a long-ish comment on the actual source of errors here, but Github appears to have lost it.
Short course: "Horner's method" is mostly a red herring here. It's just as prone to cancellation disasters.
However, they're much harder to provoke, because mpc.__mul__ appears to do its adds/subtracts on exact products, while the c - c*c*0.5 spelling does the cancelling subtract after its input products have been rounded back to 63 bits.
Random testing appears too feeble to catch the disasters in reasonable time - didn't see one over 1.002 total ULP error after 730 million tries. But now that I finally understand a cause, it's easy to construct randomish inputs a+bj with small negative a and b "close" to sqrt(a*(a - 2)) (the most obvious way to provoke cancellation problems).
>>> c = mpc(-6.4922176418510124e-21+1.1394926627101214e-10j)
>>> with workprec(10000): complex(mp.log1p(c)) # presumed correct
(-1.4201199664289643e-37+1.1394926627101214e-10j)
>>> complex(test(c))  # 2-term series only gets 4 correct digits
(-1.42033071087851e-37+1.1394926627101214e-10j)
# And cancellation is so bad the 3-term series isn't better
>>> with extraprec(10): complex(c*(c*(c/3.0 - 0.5) + 1.0))
(-1.42033071087851e-37+1.1394926627101214e-10j)

No matter how it's spelled, the real part of the result is the mathematical a - (a*a - b*b) / 2, and the two terms are identical in the first 16 significant digits):
>>> a = c.real
>>> b = c.imag
>>> with extraprec(100): a
mpf('-6.4922176418510124160942132081389312851943535838e-21')
>>> with extraprec(100): (a**2 - b**2)/2
mpf('-6.49221764185101227404006767533335278133713234072e-21')

I note that mag(c) is -32, the largest it can be and still take the "fast path", which I widened. Widening it may have been a fundamental error - but, if so, for reasons I don't yet grasp. After restoring the original condition (mag < -prec), this new "bad case" random generator hasn't found an actual bad case yet (with the widened test, it finds many almost instantly, on every run).

A bit of better :wink: news. The imaginary part of the 2-term series is, mathematically, b-a*b That can't get anywhere near any cancellation unless a is close to 1. But mag(c) is far too small in the fast path for that to happen.
So disaster analysis can focus exclusively on what happens to the real part.

Thinking out loud here: if you want to move to different log1p() implementations for the real and complex cases, I can think of two ways code could benefit:

Cancellation is a non-issue in the real case. Widening the "fast path" is fine there.

In the complex case, the fast path is doing much more work under the covers than currently appears needed. There is only one mpf multiplication that really needs very fat precision. and then no mpc operations are needed at all. Like so:

elif cmag < -mp.prec:
    # Series expansion, c - c**2/2.
    a, b = c.real, c.imag
    # Compute (a**2 = b**2) / 2 very accurately.
    a2b2 = mp.ldexp(mp.fmul(a+b, a-b, exact=True), -1)
    # a-a2b2 can lose almost all the bits of `a` to
    # cancellation. That's why we want lots of extra
    # precision in a2b2.
    result = mpc(a - a2b2, b - a*b)

Then again, I found it very elegant that the same code was used for both.

FYI. about this earlier report, of a bad fast-path case when under the widened test (mag * 2 < -prec).
>>> c = mpc(-6.4922176418510124e-21+1.1394926627101214e-10j)
>>> with workprec(10000): complex(mp.log1p(c)) # presumed correct
(-1.4201199664289643e-37+1.1394926627101214e-10j)
>>> complex(test(c))  # 2-term series only gets 4 correct digits
(-1.42033071087851e-37+1.1394926627101214e-10j)
# And cancellation is so bad the 3-term series isn't better
>>> with extraprec(10): complex(c*(c*(c/3.0 - 0.5) + 1.0))
(-1.42033071087851e-37+1.1394926627101214e-10j)

That wasn't the worst case I found - just the first. I didn't save them all. At least one case got only the first digit right.

It you use lots of precision, the Taylor series does converge to the right 53-bit result. But it takes 4 terms! 3 isn't enough no matter how many bits you use.

Despite trying several times, I still don't have a good answer to how the norm of the input relates to the worst-case accuracy for this series with N terms.  This appears to be approximately infinitely harder than out-thinking the real-valued case 😦

Empirically, as I mentioned before I have a generator now that produces nothing but cases where cancellation can matter. Using the original mag < -prec cutoff, that's gone through over 125 million cases with max total ULP error 0.5005 (*) (as indirectly noted earlier, the imaginary component of the result is going to be extremely close to the original c.imag in these cases. so that component essentially contributes 0 ULP error every time).
But I've so far failed to find a provable bound on how bad it can get. Indeed, the more I've thought about it, the more confused I've gotten about which approaches might work for that 😆.

Using the original mag < -prec cutoff, that's gone through over 125 million cases with max total ULP error 0.5005.

Hmm, does cancellation ever occur in this branch with original code?

Hmm, does cancellation ever occur in this branch with original code?

Absolutely. I covered this in some detail in the comment GIthub lost 😢  Here's a pretty dramatic example:
>>> c = mpc(-1.430796815051627e-72+1.691624553529315e-36j)
>>> mp.mag(c)
-117

# That's way less than -63, so the original will take its fast path.

>>> with workprec(10000): complex(mp.log1p(c))
(4.6709293580298264e-91+1.691624553529315e-36j)

# I presume that the 10-thousand bit result is correct.
# But the original function just manages to get the first digit right:

>>> complex(mp.log1p(c))
(4.909093465297727e-91+1.691624553529315e-36j)

# The Horner spelling repairs that, though:
>>> with extraprec(10): complex(c * (1.0 - c * 0.5))
(4.6709293580298264e-91+1.691624553529315e-36j)

It's not that Horner is somehow magical, it's that the cancellation occurs inside the implementation of mpc.__mul__, where products are computed exactly. The exact products feed into the cancelling add/subtract, allowing the low-order surviving bits to "shift up" (and closer analysis convinced me that only one "fat multiply" total is really needed in this case, which the code I showed implements).
The original spelling fails because in  x - 0.5*x**2, both terms are rounded back to 63 (= 53 + 10) bits before the massively cancelling subtraction is done. Not enough bits survive from the exact products.

Edit: it seems, flint uses same cutoff, not sure if this was justified somehow, but.

I think it's easy to show that mag < -prec is adequate for the real-valued case. I think it's also possible to complete a proof that the much looser mag * 2 < -prec is also adequate for the real-valued case.
But I'm starting to suspect nobody actually thought about it for the complex case 😜

I know few libraries, that provide complex variant of log1p.  IIRC, julia just uses Kahan's trick.  The MPC has no log1p.

Ya, IMO log1p(complex) is a pretty silly idea :wink:. The real numerical problem for log(c) is when the mathematical abs(c) is close to 1, because the real part of the mathematical result is log(abs(c)).
>>> c = mpc(0.5, mp.sqrt(.75000000001))
>>> c
mpc(real='0.5', imag='0.8660254037902122')
>>> abs(c)
mpf('1.000000000005')

# So numerically challenging despite that c.real is far from 0.

# How does mpmath do on it?
>>> mp.log(c)
mpc(real='5.0000441239368832e-12', imag='1.0471975511994844')
>>> mp.log(abs(c))
mpf('5.0000004136893552e-12')

# Oops? The real part materially differs from mp.log(|c|).
# But that's because |c| was computed with inadequate
# precision in the last line.

>>> with extraprec(100): absofc = abs(c)
>>> mp.log(absofc)
mpf('5.0000441239368832e-12')

# And that matches mp.log(c).real.

numpy supports log1p(complex) too. That's how I got sucked into this, replying to complaints about poor results there. No surprise: numpy's log1p(c) just does log(c+1). The numpy docs don't point this out, but only claim better accuracy for the real-valued version (which uses a different algorithm).

I'm going to share my "bad case" generator here, so there's a record of it on the web. It required some effort to come up with, and it's been extremely effective at "breaking" half a dozen lop1p(complex) implementations very quickly (including different approaches than mentioned here - there's apparently something inherently "very hard" about these).
from math import sqrt, ldexp, ulp
from random import random, randrange
# Generate c such that there's significant cancellation
# in computing the real part of c - c*c/2.
def getc():
    a = random() * 0.5 + 0.5
    a = ldexp(a, randrange(-500, -126))
    a = -a
    b = sqrt(a * (a - 2.0))
    u = ulp(b)
    u = ldexp(u, randrange(40))
    if random() < 0.5:
        u = -u
    u *= random()
    b += u
    return complex(a, b)

The last code I talked about hasn't hit a total ULP error over 0.5005 after 650 million of these so far.

I think I may :wink: understand this better now. Where the last code had:
            a2b2 = mp.ldexp(mp.fmul(a+b, a-b, exact=True), -1)
            result = mpc(a - a2b2, b - a*b)

I'm hopeful that this will do almost as well (and saves two mpf additions):
            b2 = mp.ldexp(mp.fmul(b, b, exact=True), -1)
            result = mpc(a + b2, b - a*b)

Unless I'm wrong, the high half of the full b*b product can cancel out all of a, leaving the low half of the full product as "the answer" - the last bit of which may be affected by the full a*a product via rounding. But I don't believe the last can actually happen unless the input actually has mp.prec significant bits. In the context of this testing, it cannot: the input components have at most 53 significant bits, and mp.prec is 63. If the inputs actually have 63 (or more) bits ... I'm not entirely sure, but offhand doubt it matters much.
So a*a is irrelevantly small in cancelling cases. In non-cancelling cases, whichever of |a| and |b2| is larger dominates the a + b2 sum, But won't it hurt to ignore a*a if a*a > b*b? Perhaps, but not by much. If the leading bit of a has weight 2^L, the leading bit of a*a has weight 2^(2*L) or 2^(2*L+1). The least important bit of a has weight 2^(L - prec + 1) So for the leading bit of a*a to directly matter, it must be that 2*L+1 >= L - prec + 1, or L >= -prec But mag(c) < -prec in this branch, so L <= mag(a) <= mag(c) < -prec too. Therefore it's not possible that L >= -prec: the leading bit of a*a is somewhere "to the right" of a's least important bit, so at worst can affect the final result indirectly via rounding.
This is fuzzy, though, because mag() may be too large (the docs say by at most 2). But if mag(c) < -prec is true, and mag(c) is too large, the true magnitude is "even more < -prec". So it doesn't change that L >= -prec can't hold.
Preliminary testing is going fine, but it's only tried 10 million cases. Since this was derived by analyzing the snot :wink: out of cancelling cases, I widened the generator to probe many more non-cancelling fast-path cases too. The reasoning for always ignoring a*a is the more subtle, so sorely needs testing.

BTW, a similar but simpler argument shows why widening the fast path is fine for the mpf case. How can we ensure that - x^3/3 can't affect the last bit directly (so that there's scant point to using a 3rd series term)? The last important bit of x again has weight 2^(L - prec + 1). The leading bit of x^3 has weight with exponent L, L+1, or L+2. So the largest it can get is L+2. But dividing by 3 leaves it worst-case maximum L+1 instead.
So we want 3*L + 1 < L - prec + 1, which simplifies to 2*L < -prec. Replace L by mag(x), and we're done :smile:.

And another: in the mpf case, the original mag < -prec condition is so strong that we could almost jurt as well return x - subtracting 0.5*x*x makes scant difference.
Same kind of argument: the leading bit of x^2 has weight 2L or 2L+1. But dividing by 2 leaves 2L as the worst case. So we want 2L < L - prec + 1, which simplifies to L < -prec + 1. Replace L by mag(x) and we're almost done. Testing for L < -prec is more restrictive than testing for L < -prec + 1, and the latter is all we really need to ensure that the second series term doesn't overlap with x's leading prec significant bits.
And all these arguments are pessimistic. Supposing mag() was exact (returns the mathematical ceiling(log(abs(x), 2)), what I've been calling L equals mag(x) only when x is 2^i for some integer i. Else mag(x) is one larger than L. The actual mp.mag(x) may be as much as 3 larger than L, and never smaller than L.
Alas, this approach appears to be of no help in the complex case. Arguments based on mag carry over to that case, but the L concept is confined to a single float. mag(c) seems pretty much useless for predicting when cancellation may occur in the computation of f(c)'s (for some function f()) .real and/or .imag components.

mag(c) seems pretty much useless for predicting when cancellation may occur in the computation of f(c)'s (for some function f()) .real and/or .imag  components.

Let me make that concrete via an earlier example:
>>> c = mpc(-1.430796815051627e-72+1.691624553529315e-36j)
>>> mp.mag(c)
-117
>>> mp.mag(c*c/2)
-237

# In the real-valued case, such a large difference in mag guarantees that
# adding/subtracting the two will leave `c` unchanged to 53 sig bits.

# But not so in the complex case:

>> with extraprec(100): complex(c*c/2)
(-1.430796815051627e-72-2.4203710234528746e-108j)

# The .real part of the result is almost exactly -c.real.
# Massive cancellation during `+` dispute huge mag difference.

Observation: in the latest code, which continues to deliver excellent test results after over 900 million trials:
            b2 = mp.ldexp(mp.fmul(b, b, exact=True), -1)
            result = mpc(a + b2, b - a*b)

consider what happens when b is 0. Then it's just the real-valued function in disquise. b2 is 0 too, and the result is mpc(a, 0). So the current run is also indirectly testing that when mag < -prec, in the real-valued case only the first term of the series actually matters.
BTW, while the mag < -prec condition was used in my argument for why a*a is irrelevantly small in the complex case, I'm still not clear on "the real reason" for why the complex case needs more series terms than the real case.

Ah! The light dawns!! In the complex version, there are really two series: one for the real part, and another for the imag part. They both have to converge, but may converge at different rates. Looking at the imag series alone, by minor variation of arguments already given, only one term should suffice: with mag < -prec, the -a*b in b-a*b can also be ignored. That series only needs one term. It's the real part that needs two terms, and because of possible heavy cancellation.
So on to test testing this code, which loses another multiply and add. We're down to just one muliply and one add now:
            b2 = mp.ldexp(mp.fmul(b, b, exact=True), -1)
            result = mpc(a + b2, b)

The last run completed a billion tries with worst case total ULP error 0.50048, reached at 3.834704113872437e - 131-6.775600261387217e-51j,

And one more twist: cancellation can't occur in a + b*b/2 unless a is negative, so it's only when a<0 that the expense of an exact product may be needed. So this suffices:
            b2 = mp.ldexp(mp.fmul(b, b, exact=a<0), -1)

The bound can be loosened to mag * 2 < -prec too, but at significant extra expense. While in the real case the 2nd-order term doesn't really matter under that condition, in the complex case the 2nd-order terms in both the real and imag series matter. That is, under the looser condition, a**2/2 can matter in a-(a+b)*(a-b)/2, and a*b can matter in b-a*b. Those both have very little effect (at worst indirectly via final rounding) under the stronger mag < -prec condition.

For clarity, I should have spelled this out earlier: what I called L for a given float x is mathematically floor(log(abs(x), 2)). mpmath.mag(x) returns an upper bound on the same expression, but with ceiling replacing floor. That's why L is usually one smaller, but the same (if mpmath.mag(x) were always exact) when x is 2 raised to an integer power.
2^L(x) gives the weight of x's most-significant bit. When doing careful error analysis involving elementary arithmetic, I just find that easiest to reason about. For example, L(x*y) is either L(x) + L(y) or one more than that. This is easy to prove. If x>0, x = f*2^L(x) for some real satisfying 1 <= f < 2. So x*y is f*2^L(x) * g*2^L(y) for some f and g both in [1, 2), which is f*g * 2^(L(x)+L(y)) with 1 <= f*g < 4. So if f*g < 2, then L(x*y) is L(x)+L(y). Else f*g can be divided by 2 to bring it back into the [1, 2) interval, and the exponent of 2 has to be increased by 1 to preserve equality.

I think I'm about done here.
One more loose end: justifying the log(1.0 + c) path for larger abs(c). The numeric challenge for log1p(c) is when abs(c+1) is close to 1, because the real part is log(abs(c+1)), and log(1+e) basically is e for tiny |e|.
By the reverse triangle inequality, abs(c+1) >= abs(c) - 1. So if abs(c) >= 3, abs(c+1) >= 2, nowhere near being a problem.
If abs(c) >= 2, abs(c+1) >= 1 which suggests there might be a problem. But there actually isn't. For abs(c+1) to be less than abs(c), adding 1 has to decrease the absolute value of c.real. For worst case, suppose abs(c) = 2. Then abs(c.real) <= 2 too, and for adding 1 to decrease its absolute value it must be that -2 <= c.real <= -1. If c.real is -2, then adding 1 gives exactly 1. Else c.real is a binary float of the form -1.bbbbbbbb..., and adding 1 in fp arithmetic gives exactly -0.bbbbbbbb.... That is, it's a cancelling add, and those are exact on their own. So c+1 is computed exactly in either case. Since no information was lost, log(c+1) is fine.
Otherwise abs(c+1) >= 2 too, which is nowhere near being a numeric problem. c+1 may not be exact, but if not it's off by 1/2 ulp rounding error in c.real, which has scant effect on abs(c). For example, if c.real is 1e-30, adding 1 loses all of it, but since  abs(c) >= 2the imaginary part must be >= 2 in this case. Losing1e-30` from the real part is numerically irrelevant to the sum of the squares of the components then.
To ensure abs(c) >= 2, if mp.mag(c) were always exact it would suffice to check that mp.mag(c) >= 2 (which would guarantee abs(c) <= 2^2 = 4, while mag=1 would guarantee abs(c) <= 2; so mag >= 2 is the smallest value "that works" for this purpose); , Because I had observed that mag was sometimes 1 too large, in the original code I changed that 2 to 3. But since then I found the docs that said mag may be 2 too large, so it should really be 4 instead. Which the "final" version of the code below uses.
Some billions of randomized test cases have been run by now, with total ULP error no worse than 1.001, and in "fast path" cases no more than 0.501. These include a billion randomized fast-path cases contrived to suffer significant cancellation in a-b*b/2.
It's been fun :wink:.
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    with extraprec(10):
        if cmag >= -mp.prec:
            arg = mp.fadd(1.0, c, exact=cmag < 4)
            result = mp.log(arg)
        else:
            a, b = c.real, c.imag
            b2 = mp.ldexp(mp.fmul(b, b, exact=a < 0.0), -1)
            result = mpc(a + b2, b)
    return result

For completeness, another version that widens the fast-path range to 2 * mag < -prec. As noted before, this requires using more of the 2-term series expansion. It requires another regular (not exact) mpf multiply, and 3 more regular mpf adds. Which triples(!) the total mpf operation count.
If you like this better, tell me and I'll run tests on it for a lot longer. No surprises after a few dozen million trials (sorst error below 0.501 ULP in each component):
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    with extraprec(10):
        if 2 * cmag >= -mp.prec:
            arg = mp.fadd(1.0, c, exact=cmag < 4)
            result = mp.log(arg)
        else:
            a, b = c.real, c.imag
            if cmag < -mp.prec:
                b2 = mp.fmul(b, b, exact=a < 0.0)
            else:
                b2 = mp.fmul(b+a, b-a, exact=a < 0.0)
                b -= a*b
            result = mpc(a + mp.ldexp(b2, -1), b)
    return result

@tim-one, would you like to create a PR?  If you want to put in randomized tests, maybe we should depend on the hypothesis.

Sorry, no, I don't want to try to create a PR. I already spent far more time on this than I planned for, and all my work has been at the Python level, outside mpmath's implementation. The internals of mpmath seem partly a different world, with lots of internal functions, data representations, and conventions I know nothing about.
So I expect it would go far easier, better, and faster if, e.g., you :smile: translated the few lines of code to internal "mpmath-speak".
The are also decisions for mpmath to make, which I don't have strong opinions about. Like, do you want different real and complex implementations of log1p()? Do you want to widen the fast path for the real-valued version? Do you just want to change a common fast path to the Horner spelling (return `c * (1.0 - c*0.5)') and leave it as an inside joke :wink: that it avoids cancellation catastrophes largely by implementation accident? That would be a minimal code change, but substantially slower than the 1 mpf add + 1 mpf multiply that's possible.
Those are all project-level decisions, though, and I'm not a project dev here. I'm just a happy mpmath user who wants to see better results for this function.

I truly can't make more time for this now, but there's something else I think needs to be addressed to get production-quality code. This line:
            arg = mp.fadd(1.0, c, exact=cmag < 4)

can make the code arbitrarily expensive the smaller |c,real| gets, regardless of the current precision (the expense isn't just from the add, but also from the potentially giant mantissa passed to log() to chew on). The current implementation does the add with double the current precision, which is sufficient for the real function, but not the complex one (a cause of the original bug report here).
But the smaller |c.real| is, the less difference it can make to the infinitely precise result (mag >= -prec in this branch, so |c.imag| can't also become arbitrarily tiny, so will increasingly dominate the result as c.real's magnitude shrinks).
Empirically, "just" tripling the precision appears so far to work:
            arg = mp.fadd(1.0, c,
                          prec=(mp.prec if cmag >= 4 else 3 * mp.prec))

but 29 * prec // 10 (i.e., an int approximation to multiplying by 2.9) does not. So there appears to be something critical about 3.
But I don't have a handle on how to prove that, nor the time to dig into it now. It's an interesting puzzle, though 😄.

No, this doesn't count as "digging" :wink:. Trying out intuitions in Python goes very fast. This appears to work, and I think the comments are qualitatively right, but there's no real justification for why "3" is sufficient when mag(a) <= mag(b). Nailing that would be "digging" (require real thought & effort, and so also significant time).
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    a, b = c.real, c.imag
    with extraprec(10):
        wp = mp.prec
        if cmag >= -wp:
            if cmag < 4:
                if mp.mag(a) > mp.mag(b):
                    # `a` already contributes the most to c's norm.
                    # After adding 1, it will utterly dominate it.
                    # We only need enough extra precision to avoid
                    # losing any of a's bits when addiog 1.
                    wp *= 2
                else:
                    # `b` contributes the most to c's norm now. After
                    # adding 1 to `a`, in (a+1)**2 + b**2 =
                    # 1 + 2*a + a**2 + b**2, `b` may be so much larger
                    # than `a` now that b**2 is close to }2*a|, or
                    # even cancel 2*a out (in which case the high-
                    # order bits of the teensy a**2 become important).
                    wp *= 3
            arg = mp.fadd(1.0, c, prec=wp)
            result = mp.log(arg)
        else:
            b2 = mp.ldexp(mp.fmul(b, b, exact=a < 0.0), -1)
            result = mpc(a + b2, b)
    return result

WRT this comment:
                    # `b` contributes the most to c's norm now. After
                    # adding 1 to `a`, in (a+1)**2 + b**2 =
                    # 1 + 2*a + a**2 + b**2, `b` may be so much larger
                    # than `a` now that b**2 is close to }2*a|, or

that's exactly what happens in the original case that was the subject of this report, c = 1.8370676479640493e-39 - 4.6885882517313053e-20j. b**2 is in the same ballpark as 2a, but adding 1 to a with prec=2*63=126 loses all the bits of a (with mag -128) to rounding. The computed result is exactly 1.
It doesn't matter to this that the log() path doesn't compute the norm. What matters is the demonstration that, despite that a is very small compared to b, it nevertheless has a significant effect on the result. Any computational method that loses a can't get the right answer.

So I think this is "the answer" (just came to me while walking): in the hard cases, the magnitude of b aquared is about |2a|. Very roughly, mag(b)*2 == mag(a). Since we don't want to lose a to rounding when adding 1, we need to use enough bits to absorb (viewing a as fixed-point) its leading -mag(a) = -mag(b)*2 0 bits and then its prec most significant bits. Since mag(b) is the larger in this case, where b has larger magnitude than a. mag(c) is close to mag(b). Finally, mag(c) >= -prec in this case too, so -mag(c) <= prec, so -mag(b) <= prec, so -mag(b)*2 <= 2*prec, so -mag(b)*2 + prec <= 3*prec. so -mag(a) + prec <= 3*prec. So uxing 3*prec in the add is enough to preserve the leading prec significant bits of a.
To get the base idea across more intuitively, the worst cases are when c is as small as it can get in this branch: mag(c) = -prec. Then since b is the dominant component in this branch, as a fixed-point number b has about prec leasing 0 bits (after the radix point). Then since a is about the square of b in the hard cases, a has about 2*prec leading 0 bits. So to preserve a's prec most significant bits after adding  1, we need to use about 3*prec bits - 2*prec of which are to preserve the leading 0 bits.

OK! Not so bad :wink:. The code gets more long-winded, though:
EXTRAPREC = 10
EXTRAPREC_MAG = mp.mag(EXTRAPREC)
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    a, b = c.real, c.imag
    with extraprec(EXTRAPREC):
        wp = mp.prec
        if cmag >= -wp:
            if cmag < 4:
                if mp.mag(a) > mp.mag(b):
                    # `a` already contributes the most to c's norm.
                    # After adding 1, it will utterly dominate it.
                    # We only need enough extra precision to avoid
                    # losing any of a's significant bits when addiog,
                    wp *= 2
                else:
                    # `b` contributes the most to c's norm now. After
                    # adding 1 to `a`, in (a+1)**2 + b**2 =
                    # 1 + 2*a + a**2 + b**2, `b` may be so much larger
                    # than `a` now that b**2 is close to }2*a|, or
                    # even cancel 2*a out (in which case the high-
                    # order bits of the teensy a**2 become important).
                    wp *= 3
            arg = mp.fadd(1.0, c, prec=wp)
            result = mp.log(arg)
        else:
            might_cancel = a < 0.0
            b2 = mp.ldexp(mp.fmul(b, b, exact=might_cancel), -1)
            apb2 = a + b2
            if might_cancel:
                if mp.mag(apb2) <= mp.mag(a) - EXTRAPREC_MAG:
                    # The guard bits were lost to cancellation. Rare.
                    # At the contrived
                    # -1.999999873062092e-40+1.999999936531045e-20j
                    # _all_ bits cancel out. Complete the full
                    # 2nd-order real series, esactly.
                    a2 = mp.ldexp(mp.fmul(a, a, exact=True), -1)
                    apb2 = a + mp.fsub(b2, a2, exact=True)
                    #print("X", mp.mag(apb2), mp.mag(a))
            result = mpc(apb2, b)
    return result

Last for now rearranging the code for clarity and adding some comments. The comment justifying using 3*prec is poor, but can't do more for now. Ah, also skipped the exact multiply in the possibly-cancelling part. It isn't needed unless there is heavy cancellation. So now the usual path through that code is one plain mpf multiply and one plain mpf add.
LOG1P_EXTRAPREC = 10
LOG1P_EXTRAPREC_MAG = mp.mag(LOG1P_EXTRAPREC)
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    a, b = c.real, c.imag
    with extraprec(LOG1P_EXTRAPREC):
        wp = mp.prec
        if cmag >= -wp:
            if cmag < 4:
                if mp.mag(a) > mp.mag(b):
                    # `a` already contributes the most to c's norm.
                    # After adding 1, it will utterly dominate it.
                    # We only need enough extra precision to avoid
                    # losing any of a's significant bits when addiog,
                    wp *= 2
                else:
                    # `b` contributes the most to c's norm now. After
                    # adding 1 to `a`, in (a+1)**2 + b**2 =
                    # 1 + 2*a + a**2 + b**2, `b` may be so much larger
                    # than `a` now that b**2 is close to }2*a|, or
                    # even cancel 2*a out (in which case the high-
                    # order bits of the teensy a**2 become important).
                    wp *= 3
            arg = mp.fadd(1.0, c, prec=wp)
            result = mp.log(arg)
        # Else c is "very small", and we use a series expansion.
        # c - c**2/2
        # The real part of that is a+(b*b-a*a)/2, and the
        # imag part b-a*b.
        # Given that cmag < -prec, it can bu shown that the "a*b"
        # is numerically insignifcant in the imag part, and
        # _usually_ the "a*a/2" in the real part. What remains
        # is cheap to compute.
        # In the real part, though, if `a` is negative, the
        # remaining a+b**2/2 can suffer massive cancellation -
        # even total.
        elif a < 0.0: # cancellation is a potential problam
            #b2 = mp.ldexp(mp.fmul(b, b, exact=True), -1)
            apb2 = a + mp.ldexp(b*b, -1)
            if mp.mag(apb2) <= mp.mag(a) - LOG1P_EXTRAPREC_MAG:
                # The guard bits were lost to cancellation. Rare.
                # At the contrived
                # -1.999999873062092e-40+1.999999936531045e-20j
                # _all_ bits cancel out. Complete the full
                # 2nd-order real series, esactly.
                a2 = mp.ldexp(mp.fmul(a, a, exact=True), -1)
                b2 = mp.ldexp(mp.fmul(b, b, exact=True), -1)
                apb2 = a + mp.fsub(b2, a2, exact=True)
            result = mpc(apb2, b)
        else: # cancellation not possible
            result = mpc(a + mp.ldexp(b*b, -1), b)
    return result```

Ya, I dug into it 😢 . There isn't real need for exact operations of any kind in the worst series-expansion cases.  a*a/2 can be done in working precision. b*b/2 in twice that (although if b has no more than prec significant bits, that's the same as exact). And the add only needs 3 times prec.
I'm uncomfortable with such large comment blocks, but don't want to write a book either :wink:. I'm also uncomfortable with the relative lack of justifying comments in mpmath's internals. So how to deal with that is another mpmath-internal project decision.
LOG1P_EXTRAPREC = 10
LOG1P_EXTRAPREC_MAG = mp.mag(LOG1P_EXTRAPREC)
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    a, b = c.real, c.imag
    with extraprec(LOG1P_EXTRAPREC):
        wp = mp.prec
        if cmag >= -wp:
            if cmag < 4:
                if mp.mag(a) > mp.mag(b):
                    # `a` already contributes the most to c's norm.
                    # After adding 1, it will utterly dominate it.
                    # We only need enough extra precision to avoid
                    # losing any of a's significant bits when addiog,
                    wp *= 2
                else:
                    # `b` contributes the most to c's norm now. After
                    # adding 1 to `a`, in (a+1)**2 + b**2 =
                    # 1 + 2*a + a**2 + b**2, `b` may be so much larger
                    # than `a` now that b**2 is close to }2*a|, or
                    # even cancel 2*a out (in which case the high-
                    # order bits of the teensy a**2 become important).
                    wp *= 3
            arg = mp.fadd(1.0, c, prec=wp)
            result = mp.log(arg)
        # Else c is "very small", and we use a series expansion.
        # c - c**2/2
        # The real part of that is a+(b*b-a*a)/2, and the
        # imag part b-a*b.
        # Given that cmag < -prec, it can bu shown that the "a*b"
        # is numerically insignifcant in the imag part, and
        # _usually_ the "a*a/2" in the real part. What remains
        # is cheap to compute.
        # In the real part, though, if `a` is negative, the
        # remaining a+b**2/2 can suffer massive cancellation -
        # even total.
        elif a < 0.0: # cancellation is a potential problam
            apb2 = a + mp.ldexp(b*b, -1)
            if mp.mag(apb2) <= mp.mag(a) - LOG1P_EXTRAPREC_MAG:
                # The guard bits were lost to cancellation. Rare.
                # At the contrived
                # -1.999999873062092e-40+1.999999936531045e-20j
                # _all_ bits cancel out.
                # Since a ~= -b*b/2 in this case, and |b| is at
                # largest (worst case) about 2**-prec, |a| is
                # about 2**(-2*prec), and the true result may
                # be as small as a**2/2, which is about
                # 2**(-4*prec), of which we want the leading
                # prec bits. To get the leading prec bits
                # starting at 2**(-4**prec) from addends
                # starting at 2**(-2*prec), we need the
                # addition to handle 3*prec bits (the first
                # 2*prec of which may cancel to exactly 0).
                a2 = a*a*0.5 # only need at worst prec bits
                b2 = mp.ldexp(mp.fmul(b, b, prec=2*wp), -1)
                apb2 = a + mp.fsub(b2, a2, prec=3*wp)
            result = mpc(apb2, b)
        else: # cancellation not possible
            result = mpc(a + mp.ldexp(b*b, -1), b)
    return result

Sorry about this ☹. The test for whether the guard bits were lost to cancellation let through far more cases than I intended. It's a reduction in mag of at least LOG1P_EXTRAPREC that's relevant, not the coded (and smaller) mp.mag(LOG1P_EXTRAPREC). After repairing that, about 1 in 2 million randomized inputs trigger it. I should probably write a different "bad case" generator that produces a much higher proportion of these cancelling cases ... OK, done, and so far no surprises.
LOG1P_EXTRAPREC = 10
def test(c):
    c = mpc(c)
    cmag = mp.mag(c)
    a, b = c.real, c.imag
    with extraprec(LOG1P_EXTRAPREC):
        wp = mp.prec
        if cmag >= -wp:
            if cmag < 4:
                if mp.mag(a) > mp.mag(b):
                    # `a` already contributes the most to c's norm.
                    # After adding 1, it will utterly dominate it.
                    # We only need enough extra precision to avoid
                    # losing any of a's significant bits when addiog,
                    wp *= 2
                else:
                    # `b` contributes the most to c's norm now. After
                    # adding 1 to `a`, in (a+1)**2 + b**2 =
                    # 1 + 2*a + a**2 + b**2, `b` may be so much larger
                    # than `a` now that b**2 is close to }2*a|, or
                    # even cancel 2*a out (in which case the high-
                    # order bits of the teensy a**2 become important).
                    wp *= 3
            arg = mp.fadd(1.0, c, prec=wp)
            result = mp.log(arg)
        # Else c is "very small", and we use a series expansion.
        # c - c**2/2
        # The real part of that is a+(b*b-a*a)/2, and the
        # imag part b-a*b.
        # Given that cmag < -prec, it can bu shown that the "a*b"
        # is numerically insignifcant in the imag part, and
        # _usually_ the "a*a/2" in the real part. What remains
        # is cheap to compute.
        # In the real part, though, if `a` is negative, the
        # remaining a+b**2/2 can suffer massive cancellation -
        # even total.
        elif a < 0.0: # cancellation is a potential problam
            apb2 = a + b*b*0.5
            if mp.mag(apb2) <= mp.mag(a) - LOG1P_EXTRAPREC:
                # The guard bits were lost to cancellation. Rare.
                # At the contrived
                # -1.999999873062092e-40+1.999999936531045e-20j
                # _all_ bits cancel out.
                # Since a ~= -b*b/2 in this case, and |b| is at
                # largest (worst case) about 2**-prec, |a| is
                # about 2**(-2*prec), and the true result may
                # be as small as a**2/2, which is about
                # 2**(-4*prec), of which we want the leading
                # prec bits. To get the leading prec bits
                # starting at 2**(-4**prec) from addends
                # starting at 2**-(2*prec), we need the
                # addition to handle 3*prec bits (the first
                # 2*prec of which may cancel to exactly 0).
                a2 = a*a*0.5 # only need at worst prec bits
                b2 = mp.ldexp(mp.fmul(b, b, prec=2*wp), -1)
                apb2 = a + mp.fsub(b2, a2, prec=3*wp)
            result = mpc(apb2, b)
        else: # cancellation not possible
            result = mpc(a + b*b*0.5, b)
    return result

And another "final" version, fleshing out comments and rearranging the code more for clarity.
I apologize for all the noise. I had no idea at the start how many subtle cancellation surprises were hiding in the details. If I had even an inkling of that, I would have set up a repository instead. I figured it would take a few hours at most - hahahaha 😝
If I ever need to work on a complex function again, at least there are days of "learning curve" I won't have to climb again (e.g., that rapid convergence of the norm says very little about the convergence rates of the derived real and imag series; and that component cancellation can be massive in a complex series even though the same series never gets anywhere near cancellation when applied to reals).
LOG1P_EXTRAPREC = 10
def test(c):
    # Note that all cases could by handled by log(1+c) provided the
    # add is done exactly. Our aim here is be much faster than that,
    # especially when |c| is small.
    c = mpc(c)
    cmag = mp.mag(c)
    a, b = c.real, c.imag
    with extraprec(LOG1P_EXTRAPREC):
        wp = mp.prec
        if cmag >= -wp:
            # |c| isn't very small. We call log(1+c) instead, but
            # are careful about the precision used by the add. The
            # real part of the result is log(|c+1|). That's
            # determined by 1 + 2*a + a**2 + b**2, and the add has
            # to preserve enough info so that no important bits of
            # that sum are lost. It doesn't matter to this that 2*a,
            # a**2, etc, are not computed explicitly here: we're
            # deducing how many bits have to be present in the sum
            # for log() to "reverse engineer" the value of 2*a +
            # a**2 + b**2 to `prec` good bits,
            if cmag < 4:
                # |c| isn't very small, or large.
                if mp.mag(a) > mp.mag(b):
                    # `a` already contributes the most to c's norm.
                    # After adding 1, it will utterly dominate it.
                    # We only need enough extra precision to avoid
                    # losing any of a's `prec` most significant bits
                    # when addiog, `b**2` is too small to matter.
                    wp *= 2
                else:
                    # b**2 is the larger of the square terms. The
                    # smallest b can be is about 2**-prec, so the
                    # smallest b**2 can be is about 2**(-2*prec). So
                    # for a bit to matter compared to b**2, it has
                    # to be at least about 2**(-3*prec). Bits of 2*a
                    # (if any) >= 2**(-3*prec) will be preserved if
                    # we use 3*prec bits for the add.
                    wp *= 3
            # Else (cmag >= 4), |c+1| >= |c| - 1 is so large that
            # working precision is fine (although that takes some
            # careful analysis for cmag=4, given that .mag() _may_
            # return a rexult too large by 2), So leave wp alone.
            arg = mp.fadd(1.0, c, prec=wp)
            result = mp.log(arg)
        else:
            # Else c is "very small", and we use a series expansion,
            # c - c**2/2. The real part of that is a+(b*b-a*a)/2,
            # and the imag part b-a*b. Given that cmag < -prec, it
            # can be shown that "a*b" is numerically insignifcant in
            # the imag part, and _usually_ the "a*a/2" in the real
            # part. What remains is cheap to compute. In the real
            # part, though, if `a` is negative, the remaining
            # a+b**2/2 can suffer massive cancellation - even total.
            real = a + b*b*0.5 # usually the real part of the result
            if (a < 0.0
                and mp.mag(real) <= mp.mag(a) - LOG1P_EXTRAPREC):
                # The guard bits were lost to cancellation. Rare. At
                # the contrived
                # -1.999999873062092e-40+1.999999936531045e-20j
                # _all_ bits cancel out. Since a ~= -b*b/2 in this
                # case, and |b| is at largest (worst case) about
                # 2**-prec, |a| is about 2**(-2*prec), and the true
                # result may be as small as a**2/2, which is about
                # 2**(-4*prec), of which we want the leading prec
                # bits. To get the leading prec bits starting at
                # 2**(-4**prec) from addends starting at
                # 2**-(2*prec), we need the subtraction to handle
                # 3*prec bits (the first 2*prec of which may cancel
                # to exactly 0).
                a2 = a*a # only need at worst prec bits
                b2 = mp.fmul(b, b, prec=2*wp)
                diff = mp.fsub(b2, a2, prec=3*wp)
                real = a + mp.ldexp(diff, -1)
            result = mpc(real, b)
    return result

Offline, it was suggested that the newer code to limit the precision used (instead of using exact=True) when adding 1 was gross overkill, because mag(c) >= -prec in this branch already ruled out very small arguments.
But, alas, that reasoning only works for the real-valued function. For complex c, mag(c) only gives an upper bound on the larger of its component's mags. It reveals almost nothing about the mag of the component with the smaller mag.
For example, in c = mpc(1e-300, 1e-10), the tiny real part has almost no effect on the complex mag. mag(c) is -32, but mag(c.real) is -996. Adding 1 to 1e-300 exactly yields a mantissa with about a thousand bits.
Given that cmag >= -prec, the newer code deduces that no more than the leading 3*prec bits of 1 + c.real can make a real difference to the outcome, so that's all the precision the newer code asks of the sum.
While I'm doing my testing on native machine doubles, of course mpmath supports values far, far smaller than 1e-300. A thousand needless bits may be bearable, but a billion perhaps not so much :wink:.

https://github.com/mpmath/mpmath/pull/803

Well, these days...  Probably I shouldn't just merge pr, but to say thank you for your work.
Sadly that it's python community fate now and I doubt it's possible to fix.

@skirpichev, was your last comment addressed to me? I saw the merge go by when it was made, and that was thanks enough. Thank you for enduring all the revisions while I sorted out the many unexpected traps, and for doing the actual work of folding my standalone function into mpmath's source code 😄


cmd/go: malformed import path error for paths with plus sign in go1.16

Go 1.15 used to compile package names with + signs in them, however, in Go 1.16 I get an error (tested on both macOS and Linux).
What version of Go are you using?

$ go version # darwin
go version go1.16.2 darwin/amd64

$ go version # linux
go version go1.16.2 linux/amd64

Does this issue reproduce with the latest release?
Yes
What did you do?

What did you expect to see?
No errors
What did you see instead?
malformed import path "github.com/siadat/a+b/x": invalid char '+'
malformed import path "github.com/siadat/a+b/y": invalid char '+'

Duplicate of #44776
Backport issue for 1.16 is #44885
You can try with tip to see if the patch works.

Closing as duplicate, per @AlexRouSg's comment. This should be fixed in 1.16.3.
+ is not allowed in module paths, but the main module and replaced modules may break some of the rules, which I think is the case here.


Train model

Hi
I've installed deepgoplus. since Readme and Document are not clear, I couldn't understand what exactly to write in the "--in-file" input :
" deepgoplus --data-root C:~\data-2016 --in-file ??? " Command. I tested all files inside the folder but got an error: 'Pandas' object has no attribute 'exp_annotations'
My goal is to train the 2016 dataset and test it after. If there are additional tips for the next steps, Please explain me.
Thank you for your help

Hi, --in-file parameter is for a fasta file with protein sequences, and the data folder with trained models should be provided in --data-root argument (or default is 'data'). if you want to train the models, please use the deepgoplus.py script

Thank you
In the training process, after going through several steps, I got the below error:
AttributeError: 'Pandas' object has no attribute 'prop_annotations'

As it's necessary, I put the 2016 dataset on the root and changed its name to data.
I tried it on both Windows (Anaconda) and Ubuntu 18.04
Is there anything that I missed?

Hi,
The problem is that in the data-2016 dataset we used column name 'annotations' which was updated to 'prop_annotations' in later version. Please change the row.prop_annotations to row.annotations in line 268 and everywhere else in the script

Hi
I've another question:
When I'm using "deepgoplus -dr /data -if test.fasta -of x.txt" for predicting protein function, I get this error :
FileNotFoundError: [Errno 2] No such file or directory: './data/diamond.res'
There is not any file named diamond.res
should I create it myself ?


leaflet.draw.js:9 Uncaught TypeError: Cannot set property 'original' of undefined

hi,
when I am executing code locally (see https://jsfiddle.net/udos/1035sp5x/) everything loads normally.
when I click the leaflet.draw "edit layers" icon, I get error

leaflet.draw.js:9 Uncaught TypeError: Cannot set property 'original' of undefined

after this also "Save" or "cancel" does not work anymore.
maybe it is due to geometry type MultiLineString.
note: on jsfiddle the error is

leaflet.draw.js:10 Uncaught TypeError: Cannot set property 'previousOptions' of undefined

I'm not sure if it is a bug or I am using it the wrong way...
thanks, udo

i think leaflet-draw does not support MultiLine.
Do you really need to have a multiLine or can you try to make an array of polyLine instead (or something else) ?

I prefer to use GeoJson objects. as a workaround I will try to do something you suggested. maybe a FeatureCollection of LineString...

instead of using a GeoJSON Feature Object in which the geometry is of type MultiLineString as in

https://jsfiddle.net/udos/1035sp5x/

a GeoJSON Feature Collection Object in which the single geometries are of type LineString as in

https://jsfiddle.net/udos/1035sp5x/1/

will not cause the error I reported

The docs should be clearer on not supporting multigeometries.  Although it's not hard to cast from multigeometry to feature collection, there's no reason that a user should assume that the control doesn't support specific features covered by the spec.

submitted a PR: https://github.com/Leaflet/Leaflet.draw/pull/569

@jameslaneconkling can you please explain me how exactly its possible to cast multigeometry to feature collection? Is something like this possible to do using jquery and leaflet?

@dkarakostis because geoJSON is just an object literal, you shouldn't need a library to do the conversion.  See the geoJSON docs.  Something like this should work:
function multiGeometry2FeatureCollection(multigeometry) {
  return {
    type: 'FeatureCollection'
    features: multigeometry.coordinates.map(function(coordinates) {
      return {type: multigeometry.type.replace('Multi', ''), coordinates: coordinates};
    })
  };
}

Thanks.
Double checking my response from the ajax request, I see that the returned
geoJSON is already a FeatureColletction:
Object {type: "FeatureCollection", totalFeatures: 1, features: Array[1],
crs: Object}
On Thu, Jul 28, 2016 at 1:29 AM, James Conkling<EMAIL_ADDRESS>wrote:

@dkarakostis https://github.com/dkarakostis because geoJSON is just an
object literal, you shouldn't need a library to do the conversion. See the geoJSON
docs http://geojson.org/geojson-spec.html. Something like this should
work:
function multiGeometry2FeatureCollection(multigeometry) {
return {
type: 'FeatureCollection'
features: multigeometry.coordinates.map(function(coordinates) {
return {type: multigeometry.type.replace('Multi', ''), coordinates: coordinates};
})
};
}
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Leaflet/Leaflet.draw/issues/560#issuecomment-235752593,
or mute the thread
https://github.com/notifications/unsubscribe-auth/APfg_2tb9nuVhDyTLACLUO_7W7eUNMXtks5qZ-nxgaJpZM4JDKg9
.


Add Cluster Labels in Embedding and Story Telling

It becomes really difficult to keep track of the different cluster, especially if there are no label to keep them apart.
It would improve the usability immensely to just show the labels on hover.

I also added this to meeting notes


Netty totalBytesRead bug

In the process of sending and receiving data using Netty's dowhile loop, since there is only one Buffer allocator, the main Reactor can only process one accept connection and then exit the while loop (totalBytesRead), which greatly reduces the efficiency of concurrent connection reading.

Sorry but I don't understand what this is all about...


Dreamcast Games Not Found

Following the Wiki and Emulator Information,  after adding Dreamcast bios and roms to all possible directories, multiple game formats, Dreamcast games were not found by emulation station on a fresh installation of ArkOS 03302023 which I almost immediately updated over WiFi to 04272023 . After searching emulationstation posts for a similar issue, I noticed /home/ark/.emulationstation/es_systems.cfg is not found, which should be loaded first before then checking for the file in /etc/emulationstation/es_systems.cfg.  The latter exists, and when opened contains the appropriate XML for the Dreamcast, but with one issue.
        <system>
                <name>dreamcast</name>
                <fullname>Sega - Dreamcast</fullname>
                <path>/roms2/dreamcast/</path>
                <extension>.7z .7Z .gdi .GDI .cdi .CDI .cue .CUE .chd .CHD .m3u .M3U</extension>
                <command>sudo perfmax %EMULATOR% %CORE%; nice -n -19 /usr/local/bin/dreamcast.sh %EMULATOR% %CORE% %ROM%; sudo perfnorm</command>
                   <emulators>
                      <emulator name="retroarch32">
                        <cores>
                          <core>flycast32_rumble</core>
                          <core>flycast_xtreme</core>
                          <core>reicast_xtreme</core>
                        </cores>
                      </emulator>
                      <emulator name="retroarch">
                        <cores>
                          <core>flycast_rumble</core>
                          <core>flycast</core>
                        </cores>
                      </emulator>
                   </emulators>
                <platform>dreamcast</platform>
                <theme>dreamcast</theme>
        </system>

The problem is <path>/roms2/dreamcast/</path>. /roms2 is not mounted, but exists in /. /roms is mounted.
/dev/mmcblk1p5 /roms exfat rw,noatime,uid=1002,gid=1002,fmask=0000,dmask=0000,allow_utime=0022,iocharset=utf8,errors=remount-ro 0 0
Once the correct path is changed to <path>/roms/dreamcast/</path>, ArkOS sees the roms. This is also the case for some other systems like Amiga.
Is it possible that some sed commands were not run in update?
Able to provide further logs on request if needed.
ArkOS version: 2.0 (04272023)
Device: RG353M
HostOS: Windows 10

Interesting.  Did you have a 2 sd card setup at one point in time?  That command you point to in the update script should not have ran unless roms2 was found in the /etc/fstab file which is only put in there after doing a switch to sd2 setup.

This was done with a clean 256GB microsd, writing the 03302023 .img directly with rufus and installing from the INT slot on the RG353M. I then updated via WiFi to 04272023. There is no second card inserted and there never has been.

Addressed with today's update.


[lncli] rpc error: code = Unknown desc = channel not found

I am attempting to force close a channel.
I issue: lncli listchannels
I choose the channel_point I want to close
It is inactive
I attempt lncli closechannel --force {channel_point}
I recieve: [lncli] rpc error: code = Unknown desc = channel not found
Any ideas what is going on?

The correct syntax for close channel is:
lncli closechannel --force funding_txid [output_index]
So if my channel_point is f9895a116215e8e550426e3c58c0053408d967eef751b674ad9fca5115b49442:1 I would call
lncli closechannel --force f9895a116215e8e550426e3c58c0053408d967eef751b674ad9fca5115b49442 1
Could you try that again?

That was it, thank you very much!
I had left the colon in the expression :1
On this field the default value is 0 correct?
For my edification where could I find the documentation for the difference between a 0 and 1?
Once again thank you for the timely assistance.

Hm, I don't know of any documentation other than https://api.lightning.community/#closechannel
The output_index is the position of the output in the funding transaction of a channel. E.g. in this channel [this channel opening] the funding transaction of the channel is at output_index 1.


Remove outdated "not implemented yet" comments in ParticlesMaterial

These features have been working as of 4.0.alpha8, which is demonstrated by the 3D particles demo I've been working on.

Thanks!


Cloud october rn

Just one customer facing bug fix this month. Database button note is being added in a separate PR.

This change is


staticfiles vs static

In most of the wagtail templates,  {% load static %} used, which is fine till now.
But now I switch using S3 storages with custom URLs, with this it is producing full URLs
If I use {% load staticfiles %} then I can get full custom S3 URLs.
Any suggestions to work it out ?
Thanks

Are you using django-s3-storage?
If so, check out the AWS_S3_PUBLIC_URL_STATIC and AWS_S3_BUCKET_NAME_STATIC settings.
Off-topic: put CloudFront in front of the S3 buckets ;)

We switched from {% load static %} to {% load staticfiles %} in Wagtail 1.4 - see https://github.com/torchbox/wagtail/pull/2192.

I am using django-storages and also using CloudFront
Thanks for the update


Automated Package Upgrade

:tada: This PR is included in version 7.7.123 :tada:
The release is available on:

npm package (@latest dist-tag)
GitHub release

Your semantic-release bot :package::rocket:


[TECH-65] page under construction

Currently missing a button or smt else to return to normal website navigation
For testing purposes, go to any main page in the code, and change the show variable to true
Should consider making this a use effect (?)

Also for some reason the overlay component doesn't work properly on all pages 😵


add config option to disable duplicate safe prevention

No. this only encourages bad behavior which will come back to bite us in the future. please discard ASAP


Question of closed form factorization, diff augmentation

I observed several techniques not mentioned in the Stylegan2 paper. (e.g closed form factorization, diff augmentation)
Are these techniques used in the actual original?

No. closed form factorization came from https://arxiv.org/abs/2007.06600, and diff augment (I think what you referring is actually non leaking augmentation/ada) is came from https://arxiv.org/abs/2006.06676.


feat(UI): adds Header component

Motivation
Add header to landing page screen. AEROGEAR-8375
What
Header for landing page ui screen
Why
header
How
Create header component with PF4 & add to Landing Page
Verification Steps

Checklist:

[ ] Code has been tested locally by PR requester
[ ] Changes have been successfully verified by another team member

Progress

[ ] Finished task
[ ] TODO

Additional Notes

@laurafitzgerald just pinging you on this to get your eyes as I try construct a header with PF4 components

Coverage remained the same at 92.744% when pulling 545b97ea24b1d643113627e62c7d5dba44ae0bd1 on damienomurchu:AG-8375-UI-HEADER into 07207e30cbcaa412660a9bac2e0639f82d97c0cf on aerogear:master.

thanks for the feedback @laurafitzgerald - if you get a chance could you take a look at the rendered header & see if it's what we were thinking of?

thanks for the feedback @laurafitzgerald - if you get a chance could you take a look at the rendered header & see if it's what we were thinking of?

@damienomurchu yes I would say it's on the right track. It's starting to look similar to the one in MDC. The black background looks well.

@laurafitzgerald thanks - just looking at the flat, think just an avatar/ user icon next to the dropdown is required, and will add this.
Will add some tests & ping you for re-review once I do so and remove the draft label

Removing this from draft status & reviews welcome

@damienomurchu 👀

@laurafitzgerald thanks for your eyes. As discussed need to wire this up to the redux store now, and will ping you for review then.

@damienomurchu thanks for pushing the changes. There's one console.log in the reducer which can be removed. Also I'm not sure about the MOBILE SECURITY SERVICE being rendered in link format. i.e. in blue and underline. It's deviating from the MDC layout and the flats somewhat. Could be worth a chat with Chris about it if it's the only way to do it in PF4.

@damienomurchu thanks for pushing the changes. There's one console.log in the reducer which can be removed. Also I'm not sure about the MOBILE SECURITY SERVICE being rendered in link format. i.e. in blue and underline. It's deviating from the MDC layout and the flats somewhat. Could be worth a chat with Chris about it if it's the only way to do it in PF4.

Console.log removed now :)
Re MOBILE SECURITY SERVICE being rendered in link format, my line of thought is that this would enable the user to always navigate back to the root page of the UI and supplement our app navigation. Wdyt? Perhaps we can discuss this & the navigation in general in the UI?

@damienomurchu thanks for pushing the changes. There's one console.log in the reducer which can be removed. Also I'm not sure about the MOBILE SECURITY SERVICE being rendered in link format. i.e. in blue and underline. It's deviating from the MDC layout and the flats somewhat. Could be worth a chat with Chris about it if it's the only way to do it in PF4.

Console.log removed now :)
Re MOBILE SECURITY SERVICE being rendered in link format, my line of thought is that this would enable the user to always navigate back to the root page of the UI and supplement our app navigation. Wdyt? Perhaps we can discuss this & the navigation in general in the UI?

Yes, I think it should be a home link. but I don't think it should behave like a normal link. i.e. it shouldn't be Blue and underlined. I think it should appear in the same way it does in the MDC and in the flats.

@laurafitzgerald agree, that can be fixed :+1:

@laurafitzgerald good point - will add to Detailed Apps UI page. Re redux testing, I was thinking about that too, and will do it in another PR I think - will also ping you probably re redux testing as its not something I've done any of yet :)

@damienomurchu cool. There's some example of how the tests can be done in my previous pr

@laurafitzgerald added redux test now for dropdown state if you could re-review (& approve if happy). Don't see need to add redux test for currentUsername until we implement it properly by the way, so have not added any redux test for that

Accidentally broke tests before, but fixed now

@laurafitzgerald thanks - responded inline. If you're happy let me know & I'll merge this in

@damienomurchu LGTM


Drupal Private files missing configuration

When Drupal is configured to use private files, those files should be served by Drupal and not NGINX directly.
# Handle private files through Drupal. Private file's path can come     # with a language prefix.     location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7         try_files $uri /index.php?$query_string;     }

https://www.nginx.com/resources/wiki/start/topics/recipes/drupal/

Thanks for the issue!
I've fixed it. 😉


Added padma kanya multiple campus domain

https://pkmc.tu.edu.np/ -> This college has IT related courses like BCA, BIM

@alka769401 First stage of verification passed, official domain for Padma Kanya Multiple Campus, Bagbazaar is pkmc.tu.edu.np as far as I verified. This is just an informational note. Pull request is still in the review. I appreciate your patience while waiting for the final decision.

@alka769401 Thank you for your patience while waiting for a review and processing your request. Pull request merged.


cmd/dist: "unexpected stale targets" on linux-ppc64le-power9osu builder

The linux-ppc64le-power9osu builder is frequently failing in cmd/dist.
It's not obvious to me exactly which cmd/dist invocation is failing, though. More logging would probably be helpful.
Curiously, the other *-ppc* builders do not seem to be affected.
2019-11-21T14:55:12-8bbfc51/linux-ppc64le-power9osu
2019-11-19T18:43:52-2ac9f1d/linux-ppc64le-power9osu
2019-10-30T20:46:44-81a74b4/linux-ppc64le-power9osu
2019-10-22T17:34:15-22278ca/linux-ppc64le-power9osu
2019-10-22T10:09:39-16f0f9c/linux-ppc64le-power9osu
2019-10-17T18:21:01-3972f97/linux-ppc64le-power9osu
2019-10-17T17:11:19-4569f1b/linux-ppc64le-power9osu
2019-10-16T23:10:06-4cabf69/linux-ppc64le-power9osu
This issue is forked from https://github.com/golang/go/issues/33598#issuecomment-544964519.
CC @laboger @bradfitz @cagedmantis @dmitshur @toothrot

I will try to reproduce the problem. I also agree it is curious that it only happens on the power9 builder, which has happened with other intermittent failures too.
I don't know if this is possible but if somehow the value for objabi.GOPPC64 is not set to power9 before all compiles start, and some are compiled for power8 but rebuilt later for power9 then the hashes wouldn't match.

The errlogs look strange, almost like the build hasn't even started yet. It doesn't display the line that says it is building go_bootstrap or even the one that provides the detail about the test run.

I found that the old power9 builder was still active out in the farm (not running Docker). Not sure but maybe these errors were happening if that buildlet was being used, since the error happened intermittently. Carlos has shut down that buildlet and we will see if that makes a difference.

I think that was the problem. Now that the old buildlet isn't running, I am not seeing the error. When it did happen, it was failing every 5 or 6 runs which would make sense since that was the 6th one in the pool.

Great, thank you!
I'll close this out; we can always reopen it if the problem isn't actually gone.


Feature request: add website for aws_s3_bucket

It looks like this doesn't yet support the website object for S3 bucket. (https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#website)

+1 terraforming s3 doesn't capture static website settings yet.

👍

This is a very old ticket and still no fix :-(


add kcp-dev/kcp-operator to Prow

This PR fixes a stray whitespace in the README for the build image.
Oh, and it also adds the new kcp-operator repo to Prow.

/approve


Add hires maps

Make maps work for results that have a spatial resolution of ~1km. eg:

Example file with hires results: radar/Projects/QA4SM_HR/07_data/hires_results/validation_2015-01-01_TO_2020-12-31.nc
Note:

use ds.drop_dims('obs') so that the additional dimension is dropped

Not sure how this should behave if we have hires results for large areas, should we reduce the resolution then? Also not sure how compatible this can be with the comparison module, might get very slow (maybe we can make a temporary "preview-version" of the data that we can use for low-resolution plots?)
In general, hires results should cover small areas only, to reduce the amount of processing and to visualize the high resolution features on a normal screen.

See release 0.6.4 https://github.com/awst-austria/qa4sm-reader/releases/tag/v0.6.4


[Q] How is GITHUB_TOKEN supposed to be passed into an action

It's not clear if using the following would do the trick:
...
with:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Could you please document an example usage?

This is a good question. The way this works currently is via an environment variable, called GITHUB_TOKEN:
https://github.com/probot/actions-adapter/blob/d6cdf509eea15b7b4abd3e204b9c0212be7006dd/index.js#L11-L14
So you would pass in the GITHUB_TOKEN via jobs.<job_id>.steps.env, like so:
...
steps:
  - name: My probot action
    uses: octocat/my-probot-action@v1  # not real, just for demonstration purposes
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

I agree the documentation should be clearer here. I'll look into adding something :+1:

Documented here, thanks for reporting this :bow:

Thanks!
Some extra context: https://twitter.com/webKnjaZ/status/1226585066551291910

Thanks for the added context.
Supporting with is possible but would require a change to the codebase, and also would require developers of actions to declare the input in their action.yml. On the upside, that would create a nice contract between actions using this adapter, and the workflows that consume them.
I think it probably makes sense to support both, giving developers freedom to choose what makes sense for them.
But right now, only env is supported by this adapter.


Wrap Phoenix.LiveViewTest.render_async/1?

I am using the new assign_async functionality, and so when visiting a route the data is asynchronously loaded.
Phoenix.LiveViewTest provides render_async/1 to wait for these: https://hexdocs.pm/phoenix_live_view/1.0.0-rc.6/Phoenix.LiveViewTest.html#render_async/2
I tried using unwrap/2 like so but it did not work:
  conn
    |> visit("/path")
    |> unwrap(fn view ->
      Phoenix.LiveViewTest.render_async(view)
    end)

The error I received was ** (stop) exited in: GenServer.call(#PID<0.741.0>, {:phoenix, :ping}, :infinity)
Thanks, it’s a great library!

Actually correction, I have got unwrap to work. That’s great. Any consideration about adding a built-in wrapper, that would perhaps skip if the underlying is a dead view? Perhaps a option to pass like so visit(path, wait_for_async: true)?

Hey @RoyalIcing! Good to see you here.
Yeah, I've thought about adding something like it.
I  don't think I would make it an option to visit because we can also use render_async/1 with LiveView's start_async, and that can be used outside of the original mount/3.
So, I was thinking something like a wait_for_async/1 function that takes the session and basically does what you're doing in unwrap/2. But I haven't figured out a good strategy for exposing functions that are LiveView-only yet.
I also worry that the name might be a bit too generic and confuse people (since it only works with LiveView's assign_async and start_async, but I don't think it works with a one-off Task).
If you have ideas, I'd love to hear them!

@RoyalIcing I'm trying to get a gut check here, and I'd love to hear your thoughts.
I have an experimental branch where I explored the idea of a more generic "waiting" mechanism that would handle async assigns. So, we wouldn't have a wait_for_async or anything like that.
Instead, the assert_has helper would take a timeout option that would specify how long you want to wait for the results. By default, it would not wait at all -- just like it is today. But if you have a LiveView that renders async, you could pass a timeout and wait:
conn
|> visit("/path")
|> assert_has("#my-data", text: "async data", timeout: 1000)

What do you think?

I like it! I think the trade-off is that you'd add the timeout only on the first assert (if you had a chain) but I think in a way that makes sense.

@RoyalIcing PhoenixTest v0.5.0 now has an experimental timeout option on assertions that will wait for async operations. Check it out when you can! In the meantime, I'll mark this as closed, since I think that should take care of it.


Delete unnecessary spaces

When copying from the web page, there is an unnecessary space after the code.

@changeworld : Thanks for your contribution! The author, @Mikejo5000, has been notified to review your proposed change.

@changeworld @Mikejo5000 - We're in the process of pulling these commits to the private repo, so we can verify the content on staging. We'll post a new PR # shortly and then close this PR.

@changeworld @Mikejo5000 - The commits in this PR were moved to the private repo (PR #3870) for validation and staging. We'll close this PR now.


xSQLServerHelper: Connect-SQL: Throw on failed connection

Pull Request (PR) description
Fixed Connect-SQL by ensuring the Status property returns 'Online' prior to returning the SQL Server object.
This Pull Request (PR) fixes the following issues:
Fixes #333
Task list:

[X] Change details added to Unreleased section of CHANGELOG.md?
[ ] Added/updated documentation, comment-based help and descriptions in .schema.mof files where appropriate?
[ ] Examples appropriately updated?
[X] New/changed code adheres to Style Guidelines?
[X] Unit and (optional) Integration tests created/updated where possible?

This change is

Codecov Report

Merging #799 into dev will decrease coverage by <1%.
The diff coverage is 100%.

@@         Coverage Diff         @@
##            dev   #799   +/-   ##
===================================
- Coverage    97%    96%   -1%
===================================
  Files        30     30
  Lines      3255   3255
===================================
- Hits       3170   3138   -32
- Misses       85    117   +32

@randomnote1 Please rebase this one.

Rebase is complete.

Review status: 0 of 3 files reviewed at latest revision, all discussions resolved.

Comments from Reviewable

Reviewed 3 of 3 files at r1.
Review status: all files reviewed at latest revision, 1 unresolved discussion.

xSQLServerHelper.psm1, line 65 at r1 (raw file):

    }

    if ( $sql.Status -match '^Online$' )

Minor: Could you make this -notmatch and throw if true? Then we can have the return at the end outside the if.

Comments from Reviewable

Review status: all files reviewed at latest revision, 1 unresolved discussion.

xSQLServerHelper.psm1, line 65 at r1 (raw file):
Previously, johlju (Johan Ljunggren) wrote…
Minor: Could you make this -notmatch and throw if true? Then we can have the return at the end outside the if.

I originally coded it like that, however I feel like this ended up be more readable. Plus this way it "defaults" to the error and only returns when confirmed good.

Comments from Reviewable

Reviewed 1 of 1 files at r2.
Review status: all files reviewed at latest revision, all discussions resolved.

Comments from Reviewable


SLO search update

I am not sure we should be exposing the new search to customers at this stage.... Old search: >1k results:
Slack Message

Updated in SiteSearch360


check that fields match reflected class

We don't appear to check that fields match reflected class.

Is this done? Or are methods done but fields are not?


[5.3] Notification UUID set prior to queuing to ensure it is consistent.

When a notification implements ShouldQueue, different queued jobs are created for each channel, and as the UUID is set in the execution of each job it will be different for each channel (despite relating to the same notification).  Without ShouldQueue implemented, the UUID is set once and therefore the same throughout all channel executions.
This commit changes that to set the UUID on the notification once prior to queuing, and then accessing that id when executing the jobs on each channel.
This means we can always rely on $this->id returning the same value within the toMail, toDatabase, etc methods even when a notification is queued, keeping it consistent with how it works if not queued.
Example Use Case
I need to know that an email received, and a DatabaseNotification record were generated by the same Notification.  This way the email URL can link them back to the database version of the notification in my application:
->action(_('Login'), url('/notifications/' . $this->id))

I could then mark it as read before moving them on to the actual content I want them to see.  Without this, a user can click on a mail notification to link into my app, but there is no association with the DatabaseNotification I can use to mark it as read - so they’ll see the same notification in an unread state in the application despite having viewed the content.
I think this is potentially a broad use case, but even if not hopefully it is appealing to have $this->id return the same value in each channel method on the Notification regardless of whether it was queued or executed live. Thanks for the consideration! :)

Did we not already make this change once before @GrahamCampbell

Each notifiable would definitely need to be considered a different notification UUID. In your change different notifiables would receive the same notification UUID.

Yikes sorry - Yes, you are totally right of course.  An alternative solution would be dispatching a single SendQueuedNotifications job for all channels, instead of separate jobs per channel.  This would keep the UUID the same across channels but still unique across notifiables.  Maybe downsides to not running separate jobs per channel though...?

That is also not going to work because if one channel fails in the queued job all will be retried.

Just based on a quick look I think we would need to have similar ID logic in queueNotification and then in sendNow we would not overwrite the notification ID if it has already been set.

I've fixed this  with a commit to 5.3

Thanks @taylorotwell - This works perfect.


Failed to parse  "ALTER TABLE table1 RENAME table2;"

When I use "ALTER TABLE table1 RENAME table2;" to rename the table, it parse failed by JSqlParser.
ALTER TABLE yxyh_basic_info_tmp RENAME yxyh_basic_info;

net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: "RENAME" <S_IDENTIFIER>
    at line 2, column 19.

Was expecting one of:

    "ADD"
    "ALGORITHM"
    "ALTER"
    "CHANGE"
    "DROP"
    "MODIFY"

ALTER TABLE table_name RENAME TO new_table_name;

Greetings,
unfortunately you seem to use very old versions of JSQLParser.
Please retry with JSQLParser 4.5 or 4.6-Snapshot as I am able to parse it. You can try online.


How to implement the library for two seprate application

Hi Wanted to know if we can implement the project in two seprate application the aidl works around

Yes it will definitely work. The Docs are contain pretty awesome guidelines to get you started.

@paragsarkar Yes, as @codekeyz says. You can copy the original @RemoteInterface to another project or application, keep the original interface name and method name. That's it!


Remove duplicate emptyArrays

Where's the root one now? core.ts?

@DanielRosenwasser There's one in utilities still.

What's the rationale? It may be noticeably bad for performance, is the upside worth it?
Before the change:
(function (ts) {
    var emptyArray = [];

    // ... use emptyArray as local variable

})(ts || (ts = {}));
var ts;

After the change:
var ts;
(function (ts) {
    ts.emptyArray = [];
})(ts || (ts = {}));

(function (ts) {

    // use ts.emptyArray via 2 lookup from global namespace

})(ts || (ts = {}));

Each emptyArray use now adds 2 lookups starting from global scope lookup. Ouch!
emptyArray exists to improve performance over clean and simple []. This change may have reverted much of that gain.

This looks similar to another well-meaning change in the name of technicality, that may end up harmful when considered in bigger context: ts.version should remain easily parseable and complexity that simplification caused.

We alreay have such lookups for every other function in the codebase; we wouldn't copy the other contents from utilities.ts to the files where they're used just to avoid ts..

I'm sorry, can you please elaborate: what is the rationale here?
Even if you believe the downsides here are small, that's not a convincing reason to make an apparently arbitrary change.

The same thing appeared in multiple places. That always leads to problems. For example, the type of emptyArray should really be ReadonlyArray<never>, but a few of the declarations had it as any[], which is definitely not safe. Life is easier if we can fix these things in only one place.


Add ordering capabilities on task lists endpoints.

Summary
Add sorting capabilities by desc/asc on task uid attribute to the tasks endpoints to facilitate the management of an instance and its indexes, or a specific index.
Summary Key Points

Add ordering capabilities on task uid for GET tasks endpoints.

Motivation
Following the specification aiming to stabilize the task API resource, we want to give users the capability to browse lists of tasks according to the direction of their choice.

Closed for the moment, will be rewritten from this base.