I did some tinkering with the Fourier series for the clipped sine wave, and on obtaining an exact result for what level to clip the triangle wave to obtain the lowest 3rd harmonic distortion. The equation for the coefficients of the Fourier series of a clipped triangle wave, with the incoming triangle wave normalized to 1 volt peak looks like this:
With constants A, B, and C yet to be determined. I didn't do this by hand...:), but I arrived at an expression for the magnitude of the nth harmonic using Wolfram Alpha, which looks something like this:
abs((2/(pi*n**2)*(n*(-2*A + C*pi)*cos(A*n) - n*(2*B + (-2 + C)*pi)*cos(B*n) + (2*sin(A*n) + sin(B*n) - sin(pi*n))))).
Run this through Scipy's nonlinear optimization pack with the script below, adding some constraints and I get, to minimize the 3rd harmonic:
status: 0 success: True njev: 11 nfev: 62 fun: 8.9688116621629401e-07 x: array([ 1.04719614, 2.09439651, 0.66666577]) message: 'Optimization terminated successfully.'
So, clip the triangle wave at 0.667 times full scale.
As a sanity check, to get minimum second harmonic:
status: 0 success: True njev: 4 nfev: 20 fun: 1.9286893333824348e-11 x: array([ 1.57079633, 1.57079633, 1. ]) message: 'Optimization terminated successfully.'
i.e. don't clip it, a triangle wave has no even harmonics.
import scipy import scipy.optimize as optimize from math import sin, cos, pi, fabs
n = 3
def f(x): return fabs(2/(pi*n**2)*(n*(-2*x[0] + x[2]*pi)*cos(x[0]*n) - n*(2*x[1] + (-2 + x[2])*pi)*cos(x[1]*n) + (2*sin(x[0]*n) + sin(x[1]*n) - sin(pi*n))))
cons = ({'type': 'eq', 'fun': lambda x: 2*x[0]/pi - x[2]}, {'type': 'eq', 'fun': lambda x: -2*x[1]/pi + 2 - x[2]}) ##set left and right intercepts
bnds = scipy.array([[0, pi/2], [pi/2, pi], [0, 1]], dtype=float) ##C must be positive and less than 1
result = optimize.minimize(f, [0, pi, 0.5], method='SLSQP', bounds=bnds, constraints=cons) print(result)