From d825d43108288d702f9eefcde0cb3041e24948a9 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Wed, 1 Oct 2025 00:27:52 +0200 Subject: [PATCH] scripts: Updated graph_shaper.py script The change removes the shapers defined there in favor of the standard ones and makes the script a lot more configurable via command-line arguments. Signed-off-by: Dmitry Butyugin --- scripts/graph_shaper.py | 193 ++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 116 deletions(-) diff --git a/scripts/graph_shaper.py b/scripts/graph_shaper.py index b9a6627c8..e97361693 100755 --- a/scripts/graph_shaper.py +++ b/scripts/graph_shaper.py @@ -5,20 +5,15 @@ # Copyright (C) 2020 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. -import optparse, math +import importlib, math, optparse, os, sys import matplotlib +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), + '..', 'klippy')) +shaper_defs = importlib.import_module('.shaper_defs', 'extras') + # A set of damping ratios to calculate shaper response for -DAMPING_RATIOS=[0.05, 0.1, 0.2] - -# Parameters of the input shaper -SHAPER_FREQ=50.0 -SHAPER_DAMPING_RATIO=0.1 - -# Simulate input shaping of step function for these true resonance frequency -# and damping ratio -STEP_SIMULATION_RESONANCE_FREQ=60. -STEP_SIMULATION_DAMPING_RATIO=0.15 +DEFAULT_DAMPING_RATIOS=[0.075, 0.1, 0.15] # If set, defines which range of frequencies to plot shaper frequency response PLOT_FREQ_RANGE = [] # If empty, will be automatically determined @@ -30,86 +25,8 @@ PLOT_FREQ_STEP = .01 # Input shapers ###################################################################### -def get_zv_shaper(): - df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2) - K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df) - t_d = 1. / (SHAPER_FREQ * df) - A = [1., K] - T = [0., .5*t_d] - return (A, T, "ZV") - -def get_zvd_shaper(): - df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2) - K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df) - t_d = 1. / (SHAPER_FREQ * df) - A = [1., 2.*K, K**2] - T = [0., .5*t_d, t_d] - return (A, T, "ZVD") - -def get_mzv_shaper(): - df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2) - K = math.exp(-.75 * SHAPER_DAMPING_RATIO * math.pi / df) - t_d = 1. / (SHAPER_FREQ * df) - - a1 = 1. - 1. / math.sqrt(2.) - a2 = (math.sqrt(2.) - 1.) * K - a3 = a1 * K * K - - A = [a1, a2, a3] - T = [0., .375*t_d, .75*t_d] - return (A, T, "MZV") - -def get_ei_shaper(): - v_tol = 0.05 # vibration tolerance - df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2) - K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df) - t_d = 1. / (SHAPER_FREQ * df) - - a1 = .25 * (1. + v_tol) - a2 = .5 * (1. - v_tol) * K - a3 = a1 * K * K - - A = [a1, a2, a3] - T = [0., .5*t_d, t_d] - return (A, T, "EI") - -def get_2hump_ei_shaper(): - v_tol = 0.05 # vibration tolerance - df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2) - K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df) - t_d = 1. / (SHAPER_FREQ * df) - - V2 = v_tol**2 - X = pow(V2 * (math.sqrt(1. - V2) + 1.), 1./3.) - a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X) - a2 = (.5 - a1) * K - a3 = a2 * K - a4 = a1 * K * K * K - - A = [a1, a2, a3, a4] - T = [0., .5*t_d, t_d, 1.5*t_d] - return (A, T, "2-hump EI") - -def get_3hump_ei_shaper(): - v_tol = 0.05 # vibration tolerance - df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2) - K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df) - t_d = 1. / (SHAPER_FREQ * df) - - K2 = K*K - a1 = 0.0625 * (1. + 3. * v_tol + 2. * math.sqrt(2. * (v_tol + 1.) * v_tol)) - a2 = 0.25 * (1. - v_tol) * K - a3 = (0.5 * (1. + v_tol) - 2. * a1) * K2 - a4 = a2 * K2 - a5 = a1 * K2 * K2 - - A = [a1, a2, a3, a4, a5] - T = [0., .5*t_d, t_d, 1.5*t_d, 2.*t_d] - return (A, T, "3-hump EI") - - def estimate_shaper(shaper, freq, damping_ratio): - A, T, _ = shaper + A, T = shaper n = len(T) inv_D = 1. / sum(A) omega = 2. * math.pi * freq @@ -123,14 +40,18 @@ def estimate_shaper(shaper, freq, damping_ratio): return math.sqrt(S*S + C*C) * inv_D def shift_pulses(shaper): - A, T, name = shaper + A, T = shaper n = len(T) ts = sum([A[i] * T[i] for i in range(n)]) / sum(A) for i in range(n): T[i] -= ts # Shaper selection -get_shaper = get_ei_shaper +def get_shaper(shaper_name, shaper_freq, damping_ratio): + for s in shaper_defs.INPUT_SHAPERS: + if shaper_name.lower() == s.name: + return s.init_func(shaper_freq, damping_ratio) + return shaper_defs.get_none_shaper() ###################################################################### @@ -148,44 +69,46 @@ def bisect(func, left, right): right = mid return .5 * (left + right) -def find_shaper_plot_range(shaper, vib_tol): +def find_shaper_plot_range(shaper, shaper_freq, test_damping_ratios, vib_tol): def eval_shaper(freq): - return estimate_shaper(shaper, freq, DAMPING_RATIOS[0]) - vib_tol + return estimate_shaper(shaper, freq, test_damping_ratios[0]) - vib_tol if not PLOT_FREQ_RANGE: - left = bisect(eval_shaper, 0., SHAPER_FREQ) - right = bisect(eval_shaper, SHAPER_FREQ, 2.4 * SHAPER_FREQ) + left = bisect(eval_shaper, 0., shaper_freq) + right = bisect(eval_shaper, shaper_freq, 2.4 * shaper_freq) else: left, right = PLOT_FREQ_RANGE return (left, right) -def gen_shaper_response(shaper): +def gen_shaper_response(shaper, shaper_freq, test_damping_ratios): # Calculate shaper vibration response on a range of frequencies response = [] freqs = [] - freq, freq_end = find_shaper_plot_range(shaper, vib_tol=0.25) + freq, freq_end = find_shaper_plot_range(shaper, shaper_freq, + test_damping_ratios, vib_tol=0.25) while freq <= freq_end: vals = [] - for damping_ratio in DAMPING_RATIOS: + for damping_ratio in test_damping_ratios: vals.append(estimate_shaper(shaper, freq, damping_ratio)) response.append(vals) freqs.append(freq) freq += PLOT_FREQ_STEP - legend = ['damping ratio = %.3f' % d_r for d_r in DAMPING_RATIOS] + legend = ['damping ratio = %.3f' % d_r for d_r in test_damping_ratios] return freqs, response, legend -def gen_shaped_step_function(shaper): +def gen_shaped_step_function(shaper, shaper_freq, + system_freq, system_damping_ratio): # Calculate shaping of a step function - A, T, _ = shaper + A, T = shaper inv_D = 1. / sum(A) n = len(T) - omega = 2. * math.pi * STEP_SIMULATION_RESONANCE_FREQ - damping = STEP_SIMULATION_DAMPING_RATIO * omega - omega_d = omega * math.sqrt(1. - STEP_SIMULATION_DAMPING_RATIO**2) - phase = math.acos(STEP_SIMULATION_DAMPING_RATIO) + omega = 2. * math.pi * system_freq + damping = system_damping_ratio * omega + omega_d = omega * math.sqrt(1. - system_damping_ratio**2) + phase = math.acos(system_damping_ratio) - t_start = T[0] - .5 / SHAPER_FREQ - t_end = T[-1] + 1.5 / STEP_SIMULATION_RESONANCE_FREQ + t_start = T[0] - .5 / shaper_freq + t_end = T[-1] + 1.5 / system_freq result = [] time = [] t = t_start @@ -214,20 +137,24 @@ def gen_shaped_step_function(shaper): result.append(val) time.append(t) - t += .01 / SHAPER_FREQ + t += .01 / shaper_freq legend = ['step', 'shaper commanded', 'system response'] return time, result, legend -def plot_shaper(shaper): +def plot_shaper(shaper_name, shaper_freq, damping_ratio, test_damping_ratios, + system_freq, system_damping_ratio): + shaper = get_shaper(shaper_name, shaper_freq, damping_ratio) shift_pulses(shaper) - freqs, response, response_legend = gen_shaper_response(shaper) - time, step_vals, step_legend = gen_shaped_step_function(shaper) + freqs, response, response_legend = gen_shaper_response( + shaper, shaper_freq, test_damping_ratios) + time, step_vals, step_legend = gen_shaped_step_function( + shaper, shaper_freq, system_freq, system_damping_ratio) fig, (ax1, ax2) = matplotlib.pyplot.subplots(nrows=2, figsize=(10,9)) ax1.set_title("Vibration response simulation for shaper '%s',\n" "shaper_freq=%.1f Hz, damping_ratio=%.3f" - % (shaper[-1], SHAPER_FREQ, SHAPER_DAMPING_RATIO)) + % (shaper_name, shaper_freq, damping_ratio)) ax1.plot(freqs, response) ax1.set_ylim(bottom=0.) fontP = matplotlib.font_manager.FontProperties() @@ -241,8 +168,7 @@ def plot_shaper(shaper): ax1.grid(which='minor', color='lightgrey') ax2.set_title("Unit step input, resonance frequency=%.1f Hz, " - "damping ratio=%.3f" % (STEP_SIMULATION_RESONANCE_FREQ, - STEP_SIMULATION_DAMPING_RATIO)) + "damping ratio=%.3f" % (system_freq, system_damping_ratio)) ax2.plot(time, step_vals) ax2.legend(step_legend, loc='best', prop=fontP) ax2.set_xlabel('Time, sec') @@ -264,13 +190,48 @@ def main(): opts = optparse.OptionParser(usage) opts.add_option("-o", "--output", type="string", dest="output", default=None, help="filename of output graph") + opts.add_option("--shaper", type="string", dest="shaper", default="mzv", + help="a shaper to plot") + opts.add_option("--shaper_freq", type="float", dest="shaper_freq", + default=50.0, help="shaper frequency") + opts.add_option("--damping_ratio", type="float", dest="damping_ratio", + default=shaper_defs.DEFAULT_DAMPING_RATIO, + help="shaper damping_ratio parameter") + opts.add_option("--test_damping_ratios", type="string", + dest="test_damping_ratios", + default=",".join(["%.3f" % dr + for dr in DEFAULT_DAMPING_RATIOS]), + help="a comma-separated list of damping ratios to test " + + "input shaper for") + opts.add_option("--system_freq", type="float", dest="system_freq", + default=60.0, + help="natural frequency of a system for step simulation") + opts.add_option("--system_damping_ratio", type="float", + dest="system_damping_ratio", default=0.15, + help="damping_ratio of a system for step simulation") options, args = opts.parse_args() if len(args) != 0: opts.error("Incorrect number of arguments") + if options.shaper.lower() not in [ + s.name for s in shaper_defs.INPUT_SHAPERS]: + opts.error("Invalid --shaper=%s specified" % options.shaper) + + if options.test_damping_ratios: + try: + test_damping_ratios = [float(s) for s in + options.test_damping_ratios.split(',')] + except ValueError: + opts.error("invalid floating point value in " + + "--test_damping_ratios param") + else: + test_damping_ratios = None + # Draw graph setup_matplotlib(options.output is not None) - fig = plot_shaper(get_shaper()) + fig = plot_shaper(options.shaper, options.shaper_freq, + options.damping_ratio, test_damping_ratios, + options.system_freq, options.system_damping_ratio) # Show graph if options.output is None: