# Buggy code
def share_convert(a_sh):
    """
    Convert shares of a in field L to shares of a in field L - 1

    Args:
        a_sh (AdditiveSharingTensor): the additive sharing tensor who owns
            the shares in field L to convert

    Return:
        An additive sharing tensor with shares in field L-1
    """
    assert isinstance(a_sh, sy.AdditiveSharingTensor)
    assert a_sh.dtype != "custom", (
        "`custom` dtype shares are unsupported in SecureNN, use dtype = `long` or `int` instead"
    )

    workers = a_sh.locations
    crypto_provider = a_sh.crypto_provider
    L = a_sh.field
    # torch_dtype = get_torch_dtype(L)
    # dtype = get_dtype(L)
    # Common randomness
    # eta_pp = _random_common_bit(*workers)
    # r = _random_common_value(L, *workers)

    # Share remotely r
    # r_sh = (
    #    (r * 1)
    #    .child[workers[0].id]
    #    .share(*workers, field=L, dtype=dtype, crypto_provider=crypto_provider)
    #    .get()
    #    .child
    # )
    # r_shares = r_sh.child

    # alpha0 = (
    #    (
    #        (r_shares[workers[0].id] + r_shares[workers[1].id].copy().move(workers[0]))
    #        > get_max_val_field(L)
    #    )
    # ).type(torch_dtype)
    # alpha1 = alpha0.copy().move(workers[1])
    # alpha = sy.MultiPointerTensor(children=[alpha0, alpha1])

    u_sh = _shares_of_zero(1, L - 1, "custom", crypto_provider, *workers)

    # 2)
    # a_tilde_sh = a_sh + r_sh
    # a_shares = a_sh.child
    # beta0 = (
    #    ((a_shares[workers[0].id] + r_shares[workers[0].id]) > get_max_val_field(L))
    #    + ((a_shares[workers[0].id] + r_shares[workers[0].id]) < get_min_val_field(L))
    # ).type(torch_dtype)
    # beta1 = (
    #    ((a_shares[workers[1].id] + r_shares[workers[1].id]) > get_max_val_field(L))
    #    + ((a_shares[workers[1].id] + r_shares[workers[1].id]) < get_min_val_field(L))
    # ).type(torch_dtype)

    # beta = sy.MultiPointerTensor(children=[beta0, beta1])

    # 4)
    # a_tilde_shares = a_tilde_sh.child
    # delta = a_tilde_shares[workers[0].id].copy().get() + a_tilde_shares[workers[1].id].copy().get()
    # Check for both positive and negative overflows
    # delta = ((delta > get_max_val_field(L)) + (delta < get_min_val_field(L))).type(torch_dtype)
    # x = a_tilde_sh.get()

    # 5)
    # x_bit = decompose(x, L)
    # x_bit_sh = x_bit.share(
    #    *workers, field=p, dtype="custom", crypto_provider=crypto_provider, **no_wrap
    # )
    # delta_sh = delta.share(
    #    *workers, field=L - 1, dtype="custom", crypto_provider=crypto_provider, **no_wrap
    # )

    # 6)
    # eta_p = private_compare(x_bit_sh, r - 1, eta_pp, L)
    # 7)
    # eta_p_sh = eta_p.share(
    #    *workers, field=L - 1, dtype="custom", crypto_provider=crypto_provider, **no_wrap
    # )

    # 9)
    # j = sy.MultiPointerTensor(
    #    children=[
    #        torch.tensor([0]).send(workers[0], **no_wrap),
    #        torch.tensor([1]).send(workers[1], **no_wrap),
    #    ]
    # )
    # eta_sh = eta_p_sh + (1 - j) * eta_pp - 2 * eta_pp * eta_p_sh
    # 10)
    # theta_sh = beta - (1 - j) * (alpha + 1) + delta_sh + eta_sh
    # 11)
    # NOTE:
    # It seems simple operation with shares in L-1 field is enough to conver a_sh from L to L-1
    # Conversion of shares is handled internally in AST ops for custom dtype
    y_sh = u_sh + a_sh
    return y_sh


