diff --git a/+misc/Gains.m b/+misc/Gains.m
index 19bc477700704a9e7d5198404e1b0718b764dbc5..d3b2f0f0fbccc5e2b47c986647339e4e7482bc23 100644
--- a/+misc/Gains.m
+++ b/+misc/Gains.m
@@ -425,11 +425,16 @@ classdef Gains < handle & matlab.mixin.Copyable
 			
 			arguments
 				obj;
-				outputType;
+				outputType = "";
 				inputName = obj.inputType;
 				leftHandSide = "none";
 			end % arguments
 			
+			if strlength(outputType) == 0
+				texString = outputType;
+				return;
+			end
+			
 			% put string together
 			if isnumeric(leftHandSide)
 				if isscalar(leftHandSide) && obj.lengthOutput > 1
diff --git a/+misc/plotRope.m b/+misc/plotRope.m
new file mode 100644
index 0000000000000000000000000000000000000000..daa417453e89fa294fa3dd9fb7c3ae5e8fde0c2d
--- /dev/null
+++ b/+misc/plotRope.m
@@ -0,0 +1,83 @@
+function f = plotRope(myOptions, rope)
+%PLOTSTRING Plots the strings defined by string
+arguments 
+	myOptions = [];
+end
+arguments (Repeating)
+	rope quantity.Discrete;
+end
+
+[myOptions, rope] = plotRopeParser(myOptions, rope);
+
+f = figure();
+for it = 1 : numel(rope)
+	ropeTemp = rope{it};
+	if myOptions.t.n <= 1
+		ropeDiscrete = ropeTemp.on(myOptions.s);
+		ropeDiscrete = reshape(ropeDiscrete, [1, size(ropeDiscrete)]);
+	else
+		ropeDiscrete = ropeTemp.on([myOptions.t, myOptions.s]);
+	end
+	
+	% plot
+	for tIdx = 1 : size(ropeDiscrete, 1)
+		if numel(ropeTemp) == 2
+			ax = plot(ropeDiscrete(tIdx, :, 1), ropeDiscrete(tIdx, :, 2));
+			
+		elseif numel(ropeTemp) == 3
+			ax = plot3(ropeDiscrete(tIdx, :, 1), ropeDiscrete(tIdx, :, 2), ropeDiscrete(tIdx, :, 3), ...
+				"LineWidth", myOptions.LineWidth, "Color", myOptions.Color);
+			
+		else
+			error("rope must be a quantity.Discrete with numel(rope) == 2 or == 3");
+		end
+	end % tIdx = 1 : size(ropeDiscrete, 1)
+	
+	if numel(ropeTemp) == 2
+		horLim = [-MAX(-ropeTemp(1)), MAX(ropeTemp(1))];
+		xlim(horLim);
+	else
+		horLim = [-MAX(-ropeTemp(1:2)), MAX(ropeTemp(1:2))];
+		xlim(horLim); ylim(horLim);
+	end
+end
+
+end % plotRope()
+
+function [myOptions, rope] = plotRopeParser(myOptions, rope)
+if isa(myOptions, "quantity.Discrete")
+	rope = {myOptions; rope{:}};
+	myOptions = [];
+end
+if isempty(myOptions)
+	if any(rope{1}(1).domain.gridIndex("t"))
+		myOptions = plotRopeOptionParser(rope{1}, "t", rope{1}(1).domain.find("t"));
+	else
+		myOptions = plotRopeOptionParser(rope{1});
+	end
+elseif isstruct(myOptions)
+	if ~isfield(myOptions, "t") && any(rope(1).domain.contains("t"))
+		myOptions.t = rope{1}(1).domain.find("t");
+	end
+	myOptions = misc.struct2namevaluepair(myOptions);
+	myOptions = plotRopeOptionParser(rope{1}, myOptions{:});
+else
+	error("myOptions is invalid data-type")
+end
+end % plotRopeParser()
+
+function myOptions = plotRopeOptionParser(rope, NameValueArgs)
+% misc.plotRopeOptionParser is a helper function for misc.plotRope -> see this function for
+% documentation.
+arguments
+	rope quantity.Discrete
+	NameValueArgs.shade = [0.4, 1];
+	NameValueArgs.LineWidth = 1;
+	NameValueArgs.Color = [0 0.4470 0.7410];
+	NameValueArgs.t = quantity.Domain("t", 0);
+	NameValueArgs.s = rope(1).domain.find("s");
+end % argumentss
+
+myOptions = NameValueArgs;
+
+end % plotRopeOptionParser()
\ No newline at end of file
diff --git a/+quantity/Discrete.m b/+quantity/Discrete.m
index ccdd429774043bae0525a6f5d5eaf484abacd9ac..7e34944b3abde7be2199a7d5656942c1b5ed9a93 100644
--- a/+quantity/Discrete.m
+++ b/+quantity/Discrete.m
@@ -1844,21 +1844,91 @@ classdef  (InferiorClasses = {?quantity.Symbolic}) Discrete ...
 				for it = 1 : obj(1).domain.n
 					VDisc(it, :) = eig(objDisc(:, :, it));
 				end % for it = 1 : obj(1).domain.n
