# coding: utf-8 # ## Damper analysis # # This software was used for the numerical analyses given in "Stabilizing air dampers for hovering aerial robotics: Design, insect-scale flight tests, and scaling" by S. B. Fuller, Z. Ern Teoh, P. Chirarattananon, N. O. Perez-Arancibia, J. Greenberg, and R. J. Wood. # # Copyright 2016, Sawyer B. Fuller, University of Washington. # This software is released under the terms of the GNU Lesser General Public License, version 3. # In[2]: import matplotlib as mlt import matplotlib.pyplot as plt import numpy as np import pdb; BK = pdb.set_trace from __future__ import division # make sure integer division is promoted to a float get_ipython().magic('matplotlib inline') get_ipython().magic("config InlineBackend.figure_format='retina'") fs = (4.5, 4.5) #(3.5,3.5)#(8.7, 8.7) # fig size in inches # In[3]: # constants g = 9.81 r2d, d2r = 180/np.pi, np.pi/180. # mass scaling with area m20 = 16e-6 # 20mm damper mass am20 = 6.1e-6 # added fluid mass due to acceleration in fluid for a cube/square shape # from m_a = 0.64 * rho * l**3 (Blevins2015) l20 = 0.02 A20 = l20**2 # mass scaling km_damper = m20/l20**2 # mdamper = km_damper * ldamper**2 # added fluid mass scaling kam_damper = am20/l20**3 # amdamper = kam_damper * ldamer**3 # from wind tunnel tests rho = 1.2 #kg/m3 # f = C * rho * A * v * |v| #from windtunnel_data.ipynb C0 = 0.563 # f = C0 * v**2 * A = C20tunnel * rho * v**2 * A C20tunnel = C0 / rho # gives 4.3. Csysid = 0.44 # from pakpong, f=rho * Csysid * v**2 * A. very similar # in measurements below, r measurements are relative to CM of flapper, # d measurements are relative to CM of flapper-damper combo # robot in teoh2012: zhibee_ddampers = 0.04 # distance between pair = 2 * 10 mm + 5 mm (bottom ext.) + 15 mm (airframe length) zhibee_dm = -.003; #; #-.003#-.002; # offset from cm to midpoint of pair of dampers theoreticalbee_dm = 0.0015 zhibee_Jflapper = 1.5e-9 J0flapper = 0 # flapper with zero moment of inertia zhibee_mflapper = 61e-6 #80e-6 ma_bee zhibee_rw = 0.007 # vector distance from flapper cm to wing center of pressure # damper midpoint offset from CM ratio relative to damper distance dm_ratio_nowings = 0#1.5/40. #1/40. dm_ratio = dm/d dm_shift_nowings = theoreticalbee_dm#0#-.0009 #-.0004\ #dm_ratio_wings = -1/40. #dm_shift_wings = zhibee_dm - dm_ratio_wings * .04 #dm_ratio_wings = 0#-.4/40. #dm_shift_wings = zhibee_dm - dm_ratio_wings * .04 # wings drag bw80 = 2e-4 # wing drag coeff, f = -b_w * v_w, for 80 mg damper kb = bw80 / zhibee_mflapper # bw = kb * mflapper print(rho * A20 * C20tunnel * (zhibee_ddampers/2)**3 / zhibee_Jflapper) # is about 1.2. wow, convenient. ! print(rho * A20 * C20tunnel * (zhibee_ddampers/2) / zhibee_mflapper) print(km_damper) torque_disturbance = 0.1e-6 time = np.r_[0:10:0.002] time_fast = np.r_[0:20:0.004] # In[4]: def compute_params(dm=theoreticalbee_dm, ddampers=zhibee_ddampers, ldamper=l20, mflapper=zhibee_mflapper, Jflapper=J0flapper, rw=zhibee_rw): """given measurements in flapper-frame coordinates ('r'), translate to center-of-mass (COM) frame coordinates ('d') """ if ddampers - ldamper - 0.015 < 0: print("note: dampers too large to accommodate flapper body, l=%.4f, d=%.4f"%(ldamper, ddampers)) A = ldamper**2 mdamper = km_damper * A # mass amdamper = kam_damper * ldamper**3 # fluidic added mass m1 = m2 = mdamper mI1 = mI2 = mdamper + amdamper # inertial mass m = m1 + m2 + mflapper mI = mI1 + mI2 + mflapper # inertial mass rmI = mI/mflapper * dm # distance from cm of flapper to midpoint of dampers d1 = ddampers/2 + dm # distance from cm to top damper d2 = -ddampers/2 + dm # distance from cm to bottom damper df = dm - rmI # distance from cm to cm of flapper #rm = m/mflapper * dm #dc = rm - rmI # distance from inertial CM to gravitational CM. # Inertial moment of inertia includes fluidic masses J1 = 1/6 * mI1 * ldamper**2 # moment of inertia about damper cm, model as cube J2 = 1/6 * mI2 * ldamper**2 JI = Jflapper + mflapper * df**2 + J1 + mI1 * d1**2 + J2 + mI2 * d2**2 dw = rw + df bw = kb * mflapper return dict(d1=d1, d2=d2, m=m, mI=mI, JI=JI, dw=dw, A=A, bw=bw) def damper_dynamics(t, q, d1, d2, m, mI, JI, dw, A, bw, C=C20tunnel, include_wings=False, torque_disturbance=0, force_disturbance=0, ): """compute xdot in equation xdot = f(x, u)""" # theta is theta2, that is pitch-heave dynamics. theta, omega, v, p = q # forces and velocities are given in body-attached coordinates. v1 = omega * d1 + v v2 = omega * d2 + v F1 = - rho * A * C * v1 * abs(v1) F2 = - rho * A * C * v2 * abs(v2) Fgravity = m * g * np.sin(theta) if include_wings: vw = omega * dw + v Fw = - bw * vw tauw = Fw * dw else: Fw = tauw = 0 tau1 = F1 * d1 # tau = f cross r tau2 = F2 * d2 thetadot = omega omegadot = 1/JI * (tau1 + tau2 + tauw + torque_disturbance) vdot = 1/mI * (F1 + F2 + Fgravity + Fw + force_disturbance) pdot = np.cos(theta) * v #if p < .13 and (p + pdot*.002 > 0.13): BK() return np.array((thetadot, omegadot, vdot, pdot)) def integrate(dynamics_function, time, q0, **kwargs): "fixed-step integration" q = np.zeros((4, time.shape[0])) q[:,0] = q0 dt = time[1] - time[0] # assume equally-spaced time for idx, t in enumerate(time): if idx > 0: qdot = dynamics_function(t, q[:,idx-1], **kwargs) #q[:,idx] = (time[idx]-time[idx-1]) * qdot + q[:,idx-1] # if unequally-spaced time q[:,idx] = dt * qdot + q[:,idx-1] return q def last_cycle_slice(x): "find last cycle (up to granularity of time step), used for re-centering for position plots" # difference from MATLAB: taking diff on a logical array gives only positives. transitions = (np.diff(np.array(x > 0, dtype='int')) > 0) transition_indices = np.nonzero(transitions)[0] if len(transition_indices) < 2: print("no cycles found") return None return slice(transition_indices[-2], transition_indices[-1]) # In[5]: # plotting constants cycle_axis = (-60, 60, -350, 350) xticks = (-30, 0, 30) yticks = (-200, 0, 200) cycle_axisw = (-60, 60, -600, 600) xticksw = (-45, 0, 45) yticksw = (-500, 0, 500) # ### Limit cycle plot # In[6]: # initial conditions array: theta, omega, v, p IC_arr = [(0, 5, 0, 0), (0, -5, 0, 0), (-0.85, 0, 0, 0), (.85, 0, 0, 0), (.15, 0, 0, 0), (-.15, 0, 0, 0), ] #(0, .1, 0), (0, -.1, 0), ]# n = len(IC_arr) thetas, omegas, vs, ps = [], [], [], [] damper_maxtheta = [] #params_dict = compute_params() params_dict = compute_params(Jflapper=zhibee_Jflapper, dm=zhibee_dm) for idx, IC in enumerate(IC_arr): theta, omega, v, p = integrate(damper_dynamics, time, IC, include_wings=True,**params_dict) damper_maxtheta.append(np.max(theta)) thetas.append(theta) omegas.append(omega) vs.append(v) ps.append(p - np.mean(p[last_cycle_slice(omega)])) IC = (0.1, -3, 0, 0) # In[7]: def limit_cycle_plot(theta, omega, color='k'): plt.plot(theta*r2d, omega*r2d, color) plt.plot(theta[0]*r2d, omega[0]*r2d, color+"o") plt.axis(cycle_axis) #xlim and ylim plt.xlabel(r'$\theta$ ($^\circ$)'); plt.ylabel(r'$\omega$ ($^\circ$/s)',) plt.xticks(xticks) plt.yticks(yticks) plt.tight_layout() fig = plt.figure(num=1, figsize=fs) #fig, axes = plt.subplots(figsize=fs) for idx in np.r_[1:len(thetas)]: limit_cycle_plot(thetas[idx], omegas[idx]) limit_cycle_plot(thetas[0], omegas[0], 'r') # plt.plot(thetas[0]*r2d, omegas[0]*r2d, 'r') # draw a higlighted one in red #plt.plot(thetas[0][0]*r2d, omegas[0][0]*r2d, 'ro') plt.savefig('limit_cycle_examples.pdf') print(params_dict) # In[14]: # check how small of a time step is necessary fig = plt.figure(num=1, figsize=fs) #fig, axes = plt.subplots(figsize=fs) plt.plot(thetas[0]*r2d, ps[0], 'r') plt.plot(thetas[0][0]*r2d, ps[0][0], 'ro') for idx in np.r_[1:len(thetas)]: theta, omega, v, p = thetas[idx], omegas[idx], vs[idx], ps[idx] plt.plot(theta*r2d, p, 'k') plt.axis((16, 18, -.16, -.15)) plt.plot(theta[0]*r2d, p[0], 'ko') #plt.axis(cycle_axis) #xlim and ylim plt.xlabel(r'$\theta$ ($^\circ$)'); plt.ylabel(r'$p$ (m))',) # In[15]: fig = plt.figure(num=1, figsize=fs) #fig, axes = plt.subplots(figsize=fs) idx = 0; theta, omega, v, p = thetas[idx], omegas[idx], vs[idx], ps[idx] plt.subplot(4,1,1) plt.plot(time, theta*r2d,'r') plt.ylabel(r'$\theta$ ($^\circ$)') plt.xticks(range(5), ()) plt.yticks((-30, 0, 30)) plt.subplot(4,1,2) plt.plot(time, omega*r2d,'r') plt.ylabel(r'$\omega$ ($^\circ$/s)',) plt.yticks((-200, 0, 200)) plt.xticks(range(5), ()) plt.subplot(4,1,3) plt.plot(time, v,'r') plt.ylabel(r'$v$ (m/s)',) plt.yticks((-1, 0, 1)) plt.ylim((-1, 1)) plt.xticks(range(5), ()) plt.subplot(4,1,4) plt.plot(time, p,'r') plt.ylabel(r'$p$ (m)',) plt.yticks((-.3, 0, .3)) plt.ylim((-.35, .35)) plt.xlabel('time (s)') plt.tight_layout() plt.savefig('limit_cycle_examples2.pdf') # test against 3d matlab simulation print(np.max(theta[-3000:])) threedsim_thetamax = .42 #.387 # matlab simulation results for 3d case. threedsim_vmax = .797 print((threedsim_thetamax - np.max(theta[-3000:]))/threedsim_thetamax) print((threedsim_vmax - np.max(v[-3000:]))/threedsim_vmax) print(np.max(omega[-1000:])) # In[16]: def draw_robot_motion(params_dict, time, theta, p, l=0.02, vertical_speed=.2, time_increment=0.1, only_show_last_cycle=True, show_wings_location=True): # plot top damper, bottom damper, connection, cm, cm position if only_show_last_cycle: sl = last_cycle_slice(p) time, theta, p = time[sl] - time[sl][0], theta[sl], p[sl] fig = plt.figure(figsize=(fs[0], fs[1])) #fig, axes = plt.subplots(figsize=fs) time = time - time[0] plt.plot(p, time*vertical_speed, linewidth=2, color="gray", zorder=0) index_increment = int(np.floor(time_increment/(time[1]-time[0]))) d1, d2 = params_dict['d1'], params_dict['d2'] for i_render in range(int(np.ceil((time[-1]/time_increment)))): px_r = p[i_render*index_increment] py_r = time[i_render*index_increment] * vertical_speed t_r = theta[i_render*index_increment] plt.plot(px_r, py_r, 'k.', MarkerSize=12, zorder=i_render) topx = px_r + d1 * np.sin(t_r) topy = py_r + d1 * np.cos(t_r) plt.plot(topx, topy, 'k.', MarkerSize=8, zorder=i_render) if show_wings_location: plt.plot(px_r + params_dict['dw'] * np.sin(t_r), py_r + params_dict['dw'] * np.cos(t_r), 'k.', MarkerSize=8, zorder=i_render) bottomx = px_r + d2 * np.sin(t_r) bottomy = py_r + d2 * np.cos(t_r) llx = (l/2 * np.sqrt(2))*np.cos(np.pi*5/4 - t_r) lly = (l/2 * np.sqrt(2))*np.sin(np.pi*5/4 - t_r) plt.plot([topx, bottomx], [topy, bottomy], 'k', linewidth=2, zorder=i_render) #plt.gca().add_patch(plt.Rectangle((px_r-l/2, py_r-l/2+d1), # l, l, angle=t_r*r2d, facecolor=[1,1,1], linewidth=.5)) plt.gca().add_patch(plt.Rectangle((topx + llx, topy + lly), l, l, angle=-t_r*r2d, facecolor=[1,1,1], linewidth=.5, zorder=i_render)) plt.gca().add_patch(plt.Rectangle((bottomx + llx, bottomy + lly), l, l, angle=-t_r*r2d, facecolor=[1,1,1], linewidth=.5, zorder=i_render)) plt.gca().axis('equal') plt.gca().set_yticks((0, .1, .2, .3)) plt.gca().set_yticklabels(('0', '1', '2', '3')) plt.gca().set_xticks((-.2, 0, .2)) plt.xlim((-.24,.24)) plt.ylabel('time (s)') plt.xlabel('position $p$ (m)') # In[17]: params_dict = compute_params() draw_robot_motion(params_dict, time, theta, p) plt.savefig('cycle_rendering.pdf') print(params_dict) # In[18]: params_dict = compute_params(Jflapper=zhibee_Jflapper, dm=zhibee_dm) time2 = np.r_[0:3:.002] IC=(.3,0,0,0) theta, omega, v, p = integrate(damper_dynamics, time2, IC, include_wings=True, **params_dict) draw_robot_motion(params_dict, time2, theta, p, only_show_last_cycle=False) # In[19]: def plot_pos_responses_to_array_of_params(param_name, param_array, plot_axis_name, string_format, plot_scale_factor=1000, include_wings=False, other_params={}): IC = (0.1, -.3, 0, 0) n = len(param_array) cycle_axis_p = (-75, 75, -500, 500) xticks_p = (-60, 0, 60) #yticks_p = (-.4, -.2, 0, .2, .4) yticks_p = (-400, -200, 0, 200, 400) thetas, omegas, vs, ps = [], [], [], [] thetas2, omegas2, vs2, ps2 = [], [], [], [] damper_maxp = [] damper_meanvel = [] for idx, param in enumerate(param_array): kwargs = {param_name:param} # create dictionary of keyword args kwargs.update(other_params) if include_wings: if 'Jflapper' not in kwargs: kwargs['Jflapper'] = zhibee_Jflapper if param_name != 'dm': kwargs['dm'] = zhibee_dm params_dict = compute_params(**kwargs) theta, omega, v, p = integrate(damper_dynamics, time, IC, include_wings=include_wings, **params_dict) last_cycle_slice1 = last_cycle_slice(omega) p = 1000 * (p - np.mean(p[last_cycle_slice1]))# shift p to orbit origin theta2, omega2, v2, p2 = integrate(damper_dynamics, time, IC, torque_disturbance=torque_disturbance, include_wings=include_wings, **params_dict) last_cycle_slice2 = last_cycle_slice(omega2) p2 = 1000 * (p2 - np.mean(p2[last_cycle_slice2])) # shift p to orbit origin damper_maxp.append(np.max(p[last_cycle_slice1])) thetas.append(theta) omegas.append(omega) vs.append(v) ps.append(p) damper_meanvel.append(np.mean(v2[last_cycle_slice2])) thetas2.append(theta2) omegas2.append(omega2) vs2.append(v2) ps2.append(p2) param_array_scaled = np.array(param_array)*plot_scale_factor fig = plt.figure(figsize=fs) #fig, axes = plt.subplots(figsize=fs) plt.plot(param_array_scaled, np.array(damper_maxp), 'ks-') plt.ylabel(r'position $p$ amplitude ($mm$)') plt.xlabel(plot_axis_name) plt.axis((np.min(param_array_scaled), np.max(param_array_scaled), 0, 800)) ax2 = plt.gca().twinx() plt.plot(param_array_scaled, np.array(damper_meanvel), '^-', color='grey') ax2.set_ylabel(r'velocity induced by torque perturbation $\tau_p$ (m/s)', color="grey") ax2.set_ylim(0, 1.5) ax2.set_xlim((np.min(param_array_scaled), np.max(param_array_scaled))) for label in ax2.get_yticklabels(): label.set_color("grey") fig.add_axes([0.4, 0.6, 0.18, 0.3]) # inset axes theta, p = thetas[0], ps[0] theta2, p2 = thetas2[0], ps2[0] plt.plot(theta[:5000]*r2d, p[:5000], 'k') plt.plot(theta[0]*r2d, p[0], 'ko') plt.axis(cycle_axis_p) #xlim and ylim plt.xlabel(r'$\theta$ ($^\circ$)'); plt.ylabel(r'$p$ ($m$)',) plt.xticks(xticks_p) plt.yticks(yticks_p) plt.text(.5, .95, string_format.format(param_array_scaled[0]), verticalalignment='top', horizontalalignment='center', transform=plt.gca().transAxes) fig.add_axes([0.61, 0.6, 0.18, 0.3]) # inset axes theta, p = thetas[-1], ps[-1] theta2, p2 = thetas2[-1], ps2[-1] plt.plot(theta[:5000]*r2d, p[:5000], 'k') plt.plot(theta[0]*r2d, p[0], 'ko') plt.axis(cycle_axis_p) #xlim and ylim plt.xticks(xticks_p) plt.yticks(()) plt.text(.5, .95, string_format.format(param_array_scaled[-1]), verticalalignment='top', horizontalalignment='center', transform=plt.gca().transAxes) plt.tight_layout() return params_dict, time, theta, omega, v, p # ## No wings # In[20]: # vary damper distance params_dict, time, theta, omega, v, p = plot_pos_responses_to_array_of_params('ddampers', np.logspace(np.log10(.03), np.log10(.08), 10), 'distance between dampers $d$ (mm)', '{:2.0f} mm') plt.savefig('pos_amplitude_vs_distance.pdf') # vary damper midpoint position dm plot_pos_responses_to_array_of_params('dm', np.logspace(np.log10(.0005), np.log10(.0025), 10), 'damper midpoint position $d_m$ (mm)', '{:2.1f} mm') plt.savefig('pos_amplitude_vs_offset.pdf') # damper size l plot_pos_responses_to_array_of_params('ldamper', np.logspace(np.log10(.01), np.log10(.03), 10), 'damper size $l$ (mm)', '{:2.0f} mm') plt.savefig('pos_amplitude_vs_length.pdf') # flapper mass plot_pos_responses_to_array_of_params('mflapper', np.logspace(np.log10(zhibee_mflapper/2), np.log10(zhibee_mflapper*4), 10), 'thruster mass $m_t$ (mg)', '{:2.0f} mg', plot_scale_factor=1e6) plt.savefig('pos_amplitude_vs_flapper_mass.pdf') # ## including wings # In[21]: # vary damper distance plot_pos_responses_to_array_of_params('ddampers', np.logspace(np.log10(.03), np.log10(.08), 10), 'distance between dampers $d$ (mm)', '{:2.0f} mm', include_wings=True) plt.savefig('pos_amplitude_vs_distance_wings.pdf') # vary damper midpoint position dm plot_pos_responses_to_array_of_params('dm', -np.logspace(np.log10(.0005), np.log10(.0045), 10), 'damper midpoint position $d_m$ (mm)', '{:2.1f} mm', include_wings=True) plt.savefig('pos_amplitude_vs_offset_wings.pdf') # damper size l plot_pos_responses_to_array_of_params('ldamper', np.logspace(np.log10(.015), np.log10(.03), 10), 'damper size $l$ (mm)', '{:2.0f} mm', include_wings=True) plt.savefig('pos_amplitude_vs_length_wings.pdf') # flapper mass params_dict, time, theta, omega, v, p = plot_pos_responses_to_array_of_params('mflapper', np.logspace(np.log10(zhibee_mflapper/2), np.log10(zhibee_mflapper*2), 10), 'thruster mass $m_t$ (mg)', '{:2.0f} mg', plot_scale_factor=1e6, include_wings=True) plt.savefig('pos_amplitude_vs_flapper_mass_wings.pdf') # In[22]: draw_robot_motion(params_dict, time, theta, p/1000) print(params_dict) # ### Design plot: damper distance for different damper sizes and vehicle mass # In[23]: ## Do a search to find parameters that result in a desired theta or p ampl. # For theta, given an m and l, find ddampers that achieves this # For position, since ddampers doesn't matter, given an m, find l that achieves # desired p amplitude import scipy.optimize time = time_fast def calculate_thetamax(ddampers, mflapper, ldamper, include_wings=0, full_output=0): "calculate amplitude of theta oscillations as a function of params" # first arg has to be the one that is being optimized if include_wings: dm = zhibee_dm Jflapper = zhibee_Jflapper else: dm = theoreticalbee_dm Jflapper = J0flapper params_dict = compute_params(mflapper=mflapper, ddampers=ddampers, ldamper=ldamper, dm=dm, Jflapper=Jflapper) theta, omega, v, p = integrate(damper_dynamics, time, IC, include_wings=include_wings, **params_dict) sl = last_cycle_slice(omega) theta = theta - np.mean(theta[sl]) # recenter if full_output: return np.max(theta[sl]), theta, omega, v, p else: return np.max(theta[sl]) def cost_function_theta(ddampers, mflapper, ldamper, thetamax_desired, include_wings): thetamax = calculate_thetamax(ddampers[0], mflapper, ldamper, include_wings) print("d=%3.5fmm gives thetamax=%3.2f deg"%(ddampers[0]*1000, thetamax*r2d)) #doing integration cycle return thetamax - thetamax_desired #(thetamax - thetamax_desired)**2 def find_ddampers_theta(mflapper, ldamper, thetamax_desired=10*d2r, include_wings=0, ddampers0=.04, epsilon=1*d2r, max_iter=30): # iterate search for correct ddampers to get within epsilon of desired theta # basic linear search: # scipy.optimize.fmin(func, x0, args=(), xtol=0.0001, ftol=0.0001, maxiter=None, maxfun=None, # full_output=0, disp=1, retall=0, callback=None) # quadratic search (faster?) results = scipy.optimize.leastsq(cost_function_theta, ddampers0, args=(mflapper, ldamper, thetamax_desired, include_wings), maxfev=max_iter, full_output=1, epsfcn=0.001, xtol=.001, ftol=.0001) # full_output=1, epsfcn=.01) ddampers = results[0] details = results[1:] return ddampers, details def calculate_pmax(ldamper, mflapper, ddampers, include_wings=False, full_output=0): "calculate pmax as a function of parameters l and m (not d or dm)" if include_wings: dm = zhibee_dm Jflapper = zhibee_Jflapper else: dm = theoreticalbee_dm Jflapper = J0flapper params_dict = compute_params(mflapper=mflapper, ddampers=ddampers, ldamper=ldamper, dm=dm, Jflapper=Jflapper) theta, omega, v, p = integrate(damper_dynamics, time, IC, include_wings=include_wings, **params_dict) sl = last_cycle_slice(omega) p = p - np.mean(p[sl]) # recenter if full_output: return np.max(p[sl]), theta, omega, v, p else: return np.max(p[sl]) def cost_function_p(ldamper, mflapper, ddampers, pmax_desired, include_wings): pmax = calculate_pmax(ldamper[0], mflapper, ddampers, include_wings) print("l=%3.5f mm gives pmax=%3.2f mm"%(ldamper[0]*1000, pmax*1000)) return pmax - pmax_desired def find_ldamper_p(mflapper, ddampers, pmax_desired=0.25, include_wings=0, ldamper0=.01, max_iter=30): results = scipy.optimize.leastsq(cost_function_p, ldamper0, args=(mflapper, ddampers, pmax_desired, include_wings), maxfev=max_iter, full_output=1, epsfcn=0.0001, xtol=.0001, ftol=.0001) ldamper = results[0] details = results[1:] return ldamper, details if 0: # test pmax, theta, omega, v, p = calculate_pmax(ldamper=.06, mflapper=4*zhibee_mflapper, ddampers=.16, include_wings=0, full_output=1) print("pmax", pmax) thetamax, theta, omega, v, p = calculate_thetamax(ddampers=.04, mflapper=zhibee_mflapper, ldamper=0.023, include_wings=0, full_output=1) print("thetamax", thetamax*r2d) limit_cycle_plot(theta, omega) # print(find_ldamper_p(mflapper=zhibee_mflapper, ddampers=.16, # pmax_desired=.25, include_wings=0)) print(find_ddampers_theta(mflapper=zhibee_mflapper, ldamper=0.01, thetamax_desired=15*d2r, include_wings=0)) # In[24]: # utility function for optimizatoin verification def find_and_report_value(function, error_threshold, *args): value, details = function(*args) #print "for ldamper=%2.f mm, mflapper=%2.f mg, thetamax_desired=%2.f deg, result is ddamper=%3.2f mm"%\ # (ldamper*1000, mflapper*1e6, thetamax_desired*r2d, ddamper*1000) problem = not details[-2].startswith("The relative error between two consecutive iterates is at most") if problem: print(("no convergence. details: ", details)) error = details[1]['fvec'] if error > error_threshold: print("large error=%3.3f "%error) return value, problem, error # ### position optimiziation - no wings # In[25]: # position optimization - basic pmax_desired = .25 show_variability = 1 et = 0.001 # error threshold ddampers_arr = np.array((.03, .04, .08, .1601)) mflapper_arr = np.array((20e-6, 40e-6, 80e-6, 160e-6, 320e-6)) ldamper_arr = np.empty((len(ddampers_arr), len(mflapper_arr))) problem_arr = np.zeros_like(ldamper_arr) error_arr = np.empty_like(ldamper_arr) if show_variability: p_shift = 0.01 # for showing sensitivitity ldamper2_arr = np.empty_like(ldamper_arr) ldamper3_arr = np.empty_like(ldamper_arr) problem2_arr = np.empty_like(ldamper_arr) problem3_arr = np.empty_like(ldamper_arr) error2_arr, error3_arr = np.empty_like(ldamper_arr), np.empty_like(ldamper_arr) for idx1, ddampers in enumerate(ddampers_arr): for idx2, mflapper in enumerate(mflapper_arr): #ldamper, details = find_ldamper_p(mflapper, ddampers, pmax_desired) ldamper_arr[idx1, idx2], problem_arr[idx1,idx2], error_arr[idx1, idx2] = find_and_report_value(find_ldamper_p, et, mflapper, ddampers, pmax_desired) if show_variability: ldamper2_arr[idx1, idx2], problem2_arr[idx1,idx2], error2_arr[idx1, idx2] = find_and_report_value(find_ldamper_p, et, mflapper, ddampers, pmax_desired - p_shift) ldamper3_arr[idx1, idx2], problem3_arr[idx1,idx2], error3_arr[idx1, idx2] = find_and_report_value(find_ldamper_p, et, mflapper, ddampers, pmax_desired + p_shift) print(ldamper_arr) print(problem_arr) error_arr, error2_arr, error3_arr = error_arr < et, error2_arr < et, error3_arr < et print(np.array(error_arr < et, dtype=int)) if show_variability: pass #print(problem2_arr) #print(problem3_arr) # In[26]: # data validity masks (position) # calculate masks for dampers that are too close to body or other errors print(ldamper_arr) ldamper_mask = [] for idx, ddampers in enumerate(ddampers_arr): # use lower bound ddampers3 ldamper_mask.append(ddampers_arr[idx] > ldamper3_arr[idx] + 2*zhibee_rw) # also mask if errors ldamper_mask[-1] = ldamper_mask[-1] & error_arr[idx] ldamper_mask[-1] = ldamper_mask[-1] & ~np.array(problem_arr[idx], dtype=bool) ldamper_mask = np.array(ldamper_mask) print(np.array(ldamper_mask, dtype=int)) print(ldamper_arr[-1][ldamper_mask[-1]]) print(ldamper_arr[np.array(ldamper_mask)]) # In[27]: # position model for ldmapers # calculate exponential multivariate fit for ldampers as a function of mflapper and ldamper which_to_plot = np.array((1, 2)) # format d = exp(a) * mf**b*d**c # log of this: log d = a + b log mf + c log d, curve fit finds a, b, c from scipy.optimize import curve_fit def candidate_function(x, a, b, c): return a + b*x[0] + c*x[1] mflapper_arr2, ddampers_arr2 = np.meshgrid(mflapper_arr, ddampers_arr[which_to_plot]) xdata = (np.log(mflapper_arr2[ldamper_mask[which_to_plot]]), np.log(ddampers_arr2[ldamper_mask[which_to_plot]])) ydata = np.log(ldamper_arr[which_to_plot][ldamper_mask[which_to_plot]]) popt, pcov = curve_fit(candidate_function, xdata, ydata) # linear version for testing for a single ldamper case: # fit ddampers = exp fit[0] * mflapper**fit[1] # log ddampers = fit[0] + fit[1] * log mflapper #fit, *junk = np.linalg.lstsq(np.hstack((np.ones((5,1)), np.log(mflapper_arr[:,np.newaxis]))), # np.log(ddampers_arr[:1,:].flatten())) # print(np.exp(fit[0]) * mflapper_modeldata**fit[1]*1000) print(popt) #print(np.exp(popt[0]), popt[1]*1e6, popt[2]*1000) print(np.exp(popt[0]), popt[1], popt[2]) def ldamper_est(mflapper, ddampers, popt): return np.exp(popt[0])*mflapper**popt[1]*ddampers**popt[2] # In[28]: fig = plt.figure(figsize=fs) colors = 'rgbc' markers = 'so^v' lines = ['-', '--', ':', '-.'] #for idx, ddampers in enumerate(ddampers_arr[which_to_plot]): for idx in which_to_plot: ddampers = ddampers_arr[idx] ldamper = ldamper_arr[idx][ldamper_mask[idx]] # estimate mflapper = mflapper_arr[ldamper_mask[idx]] plt.loglog(mflapper*1e6, ldamper*1000, markers[idx]+lines[idx], color='k', linewidth=2) # variability ldamper2 = ldamper2_arr[idx][ldamper_mask[idx]] ldamper3 = ldamper3_arr[idx][ldamper_mask[idx]] plt.fill_between(mflapper*1e6, ldamper2*1000, ldamper3*1000, facecolor=[.8,.8,.8], linewidth=0.5) mflapper_modeldata = np.logspace(-6, -3, 10) for idx, ddampers in enumerate(ddampers_arr[which_to_plot]): # fit model plt.loglog(mflapper_modeldata*1e6, ldamper_est(mflapper_modeldata, ddampers, popt)*1000, 'k', lw=.5) #plt.loglog(mflapper_modeldata*1e6, # (np.exp(fit[0]) * mflapper_modeldata**fit[1])*1000) plt.xlabel('thruster mass $m_t$ (mg)') plt.ylabel('damper length $l$ (mm)') plt.ylim(5, 50) plt.xlim(15, 400) #yticks2 = (40, 50, 70, 100, 200) yticks2 = (6, 10, 20, 30, 40) #xticks2 = mflapper_arr*1e6 xticks2 = (20, 40, 70, 100, 200, 300) plt.gca().set_yticks(yticks2) plt.gca().set_yticklabels([str(ytick) for ytick in yticks2]) plt.gca().set_xticks(xticks2) plt.gca().set_xticklabels(["%.f"%xtick for xtick in xticks2]) plt.legend(['$d$ = %2.f mm'%(ddampers*1000) for ddampers in ddampers_arr[which_to_plot]], loc='upper left') plt.grid(which='minor') plt.grid(which='major') plt.savefig('mass_v_damper_length_position_25cm.pdf') # ### position optimization - scale-invariant - no wings # In[29]: # position optimization - scale-invariant - no wings pmax_desired = .25 ddampers_ratio = pmax_desired/zhibee_ddampers #ddampers_ratio = 5 show_variability = 1 et = 0.001 # error threshold in m ddampers_arr = np.array((.03, .04, .08, .160)) mflapper_arr = np.array((20e-6, 40e-6, 80e-6, 160e-6, 320e-6)) ldamper_arr = np.empty((len(ddampers_arr), len(mflapper_arr))) problem_arr = np.zeros_like(ldamper_arr) error_arr = np.empty_like(ldamper_arr) if show_variability: p_shift = 0.01 # for showing sensitivitity ldamper2_arr = np.empty_like(ldamper_arr) ldamper3_arr = np.empty_like(ldamper_arr) problem2_arr = np.empty_like(ldamper_arr) problem3_arr = np.empty_like(ldamper_arr) error2_arr, error3_arr = np.empty_like(ldamper_arr), np.empty_like(ldamper_arr) for idx1, ddampers in enumerate(ddampers_arr): for idx2, mflapper in enumerate(mflapper_arr): #ldamper, details = find_ldamper_p(mflapper, ddampers, pmax_desired) pmax_desired_scaled = ddampers_ratio * ddampers print("pmax_desired", pmax_desired_scaled) ldamper_arr[idx1, idx2], problem_arr[idx1,idx2], error_arr[idx1, idx2] = find_and_report_value(find_ldamper_p, et, mflapper, ddampers, pmax_desired_scaled) if show_variability: ldamper2_arr[idx1, idx2], problem2_arr[idx1,idx2], error2_arr[idx1, idx2] = find_and_report_value(find_ldamper_p, et, mflapper, ddampers, (pmax_desired - p_shift)/pmax_desired*pmax_desired_scaled) ldamper3_arr[idx1, idx2], problem3_arr[idx1,idx2], error3_arr[idx1, idx2] = find_and_report_value(find_ldamper_p, et, mflapper, ddampers, (pmax_desired + p_shift)/pmax_desired*pmax_desired_scaled) ldamper_arr = np.abs(ldamper_arr) # sometimes optimization finds negative print("ldamper", ldamper_arr) print("problems", problem_arr) print("error size ok", np.array(np.abs(error_arr) < et, dtype=int)) error_arr = np.abs(error_arr) < et if show_variability: #print(problem2_arr) #print(problem3_arr) ldamper2_arr = np.abs(ldamper2_arr) # sometimes optimization finds negative ldamper3_arr = np.abs(ldamper3_arr) # sometimes optimization finds negative error2_arr, error3_arr = np.abs(error2_arr) < et, np.abs(error3_arr) < et # In[30]: # data validity masks (position) # calculate masks for dampers that are too close to body or other errors print(ldamper_arr) min_damper_l = 5e-3 ldamper_mask = [] for idx, ddampers in enumerate(ddampers_arr): if show_variability: # use lower bound ddampers3 ldamper_mask.append(ddampers_arr[idx] > ldamper3_arr[idx] + 2*zhibee_rw) else: ldamper_mask.append(ddampers_arr[idx] > ldamper_arr[idx] + 2*zhibee_rw) print("ldamper_mask row", ldamper_mask[-1]) # also mask if errors ldamper_mask[-1] = ldamper_mask[-1] & error_arr[idx] ldamper_mask[-1] = ldamper_mask[-1] & ~np.array(problem_arr[idx], dtype=bool) # remove dampers that are too small ldamper_mask[-1] = ldamper_mask[-1] & (ldamper3_arr[idx] > min_damper_l) ldamper_mask = np.array(ldamper_mask) print(np.array(ldamper_mask, dtype=int)) #print(ldamper_arr[-1][ldamper_mask[-1]]) #print(ldamper_arr[np.array(ldamper_mask)]) # In[31]: # position model for ldmapers # calculate exponential multivariate fit for ldampers as a function of mflapper and ldamper #which_to_plot = np.array((1, 2)) which_to_plot = np.array((0, 1, 2, 3)) # format d = exp(a) * mf**b*d**c # log of this: log d = a + b log mf + c log d, curve fit finds a, b, c from scipy.optimize import curve_fit def candidate_function(x, a, b, c): return a + b*x[0] + c*x[1] mflapper_arr2, ddampers_arr2 = np.meshgrid(mflapper_arr, ddampers_arr[which_to_plot]) xdata = (np.log(mflapper_arr2[ldamper_mask[which_to_plot]]), np.log(ddampers_arr2[ldamper_mask[which_to_plot]])) ydata = np.log(ldamper_arr[which_to_plot][ldamper_mask[which_to_plot]]) popt, pcov = curve_fit(candidate_function, xdata, ydata) # linear version for testing for a single ldamper case: # fit ddampers = exp fit[0] * mflapper**fit[1] # log ddampers = fit[0] + fit[1] * log mflapper #fit, *junk = np.linalg.lstsq(np.hstack((np.ones((5,1)), np.log(mflapper_arr[:,np.newaxis]))), # np.log(ddampers_arr[:1,:].flatten())) # print(np.exp(fit[0]) * mflapper_modeldata**fit[1]*1000) print(popt) #print(np.exp(popt[0]), popt[1]*1e6, popt[2]*1000) print(np.exp(popt[0]), popt[1], popt[2]) def ldamper_est(mflapper, ddampers, popt): return np.exp(popt[0])*mflapper**popt[1]*ddampers**popt[2] # In[32]: fig = plt.figure(figsize=fs) colors = 'rgbc' markers = 'so^v' lines = ['-', '--', ':', '-.'] #for idx, ddampers in enumerate(ddampers_arr[which_to_plot]): for idx in which_to_plot: ddampers = ddampers_arr[idx] ldamper = ldamper_arr[idx][ldamper_mask[idx]] # estimate mflapper = mflapper_arr[ldamper_mask[idx]] plt.loglog(mflapper*1e6, ldamper*1000, markers[idx]+lines[idx], color='k', linewidth=2) # variability ldamper2 = ldamper2_arr[idx][ldamper_mask[idx]] ldamper3 = ldamper3_arr[idx][ldamper_mask[idx]] plt.fill_between(mflapper*1e6, ldamper2*1000, ldamper3*1000, facecolor=[.8,.8,.8], linewidth=0.5) mflapper_modeldata = np.logspace(-6, -3, 10) for idx, ddampers in enumerate(ddampers_arr[which_to_plot]): # fit model plt.loglog(mflapper_modeldata*1e6, ldamper_est(mflapper_modeldata, ddampers, popt)*1000, 'k', lw=.5) #plt.loglog(mflapper_modeldata*1e6, # (np.exp(fit[0]) * mflapper_modeldata**fit[1])*1000) plt.xlabel('thruster mass $m_t$ (mg)') plt.ylabel('damper length $l$ (mm)') plt.ylim(5, 50) plt.xlim(15, 400) #yticks2 = (40, 50, 70, 100, 200) yticks2 = (6, 10, 20, 30, 40) #xticks2 = mflapper_arr*1e6 xticks2 = (20, 40, 70, 100, 200, 300) plt.gca().set_yticks(yticks2) plt.gca().set_yticklabels([str(ytick) for ytick in yticks2]) plt.gca().set_xticks(xticks2) plt.gca().set_xticklabels(["%.f"%xtick for xtick in xticks2]) plt.legend(['$d$ = %2.f mm'%(ddampers*1000) for ddampers in ddampers_arr[which_to_plot]], loc='upper left') plt.grid(which='minor') plt.grid(which='major') plt.savefig('mass_v_damper_length_position_25cm_sizescaled.pdf') # ### theta optimization - no wings # In[33]: # theta version - no wings thetamax_desired = 15*d2r show_variability = 1 et = 1 # error threshold (deg) ldamper_arr = np.array((.01, .015, .02, .03)) mflapper_arr = np.array((20e-6, 40e-6, 80e-6, 160e-6, 320e-6)) ddampers_arr = np.empty((len(ldamper_arr), len(mflapper_arr))) problem_arr = np.zeros((len(ldamper_arr), len(mflapper_arr))) error_arr = np.empty_like(ddampers_arr) if show_variability: theta_shift = 1*d2r # for showing sensitivitity ddampers2_arr = np.empty((len(ldamper_arr), len(mflapper_arr))) thetamax_desired2 = thetamax_desired - theta_shift ddampers3_arr = np.empty((len(ldamper_arr), len(mflapper_arr))) thetamax_desired3 = thetamax_desired + theta_shift problem2_arr = np.zeros((len(ldamper_arr), len(mflapper_arr))) problem3_arr = np.zeros((len(ldamper_arr), len(mflapper_arr))) error2_arr, error3_arr = np.empty_like(ddampers_arr), np.empty_like(ddampers_arr) for idx1, ldamper in enumerate(ldamper_arr): for idx2, mflapper in enumerate(mflapper_arr): ddampers, details = find_ddampers_theta(mflapper, ldamper, thetamax_desired) ddampers_arr[idx1, idx2] = ddampers #print "for ldamper=%2.f mm, mflapper=%2.f mg, thetamax_desired=%2.f deg, result is ddamper=%3.2f mm"%\ # (ldamper*1000, mflapper*1e6, thetamax_desired*r2d, ddamper*1000) problem = not details[-2].startswith("The relative error between two consecutive iterates is at most") if problem: print(("no convergence. details: ", details)) problem_arr[idx1, idx2] = problem theta_error = details[1]['fvec'] * r2d if theta_error > 1: print("large theta error=%3.2f deg"%theta_error) error_arr[idx1, idx2] = theta_error if show_variability: ddampers, details = find_ddampers_theta(mflapper, ldamper, thetamax_desired2) ddampers2_arr[idx1, idx2] = ddampers if not details[-2].startswith("The relative error between two consecutive iterates is at most"): print(("no convergence. details: ", details)) problem2_arr[idx1, idx2] = 1 theta_error = details[1]['fvec'] * r2d if theta_error > 1: print("large theta error=%3.2f deg"%theta_error) error2_arr[idx1, idx2] = theta_error ddampers, details = find_ddampers_theta(mflapper, ldamper, thetamax_desired3) ddampers3_arr[idx1, idx2] = ddampers if not details[-2].startswith("The relative error between two consecutive iterates is at most"): print(("no convergence. details: ", details)) problem3_arr[idx1, idx2] = 1 theta_error = details[1]['fvec'] * r2d if theta_error > 1: print("large theta error=%3.2f deg"%theta_error) error3_arr[idx1, idx2] = theta_error print(ddampers_arr) print(problem_arr) error_arr = np.abs(error_arr) < et if show_variability: error2_arr, error3_arr = error2_arr < et, error3_arr < et print(problem2_arr) print(problem3_arr) # In[34]: # calculate masks for dampers that are too close to body print(ddampers_arr) ddampers_mask = [] for idx, ldamper in enumerate(ldamper_arr): # use ddampers ddampers_mask.append(ddampers_arr[idx] > ldamper_arr[idx] + 2*zhibee_rw) # use lower bound ddampers3 #ddampers_mask.append(ddampers3_arr[idx] > ldamper_arr[idx] + 2*zhibee_rw) ddampers_mask[-1] = ddampers_mask[-1] & error_arr[idx] ddampers_mask[-1] = ddampers_mask[-1] & ~np.array(problem_arr[idx], dtype=bool) print(np.array(ddampers_mask, dtype=int)) print(np.array(problem_arr, dtype=int)) print(np.array(error_arr, dtype=int)) print(ddampers_arr[-1][ddampers_mask[-1]]) # In[35]: # calculate exponential multivariate fit for ddampers as a function of mflapper and ldamper # format d = exp(a) * mf**b*l**c # log of this: log d = a + b log mf + c log l, curve fit finds a, b, c from scipy.optimize import curve_fit def candidate_function(x, a, b, c): return a + b*x[0] + c*x[1] mflapper_arr2, ldamper_arr2 = np.meshgrid(mflapper_arr, ldamper_arr) xdata = (np.log(mflapper_arr2[np.vstack(ddampers_mask)]), np.log(ldamper_arr2[np.vstack(ddampers_mask)])) ydata = np.log(ddampers_arr[np.vstack(ddampers_mask)]) popt, pcov = curve_fit(candidate_function, xdata, ydata) # linear version for testing for a single ldamper case: # fit ddampers = exp fit[0] * mflapper**fit[1] # log ddampers = fit[0] + fit[1] * log mflapper #fit, *junk = np.linalg.lstsq(np.hstack((np.ones((5,1)), np.log(mflapper_arr[:,np.newaxis]))), # np.log(ddampers_arr[:1,:].flatten())) # print(np.exp(fit[0]) * mflapper_modeldata**fit[1]*1000) print(popt) print(np.exp(popt[0]), popt[1], popt[2]) def ddampers_est(mflapper, ldamper, popt): return np.exp(popt[0])*mflapper**popt[1]*ldamper**popt[2] # In[36]: fig = plt.figure(figsize=fs) colors = 'rgbc' markers = 'so^v' lines = ['-', '--', ':', '-.'] for idx, ldamper in enumerate(ldamper_arr): ddampers = ddampers_arr[idx][ddampers_mask[idx]] # estimate mflapper = mflapper_arr [ddampers_mask[idx]] plt.loglog(mflapper*1e6, ddampers*1000, markers[idx]+lines[idx], color='k', linewidth=2) if show_variability: ddampers2 = ddampers2_arr[idx][ddampers_mask[idx]] ddampers3 = ddampers3_arr[idx][ddampers_mask[idx]] plt.fill_between(mflapper*1e6, ddampers2*1000, ddampers3*1000, facecolor=[.8,.8,.8], linewidth=0.5) mflapper_modeldata = np.logspace(-6, -3, 10) for idx, ldamper in enumerate(ldamper_arr): # fit model plt.loglog(mflapper_modeldata*1e6, ddampers_est(mflapper_modeldata, ldamper, popt)*1000, 'k', lw=.5) #plt.loglog(mflapper_modeldata*1e6, # (np.exp(fit[0]) * mflapper_modeldata**fit[1])*1000) plt.xlabel('thruster mass $m_t$ (mg)') plt.ylabel('damper distance $d$ (mm)') plt.ylim(35, 300) plt.xlim(15, 400) yticks2 = (40, 50, 70, 100, 200) #xticks2 = mflapper_arr*1e6 xticks2 = (20, 40, 70, 100, 200, 300) plt.gca().set_yticks(yticks2) plt.gca().set_yticklabels([str(ytick) for ytick in yticks2]) plt.gca().set_xticks(xticks2) plt.gca().set_xticklabels(["%.f"%xtick for xtick in xticks2]) plt.legend(['$l$ = %2.f mm'%(ldamper*1000) for ldamper in ldamper_arr], loc='upper left') plt.grid(which='minor') plt.grid(which='major') plt.savefig('mass_v_damper_distance_15deg.pdf') # ### theta optimization - wings # In[37]: # find necessary damper length with wings, large oscillation amplitude thetamax_desired = 30*d2r show_variability = 1 time = time_fast ldamper_arr = np.array((.01, .015, .02, .03)) mflapper_arr = np.array((20e-6, 40e-6, 80e-6, 160e-6, 320e-6)) ddampers_arr = np.empty((len(ldamper_arr), len(mflapper_arr))) problem_arr = np.zeros((len(ldamper_arr), len(mflapper_arr))) if show_variability: theta_shift = 1*d2r # for showing sensitivitity ddampers2_arr = np.empty((len(ldamper_arr), len(mflapper_arr))) thetamax_desired2 = thetamax_desired - theta_shift ddampers3_arr = np.empty((len(ldamper_arr), len(mflapper_arr))) thetamax_desired3 = thetamax_desired + theta_shift problem2_arr = np.zeros((len(ldamper_arr), len(mflapper_arr))) problem3_arr = np.zeros((len(ldamper_arr), len(mflapper_arr))) for idx1, ldamper in enumerate(ldamper_arr): for idx2, mflapper in enumerate(mflapper_arr): ddampers, details = find_ddampers_theta(mflapper, ldamper, thetamax_desired, include_wings=1) ddampers_arr[idx1, idx2] = ddampers #print "for ldamper=%2.f mm, mflapper=%2.f mg, thetamax_desired=%2.f deg, result is ddampers=%3.2f mm"%\ # (ldamper*1000, mflapper*1e6, thetamax_desired*r2d, ddampers*1000) problem = not details[-2].startswith("The relative error between two consecutive iterates is at most") if problem: print(("no convergence. details: ", details)) problem_arr[idx1, idx2] = problem theta_error = details[1]['fvec'] * r2d if theta_error > 1: print("large theta error=%3.2f deg"%theta_error ) if show_variability: ddampers, details = find_ddampers_theta(mflapper, ldamper, thetamax_desired2, include_wings=1) ddampers2_arr[idx1, idx2] = ddampers if not details[-2].startswith("The relative error between two consecutive iterates is at most"): print(("no convergence. details: ", details)) problem2_arr[idx1, idx2] = 1 theta_error = details[1]['fvec'] * r2d if theta_error > 1: print("large theta error=%3.2f deg"%theta_error) ddampers, details = find_ddampers_theta(mflapper, ldamper, thetamax_desired3, include_wings=1) ddampers3_arr[idx1, idx2] = ddampers if not details[-2].startswith("The relative error between two consecutive iterates is at most"): print(("no convergence. details: ", details)) problem3_arr[idx1, idx2] = 1 theta_error = details[1]['fvec'] * r2d if theta_error > 1: print("large theta error=%3.2f deg"%theta_error ) print(ddampers_arr) print(problem_arr) if show_variability: print(problem2_arr); print(problem3_arr) # In[38]: # calculate masks for dampers that are too close to body print(ddampers_arr) ddampers_mask = [] for idx, ldamper in enumerate(ldamper_arr): # use ddampers #ddampers_mask.append(ddampers_arr[idx] > ldamper_arr[idx] + 2*zhibee_rw) # use lower bound ddampers3 ddampers_mask.append(ddampers3_arr[idx] > ldamper_arr[idx] + 2*zhibee_rw) print(ddampers_mask) # hack because 1 data point is nonsensical ddampers_mask[-1][0] = False print(ddampers_mask) # In[39]: # same fit for flapping-wings case mflapper_arr2, ldamper_arr2 = np.meshgrid(mflapper_arr, ldamper_arr) xdata = (np.log(mflapper_arr2[np.vstack(ddampers_mask)]), np.log(ldamper_arr2[np.vstack(ddampers_mask)])) ydata = np.log(ddampers_arr[np.vstack(ddampers_mask)]) popt, pcov = curve_fit(candidate_function, xdata, ydata) print(popt) #print(np.exp(popt[0]), popt[1]*1e6, popt[2]*1000) print(np.exp(popt[0]), popt[1], popt[2]) def ddampers_est(mflapper, ldamper, popt): return np.exp(popt[0])*mflapper**popt[1]*ldamper**popt[2] # In[40]: fig = plt.figure(figsize=fs) colors = 'rgbc' markers = 'so^v' lines = ['-', '--', ':', '-.'] #for idx, ldamper in enumerate(ldamper_arr): # ddampers = np.clip(ddampers_arr[idx], ldamper_arr[idx], np.inf) # plt.semilogy(mflapper_arr*1e6, ddampers*1000, markers[idx]+lines[idx], color='k', linewidth=1) #plt.xlabel('flapper mass (mg)') #plt.ylabel('damper distance (mm)') for idx, ldamper in enumerate(ldamper_arr): ddampers = ddampers_arr[idx][ddampers_mask[idx]] # estimate mflapper = mflapper_arr [ddampers_mask[idx]] plt.loglog(mflapper*1e6, ddampers*1000, markers[idx]+lines[idx], color='k', linewidth=2) # variability ddampers2 = ddampers2_arr[idx][ddampers_mask[idx]] ddampers3 = ddampers3_arr[idx][ddampers_mask[idx]] plt.fill_between(mflapper*1e6, ddampers2*1000, ddampers3*1000, facecolor=[.8,.8,.8], linewidth=0.5) for idx, ldamper in enumerate(ldamper_arr): plt.loglog(mflapper_modeldata*1e6, ddampers_est(mflapper_modeldata, ldamper, popt)*1000, 'k', lw=.5) #plt.loglog(mflapper_modeldata*1e6, # (np.exp(fit[0]) * mflapper_modeldata**fit[1])*1000) #print(ddampers_est(mflapper_modeldata, ldamper, popt)*1000) plt.xlabel('thruster mass $m_t$ (mg)') plt.ylabel('damper distance $d$ (mm)') plt.ylim(35, 300) plt.xlim(15, 400) yticks2 = (40, 50, 70, 100, 200) #xticks2 = mflapper_arr*1e6 xticks2 = (20, 40, 70, 100, 200, 300) plt.gca().set_yticks(yticks2) plt.gca().set_yticklabels([str(ytick) for ytick in yticks2]) plt.gca().set_xticks(xticks2) plt.gca().set_xticklabels(["%.f"%xtick for xtick in xticks2]) #plt.gca().set_xticks(xticks2) #plt.gca().set_xticklabels([sprintf("%1f"%xtick) for xtick in xticks]) plt.legend(['$d$ = %2.f mm'%(ldamper*1000) for ldamper in ldamper_arr], loc='best') plt.grid(which='minor') plt.grid(which='major') plt.savefig('mass_v_damper_distance_30deg_wings.pdf') # In[ ]: