# -*- coding: utf-8 -*- """ Monotonic increasing sinusoidal curve with same entrance/exit angle """ import numpy as np import pylab def discrt_tan(x0, y0, x1, y1): """ discrete tangent function for tuple of points """ return np.divide(np.subtract(y1,y0), np.subtract(x1,x0)) def curve(xs, size, mid_angle=0.0): """ monotonic raising curve function with sinusoidal accent. Function for a monotonic raising curve with same entrance/exit angle. Sinusoidal setup for *monotonic increasing* constraints angles in following fashion; - mid_angle must be between [0..pi/4) - approaching pi/4 yields straight line. - generated entrance/exit angles are between [45..~63.43] degrees The curve is expresses as; y = x + [sin(2PI * x / size)] / accent Args: xs (array like): input value(s) size: size range of the input/output values mid_angle: middle angle of the sinusoidal in radians Returns: array-like curve value. Raises: `ValueError` for params. that out of range; mid_angle - must be between [0..pi/4) xs - must be in range between [0..size] Todo: - `accent` does not well-define middle angle parameter. - if curve will not be limited in size range, derivation will be step-wise continuous. More calculation can be made for continuous derivation. (tangents at 0 and size) """ if mid_angle<0 or mid_angle>=np.pi/4: raise ValueError("mid-angle must be between [0..pi/4)") if np.any(xs < 0) or np.any(xs > size): raise ValueError("xs must be between [0..size]") accent = 2*np.pi/size * np.tan(np.pi/4+mid_angle) # TODO: not well-defined mid-angle param. return xs + np.sin(2*np.pi*xs/size) / accent if __name__ == "__main__": """ Test and plot `curve` function for various values and mid_angle degrees """ eps = 1e-11 #eps = np.finfo(float).eps size = 1 print "### testing values..." print "x= {:.15f}, y={:.15f}".format(size, curve(size, size)) print "x= {:.15f}, y={:.15f}".format(np.subtract(size,eps), curve(np.subtract(size,eps), size)) print "x= {:.15f}, y={:.15f}".format(size/2.0, curve(size/2.0, size)) # angles at 0, mid, size mids = [0, np.pi/128, np.pi/64, np.pi/32, np.pi/16, np.pi/8, np.pi/6, np.pi/4-eps] max_err = 0.0 params = {'legend.fontsize': 'small', 'axes.labelsize': 'small', 'axes.titlesize':'small', 'xtick.labelsize':'small', 'ytick.labelsize':'small'} pylab.rcParams.update(params) colc = 2 rowc = (len(mids)+1)/colc f, axarr = pylab.subplots(rowc, colc) print "\n### testing angles..." for i,mid in enumerate(mids): print "mid (input) = {:.15f}".format(np.rad2deg(mid)) t = discrt_tan(size/2.0, curve(size/2.0, size, mid), np.add(size/2.0,eps), curve(np.add(size/2.0,eps), size, mid)) print "angle at mid = {:.15f}".format(np.rad2deg(np.arctan(t))) t = discrt_tan(0, curve(0, size, mid), eps, curve(eps, size, mid)) print "angle at 0 = {:.15f}".format(np.rad2deg(np.arctan(t))) t1 = discrt_tan(size, curve(size, size, mid), np.subtract(size,eps), curve(np.subtract(size,eps), size, mid)) print "angle at size = {:.15f}".format(np.rad2deg(np.arctan(t1))) # error err = np.abs(np.subtract(np.rad2deg(np.arctan(t1)), np.rad2deg(np.arctan(t)))) max_err = np.maximum(max_err, err) print "error = {:.15f}\n".format(err) # plot xs = np.linspace(0, size, 100) ys = curve(xs, size, mid) row = i/colc col = i - row*colc axarr[row, col].plot(xs, ys) axarr[row, col].set_title('mid_angle={:.4f} rad.'.format(mid)) axarr[row, col].text(0.95, 0.15, 'alpha={:.6f} deg.'.format(np.rad2deg(np.arctan(t))), verticalalignment='bottom', horizontalalignment='right', transform=axarr[row, col].transAxes, color='green', fontsize=10) axarr[row, col].text(0.95, 0.03, 'err={:.6f} deg.'.format(err), verticalalignment='bottom', horizontalalignment='right', transform=axarr[row, col].transAxes, color='red', fontsize=10) print"Max. Error = {:.15f}".format(max_err) pylab.show()