-			else	
+				V = quantity.Discrete(VDisc, obj(1).domain, "name", "V");
+			else
 				% [V,D] = eig(A) or [V,D,W] = eig(A)
 				VDisc = zeros([obj(1).domain.n, size(obj)]);
 				DDisc = zeros([obj(1).domain.n, size(obj)]);
 				WDisc = zeros([obj(1).domain.n, size(obj)]);
-				for it = 1 : obj(1).domain.n
-					[VDisc(it, :, :), DDisc(it, :, :), WDisc(it, :, :)] = ...
-						eig(objDisc(:, :, it));
+				VDiscOld = zeros(size(obj));
+				WDiscOld = zeros(size(obj));
+				for zt = 1 : obj(1).domain.n
+					[VDiscTemp, DDisc(zt, :, :), WDiscTemp] = eig(objDisc(:, :, zt));
+					if zt > 1
+						VDiscOld(:, :) = VDisc(zt-1, :, :);
+						WDiscOld(:, :) = WDisc(zt-1, :, :);
+						for jt = 1 : size(obj, 1)
+							% as the builtin eig() scales the eigenvectors, as slight change of the
+							% values can cause a change of the sign of the eigenvector. As eig is
+							% called pointwise, often a non-smooth eigenvector-function results.
+							% Due to this, every eigenvector is compared with the previous one and
+							% if there it makes the result more smooth, the sign is changed.
+							if norm(VDiscTemp(:, jt) - VDiscOld(:, jt)) ...
+									> norm(VDiscTemp(:, jt) + VDiscOld(:, jt))
+								VDiscTemp(:, jt) = -VDiscTemp(:, jt);
+							end
+							if norm(WDiscTemp(:, jt) - WDiscOld(:, jt)) ...
+									> norm(WDiscTemp(:, jt) + WDiscOld(:, jt))
+								WDiscTemp(:, jt) = -WDiscTemp(:, jt);
+							end
+						end % for jt = 1 : size(obj, 1)
+					end
+					VDisc(zt, :, :) = VDiscTemp;
+					WDisc(zt, :, :) = WDiscTemp;
 				end % for it = 1 : obj(1).domain.n
 				D = quantity.Discrete(DDisc, obj(1).domain, "name", "D");
 				W = quantity.Discrete(WDisc, obj(1).domain, "name", "W");
+				V = quantity.Discrete(VDisc, obj(1).domain, "name", "V");
 			end
-			V = quantity.Discrete(VDisc, obj(1).domain, "name", "V");
 		end % eig()
 		