# Traceback (TypeError)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-902fb9b77716> in <module>
12 x = th.tensor([1.1,2.0,3.2,4.0]).fix_precision().share(*workers, crypto_provider=sw, requires_grad=True)
13 m=th.nn.Linear(4, 1).fix_precision().share(*workers, crypto_provider=sw, requires_grad=True)
---> 14 o=m(x)

/usr/local/lib/python3.7/site-packages/torch/nn/modules/module.py in __call__(self, *input, **kwargs)
491             result = self._slow_forward(*input, **kwargs)
492         else:
--> 493             result = self.forward(*input, **kwargs)
494         for hook in self._forward_hooks.values():
495             hook_result = hook(self, input, result)

/usr/local/lib/python3.7/site-packages/torch/nn/modules/linear.py in forward(self, input)
90     @weak_script_method
91     def forward(self, input):
---> 92         return F.linear(input, self.weight, self.bias)
93
94     def extra_repr(self):

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/hook/hook.py in overloaded_func(*args, **kwargs)
745                 handle_func_command = TorchTensor.handle_func_command
746
--> 747             response = handle_func_command(command)
748
749             return response

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/native.py in handle_func_command(cls, command)
310             new_command = (cmd, None, new_args, new_kwargs)
311             # Send it to the appropriate class and get the response
--> 312             response = new_type.handle_func_command(new_command)
313             # Put back the wrappers where needed
314             response = syft.frameworks.torch.hook_args.hook_response(

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/autograd.py in handle_func_command(cls, command)
236             # Try to get recursively the attributes in cmd = "<attr1>.<attr2>.<attr3>..."
237             cmd = cls.rgetattr(cls, cmd)
--> 238             return cmd(*args, **kwargs)
239         except AttributeError:
240             pass

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/autograd.py in linear(*args)
201                     Un-hook the function to have its detailed behaviour
202                     """
--> 203                     return torch.nn.functional.native_linear(*args)
204
205                 module.linear = linear

/usr/local/lib/python3.7/site-packages/torch/nn/functional.py in linear(input, weight, bias)
1406         ret = torch.addmm(bias, input, weight.t())
1407     else:
-> 1408         output = input.matmul(weight.t())
1409         if bias is not None:
1410             output += bias

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/autograd.py in method_with_grad(*args, **kwargs)
138                 )
139
--> 140                 result = getattr(new_self, name)(*new_args, **new_kwargs)
141
142                 # Put back SyftTensor on the tensors found in the response

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/precision.py in matmul(self, *args, **kwargs)
414
415         # Send it to the appropriate class and get the response
--> 416         response = getattr(new_self, "matmul")(*new_args, **new_kwargs)
417
418         # Put back SyftTensor on the tensors found in the response

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/additive_shared.py in matmul(self, other)
515             return self._public_mul(other, "matmul")
516
--> 517         return self._private_mul(other, "matmul")
518
519     def mm(self, *args, **kwargs):

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/tensors/interpreters/additive_shared.py in _private_mul(self, other, equation)
413             raise AttributeError("For multiplication a crypto_provider must be passed.")
414
--> 415         shares = spdz.spdz_mul(cmd, self, other, self.crypto_provider, self.field)
416
417         return shares

/usr/local/lib/python3.7/site-packages/syft/frameworks/torch/crypto/spdz.py in spdz_mul(cmd, x_sh, y_sh, crypto_provider, field)
43         j = sy.MultiPointerTensor(children=[j1, j0])
44     else:
---> 45         j = sy.MultiPointerTensor(children=[j1] + j0.child.values())
46
47     delta_b = cmd(delta, b)

TypeError: can only concatenate list (not "dict_values") to list

# Fixed code
def share_convert(a_sh):
    """
    Convert shares of a in field L to shares of a in field L - 1

    Args:
        a_sh (AdditiveSharingTensor): the additive sharing tensor who owns
            the shares in field L to convert

    Return:
        An additive sharing tensor with shares in field L-1
    """
    assert isinstance(a_sh, sy.AdditiveSharingTensor)
    assert a_sh.dtype != "custom", (
        "`custom` dtype shares are unsupported in SecureNN, use dtype = `long` or `int` instead"
    )

    workers = a_sh.locations
    crypto_provider = a_sh.crypto_provider
    L = a_sh.field
    # torch_dtype = get_torch_dtype(L)
    # dtype = get_dtype(L)
    # Common randomness
    # eta_pp = _random_common_bit(*workers)
    # r = _random_common_value(L, *workers)

    # Share remotely r
    # r_sh = (
    #    (r * 1)
    #    .child[workers[0].id]
    #    .share(*workers, field=L, dtype=dtype, crypto_provider=crypto_provider)
    #    .get()
    #    .child
    # )
    # r_shares = r_sh.child

    # WORKS WITH N PARTIES, NEEDS BUGFIXING IN WRAP
    # alpha0 = wrap(r_sh, L)
    # alphas = [alpha0.copy().move(w) for w in workers[1:]]
    # alpha = sy.MultiPointerTensor(children=[alpha0, *alphas])

    # WORKS WITH 2 PARTIES
    # alpha0 = (
    #    (
    #        (r_shares[workers[0].id] + r_shares[workers[1].id].copy().move(workers[0]))
    #        > get_max_val_field(L)
    #    )
    # ).type(torch_dtype)
    # alpha1 = alpha0.copy().move(workers[1])
    # alpha = sy.MultiPointerTensor(children=[alpha0, alpha1])

    u_sh = _shares_of_zero(1, L - 1, "custom", crypto_provider, *workers)

    # 2)
    # a_tilde_sh = a_sh + r_sh
    # a_shares = a_sh.child
    # beta0 = (
    #    ((a_shares[workers[0].id] + r_shares[workers[0].id]) > get_max_val_field(L))
    #    + ((a_shares[workers[0].id] + r_shares[workers[0].id]) < get_min_val_field(L))
    # ).type(torch_dtype)
    # beta1 = (
    #    ((a_shares[workers[1].id] + r_shares[workers[1].id]) > get_max_val_field(L))
    #    + ((a_shares[workers[1].id] + r_shares[workers[1].id]) < get_min_val_field(L))
    # ).type(torch_dtype)

    # beta = sy.MultiPointerTensor(children=[beta0, beta1])

    # 4)
    # a_tilde_shares = a_tilde_sh.child
    # delta = a_tilde_shares[workers[0].id].copy().get() + a_tilde_shares[workers[1].id].copy().get()
    # Check for both positive and negative overflows
    # delta = ((delta > get_max_val_field(L)) + (delta < get_min_val_field(L))).type(torch_dtype)
    # x = a_tilde_sh.get()

    # 5)
    # x_bit = decompose(x, L)
    # x_bit_sh = x_bit.share(
    #    *workers, field=p, dtype="custom", crypto_provider=crypto_provider, **no_wrap
    # )
    # delta_sh = delta.share(
    #    *workers, field=L - 1, dtype="custom", crypto_provider=crypto_provider, **no_wrap
    # )

    # 6)
    # eta_p = private_compare(x_bit_sh, r - 1, eta_pp, L)
    # 7)
    # eta_p_sh = eta_p.share(
    #    *workers, field=L - 1, dtype="custom", crypto_provider=crypto_provider, **no_wrap
    # )

    # 9)
    # j = sy.MultiPointerTensor(
    #    children=[
    #        torch.tensor([int(i != 0)]).send(w, **no_wrap)
    #        for i, w in enumerate(workers)
    #    ]
    # )
    # eta_sh = eta_p_sh + (1 - j) * eta_pp - 2 * eta_pp * eta_p_sh
    # 10)
    # theta_sh = beta - (1 - j) * (alpha + 1) + delta_sh + eta_sh
    # 11)
    # NOTE:
    # It seems simple operation with shares in L-1 field is enough to conver a_sh from L to L-1
    # Conversion of shares is handled internally in AST ops for custom dtype
    y_sh = u_sh + a_sh
    return y_sh