<jupyter_start><jupyter_text>Inside `ulinalg`Inside `ulinalg`, you would define a method consisting of an argument replacer and argument extractor.- The argument extractor is the simpler of the two: It "extracts" the array arguments from the method as a `tuple`.- The argument replacer "replaces" the array arguments inside args, kwargs with the supplied arrays.<jupyter_code>import uarray as ua
from uarray import all_of_type
import unumpy as unp
def solve_argreplacer(args, kwargs, arrays):
    out_args = arrays + args[2:]
    return out_args, kwargs

@unp.create_numpy(solve_argreplacer)
@all_of_type(unp.ndarray)
def solve(a, b, sym_pos=False, lower=False, overwrite_a=False, overwrite_b=False, debug=None, check_finite=True, assume_a='gen', transposed=False):
    return (a, b)<jupyter_output><empty_output><jupyter_text>Inside a Numpy backend for `ulinalg` (ideally, `scipy.linalg` itself)Here, you register the implementation for the backend itself.<jupyter_code>import unumpy.numpy_backend as NumpyBackend
import scipy.linalg as linalg
ua.set_global_backend(NumpyBackend)
NumpyBackend._implementations[solve] = linalg.solve<jupyter_output><empty_output><jupyter_text>Inside PyTorch or a PyTorch backend for `ulinalg` (ideally, PyTorch itself)Here, you have to perform some translation, because the PyTorch API isn't 1:1 with the NumPy API. See [this documentation page](https://pytorch.org/docs/stable/torch.htmltorch.solve). In practice, you would select the function based on the input arguments, but let's ignore that for now.<jupyter_code>import unumpy.torch_backend as TorchBackend
import torch

ua.register_backend(TorchBackend)
def solve_impl(a, b, sym_pos=False, lower=False, overwrite_a=False, overwrite_b=False, debug=None, check_finite=True, assume_a='gen', transposed=False):
    return torch.solve(b, a)[0]
TorchBackend._implementations[solve] = solve_impl<jupyter_output><empty_output><jupyter_text>User ExperienceThe user simply imports the right library and uses everything as normal. Let's try solving this fruit puzzle:<jupyter_code>a = [[3.0, 0.0, 0.0], [1.0, 2.0, 0.0], [0.0, 1.0, -2.0]]
b = [[30.0], [18.0], [2.0]]
ta = torch.tensor(a, dtype=torch.float32); tb = torch.tensor(b, dtype=torch.float32)

tx = solve(ta, tb)
tsol = tx[0] + tx[1] + tx[2]
print(tsol)
print(type(tsol))
import numpy as np

na = np.array(a); nb = np.array(b)

nx = solve(na, nb)
nsol = nx[0] + nx[1] + nx[2]
print(nsol)
print(type(nsol))<jupyter_output>[15.]
<class 'numpy.ndarray'><jupyter_text>Manually choosing the back-endIt's also possible to use context managers to safely set the back-end.<jupyter_code>import uarray as ua
with ua.set_backend(NumpyBackend, coerce=True):
    print(type(solve(a, b)))
        
with ua.set_backend(TorchBackend, coerce=True):
    print(type(solve(ta, tb)))<jupyter_output><class 'numpy.ndarray'>
<class 'torch.Tensor'>
{'fraction_non_alphanumeric': 0.11814051031108004, 'fraction_numerical': 0.014680181754631249, 'mean_word_length': 6.301020408163265, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 22, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '476525', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 1033, 'n_tokens_neox': 1024, 'n_words': 278, 'path': 'notebooks/02_basic_dev_tutorial.ipynb', 'repo_name': 'Czaki/uarray', 'size': '2861'}