+		function [l, u] = luDecomposition(obj)
+			% luDecomposition split matrix OBJ into lower triangular matrix l and upper triangular
+			% matrix u, such that l * u = obj.
+			n = size(obj, 1);
+			assert(numel(obj) == n*n, "obj must be a quadratic matrix");
+			u = obj;
+			l = obj.eye([n, n], obj(1).domain);
+
+			% simple algorithm from wikipedia:
+			for it = 1 : n-1
+				for k = it+1 : n
+					l(k, it) = u(k, it) / u(it, it);
+					for jt = it : n
+						u(k, jt) = u(k, jt) - l(k, it) * u(it, jt);
+					end % for jt
+				end % for k
+			end % for it
+		end % luDecomposition()
+		
+		function [V, J, l] = jordanReal(obj)
+			% jordanReal Real Jordan form of the matrix OBJ pointwise over the (scalar) domain.
+			% [V, J] = jordanReal(OBJ) computes the transformation matrix V and the jordan
+			% block matrix J, so that OBJ*V = V*J. The columns of OBJ are the generalised 
+			% eigenvectors.
+			%
+			% [V, J, l] = jordanReal(OBJ) also returns the lengths of the jordan
+			% blocks. l is a vector containing the lengths. So length(l) is the
+			% number of jordan blocks.
+			assert(numel(obj(1).domain)==1, "quantity.Discrete/jordanReal is only implemented for one domain");
+			assert((ndims(obj) <= 2) && (size(obj, 1) == size(obj, 2)), ...
+				"quantity.Discrete/jordanReal is only implemented for quadratic quantities");
+			
+			objDisc = permute(obj.on(), [2, 3, 1]);
+			
+			VDisc = zeros([obj(1).domain.n, size(obj)]);
+			JDisc = zeros([obj(1).domain.n, size(obj)]);
+			lDisc = zeros([obj(1).domain.n, size(obj, 1)]);
+			for it = 1 : obj(1).domain.n
+				[VDisc(it, :, :), JDisc(it, :, :), lTemp] = ...
+					misc.jordanReal(objDisc(:, :, it));
+				lDisc(it, 1:numel(lTemp)) = lTemp;
+			end % for it = 1 : obj(1).domain.n
+			V = quantity.Discrete(VDisc, obj(1).domain, "name", "V");
+			J = quantity.Discrete(JDisc, obj(1).domain, "name", "J");
+			l = quantity.Discrete(lDisc, obj(1).domain, "name", "l");
+		end % jordanReal()
+		
 		function y = log(obj)
 		%  log    Natural logarithm.
 		%     log(X) is the natural logarithm of the elements of X.
