Repository: arminwitte/llsi

Issue:
Save input output names in model
Compare and simulate could be calles only by specifying the sysiddata object.

Hints from issue comments:

Solution patch:
diff --git a/src/llsi/arx.py b/src/llsi/arx.py
index 8eda550..d46a988 100644
--- a/src/llsi/arx.py
+++ b/src/llsi/arx.py
@@ -51,7 +51,15 @@ def ident(self, order):
         b = theta[:nb]
         a = np.hstack(([1.0], theta[nb:]))
 
-        mod = PolynomialModel(b=b, a=a, nk=nk, Ts=self.Ts, cov=cov)
+        mod = PolynomialModel(
+            b=b,
+            a=a,
+            nk=nk,
+            Ts=self.Ts,
+            cov=cov,
+            input_names=self.input_names,
+            output_names=self.output_names,
+        )
 
         return mod
 
diff --git a/src/llsi/ltimodel.py b/src/llsi/ltimodel.py
index b42f164..44d56af 100644
--- a/src/llsi/ltimodel.py
+++ b/src/llsi/ltimodel.py
@@ -12,11 +12,13 @@
 
 
 class LTIModel(ABC):
-    def __init__(self, Ts=1.0, nu=1, ny=1):
+    def __init__(self, Ts=1.0, nu=1, ny=1, input_names=[], output_names=[]):
         self.Ts = Ts
         self.info = {}
         self.nu = nu
         self.ny = ny
+        self.input_names = input_names
+        self.output_names = output_names
 
     def impulse_response(self, N=100):
         t = np.linspace(0, (N - 1) * self.Ts, N)
diff --git a/src/llsi/polynomialmodel.py b/src/llsi/polynomialmodel.py
index 31574de..7c39a1c 100644
--- a/src/llsi/polynomialmodel.py
+++ b/src/llsi/polynomialmodel.py
@@ -12,8 +12,21 @@
 
 
 class PolynomialModel(LTIModel):
-    def __init__(self, a=None, b=None, na=1, nb=1, nu=1, ny=1, nk=0, cov=None, Ts=1.0):
-        super().__init__(Ts=Ts)
+    def __init__(
+        self,
+        a=None,
+        b=None,
+        na=1,
+        nb=1,
+        nu=1,
+        ny=1,
+        nk=0,
+        cov=None,
+        Ts=1.0,
+        input_names=[],
+        output_names=[],
+    ):
+        super().__init__(Ts=Ts, input_names=input_names, output_names=output_names)
 
         if a is not None:
             self.a = np.atleast_2d(a).T
@@ -102,6 +115,8 @@ def from_scipy(cls, mod):
 
     def __repr__(self):
         s = f"PolynomialModel with Ts={self.Ts}\n"
+        s += f"input(s): {self.input_names}\n"
+        s += f"output(s): {self.output_names}\n"
         s += f"b:\n{self.b}\n"
         s += f"a:\n{self.a}\n"
 
diff --git a/src/llsi/statespacemodel.py b/src/llsi/statespacemodel.py
index c5268f8..39e3167 100644
--- a/src/llsi/statespacemodel.py
+++ b/src/llsi/statespacemodel.py
@@ -21,7 +21,19 @@ class StateSpaceModel(LTIModel):
     https://en.wikipedia.org/wiki/State-space_representation
     """
 
-    def __init__(self, A=None, B=None, C=None, D=None, Ts=1.0, nx=0, nu=1, ny=1):
+    def __init__(
+        self,
+        A=None,
+        B=None,
+        C=None,
+        D=None,
+        Ts=1.0,
+        nx=0,
+        nu=1,
+        ny=1,
+        input_names=[],
+        output_names=[],
+    ):
         """
 
         Parameters
