| 
									
										
										
										
											2018-03-02 18:18:35 -05:00
										 |  |  | # Simple math helper functions | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2019-12-17 09:51:45 -05:00
										 |  |  | # Copyright (C) 2018-2019  Kevin O'Connor <kevin@koconnor.net> | 
					
						
							| 
									
										
										
										
											2018-03-02 18:18:35 -05:00
										 |  |  | # | 
					
						
							|  |  |  | # This file may be distributed under the terms of the GNU GPLv3 license. | 
					
						
							| 
									
										
										
										
											2019-12-05 14:32:13 -05:00
										 |  |  | import math, logging, multiprocessing, traceback | 
					
						
							| 
									
										
										
										
											2019-12-17 09:51:45 -05:00
										 |  |  | import queuelogger | 
					
						
							| 
									
										
										
										
											2018-06-22 13:57:15 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Coordinate descent | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							| 
									
										
										
										
											2018-03-02 18:18:35 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | # Helper code that implements coordinate descent | 
					
						
							|  |  |  | def coordinate_descent(adj_params, params, error_func): | 
					
						
							|  |  |  |     # Define potential changes | 
					
						
							|  |  |  |     params = dict(params) | 
					
						
							|  |  |  |     dp = {param_name: 1. for param_name in adj_params} | 
					
						
							|  |  |  |     # Calculate the error | 
					
						
							|  |  |  |     best_err = error_func(params) | 
					
						
							| 
									
										
										
										
											2018-07-24 08:32:35 -04:00
										 |  |  |     logging.info("Coordinate descent initial error: %s", best_err) | 
					
						
							| 
									
										
										
										
											2018-03-02 18:18:35 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     threshold = 0.00001 | 
					
						
							|  |  |  |     rounds = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while sum(dp.values()) > threshold and rounds < 10000: | 
					
						
							|  |  |  |         rounds += 1 | 
					
						
							|  |  |  |         for param_name in adj_params: | 
					
						
							|  |  |  |             orig = params[param_name] | 
					
						
							|  |  |  |             params[param_name] = orig + dp[param_name] | 
					
						
							|  |  |  |             err = error_func(params) | 
					
						
							|  |  |  |             if err < best_err: | 
					
						
							|  |  |  |                 # There was some improvement | 
					
						
							|  |  |  |                 best_err = err | 
					
						
							|  |  |  |                 dp[param_name] *= 1.1 | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             params[param_name] = orig - dp[param_name] | 
					
						
							|  |  |  |             err = error_func(params) | 
					
						
							|  |  |  |             if err < best_err: | 
					
						
							|  |  |  |                 # There was some improvement | 
					
						
							|  |  |  |                 best_err = err | 
					
						
							|  |  |  |                 dp[param_name] *= 1.1 | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             params[param_name] = orig | 
					
						
							|  |  |  |             dp[param_name] *= 0.9 | 
					
						
							| 
									
										
										
										
											2019-02-27 13:04:56 -05:00
										 |  |  |     logging.info("Coordinate descent best_err: %s  rounds: %d", | 
					
						
							|  |  |  |                  best_err, rounds) | 
					
						
							| 
									
										
										
										
											2018-03-02 18:18:35 -05:00
										 |  |  |     return params | 
					
						
							| 
									
										
										
										
											2018-06-22 13:57:15 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-15 18:52:28 -04:00
										 |  |  | # Helper to run the coordinate descent function in a background | 
					
						
							|  |  |  | # process so that it does not block the main thread. | 
					
						
							|  |  |  | def background_coordinate_descent(printer, adj_params, params, error_func): | 
					
						
							|  |  |  |     parent_conn, child_conn = multiprocessing.Pipe() | 
					
						
							|  |  |  |     def wrapper(): | 
					
						
							| 
									
										
										
										
											2019-12-17 09:51:45 -05:00
										 |  |  |         queuelogger.clear_bg_logging() | 
					
						
							| 
									
										
										
										
											2019-12-05 14:32:13 -05:00
										 |  |  |         try: | 
					
						
							|  |  |  |             res = coordinate_descent(adj_params, params, error_func) | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             child_conn.send((True, traceback.format_exc())) | 
					
						
							|  |  |  |             child_conn.close() | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         child_conn.send((False, res)) | 
					
						
							| 
									
										
										
										
											2018-10-15 18:52:28 -04:00
										 |  |  |         child_conn.close() | 
					
						
							|  |  |  |     # Start a process to perform the calculation | 
					
						
							|  |  |  |     calc_proc = multiprocessing.Process(target=wrapper) | 
					
						
							|  |  |  |     calc_proc.daemon = True | 
					
						
							|  |  |  |     calc_proc.start() | 
					
						
							|  |  |  |     # Wait for the process to finish | 
					
						
							|  |  |  |     reactor = printer.get_reactor() | 
					
						
							|  |  |  |     gcode = printer.lookup_object("gcode") | 
					
						
							|  |  |  |     eventtime = last_report_time = reactor.monotonic() | 
					
						
							|  |  |  |     while calc_proc.is_alive(): | 
					
						
							|  |  |  |         if eventtime > last_report_time + 5.: | 
					
						
							|  |  |  |             last_report_time = eventtime | 
					
						
							| 
									
										
										
										
											2019-03-04 13:04:18 -05:00
										 |  |  |             gcode.respond_info("Working on calibration...", log=False) | 
					
						
							| 
									
										
										
										
											2018-10-15 18:52:28 -04:00
										 |  |  |         eventtime = reactor.pause(eventtime + .1) | 
					
						
							|  |  |  |     # Return results | 
					
						
							| 
									
										
										
										
											2019-12-05 14:32:13 -05:00
										 |  |  |     is_err, res = parent_conn.recv() | 
					
						
							|  |  |  |     if is_err: | 
					
						
							|  |  |  |         raise Exception("Error in coordinate descent: %s" % (res,)) | 
					
						
							| 
									
										
										
										
											2018-10-15 18:52:28 -04:00
										 |  |  |     calc_proc.join() | 
					
						
							|  |  |  |     parent_conn.close() | 
					
						
							|  |  |  |     return res | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-22 13:57:15 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Trilateration | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Trilateration finds the intersection of three spheres.  See the | 
					
						
							|  |  |  | # wikipedia article for the details of the algorithm. | 
					
						
							|  |  |  | def trilateration(sphere_coords, radius2): | 
					
						
							|  |  |  |     sphere_coord1, sphere_coord2, sphere_coord3 = sphere_coords | 
					
						
							|  |  |  |     s21 = matrix_sub(sphere_coord2, sphere_coord1) | 
					
						
							|  |  |  |     s31 = matrix_sub(sphere_coord3, sphere_coord1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     d = math.sqrt(matrix_magsq(s21)) | 
					
						
							|  |  |  |     ex = matrix_mul(s21, 1. / d) | 
					
						
							|  |  |  |     i = matrix_dot(ex, s31) | 
					
						
							|  |  |  |     vect_ey = matrix_sub(s31, matrix_mul(ex, i)) | 
					
						
							|  |  |  |     ey = matrix_mul(vect_ey, 1. / math.sqrt(matrix_magsq(vect_ey))) | 
					
						
							|  |  |  |     ez = matrix_cross(ex, ey) | 
					
						
							|  |  |  |     j = matrix_dot(ey, s31) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     x = (radius2[0] - radius2[1] + d**2) / (2. * d) | 
					
						
							|  |  |  |     y = (radius2[0] - radius2[2] - x**2 + (x-i)**2 + j**2) / (2. * j) | 
					
						
							|  |  |  |     z = -math.sqrt(radius2[0] - x**2 - y**2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ex_x = matrix_mul(ex, x) | 
					
						
							|  |  |  |     ey_y = matrix_mul(ey, y) | 
					
						
							|  |  |  |     ez_z = matrix_mul(ez, z) | 
					
						
							|  |  |  |     return matrix_add(sphere_coord1, matrix_add(ex_x, matrix_add(ey_y, ez_z))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Matrix helper functions for 3x1 matrices | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_cross(m1, m2): | 
					
						
							|  |  |  |     return [m1[1] * m2[2] - m1[2] * m2[1], | 
					
						
							|  |  |  |             m1[2] * m2[0] - m1[0] * m2[2], | 
					
						
							|  |  |  |             m1[0] * m2[1] - m1[1] * m2[0]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_dot(m1, m2): | 
					
						
							|  |  |  |     return m1[0] * m2[0] + m1[1] * m2[1] + m1[2] * m2[2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_magsq(m1): | 
					
						
							|  |  |  |     return m1[0]**2 + m1[1]**2 + m1[2]**2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_add(m1, m2): | 
					
						
							|  |  |  |     return [m1[0] + m2[0], m1[1] + m2[1], m1[2] + m2[2]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_sub(m1, m2): | 
					
						
							|  |  |  |     return [m1[0] - m2[0], m1[1] - m2[1], m1[2] - m2[2]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_mul(m1, s): | 
					
						
							|  |  |  |     return [m1[0]*s, m1[1]*s, m1[2]*s] | 
					
						
							| 
									
										
										
										
											2025-05-07 00:06:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Matrix helper functions for 3x3 matrices | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_det(a): | 
					
						
							|  |  |  |     x0, x1, x2 = a | 
					
						
							|  |  |  |     return matrix_dot(x0, matrix_cross(x1, x2)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def matrix_inv(a): | 
					
						
							|  |  |  |     x0, x1, x2 = a | 
					
						
							|  |  |  |     inv_det = 1. / matrix_det(a) | 
					
						
							|  |  |  |     return [matrix_mul(matrix_cross(x1, x2), inv_det), | 
					
						
							|  |  |  |             matrix_mul(matrix_cross(x2, x0), inv_det), | 
					
						
							|  |  |  |             matrix_mul(matrix_cross(x0, x1), inv_det)] |