@@ -2502,7 +2572,31 @@ classdef  (InferiorClasses = {?quantity.Symbolic}) Discrete ...
 				O = ones([domain.gridLength, valueSize(:)']);
 				P = quantity.Discrete(O, domain, 'size', valueSize, varargin{:});
 			end
-		end
+		end % ones()
+		
+		function I = eye(N, domain, varargin)
+			%  eye Identity matrix.
+			%     eye(N, domain) is the N-by-N identity matrix on the domain domain.
+			%  
+			%     eye([M,N], domain) is an M-by-N matrix with 1's on the diagonal and zeros 
+			%		elsewhere.
+			%     eye([M,N], domain, varargin) passes further inputs to the quantity.Discrete
+			%     constructor.
+			
+			if isscalar(N)
+				N = [N, N];
+			elseif ~ismatrix(N)
+				error("eye only supports ndims(N) == 1 or 2");
+			end
+			
+			if any( N == 0)
+				P = quantity.Discrete.empty(N);
+			else
+				IvalueDiscrete = reshape(eye(N), [ones(1, numel(domain)), N]);
+				IvalueDiscrete = repmat(IvalueDiscrete, [domain.n, 1]);
+				I = quantity.Discrete(IvalueDiscrete, domain, varargin{:});
+			end
+		end % eye()
 		
 		function P = zeros(valueSize, domain, varargin)
 			%ZEROS initializes an zero quantity.Discrete object
diff --git a/+quantity/Symbolic.m b/+quantity/Symbolic.m
index a6845affbf14f8aec6249850168b65eeb0f79c66..6693c507b341b472cc227a8ebae0b2732b0be2ee 100644
--- a/+quantity/Symbolic.m
+++ b/+quantity/Symbolic.m
@@ -828,7 +828,25 @@ classdef Symbolic < quantity.Function
 			%ZEROS initializes an zero quantity.Discrete object of the size
 			%specified by the input valueSize.
 			P = quantity.Symbolic(zeros(valueSize), varargin{:});
-		end
+		end % zeros()
+		
+		function P = ones(valueSize, varargin)
+			%ONES initializes an ones-quantity.Discrete object
+			%	P = ones(VALUESIZE, DOMAIN) creates a matrix of size
+			%	VALUESIZE on the DOMAIN with ones as entries.
+			P = quantity.Symbolic(ones(valueSize), varargin{:});
+		end % ones()
+		
+		function P = eye(N, varargin)
+			%  eye Identity matrix.
+			%     eye(N, domain) is the N-by-N identity matrix on the domain domain.
+			%  
+			%     eye([M,N], domain) is an M-by-N matrix with 1's on the diagonal and zeros 
+			%		elsewhere.
+			%     eye([M,N], domain, varargin) passes further inputs to the quantity.Discrete
+			%     constructor.
+			P = quantity.Symbolic(eye(N), varargin{:});
+		end % eye()
 
 	end % methods (Static)
 	
diff --git a/+unittests/+quantity/testDiscrete.m b/+unittests/+quantity/testDiscrete.m
index 652a87fa96875c7bb50c43b352bf80b44ac207cf..825f7ee01e8bf0a49ff948b40b9b2c573085e696 100644
--- a/+unittests/+quantity/testDiscrete.m
+++ b/+unittests/+quantity/testDiscrete.m
@@ -4,8 +4,36 @@ function [tests] = testDiscrete()
 tests = functiontests(localfunctions);
 end
 
-function testCatBug(tc)
 
+
+function testEye(tc)
+myDomain = [quantity.Domain("z", linspace(0, 1, 11)), quantity.Domain("zeta", linspace(0, 1, 5))];
+tc.verifyEqual(MAX(abs(quantity.Discrete.eye(1, myDomain) - 1)), 0);
+tc.verifyEqual(MAX(abs(quantity.Discrete.eye(2, myDomain) - eye(2))), 0);
+tc.verifyEqual(MAX(abs(quantity.Discrete.eye([2, 1], myDomain) - eye([2, 1]))), 0);
+tc.verifyEqual(MAX(abs(quantity.Discrete.eye([2, 2], myDomain(2)) - eye([2, 2]))), 0);
+tc.verifyEqual(MAX(abs(quantity.Discrete.eye([4, 4], myDomain) - eye(4))), 0);
+end % testEye()
+
+function testLuDecomposition(tc)
+myDomain = [quantity.Domain("z", linspace(0, 1, 11)), quantity.Domain("zeta", linspace(0, 1, 5))];
+syms z zeta
+quanSymbolic = quantity.Symbolic([z*zeta, z+1, 2; z^2, zeta^2, -2; 1, 2, 3], myDomain);
+quan = quantity.Discrete(quanSymbolic);
+% discrete case
+[l, u] = quan.luDecomposition();
+tc.verifyEqual(MAX(abs(l*u - quan)), 0, 'AbsTol', 1e3*eps);
+tc.verifyEqual(MAX(abs(l.*triu(ones(size(l)), 1))), 0, 'AbsTol', 1e3*eps);
+tc.verifyEqual(MAX(abs(u.*tril(ones(size(u)), -1))), 0, 'AbsTol', 1e3*eps);
+% symbolic case
+[l, u] = quanSymbolic.luDecomposition();
+tc.verifyEqual(MAX(abs(l*u - quanSymbolic)), 0);
+tc.verifyEqual(MAX(abs(l.*triu(ones(size(l)), 1))), 0);
+tc.verifyEqual(MAX(abs(u.*tril(ones(size(u)), -1))), 0);
+tc.verifyTrue(isa(quanSymbolic, "quantity.Symbolic"));
+end % testLuDecomposition()
+
+function testCatBug(tc)
 z = quantity.Domain("z", linspace(0, 1, 7));
 zeta = quantity.Domain("zeta", linspace(0, 1, 5));
 A = quantity.Discrete(sin(z.grid * pi), z);
@@ -19,10 +47,7 @@ x = A * subs(A, "z", "zeta");
 C1 = [0*z.Symbolic + zeta.Symbolic; x];
 C2 = [zeta.Symbolic + 0*z.Symbolic; x];
 tc.verifyEqual(C1.on([z, zeta]), C2.on([z, zeta]));
-
 end
-
-
 function testEigScalar(tc)
 z = quantity.Domain("z", linspace(-1, 1, 11));
 [Vz, Dz, Wz] = z.Discrete.eig();
@@ -50,6 +75,19 @@ tc.verifyEqual(eig(quan.at(0.4)), Eq.at(0.4), "AbsTol", 1e-15);
 tc.verifyEqual(on(Eq), on(Dq.diag2vec()), "AbsTol", 1e-15);
 end % testEigMat()
 
+function testJordanRealMat(tc)
+z = quantity.Domain("z", linspace(-1, 1, 11));
+zSym = sym("z");
+quan = quantity.Discrete(quantity.Symbolic([1+zSym^2, 2; -zSym, zSym], z));
+% all output arguments
+[V, J, l] = quan.jordanReal();
+[V0p4, J0p4, l0p4] = misc.jordanReal(quan.at(0.4));
+tc.verifyEqual(J0p4, J.at(0.4), "AbsTol", 1e-15);
+tc.verifyEqual(V0p4, V.at(0.4), "AbsTol", 1e-15);
+tc.verifyEqual([l0p4; 0], l.at(0.4), "AbsTol", 1e-15);
+tc.verifyEqual(on(quan * V), on(V * J), "AbsTol", 1e-15);
+end % testJordanRealMat()
+
 function testPower(tc)
 %% scalar case
 z = quantity.Domain("z", linspace(0, 1, 11));
diff --git a/+unittests/+quantity/testSymbolic.m b/+unittests/+quantity/testSymbolic.m
index 87b447ca0367e560a8bdab3331dac98404d447a7..420ec50e478d2ada8411fd32a0c8e1c01f47b3df 100644
--- a/+unittests/+quantity/testSymbolic.m
+++ b/+unittests/+quantity/testSymbolic.m
@@ -4,6 +4,25 @@ function [tests ] = testSymbolic()
 tests = functiontests(localfunctions());
 end
 
+
+function testOnes(tc)
+myDomain = [quantity.Domain("z", linspace(0, 1, 11)), quantity.Domain("zeta", linspace(0, 1, 5))];
+tc.verifyEqual(MAX(abs(quantity.Symbolic.ones(1, myDomain) - 1)), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.ones(2, myDomain) - ones(2))), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.ones([2, 1], myDomain) - ones([2, 1]))), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.ones([2, 2], myDomain(2)) - ones([2, 2]))), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.ones([4, 4], myDomain) - ones(4))), 0);
+end % testOnes()
+
+function testEye(tc)
+myDomain = [quantity.Domain("z", linspace(0, 1, 11)), quantity.Domain("zeta", linspace(0, 1, 5))];
+tc.verifyEqual(MAX(abs(quantity.Symbolic.eye(1, myDomain) - 1)), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.eye(2, myDomain) - eye(2))), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.eye([2, 1], myDomain) - eye([2, 1]))), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.eye([2, 2], myDomain(2)) - eye([2, 2]))), 0);
+tc.verifyEqual(MAX(abs(quantity.Symbolic.eye([4, 4], myDomain) - eye(4))), 0);
+end % testEye()
+
 function testValueContinuousBug(tc)
 z = quantity.Domain("z", linspace(0, 1, 7));
 zeta = quantity.Domain("zeta", linspace(0, 1, 5));