[docs]defmerge_dicts(a,b):""" Merge 2 dictionaries and their subdictionaries. """forkeyinb:ifkeyinaandisinstance(a[key],dict)andisinstance(b[key],dict):merge_dicts(a[key],b[key])else:a[key]=b[key]returna
[docs]defobj_str_insert(__str__):""" Decorator to insert the return value of __str__ into '<classname {returnvalue} at 0x...>'. """@_ft.wraps(__str__)defwrapper(self):obj_str=object.__repr__(self)returnobj_str.replace("at 0x",f"{__str__(self)} at 0x")returnwrapper
[docs]@_ctxlib.contextmanagerdefsuppress_stdout():""" Context manager that attempts to silence regular stdout and stderr. Some binary components may yet circumvene this if they access the underlying OS's stdout directly, like streaming to `/dev/stdout`. """withopen(_os.devnull,"w")asdevnull:old_stdout=_sys.stdoutold_stderr=_sys.stderr_sys.stdout=devnull_sys.stderr=devnulltry:yieldfinally:_sys.stdout=old_stdout_sys.stderr=old_stderr
[docs]defget_qualified_class_name(x):""" Return an object's module and class name. """if_inspect.isclass(x):returnf"{x.__module__}.{str(x.__name__)}"returnf"{x.__class__.__module__}.{str(x.__class__.__name__)}"
[docs]deflistify_input(value):""" Turn any non-list values into a list containing the value. Sequences will be converted to a list using `list()`, `None` will be replaced by an empty list. """ifvalueisNone:return[]ifisinstance(value,str):return[str]try:returnlist(value)exceptException:return[value]
[docs]defsanitize_ndarray(arr_input,shape,dtype=None):""" Convert an object to an ndarray and shape, avoiding to copy it wherever possible. """kwargs={"copy":False}ifdtypeisnotNone:kwargs["dtype"]=dtypearr=_np.array(arr_input,**kwargs)arr.shape=shapereturnarr
[docs]defassert_samelen(*args):""" Assert that all input arguments have the same length. """len_=Noneassertall(((len_:=len(arg))iflen_isNoneelselen(arg))==len_forarginargs),"Input arguments should be of same length."
[docs]defimmutable():""" Decorator to mark a method as immutable, so that any calls to it return, and are performed on, a copy of the instance. """defimmutable_decorator(f):@_ft.wraps(f)defimmutable_action(self,*args,**kwargs):new_instance=self.__copy__()f(new_instance,*args,**kwargs)returnnew_instancereturnimmutable_actionreturnimmutable_decorator
[docs]defunique(iter_:_t.Iterable[_t.Any]):""" Return a new list containing all the unique elements of an iterator. """return[*set(iter_)]
[docs]defrotation_matrix_from_vectors(vec1,vec2):""" Find the rotation matrix that aligns vec1 to vec2. :param vec1: A 3d "source" vector :param vec2: A 3d "destination" vector :return mat: A transform matrix (3x3) which when applied to vec1, aligns it with vec2. """if(_np.isnan(vec1).any()or_np.isnan(vec2).any()ornot_np.any(vec1)ornot_np.any(vec2)):raiseValueError("Vectors should not contain nan and their norm should not be 0.")a=(vec1/_np.linalg.norm(vec1)).reshape(3)b=(vec2/_np.linalg.norm(vec2)).reshape(3)v=_np.cross(a,b)ifany(v):# if not all zeros thenc=_np.dot(a,b)s=_np.linalg.norm(v)kmat=_np.array([[0,-v[2],v[1]],[v[2],0,-v[0]],[-v[1],v[0],0]])return_np.eye(3)+kmat+kmat.dot(kmat)*((1-c)/(s**2))else:return_np.eye(3)# cross of all zeros only occurs on identical directions