Repository: netket/netket

Issue:
Cannot create Spin hilbert with odd sites and S=1
Reported by @fabienalet 

```python
>>> import netket as nk
>>> nk.hilbert.Spin(1.0, N=3, total_sz=1.0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/filippovicentini/Dropbox/Ricerca/Codes/Python/netket/netket/hilbert/spin.py", line 88, in __init__
    _check_total_sz(total_sz, N)
  File "/home/filippovicentini/Dropbox/Ricerca/Codes/Python/netket/netket/hilbert/spin.py", line 40, in _check_total_sz
    raise Exception(
Exception: Cannot fix the total magnetization: Nspins + totalSz must be even.
```

the cuprit is the second check in here: (size is number of spins)
```python
def _check_total_sz(total_sz, size):
    if total_sz is None:
        return

    m = round(2 * total_sz)
    if np.abs(m) > size:
        raise Exception(
            "Cannot fix the total magnetization: 2|M| cannot " "exceed Nspins."
        )

    if (size + m) % 2 != 0:
        raise Exception(
            "Cannot fix the total magnetization: Nspins + " "totalSz must be even."
        )
```

The second check works only for Spins 1/2.
Can we generalise it to always work?
I'm too tired to think how to now.

Cannot create Spin hilbert with odd sites and S=1
Reported by @fabienalet 

```python
>>> import netket as nk
>>> nk.hilbert.Spin(1.0, N=3, total_sz=1.0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/filippovicentini/Dropbox/Ricerca/Codes/Python/netket/netket/hilbert/spin.py", line 88, in __init__
    _check_total_sz(total_sz, N)
  File "/home/filippovicentini/Dropbox/Ricerca/Codes/Python/netket/netket/hilbert/spin.py", line 40, in _check_total_sz
    raise Exception(
Exception: Cannot fix the total magnetization: Nspins + totalSz must be even.
```

the cuprit is the second check in here: (size is number of spins)
```python
def _check_total_sz(total_sz, size):
    if total_sz is None:
        return

    m = round(2 * total_sz)
    if np.abs(m) > size:
        raise Exception(
            "Cannot fix the total magnetization: 2|M| cannot " "exceed Nspins."
        )

    if (size + m) % 2 != 0:
        raise Exception(
            "Cannot fix the total magnetization: Nspins + " "totalSz must be even."
        )
```

The second check works only for Spins 1/2.
Can we generalise it to always work?
I'm too tired to think how to now.

Cannot create Spin hilbert with odd sites and S=1
Reported by @fabienalet 

```python
>>> import netket as nk
>>> nk.hilbert.Spin(1.0, N=3, total_sz=1.0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/filippovicentini/Dropbox/Ricerca/Codes/Python/netket/netket/hilbert/spin.py", line 88, in __init__
    _check_total_sz(total_sz, N)
  File "/home/filippovicentini/Dropbox/Ricerca/Codes/Python/netket/netket/hilbert/spin.py", line 40, in _check_total_sz
    raise Exception(
Exception: Cannot fix the total magnetization: Nspins + totalSz must be even.
```

the cuprit is the second check in here: (size is number of spins)
```python
def _check_total_sz(total_sz, size):
    if total_sz is None:
        return

    m = round(2 * total_sz)
    if np.abs(m) > size:
        raise Exception(
            "Cannot fix the total magnetization: 2|M| cannot " "exceed Nspins."
        )

    if (size + m) % 2 != 0:
        raise Exception(
            "Cannot fix the total magnetization: Nspins + " "totalSz must be even."
        )
```

The second check works only for Spins 1/2.
Can we generalise it to always work?
I'm too tired to think how to now.



Discussion:

Resolving patch:
diff --git a/netket/hilbert/spin.py b/netket/hilbert/spin.py
--- a/netket/hilbert/spin.py
+++ b/netket/hilbert/spin.py
@@ -26,20 +26,31 @@
 from ._deprecations import graph_to_N_depwarn
 
 
-def _check_total_sz(total_sz, size):
+def _check_total_sz(total_sz, S, size):
     if total_sz is None:
         return
 
+    local_size = 2 * S + 1
+
     m = round(2 * total_sz)
-    if np.abs(m) > size:
-        raise Exception(
+    if np.abs(m) > size * (2 * S):
+        raise ValueError(
             "Cannot fix the total magnetization: 2|M| cannot " "exceed Nspins."
         )
 
-    if (size + m) % 2 != 0:
-        raise Exception(
-            "Cannot fix the total magnetization: Nspins + " "totalSz must be even."
-        )
+    # If half-integer spins (1/2, 3/2)
+    if local_size % 2 == 0:
+        # Check that the total magnetization is odd if odd spins or even if even # of spins
+        if (size + m) % 2 != 0:
+            raise ValueError(
+                "Cannot fix the total magnetization: Nspins + 2*totalSz must be even."
+            )
+    # else if full-integer (S=1,2)
+    else:
+        if m % 2 != 0:
+            raise ValueError(
+                "Cannot fix the total magnetization to a half-integer number"
+            )
 
 
 @jit(nopython=True)
@@ -85,7 +96,7 @@ def __init__(
             local_states[i] = -round(2 * s) + 2 * i
         local_states = local_states.tolist()
 
-        _check_total_sz(total_sz, N)
+        _check_total_sz(total_sz, s, N)
         if total_sz is not None:
 
             def constraints(x):


Test patch:
diff --git a/Test/Hilbert/test_hilbert.py b/Test/Hilbert/test_hilbert.py
--- a/Test/Hilbert/test_hilbert.py
+++ b/Test/Hilbert/test_hilbert.py
@@ -30,6 +30,12 @@
 # Spin 1/2 with total Sz
 hilberts["Spin 1/2 with total Sz"] = Spin(s=0.5, total_sz=1.0, N=20)
 
+# Spin 1/2 with total Sz
+hilberts["Spin 1 with total Sz, even sites"] = Spin(s=1.0, total_sz=5.0, N=6)
+
+# Spin 1/2 with total Sz
+hilberts["Spin 1 with total Sz, odd sites"] = Spin(s=1.0, total_sz=2.0, N=7)
+
 # Spin 3
 hilberts["Spin 3"] = Spin(s=3, N=25)