@@ -44,7 +56,7 @@ def __init__(self, A=None, B=None, C=None, D=None, Ts=1.0, nx=0, nu=1, ny=1):
         None.
 
         """
-        super().__init__(Ts=Ts)
+        super().__init__(Ts=Ts, input_names=input_names, output_names=output_names)
 
         # set A matrix and number of states
         if A is not None:
@@ -312,7 +324,15 @@ def from_fir(cls, mod):
         B[0] = 1.0
         C = b[1:].reshape(1, -1)
         D = b[0]
-        mod_out = cls(A=A, B=B, C=C, D=D, Ts=mod.Ts)
+        mod_out = cls(
+            A=A,
+            B=B,
+            C=C,
+            D=D,
+            Ts=mod.Ts,
+            input_names=mod.input_names,
+            output_names=mod.output_names,
+        )
         return mod_out
 
     def to_json(self, filename=None):
@@ -329,6 +349,8 @@ def to_json(self, filename=None):
         data["nx"] = self.ny
         data["nu"] = self.ny
         data["ny"] = self.ny
+        data["input_names"] = self.input_names
+        data["output_names"] = self.output_names
 
         if filename is not None:
             with open(filename, "w") as f:
@@ -350,12 +372,16 @@ def from_json(cls, filename):
             nx=data["nx"],
             nu=data["nu"],
             ny=data["ny"],
+            input_names=data["input_names"],
+            output_names=data["output_names"],
         )
         mod.info = data["info"]
         return mod
 
     def __repr__(self) -> str:
         s = f"StateSpaceModel with Ts={self.Ts}\n"
+        s += f"input(s): {self.input_names}\n"
+        s += f"output(s): {self.output_names}\n"
         s += f"A:\n{self.A}\n"
         s += f"B:\n{self.B}\n"
         s += f"C:\n{self.C}\n"
diff --git a/src/llsi/subspace.py b/src/llsi/subspace.py
index 7ef9669..2067f3d 100644
--- a/src/llsi/subspace.py
+++ b/src/llsi/subspace.py
@@ -155,7 +155,15 @@ def ident(self, order):
         A, B, C, D = self._abcd_observability_matrix(U1, U2, L11, L31, Sigma_sqrt, n, r)
         # ====================================================================
 
-        mod = StateSpaceModel(A=A, B=B, C=C, D=D, Ts=self.Ts)
+        mod = StateSpaceModel(
+            A=A,
+            B=B,
+            C=C,
+            D=D,
+            Ts=self.Ts,
+            input_names=self.input_names,
+            output_names=self.output_names,
+        )
         mod.info["Hankel singular values"] = s_
 
         return mod
@@ -214,7 +222,15 @@ def ident(self, order):
 
         A, B, C, D = self._abcd_observability_matrix(U1, U2, L11, L31, Sigma_sqrt, n, r)
 
-        mod = StateSpaceModel(A=A, B=B, C=C, D=D, Ts=self.Ts)
+        mod = StateSpaceModel(
+            A=A,
+            B=B,
+            C=C,
+            D=D,
+            Ts=self.Ts,
+            input_names=self.input_names,
+            output_names=self.output_names,
+        )
         mod.info["Hankel singular values"] = s_
 
         return mod
diff --git a/src/llsi/sysidalgbase.py b/src/llsi/sysidalgbase.py
index 4b208ea..0b5a7c9 100644
--- a/src/llsi/sysidalgbase.py
+++ b/src/llsi/sysidalgbase.py
@@ -14,6 +14,9 @@
 class SysIdAlgBase(ABC):
     def __init__(self, data, y_name, u_name, settings):
 
+        self.input_names = u_name
+        self.output_names = y_name
+
         y_name = np.atleast_1d(y_name)  # now I can iterate over
         y = []
         for name in y_name:


Test patch:
diff --git a/tests/test_polynomialmodel.py b/tests/test_polynomialmodel.py
index 21d3880..f59e3f4 100644
--- a/tests/test_polynomialmodel.py
+++ b/tests/test_polynomialmodel.py
@@ -16,7 +16,10 @@
 @pytest.fixture
 def model():
     return PolynomialModel(
-        b=np.array([0.04761905, 0.04761905]), a=np.array([1.0, -0.9047619])
+        b=np.array([0.04761905, 0.04761905]),
+        a=np.array([1.0, -0.9047619]),
+        input_names=["input"],
+        output_names=["output"],
     )
 
 
@@ -160,7 +163,7 @@ def test_reshape(model):
 
 
 def test_repr_str(model):
-    s = "PolynomialModel with Ts=1.0\nb:\n[0.04761905 0.04761905]\na:\n[ 1.        -0.9047619]\n"
+    s = "PolynomialModel with Ts=1.0\ninput(s): ['input']\noutput(s): ['output']\nb:\n[0.04761905 0.04761905]\na:\n[ 1.        -0.9047619]\n"
     assert model.__repr__() == s
     assert model.__str__() == s
 
diff --git a/tests/test_statespacemodel.py b/tests/test_statespacemodel.py
index 77043fc..1174354 100644
--- a/tests/test_statespacemodel.py
+++ b/tests/test_statespacemodel.py
@@ -16,7 +16,15 @@
 
 @pytest.fixture
 def model():
-    return StateSpaceModel(A=[[0.8, 0.8], [0, 0.8]], B=[1, 1], C=[1, 0], D=0, Ts=1)
+    return StateSpaceModel(
+        A=[[0.8, 0.8], [0, 0.8]],
+        B=[1, 1],
+        C=[1, 0],
+        D=0,
+        Ts=1,
+        input_names=["input"],
+        output_names=["output"],
+    )
 
 
 def test_init():
@@ -79,6 +87,8 @@ def test_from_PT1():
 
 def test_repr_str(model):
     s = """StateSpaceModel with Ts=1
+input(s): ['input']
+output(s): ['output']
 A:
 [[0.8 0.8]
  [0.  0.8]]
diff --git a/tests/test_sysidalg.py b/tests/test_sysidalg.py
index 6caf998..a6a97c9 100644
--- a/tests/test_sysidalg.py
+++ b/tests/test_sysidalg.py
@@ -284,6 +284,18 @@ def test_oe_deterministic_stochastic(data_siso_deterministic_stochastic, poly_mo
     np.testing.assert_allclose(i, i_, rtol=5e-2, atol=5e-2)
 
 
+def test_poly_input_output_name(data_siso_deterministic, poly_mod):
+    mod = sysid(data_siso_deterministic, "y", "u", (0, 100, 0), method="fir")
+    assert mod.input_names == "u"
+    assert mod.output_names == "y"
+
+
+def test_ss_input_output_name(data_mimo_deterministic, poly_mod):
+    mod = sysid(data_mimo_deterministic, ["y0", "y1"], ["u0", "u1"], 2, method="n4sid")
+    assert mod.input_names == ["u0", "u1"]
+    assert mod.output_names == ["y0", "y1"]
+
+
 #######################################################################################