# Implementation of module {hacks}. # Last edited on 2021-02-18 22:23:00 by jstolfi import color import palette import rn import pyx from math import sqrt, floor, ceil, sin, cos, inf, nan, pi from PIL import Image import sys def real_quadratic_roots(A, B, C): assert A != 0 if A < 0: # Reverse signs to make {A} positive: A = -A; B = -B; C = -C # Rescale by replacing {t = M s} so that {A} is 1: M = 1/sqrt(A) A = 1; B = B*M # sys.stderr.write("A = %10.7f B = %10.7f C = %10.7f M = %12.7f \n" % (A, B, C, M)) # Now solve {s^2 + B*s + C = 0} Delta = B*B - 4*C if Delta <= 0: # Zero or one root return None, None elif Delta == inf: # Pretend the roots are infinite but symmetric: return -inf, +inf else: sD = sqrt(Delta) s0 = (-B - sD)/2; t0 = M*s0 s1 = (-B + sD)/2; t1 = M*s1 assert -inf < t0 and t0 < t1 and t1 < +inf return t0, t1 # GEOMETRY def is_point(p): if type(p) is not tuple: return False if len(p) != 2: return False for c in p: if (type(c) is not float) and (type(c) is not int): return False return True # PLOTTING def plot_line(c, clr, wd, p, q): if clr == None: clr = pyx.color.rgb.black peps = 0 if p != q else 1.0e-8*max(1,rn.norm(p)) sty = [ pyx.style.linejoin.round, pyx.style.linecap.round, pyx.style.linewidth(wd), clr ] c.stroke(pyx.path.line(p[0]-peps, p[1]-peps, q[0]+peps, q[1]+peps), sty) def plot_box(c, clr, B): if clr == None: clr = pyx.color.rgb.black xp = B[0][0]; xsz = B[1][0] - xp yp = B[0][1]; ysz = B[1][1] - yp sty = [ clr ] c.fill(pyx.path.rect(xp, yp, xsz, ysz), sty) def plot_frame(c, clr, wd, pt, szx, szy, mrg): if clr == None: clr = pyx.color.rgb.black if mrg == None: mrg = 0 if pt == None: pt = (0, 0) xmin = pt[0] + mrg; xmax = pt[0] + szx - mrg ymin = pt[1] + mrg; ymax = pt[1] + szy - mrg sty = [ pyx.style.linejoin.round, pyx.style.linecap.round, pyx.style.linewidth(wd), clr ] c.stroke(pyx.path.line( xmin, ymin, xmax, ymin), sty) c.stroke(pyx.path.line( xmax, ymin, xmax, ymax), sty) c.stroke(pyx.path.line( xmax, ymax, xmin, ymax), sty) c.stroke(pyx.path.line( xmin, ymax, xmin, ymin), sty) def plot_grid(c, clr, wd, pt, szx, szy, mrg, xstep, ystep): if clr == None: clr = pyx.color.grey(0.8) if mrg == None: mrg = 0 if pt == None: pt = (0, 0) if xstep == None: xstep = 1 if ystep == None: ystep = 1 xmin = pt[0] + mrg; xmax = pt[0] + szx - mrg ymin = pt[1] + mrg; ymax = pt[1] + szy - mrg # Grid line index ranges, with some extra: kxmin = int(floor(xmin/xstep)) - 1; kxmax = int(ceil(xmax/xstep)) + 1 kymin = int(floor(ymin/ystep)) - 1; kymax = int(ceil(ymax/ystep)) + 1 sty = [ pyx.style.linestyle.dashed, pyx.style.linejoin.round, pyx.style.linecap.round, pyx.style.linewidth(wd), clr ] eps = 1.0e-6*wd for dkx in range(kxmax-kxmin+1): x = (kxmin+dkx)*xstep if x >= xmin+eps and x < xmax-eps: c.stroke(pyx.path.line(x, ymin, x, ymax), sty) for dky in range(kymax-kymin+1): y = (kymin+dky)*ystep if y >= ymin+eps and y < ymax-eps: c.stroke(pyx.path.line(xmin, y, xmax, y), sty) def write_plot(c, name): sys.stderr.write("writing %s.eps, %s.png, %s.eps ...\n" % (name, name, name)) c.writeEPSfile(name + ".eps") convert_eps_to_png(name) convert_eps_to_jpg(name) sys.stderr.write("done.\n") def convert_eps_to_png(name): im = Image.open(name + '.eps') im.load(scale=2) fig = im.convert('RGBA') fig.save(name + '.png', lossless = True) im.close() def convert_eps_to_jpg(name): im = Image.open(name + '.eps') im.load(scale=2) fig = im.convert('RGB') fig.save(name + '.jpg', quality = 95) im.close() def adjust_dash_pattern(rdist, rlen, rgap): assert rlen > 0 if rgap <= 0: return False, None # No gaps -- might as wel use solid. if rdist <= rlen: return False, None # Not enough space for a single dash. # Compute the approx number {ngap} of gaps: xgap = (rdist - rlen)/(rgap + rlen) # Fractional number of gaps with ideal pattern. assert xgap >= 0 ngap = floor(xgap + 0.5) if ngap == 0: ngap = 1 # Check whether {ngap+1} or {ngap-1} may be better: nbest = None ebest = +inf for dn in 0, -1, +1: nk = ngap + dn if nk >= 0: rdk = nk*(rlen+rgap) + rlen edk = rdk/rdist if rdk >= rdist else rdist/rdk if edk < ebest: ebest = edk; nbest = nk assert nbest != None # Compute the adjustment factor {fac}: fac = rdist/(nbest*(rlen+rgap) + rlen) minfac = 0.75; maxfac = 1/minfac if fac < minfac or fac > maxfac: # Would have to shrink or squeeze too much. Only if {rdist} is small, so: return False, None # Okeey: dashpat = [ fac*rlen, fac*rgap ] return True, dashpat def colors_RGB(nc): Ymin = 0.540; Ymax = 0.560 Smin = 0.600; Smax = 1.000 colors = [None]*nc nh = nc//2 for k in range(nh): RGBk = palette.discrete(k, Ymin,Ymax,Smin,Smax) colors[k] = pyx.color.rgb(RGBk[0], RGBk[1], RGBk[2]) colors[nc-1-k] = pyx.color.rgb(1-RGBk[0], 1-RGBk[1], 1- RGBk[2]) if nc % 2 == 1: # Insert a gray color: colors[nh] = pyx.color.rgb(0.500,0.500,0.500) return colors