def phi_distance(polar_drive: Iterable[float], polar_driven: Iterable[float], k: int = 1, distance_function=standard_deviation_distance) -> Tuple[float, List[float], List[float], float, float]: """ Get the distance between two phi functions :param polar_drive: polar coordinates of the drive contour :param polar_driven: polar coordinates of the driven contour :param k: the k parameter :param distance_function: the distance function used to calculate the distance :param torque_weight: weight of torque term :return: the distance between phi functions, two d_phi functions and center distances """ y_drive, dist_drive, phi_drive = compute_dual_gear(polar_drive) y_driven, dist_driven, phi_driven = compute_dual_gear(polar_driven) y_driven, dist_driven, phi_driven = compute_dual_gear(y_driven) d_phi_drive = list(differentiate_function(pre_process(phi_drive))) d_phi_driven = list(differentiate_function(pre_process(phi_driven))) # d_phi_driven = [1.0 / phi for phi in differentiate_function(pre_process(phi_driven))] offset = align(d_phi_drive, d_phi_driven, distance_function=distance_function, k=k) if k != 1: assert len(d_phi_driven) % k == 0 extended_d_phi_driven = list( extend_part(d_phi_driven, offset, offset + int(len(d_phi_driven) / k), len(d_phi_drive))) offset = 0 else: extended_d_phi_driven = d_phi_driven return distance_function(d_phi_drive, extended_d_phi_driven[offset:] + extended_d_phi_driven[:offset]), \ d_phi_drive, d_phi_driven, dist_drive, dist_driven
def gradual_average(drive_model: Model, driven_model: Model, drive_center: Tuple[float, float], driven_center: Tuple[float, float], count_of_averages: int): """ Gradually average two contours :param drive_model: The drive model :param driven_model: The driven model :param drive_center: center of drive :param driven_center: center of driven :param count_of_averages: count of average values :return: None """ debugger, opt_config, plotter = init((drive_model, driven_model), 'optimization_config.yaml') drive_contour, driven_contour = get_inputs(debugger, drive_model, driven_model, plotter) distance, d_drive, d_driven, dist_drive, dist_driven = \ contour_distance(drive_contour, drive_center, driven_contour, driven_center, 1024) for average in np.linspace(0, 1, count_of_averages, True): center_dist = dist_drive * 0.5 + dist_driven * 0.5 reconstructed_drive = rebuild_polar(center_dist, align_and_average(d_drive, d_driven, average)) reconstructed_driven, center_dist, phi = compute_dual_gear(list(reconstructed_drive)) reconstructed_drive_contour = toCartesianCoordAsNp(reconstructed_drive, 0, 0) reconstructed_driven_contour = toCartesianCoordAsNp(reconstructed_driven, center_dist, 0) reconstructed_driven_contour = np.array(rotate(reconstructed_driven_contour, phi[0], (center_dist, 0))) average_str = '%1.8f' % average plotter.draw_contours(debugger.file_path(average_str + '.png'), [ ('math_drive', reconstructed_drive_contour), ('math_driven', reconstructed_driven_contour) ], [(0, 0), (center_dist, 0)]) save_contour(debugger.file_path(average_str + '_drive.dat'), reconstructed_drive_contour) save_contour(debugger.file_path(average_str + '_driven.dat'), reconstructed_driven_contour)
def main_stage_one(drive_model: Model, driven_model: Model, do_math_cut=True, math_animation=False, reply_cut_anim=False, save_cut_anim=True, opt_config='optimization_config.yaml', k=1): # initialize logging system, configuration files, etc. opt_config = os.path.join(os.path.dirname(__file__), opt_config) debugger, opt_config, plotter = init((drive_model, driven_model), opt_config) logger.info(f'Optimizing {drive_model.name} with {driven_model.name}') plt.close('all') character_str = f'{drive_model.name}, {driven_model.name}' print('starting ' + character_str) # get input polygons cart_input_drive, cart_input_driven = get_inputs(debugger, drive_model, driven_model, plotter, uniform=False) counts = cart_input_drive.shape[0], cart_input_driven.shape[0] start_time = perf_counter_ns() cart_input_drive, cart_input_driven = get_inputs(debugger, drive_model, driven_model, plotter, uniform=True) print('pre-processing done for ' + character_str) pre_processing = perf_counter_ns() # optimization center, center_distance, cart_drive, score = optimize_center(cart_input_drive, cart_input_driven, debugger, opt_config, plotter, k=k) print('optimization done for ' + character_str) logger.info(f'score = {score}') drive_model.center_point = (0, 0) cart_drive = shape_factory.uniform_and_smooth(cart_drive, drive_model) start_time = perf_counter_ns() *_, center_distance, phi = compute_dual_gear(toExteriorPolarCoord(Point(0, 0), cart_drive, 1024), k) # add teeth cart_drive = add_teeth((0, 0), center_distance, debugger, cart_drive, drive_model, plotter) # rotate and cut cart_driven_gear = rotate_and_carve(cart_drive, (0, 0), center_distance, debugger, drive_model, phi, None, k=k, replay_anim=False, save_anim=False) rotate_and_cut = perf_counter_ns() logger.info('rotate_and_carve done in' + str(rotate_and_cut - start_time)) logger.info('count of follower:' + str(cart_driven_gear.shape[0])) # save 2D contour fabrication.generate_2d_obj(debugger, 'drive_2d_(0,0).obj', cart_drive) fabrication.generate_2d_obj(debugger, f'driven_2d_({center_distance, 0}).obj', cart_driven_gear) # generate 3D mesh with axle hole fabrication.generate_3D_with_axles(8, debugger.file_path('drive_2d_(0,0).obj'), debugger.file_path(f'driven_2d_({center_distance, 0}).obj'), (0, 0), (center_distance, 0), debugger, 6)
def math_cut(drive_model: Model, cart_drive: np.ndarray, reporter: Reporter, plotter: Optional[Plotter], animation=False, center_point: Optional[Tuple[float, float]] = None): center = center_point or drive_model.center_point polar_math_drive = toExteriorPolarCoord(Point(center[0], center[1]), cart_drive, drive_model.sample_num) polar_math_driven, center_distance, phi = compute_dual_gear( polar_math_drive, k=drive_model.k) if animation: plot_sampled_function((polar_math_drive, polar_math_driven), (phi, ), reporter.get_math_debug_dir_name(), 100, 0.001, [(0, 0), (center_distance, 0)], (8, 8), ((-0.5, 1.5), (-1.1, 1.1)), plotter=plotter) # save figures plotter.draw_contours( reporter.file_path('math_drive.png'), [('math_drive', toCartesianCoordAsNp(polar_math_drive, 0, 0))], None) plotter.draw_contours( reporter.file_path('math_driven.png'), [('math_driven', toCartesianCoordAsNp(polar_math_driven, 0, 0))], None) plotter.draw_contours( reporter.file_path('math_results.png'), [('math_drive', toCartesianCoordAsNp(polar_math_drive, 0, 0)), ('math_driven', np.array( rotate( list( toCartesianCoordAsNp(polar_math_driven, center_distance, 0)), phi[0], (center_distance, 0))))], [(0, 0), (center_distance, 0)]) logging.info('math rotate complete') logging.info(f'Center Distance = {center_distance}') return center_distance, phi, polar_math_drive, polar_math_driven
def sample_result(drive_contour: np.ndarray, drive_polygon: Polygon, sample_window: Tuple[float, float, float, float], k: int) \ -> Union[Tuple[float, float, float, np.ndarray], None]: """ sample the center of the sample window and get the driven gear :param drive_contour: the drive gear contour :param drive_polygon: the driving polygon :param sample_window: the window in which to take sample (minx, maxx, miny, maxy) :param k: the drive/driven ratio :return: center_x, center_y, center_distance, the driven gear | None if not possible """ min_x, max_x, min_y, max_y = sample_window center_x, center_y = (min_x + max_x) / 2, (min_y + max_y) / 2 if not drive_polygon.contains(Point(center_x, center_y)): return None polar_coordinates = toExteriorPolarCoord(Point(center_x, center_y), drive_contour, drive_contour.shape[0]) driven, center_distance, phi = compute_dual_gear(polar_coordinates, k) driven_contour = toCartesianCoordAsNp(driven, 0, 0) return center_x, center_y, center_distance, driven_contour
def optimize_center(cart_input_drive, cart_input_driven, debugger, opt_config, plotter, k=1): debug_suite = ReportingSuite(debugger, plotter, plt.figure(figsize=(16, 9))) results = sampling_optimization( cart_input_drive, cart_input_driven, opt_config['sampling_count'], opt_config['keep_count'], opt_config['resampling_accuracy'], opt_config['max_sample_depth'], debug_suite, opt_config['torque_weight'], k=k, mismatch_penalty=opt_config['mismatch_penalty']) results.sort(key=lambda total_score, *_: total_score) best_result = results[0] logging.info(f'Best result with score {best_result[0]}') score, polar_drive = best_result polar_driven, center_distance, phi = compute_dual_gear(polar_drive, k) drive_contour = toCartesianCoordAsNp(polar_drive, 0, 0) driven_contour = toCartesianCoordAsNp(polar_driven, center_distance, 0) driven_contour = np.array( rotate(driven_contour, phi[0], (center_distance, 0))) plotter.draw_contours(debugger.file_path('optimize_result.png'), [('carve_drive', drive_contour), ('carve_driven', driven_contour)], [(0, 0), (center_distance, 0)]) save_contour(debugger.file_path('optimized_drive.dat'), drive_contour) save_contour(debugger.file_path('optimized_driven.dat'), driven_contour) return (0, 0), center_distance, toCartesianCoordAsNp(polar_drive, 0, 0), score
def sampling_optimization(drive_contour: np.ndarray, driven_contour: np.ndarray, sampling_count: Tuple[int, int], keep_count: int, sampling_accuracy: int, iteration_count: int, debugging_suite: ReportingSuite, torque_weight: float = 0.0, k: int = 1, mismatch_penalty=0.5) -> List[Tuple[float, Polar_T]]: logger.info(f'Initiating Sampling Optimization with torque_weight = {torque_weight},' f' mismatch_penalty = {mismatch_penalty}') logger.info(f'k={k}') drive_polygon = Polygon(drive_contour) driven_polygon = Polygon(driven_contour) min_x, min_y, max_x, max_y = drive_polygon.bounds window_pairs = (min_x, max_x, min_y, max_y) min_x, min_y, max_x, max_y = driven_polygon.bounds window_pairs = [(window_pairs, (min_x, max_x, min_y, max_y))] x_sample, y_sample = sampling_count # start iteration results = [] # dummy for iteration in range(iteration_count): path = debugging_suite.debugger.file_path('iteration_' + str(iteration)) os.makedirs(path, exist_ok=True) # if k == 1: window_pairs = list(itertools.chain.from_iterable([ itertools.product(split_window(drive_window, x_sample, y_sample), split_window(driven_window, x_sample, y_sample)) for drive_window, driven_window in window_pairs ])) # else: # # do not allow the driven gear with k to move center # window_pairs = list(itertools.chain.from_iterable([ # itertools.product(split_window(drive_window, x_sample, y_sample), # [driven_window]) # for drive_window, driven_window in window_pairs # ])) results = sample_in_windows(drive_contour, driven_contour, window_pairs, keep_count, debugging_suite.sub_suite(os.path.join(path, 'result_')), sampling_accuracy=sampling_accuracy, torque_weight=torque_weight, k=k, mismatch_penalty=mismatch_penalty) window_pairs = [(drive_window, driven_window) for _, drive_window, driven_window, *__ in results] if debugging_suite.plotter is not None: for index, final_result in enumerate(results): score, *_, reconstructed_drive, max_phi, m_penalty = final_result driven, center_distance, phi = compute_dual_gear(reconstructed_drive, k) final_drive = toCartesianCoordAsNp(reconstructed_drive, 0, 0) final_driven = np.array( psf_rotate(toCartesianCoordAsNp(driven, center_distance, 0), phi[0], (center_distance, 0))) debugging_suite.plotter.draw_contours( os.path.join(path, f'final_result_{index}_{"%.6f" % (score,)}.png'), [('carve_drive', final_drive), ('carve_driven', final_driven)], [(0, 0), (center_distance, 0)]) save_contour(os.path.join(path, f'final_result_{index}_drive.dat'), final_drive) save_contour(os.path.join(path, f'final_result_{index}_driven.dat'), final_driven) d_drive = differentiate_function(pre_process(phi)) save_information(os.path.join(path, f'final_result_{index}.txt'), (0, 0), (center_distance, 0), score, max_dphi_drive=max(d_drive), actual_distance=score - torque_weight * max_phi, mismatch_penalty=m_penalty) results = results[:keep_count] results.sort(key=lambda dist, *_: dist) results = [(score, reconstructed_drive) for score, drive_window, driven_window, reconstructed_drive, max_phi, m_penalty in results] return results
def sample_in_windows(drive_contour: np.ndarray, driven_contour: np.ndarray, window_pairs: List[Tuple[Window_T, Window_T]], keep_count: int, debugging_suite: ReportingSuite, k: int = 1, center_determine_function=center_of_window, sampling_accuracy=1024, torque_weight=0.0, mismatch_penalty: float = 0.5) \ -> List[Tuple[float, Window_T, Window_T, Polar_T, float, float]]: """ find the best sample windows :param drive_contour: the drive contour :param driven_contour: the driven contour :param window_pairs: pair of windows :param keep_count: count of the windows to be kept :param debugging_suite: the debugging suite :param k: the k of the opposing gear :param center_determine_function: function to determine from window to center :param sampling_accuracy: number of samples when converting to polar contour :param torque_weight: weight of torque term :param mismatch_penalty: penalty for the extended d_driven not matching start and end (only for k>1) :return: list of (score, drive_window, driven_window, reconstructed_drive, max_phi, mismatch_penalty) """ results = [] path_prefix = debugging_suite.path_prefix # store in a directory if debugging_suite.figure is not None: debugging_suite.figure.clear() # clear the figure plt.figure(debugging_suite.figure.number) subplots = debugging_suite.figure.subplots(2, 2) update_polygon_subplots(drive_contour, driven_contour, subplots[0]) else: subplots = None for index, (drive_window, driven_window) in enumerate(window_pairs): center_drive = center_determine_function(drive_window) center_driven = center_determine_function(driven_window) if not (Polygon(drive_contour).contains(Point(*center_drive)) and Polygon(driven_contour).contains( Point(*center_driven))): # not good windows continue # polar, dist, phi = compute_dual_gear( # toExteriorPolarCoord(Point(*center_driven), driven_contour, sampling_accuracy), 1) # polar, dist, phi = compute_dual_gear(polar, 1) # d_phi = differentiate_function(pre_process(phi)) # fig, splts = plt.subplots(2, 2, figsize=(9, 9)) # splts[0][1].plot(*driven_contour.transpose()) # splts[0][1].axis('equal') # splts[1][0].plot(np.linspace(0, 2 * math.pi, len(phi), endpoint=False), phi) # splts[1][1].plot(np.linspace(0, 2 * math.pi, len(d_phi), endpoint=False), d_phi, color='red') # plt.show() #checkpoint passed distance, d_drive, d_driven, dist_drive, dist_driven = \ contour_distance(drive_contour, center_drive, driven_contour, center_driven, sampling_accuracy, k) # splts[1][1].plot(np.linspace(0, 2 * math.pi, len(d_driven), endpoint=False), d_driven, color='blue') # plt.show() reconstructed_drive = rebuild_polar(0.9, align_and_average(d_drive, d_driven, k=k)) list_reconstructed_drive = list(reconstructed_drive) max_phi = max(differentiate_function(pre_process(compute_dual_gear(list_reconstructed_drive)[-1]))) final_score = distance + torque_weight * max_phi m_penalty = None if k != 1: m_penalty = mismatch_penalty * abs(d_driven[0] - d_driven[-1]) logger.info(f'{index} gear: mismatching start = {d_driven[0]}, end = {d_driven[-1]}, penalized {m_penalty}') final_score += m_penalty logging.info(f'{index} gear: plain score={distance}, max of phi\'={max_phi}') results.append((final_score, drive_window, driven_window, list_reconstructed_drive, max_phi, m_penalty)) if subplots is not None: update_polygon_subplots(drive_contour, driven_contour, subplots[0]) # clear sample regions reconstructed_driven, plt_center_dist, plt_phi = compute_dual_gear(list_reconstructed_drive, k) reconstructed_drive_contour = toCartesianCoordAsNp(reconstructed_drive, 0, 0) reconstructed_driven_contour = toCartesianCoordAsNp(reconstructed_driven, 0, 0) update_polygon_subplots(reconstructed_drive_contour, reconstructed_driven_contour, subplots[1]) min_x, max_x, min_y, max_y = drive_window sample_region = Rectangle((min_x, min_y), max_x - min_x, max_y - min_y, color='red', fill=False) subplots[0][0].add_patch(sample_region) min_x, max_x, min_y, max_y = driven_window sample_region = Rectangle((min_x, min_y), max_x - min_x, max_y - min_y, color='red', fill=False) subplots[0][1].add_patch(sample_region) subplots[0][0].scatter(*center_drive, 5) subplots[0][1].scatter(*center_driven, 5) subplots[1][0].scatter(0, 0, 5) subplots[1][1].scatter(0, 0, 5) plt.savefig(path_prefix + f'{index}.png') save_contour(path_prefix + f'{index}_drive.dat', reconstructed_drive_contour) save_contour(path_prefix + f'{index}_driven.dat', reconstructed_driven_contour) save_information(path_prefix + f'{index}.txt', center_drive, center_driven, final_score, max_dphi_drive=max_phi, actual_distance=distance, mismatch_penalty=m_penalty) # get information about thee phi' functions original_figure = plt.gcf() figure, new_subplots = plt.subplots(2, 2, figsize=(16, 16)) new_subplots[0][0].plot(np.linspace(0, 2 * math.pi, len(d_drive), endpoint=False), d_drive) new_subplots[0][0].axis([0, 2 * math.pi, 0, 2 * math.pi]) new_subplots[0][1].plot(np.linspace(0, 2 * math.pi, len(d_driven), endpoint=False), d_driven) new_subplots[0][1].axis([0, 2 * math.pi, 0, 2 * math.pi]) new_subplots[1][0].plot(np.linspace(0, 2 * math.pi, len(d_drive), endpoint=False), align_and_average(d_drive, d_driven, k=k)) new_subplots[1][0].axis([0, 2 * math.pi, 0, 2 * math.pi]) debugging_suite.plotter.draw_contours( path_prefix + f'drive_contour_{index}.png', [('carve_drive', drive_contour)], [center_drive]) debugging_suite.plotter.draw_contours( path_prefix + f'driven_contour_{index}.png', [('carve_driven', driven_contour)], [center_driven]) final_driven = np.array( psf_rotate(toCartesianCoordAsNp(reconstructed_driven, plt_center_dist, 0), plt_phi[0], (plt_center_dist, 0))) debugging_suite.plotter.draw_contours( path_prefix + f'reconstructed_contour_{index}.png', [('carve_drive', reconstructed_drive_contour), ('carve_driven', final_driven)], [(0, 0), (plt_center_dist, 0)]) if k != 1: # then offset shall be 0 new_subplots[1][1].plot(np.linspace(0, 2 * math.pi, len(d_drive), endpoint=False), extend_part(d_driven, 0, int(len(d_driven) / k), len(d_drive))) new_subplots[1][1].axis('equal') plt.axis('equal') plt.savefig(path_prefix + f'{index}_functions.png') plt.close() plt.figure(original_figure.number) results.sort(key=lambda dist, *_: dist) return results[:keep_count]