(* 
	Evgenii Rudnyi (c) 2004, 2005, 2006, http://Evgenii.Rudnyi.Ru/
*)

(* Title: post4MOR.m *)
(* Context: Imtek`post4MOR`*)
(* Author: Evgenii Rudnyi *)
(* Date: December 2004, Freiburg *)
(* Summary: This is a package for post-processing dynamic models.*)
(* Package Copyright: GNU GPL *)
(* Package Version: 2.1 *)
(* Mathematica Version: 5.1 *)
(* History: *)
(* Keywords: *)
(* Sources: *)
(* Warnings: *)
(* Limitations: *)
(* Discussion: *)
(* Requirements: *)
(* Examples: See documentations *)
(* *)

(* Whereever the GNU GPL is not applicable, 
  the software should be used in the same spirit. *)

(* Users of this code must verify correctness for their application. *)
(* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA 02111-1307 USA *)
(* Disclaimer: *)
(* <one line to give the program's name and a brief idea of what it does.> *)
(* Copyright (C) 2004 Evgenii Rudnyi*)

(* This program is free software; *)
(* you can redistribute it and or modify it under the terms of the GNU General Public License *)
(* as published by the Free Software Foundation; either version 2 of the License,*)
(* or (at your option) any later version.This program is distributed in the \
hope that *)
(* it will be useful, but WITHOUT ANY WARRANTY; *)
(* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *)
(* See the GNU General Public License for more details. 
      You should have received a copy of *)
(* the GNU General Public License along with this program; if not, 
  write to the *)
(* Free Software Foundation,Inc.,59 Temple Place,Suite 330,Boston,
  MA 02111-1307 USA *)

BeginPackage["Imtek`Post4MOR`", {"LinearAlgebra`MatrixManipulation`", "Utilities`FilterOptions`", "Graphics`MultipleListPlot`"}];

DynamicSystem::usage = "Use MakeDynamicSystem, MakeFirstOrderSystem, and MakeSecondOrderSystem to construct the object; MatrixMQ, MatrixEQ, MatrixDQ, FirstOrderSystemQ, SecondOrderSystemQ to make a query; MatrixM, MatrixE, MatrixK, MatrixA, MatrixB, MatrixC, MatrixD, OutputNames to select a component.";

MakeDynamicSystem::usage = "MakeDynamicSystem[matM, matE, matK, matB, matC, namesC, matD] gives a DynamicSystem object. matM, matE, and matD should be matrices or Null. matK, matB, and matC must be matrices. namesC is a list of strings. matD is optional and by default is Null. MakeDynamicSystem[matM, matE, matK, matB, matD] makes matC equal to the identity matrix.";
MakeFirstOrderSystem::usage = "MakeFirstOrderSystem[matA, matB, matC, namesC, matD] and MakeFirstOrderSystem[matE, matA, matB, matC, namesC, matD] give a DynamicSystem object for a first order system. matE, and matD should be matrices or Null. matA, matB, and matC must be matrices. namesC is a list of strings. matD is optional and by default is Null. MakeFirstOrderSystem[matE, matA, matB, matD] makes matC equal to the identity matrix.";
MakeSecondOrderSystem::usage = "MakeSecondOrderSystem[matM, matK, matB, matC, namesC, matD] and MakeSecondOrderSystem[matM, matE, matK, matB, matC, namesC, matD] give a DynamicSystem object for a second order system. matM, matE, and matD should be matrices or Null. matK, matB, and matC must be matrices. namesC is a list of strings. matD is optional and by default is Null. MakeSecondOrderSystem[matM, matE, matK, matB, matD] makes matC equal to the identity matrix.";

MatrixM::usage = "MatrixM[sys] gives system matrix M of sys.";
MatrixE::usage = "MatrixE[sys] gives system matrix E of sys.";
MatrixA::usage = "MatrixA[sys] gives system matrix A of sys.";
MatrixK::usage = "MatrixK[sys] gives system matrix K of sys.";
MatrixB::usage = "MatrixB[sys] gives system matrix B of sys.";
MatrixC::usage = "MatrixC[sys] gives system matrix C of sys.";
MatrixD::usage = "MatrixD[sys] gives system matrix D of sys.";
OutputNames::usage = "OutputNames[sys] gives a list of output names of sys.";

MatrixMQ::usage = "MatrixMQ[sys] gives True if system matrix M is present in sys and False otherwise.";
MatrixEQ::usage = "MatrixEQ[sys] gives True if system matrix E is present in sys and False otherwise.";
MatrixDQ::usage = "MatrixMD[sys] gives True if system matrix D is present in sys and False otherwise.";
FirstOrderSystemQ::usage = "FirstOrderSystemQ[sys] gives True if sys represent a first order system and False otherwise.";
SecondOrderSystemQ::usage = "SecondOrderSystemQ[sys] gives True if sys represent a second order system and False otherwise.";
ExplicitSystemQ::usage = "ExplicitSystemQ[sys] gives True if sys represent an explicit first order system and False otherwise.";

DeleteOutputs::usage = "DeleteOutputs[sys, names] gives a new system when outputs defined in a list of names are deleted.";
AddOutputs::usage = "AddOutputs[sys, matCadd, names] gives a new system with additional outputs defined by rows of matCadd and a list of names.";
DeleteDamping::usage = "DeleteDamping[sys] gives a new system without system matrix E. sys must be a second order system.";
AddDamping::usage = "AddDamping[sys, matE] gives a new system with matrix E inreased by matE. AddDamping[sys, alpha, beta] gives a new system with matrix E increased by Rayleigh damping alpha*matM+beta*matK. sys must be a second order system.";
AddDampingRatio::usage = "AddDampingRatio[sys, imatK] gives a new system with matrix K inreased by I imatK. AddDamping[sys, dmprat] gives a new system with matrix K increased by I 2 dmprat MatrixK. sys must be a second order system.";
ToFirstOrderSystem::usage = "ToFirstOrderSystem[sys] gives a new system in the form of a first order system.";
ToExplicitSystem::usage = "ToExplicitSystem[sys] gives a new system in the form of an explicit first order system.";

TakeSystem::usage = "TakeSystem[sys, dim] gives a new system of dimension dim. This operation makes sense only when an original system has been obtained by iterative model reduction method.";
ReadSystem::usage = "ReadSystem[baseName] reads a system from files with the base name baseName. ReadSystem[file, type] reads a system from a file written in the old format of mor4ansys 1.5. type must be a string FirstOrderSystem or SecondOrderSystem.";
WriteSystem::usage = "WriteSystem[sys, baseName] writes a system to files with the base name baseName.";

SimulationResult::usage = "Use MakeSimulationResult to construct the object; XSeries, XName, YSeries, YNames to select a component.";

MakeSimulationResult::usage = "MakeSimulationResult[XSeries, Xname, YSeries, YNames] gives a SimulationResult object. XSeries must be a vector, YSeries must be a matrix, XName must be a string and YNames must be a list of strings.";

XSeries::usage = "XSeries[res] gives XSeries of res.";
XName::usage = "XName[res] gives XName of res.";
YSeries::usage = "YSeries[res] gives YSeries of res.";
YNames::usage = "YNames[res] gives YNames of res.";

TransformResult::usage = "TransformResult[res, names] gives a new res whose YSeries correspond to names. res also can be a list of SimulationResult. Option TransformFunction to define fun[y, x].";
TransformFunction::usage = "An option for TransformResult to define a function fun[y, x]. If it is a single function, it is applied to all YSeries. Alternatively, one can specify a list of pairs {name, fun}.";

CompatibleResultQ::usage = "CompatibleResultQ[res1, res2, ...] gives True if all objects are compatible and False otherwise.";

Difference::usage = "Difference[res1, {listres}, ops] gives a list of differences between res1 and each object in listres. All SimulationResult must be compatible. Option is ErrorFunction, by default ErrorFunction->Subtract.";

ErrorFunction::usage = "An option for Difference, LocalError and LocalErrroIndicator.";

TakeResult::usage = "TakeResult[res, num] takes first num values for results.";

ReadResult::usage = "ReadResult[file] reads SimulationResult from file in Matrix Market format. ReadResult[file, XName] reads SimulationResult from file in my old format, where XName should be \"Time\" or \"Frequency\".";
WriteResult::usage = "WriteResult[res, file] writes res to file in Matrix Market format. WriteResult[res, file, \"Table\"] writes res as a table.";

WriteResultOld::usage = "WriteResultOld[res, file] writes res to file in my old format.";
WriteResultAsDirichlet::usage = "WriteResultAsDirichlet[res, i, file] writes the i-th set of res to file as ANSYS script to fix values as Dirichlet constraints.";

Log10::usage = "Log10[x] gives Log[10, x].";

Log10RelativeError::usage = "Log10RelativeError[x, y] gives Log10[Norm[x - y]/Norm[x]].";

PlotResult::usage = "PlotResult[listres, ops] makes plots for the list of compatible SimulationResult. Options: CommonTitle for a title, FunctionX and FunctionY to modify series values, MultipleListPlot options.";

CommonTitle::usage = "An option for PlotResult.";
FunctionX::usage = "An option for PlotResult.";
FunctionY::usage = "An option for PlotResult.";

StationarySolution::usage = "StationorySolution[sys, ops] gives a stationary solution. Options: InputFunction, LinearSolve options.";
HarmonicSolution::usage = "HarmonicSolution[freq, sys, ops] gives SimulationResult for a given list of freq. Options: InputFunction, LinearSolve options.";
FrequencyConvergence::usage = "FrequencyConvergence[freq, sys, start, finish] gives an object for LocalError and LocalErrorIndicator computed for sys with dimensions from start to finish for frequency freq. FrequencyConvergence[freq, sys, start, finish, step] allows us to specify step.";
LocalError::usage = "LocalError[conv, true, ops] gives a list of local errors computed from the object conv obtained by FrequencyConvergence and a list of true values for the same frequency. Option is ErrorFunction (by default Log10RelativeError).";
LocalErrorIndicator::usage = "LocalErrorIndicator[conv, true, ops] gives a list of local error estimates computed from the object conv obtained by FrequencyConvergence. Option is ErrorFunction (by default Log10RelativeError).";
TransientSolution::usage = "TransientSolution[time, sys, ops] gives SimulationResult for a given list time. Options: Verbose, InputFunction, InitialState, NDSolve options. It uses different approaches for different types of DynamicSystem. Use ToFirstOrderSystem or ToExplicitSystem, if TransienSolution fails.";

AnsysTransientSolution::usage = "AnsysTransientSolution[time, sys, ops] gives SimulationResult for a given list time by using integrators like in ANSYS: backward Euler for a first order system and Newmark for a second order
system. Options: InputFunction, InitialState, TINTP.";
TINTP::usage = "An option for AnsysTransientSolution: TINTP->{gamma, alpha, delta, theta}";
InputFunction::usage = "An option for StationorySolution, TransientSolution, and HarmonicSolution.";
Verbose::usage = "An option for TransientSolution.";
InitialState::usage = "An option for TransientSolution.";

Begin["`Private`"];

(*SystemFirstOrder*)

Format[sys_DynamicSystem] := If[Length[sys] == 7 && MatrixQ[MatrixK[sys]], 
	"DynamicSystem[{"<>ToString[Length[MatrixK[sys]]]<>","<>ToString[Dimensions[MatrixB[sys]][[2]]]<>","<>ToString[Length[MatrixC[sys]]]<>"}, ...]",
	"- DynamicSystem - "
]

(*
SetAttributes[{
	DynamicSystem,
	SimulationResult,
	FunctionX,
	FunctionY,
	CommonTitle
}, Protected]
*)

checkConsistence[sys_DynamicSystem] := Module[{dim},
	dim = Dimensions[MatrixK[sys]];	
	Which[
		MatrixMQ[sys] && Dimensions[originalMatrixM[sys]] != dim,
			Throw["DynamicSystem: MatrixM and MatrixK are not compatible"],
		MatrixEQ[sys] && Dimensions[originalMatrixE[sys]] != dim,
			Throw["DynamicSystem: MatrixE and MatrixK are not compatible"],
		dim[[1]] != Length[MatrixB[sys]],
			Throw["DynamicSystem: MatrixB and MatrixK are not compatible"],
		dim[[1]] != Dimensions[MatrixC[sys]][[2]],
			Throw["DynamicSystem: MatrixC and MatrixK are not compatible"],
		Length[MatrixC[sys]] != Length[OutputNames[sys]],
			Throw["DynamicSystem: MatrixC and OutputNames are not compatible"],
		MatrixDQ[sys] && (Length[originalMatrixD[sys]] != Length[MatrixC[sys]]),
			Throw["DynamicSystem: MatrixD and MatrixC are not compatible"],
		MatrixDQ[sys] && (Dimensions[originalMatrixD[sys]][[2]] != Dimensions[MatrixB[sys]][[2]]),
			Throw["DynamicSystem: MatrixD and MatrixB are not compatible"]
	];
	sys
]

defaultMatrixC[n_Integer] := SparseArray[{{i_, i_}->1.}, {n, n}]

defaultOutputNames[n_Integer] := Table[ToString[i], {i, n}]

MakeDynamicSystem[
	matM_ ,
	matE_ ,
	matK_ ,
	matB_ ,
	matD_ : Null] := (
	checkConsistence[DynamicSystem[matM, matE, matK, matB, defaultMatrixC[Length[matK]], defaultOutputNames[Length[matK]], matD]]
) /; ((MatrixQ[matM] || matM === Null) 
	&& (MatrixQ[matE] || matE === Null)
	&& (MatrixQ[matK])
	&& (MatrixQ[matB])
	&& (MatrixQ[matD] || matD === Null))

MakeDynamicSystem[
	matM_ ,
	matE_ ,
	matK_ ,
	matB_ ,
	matC_ ,
	namesC:{___String},
	matD_ : Null] := (
	checkConsistence[DynamicSystem[matM, matE, matK, matB, matC, namesC, matD]]
) /; ((MatrixQ[matM] || matM === Null) 
	&& (MatrixQ[matE] || matE === Null)
	&& (MatrixQ[matK])
	&& (MatrixQ[matB])
	&& (MatrixQ[matC])
	&& (MatrixQ[matD] || matD === Null))

originalMatrixM[sys_DynamicSystem] := sys[[1]]

originalMatrixE[sys_DynamicSystem] := sys[[2]]

originalMatrixD[sys_DynamicSystem] := sys[[7]]

MatrixMQ[sys_DynamicSystem] := sys[[1]] =!= Null

MatrixEQ[sys_DynamicSystem] := sys[[2]] =!= Null

MatrixDQ[sys_DynamicSystem] := sys[[7]] =!= Null

FirstOrderSystemQ[sys_DynamicSystem] := sys[[1]] === Null

SecondOrderSystemQ[sys_DynamicSystem] := sys[[1]] =!= Null

ExplicitSystemQ[sys_DynamicSystem] := (sys[[1]] === Null && sys[[2]] === Null)

MatrixM[sys_DynamicSystem] := If[MatrixMQ[sys],
	sys[[1]],
	SparseArray[{},Dimensions[MatrixK[sys]]]
]

MatrixE[sys_DynamicSystem] := If[MatrixEQ[sys],
	sys[[2]],
	If[FirstOrderSystemQ[sys],
		SparseArray[{{i_, i_}->1.}, Dimensions[MatrixK[sys]]],
		SparseArray[{}, Dimensions[MatrixK[sys]]]
	]
]

MatrixK[sys_DynamicSystem] := sys[[3]]

MatrixA[sys_DynamicSystem] := -sys[[3]]

MatrixB[sys_DynamicSystem] := sys[[4]]

MatrixC[sys_DynamicSystem] := sys[[5]]

OutputNames[sys_DynamicSystem] := sys[[6]]

MatrixD[sys_DynamicSystem] := If[MatrixDQ[sys],
	sys[[7]],
	SparseArray[{},{Length[MatrixC[sys]], Dimensions[MatrixB[sys]][[2]]}]
]

MakeFirstOrderSystem[
	matA_,
	matB_,
	matC_,
	namesC:{___String},
	matD_ : Null] := 
MakeDynamicSystem[Null, Null, -matA, matB, matC, namesC, matD] /;
	((MatrixQ[matA])
	&& (MatrixQ[matB])
	&& (MatrixQ[matC])
	&& (MatrixQ[matD] || matD === Null))

MakeFirstOrderSystem[
	matE_, 
	matA_,
	matB_,
	matD_ : Null] := 
MakeDynamicSystem[Null, matE, -matA, matB, matD] /;
	((MatrixQ[matE] || matE === Null)
	&& (MatrixQ[matA])
	&& (MatrixQ[matB])
	&& (MatrixQ[matD] || matD === Null))

MakeFirstOrderSystem[
	matE_, 
	matA_,
	matB_,
	matC_,
	namesC:{___String},
	matD_ : Null] := 
MakeDynamicSystem[Null, matE, -matA, matB, matC, namesC, matD] /;
	((MatrixQ[matE] || matE === Null)
	&& (MatrixQ[matA])
	&& (MatrixQ[matB])
	&& (MatrixQ[matC])
	&& (MatrixQ[matD] || matD === Null))

MakeSecondOrderSystem[
	matM_,
	matK_,
	matB_,
	matC_,
	namesC:{___String},
	matD_ : Null] := 
MakeDynamicSystem[matM, Null, matK, matB, matC, namesC, matD] /;
	((MatrixQ[matM])
	&& (MatrixQ[matK])
	&& (MatrixQ[matB])
	&& (MatrixQ[matC])
	&& (MatrixQ[matD] || matD === Null))

MakeSecondOrderSystem[
	matM_,
	matE_,
	matK_,
	matB_,
	matD_ : Null] := 
MakeDynamicSystem[matM, matE, matK, matB, matD] /;
	((MatrixQ[matM])
	&& (MatrixQ[matE] || matE === Null)
	&& (MatrixQ[matK])
	&& (MatrixQ[matB])
	&& (MatrixQ[matD] || matD === Null))

MakeSecondOrderSystem[
	matM_,
	matE_,
	matK_,
	matB_,
	matC_,
	namesC:{___String},
	matD_ : Null] := 
MakeDynamicSystem[matM, matE, matK, matB, matC, namesC, matD] /;
	((MatrixQ[matM])
	&& (MatrixQ[matE] || matE === Null)
	&& (MatrixQ[matK])
	&& (MatrixQ[matB])
	&& (MatrixQ[matC])
	&& (MatrixQ[matD] || matD === Null))

(*some functions for systemSecondOrder*)

DeleteOutputs[sys_DynamicSystem, name_String] := DeleteOutputs[sys, {name}] 

DeleteOutputs[sys_DynamicSystem, names:{___String}] := Module[{del,dd},
	del = Flatten[Map[Function[{n1},
		dd = {};
		MapIndexed[
			If[#1===n1,
				AppendTo[dd, #2]
			]&
		,OutputNames[sys]];
		dd		
	],names], 1];
	MakeDynamicSystem[
		originalMatrixM[sys],
		originalMatrixE[sys],
		MatrixK[sys], 
		MatrixB[sys], 
		Delete[MatrixC[sys], del], 
	  Delete[OutputNames[sys], del], 
		originalMatrixD[sys]
	]
]

AddOutputs[sys_DynamicSystem, matCadd_, name_String] := AddOutputs[sys, matCadd, {name}]

AddOutputs[sys_DynamicSystem, matCadd_, names:{___String}] := MakeDynamicSystem[
	originalMatrixM[sys],
	originalMatrixE[sys],
	MatrixK[sys], 
	MatrixB[sys], 
	Join[MatrixC[sys], matCadd], 
	Join[OutputNames[sys], names], 
	originalMatrixD[sys]
] /; MatrixQ[matCadd] && Dimensions[matCadd][[2]] == Length[MatrixK[sys]]

DeleteDamping[sys_DynamicSystem] := MakeSecondOrderSystem[
	MatrixM[sys],
	MatrixK[sys],
	MatrixB[sys],
	MatrixC[sys],
	OutputNames[sys],
	originalMatrixD[sys]
]/; SecondOrderSystemQ[sys]

AddDamping[sys_DynamicSystem, matE_] :=
MakeSecondOrderSystem[
	MatrixM[sys],
	MatrixE[sys] + matE,
	MatrixK[sys],
	MatrixB[sys],
	MatrixC[sys],
	OutputNames[sys],
	originalMatrixD[sys]
]/; (SecondOrderSystemQ[sys] && MatrixQ[matE])

AddDamping[sys_DynamicSystem, alpha_, beta_ ] :=
MakeSecondOrderSystem[
	MatrixM[sys],
	MatrixE[sys] + N[alpha]*MatrixM[sys] + N[beta]*MatrixK[sys],
	MatrixK[sys],
	MatrixB[sys],
	MatrixC[sys],
	OutputNames[sys],
	originalMatrixD[sys]
] /; (SecondOrderSystemQ[sys] && NumericQ[alpha] && NumericQ[beta])

AddDampingRatio[sys_DynamicSystem, dmprat_] :=
MakeSecondOrderSystem[
	MatrixM[sys],
	MatrixE[sys],
	MatrixK[sys] + I*2*dmprat*MatrixK[sys],
	MatrixB[sys],
	MatrixC[sys],
	OutputNames[sys],
	originalMatrixD[sys]
]/; (SecondOrderSystemQ[sys] && NumericQ[dmprat])

AddDampingRatio[sys_DynamicSystem, imatK_] :=
MakeSecondOrderSystem[
	MatrixM[sys],
	MatrixE[sys],
	MatrixK[sys] + I imatK,
	MatrixB[sys],
	MatrixC[sys],
	OutputNames[sys],
	originalMatrixD[sys]
]/; (SecondOrderSystemQ[sys] && MatrixQ[matE])


(*functions for both systems*)

ToFirstOrderSystem[sys_DynamicSystem] := Module[
	{zero = SparseArray[{}, Dimensions[MatrixK[sys]]], 
	one = SparseArray[{{i_, i_}->1.}, Dimensions[MatrixK[sys]]]},
	MakeFirstOrderSystem[
		BlockMatrix[{{MatrixE[sys], MatrixM[sys]},{-one, zero}}],
		-BlockMatrix[{{MatrixK[sys], zero},{zero, one}}],
		BlockMatrix[{{MatrixB[sys]},{SparseArray[{}, {Length[MatrixK[sys]],Dimensions[MatrixB[sys]][[2]]}] }}],
		BlockMatrix[{{MatrixC[sys], SparseArray[{}, {Length[MatrixC[sys]],Length[MatrixK[sys]]}] }}],
		OutputNames[sys],
		originalMatrixD[sys]
	]
] /; SecondOrderSystemQ[sys]

ToFirstOrderSystem[sys_DynamicSystem] := sys /; FirstOrderSystemQ[sys]

ToExplicitSystem[sys_DynamicSystem] := Module[{inv},
	If[MatrixEQ[sys],
			inv = Inverse[MatrixE[sys]];
			MakeFirstOrderSystem[
			inv.MatrixA[sys],
			inv.MatrixB[sys],
			MatrixC[sys],
			OutputNames[sys],
			originalMatrixD[sys]
		]
	,
		sys
	]
] /; FirstOrderSystemQ[sys]

ToExplicitSystem[sys_DynamicSystem] := ToExplicitSystem[ToFirstOrderSystem[sys]] /; SecondOrderSystemQ[sys]

TakeSystem[sys_DynamicSystem, dim_Integer] := (
	If[dim > Length[MatrixK[sys]],
		Throw["TakeSystem: dimension is smaller then required"]
	];
	DynamicSystem[
		If[MatrixMQ[sys], Take[MatrixM[sys], dim, dim], Null],
		If[MatrixEQ[sys], Take[MatrixE[sys], dim, dim], Null],
		Take[MatrixK[sys], dim, dim],
		Take[MatrixB[sys], dim],
		Take[MatrixC[sys], Length[MatrixC[sys]], dim],
		OutputNames[sys],
		If[MatrixDQ[sys], Take[MatrixD[sys], dim, dim], Null]
	]
)

fileQ[name_String] := Module[{in},
	Off[OpenRead::"noopen"];
	in = OpenRead[name];
	On[OpenRead::"noopen"];
	If [in === $Failed,
		False,
		Close[in]; True
	]
]

readMatrix1[name_String] := If[fileQ[name],
	Import[name, "MTX"],
	Throw["ReadSystem: No file "<>name]
]

readMatrix2[name_String] := If[fileQ[name],
	Import[name, "MTX"],
	Null
]

readNames[name_String] := Module[{in, Cnames},
	If[!fileQ[name], Throw["ReadSystem: No file "<>name]]; 
	in = OpenRead[name, DOSTextFormat->True];
	Cnames = ReadList[in, Word, WordSeparators->{" ", "\t", "\n", FromCharacterCode[10], FromCharacterCode[13]}];
	Close[in];
	Cnames 
]

readFirstOrder[baseName_String] := MakeFirstOrderSystem[
	readMatrix2[baseName<>".E"],
	readMatrix1[baseName<>".A"],
	readMatrix1[baseName<>".B"],
	readMatrix1[baseName<>".C"],
	readNames[baseName<>".C.names"],
	readMatrix2[baseName<>".D"]
]

readSecondOrder[baseName_String] := MakeSecondOrderSystem[
	readMatrix1[baseName<>".M"],
	readMatrix2[baseName<>".E"],
	readMatrix1[baseName<>".K"],
	readMatrix1[baseName<>".B"],
	readMatrix1[baseName<>".C"],
	readNames[baseName<>".C.names"],
	readMatrix2[baseName<>".D"]
]

ReadSystem[baseName_String] := (
	Which[
		fileQ[baseName<>".A"],
			readFirstOrder[baseName],
		fileQ[baseName<>".K"],
			readSecondOrder[baseName],
		True,
			Throw["ReadSystem: no files with A or K matrix"]
	]
)

ReadSystem[name_String, head_String] :=Module[
	{in, dimMax, Tini, redC, redK, ninp, funinp, bredin, nout, matout, names},
	in = OpenRead[name, DOSTextFormat->True];
	If[in == $Failed, Throw["ReadSystem: no such file"]];
	dimMax = Round[Read[in, Number]];
	Tini = N[Read[in, Number]];
	redC = N[Map[ReadList[in, Number, dimMax] &, Range[dimMax]]];
	redK = N[Map[ReadList[in, Number, dimMax] &, Range[dimMax]]];
	ninp = Round[Read[in, Number]];
	bredin = N[Map[ReadList[in, Number, dimMax] &, Range[ninp]]];
	nout = Round[Read[in, Number]];
	matout = {};
	names = {};
	Scan[
		(AppendTo[names, Read[in, Word, WordSeparators->{" ", "\t", "\n", FromCharacterCode[10], FromCharacterCode[13]}]]; 
		AppendTo[matout, N[ReadList[in, Number, dimMax]]])&, 
		Range[nout]
	];
	Close[in];
	Switch[
		head,
		"FirstOrderSystem",
			MakeFirstOrderSystem[redC, -redK, Transpose[bredin], matout, names],
		"SecondOrderSystem",
			MakeSecondOrderSystem[redC, redK, Transpose[bredin], matout, names],
		_,
			Throw["ReadSystem: wrong argument"]
	]
]

writeNames[name_String, list_List] := Module[{in},
	in = OpenWrite[name, FormatType -> OutputForm];
	Scan[
		Write[in, #]&
	,list];
	Close[in]
]

WriteSystem[sys_DynamicSystem, baseName_String] := (
	If[MatrixMQ[sys], Export[baseName<>".M", MatrixM[sys], "MTX"]];
	If[MatrixEQ[sys], Export[baseName<>".E", MatrixE[sys], "MTX"]];
	If[FirstOrderSystemQ[sys],
		Export[baseName<>".A", MatrixA[sys], "MTX"],
		Export[baseName<>".K", MatrixK[sys], "MTX"]
	];
	Export[baseName<>".B", MatrixB[sys], "MTX"];
	Export[baseName<>".C", MatrixC[sys], "MTX"];
	writeNames[baseName<>".C.names", OutputNames[sys]];
	If[MatrixDQ[sys], Export[baseName<>".D", MatrixD[sys], "MTX"]]
)

Format[res_SimulationResult] := "- SimulationResult - "

checkConsistence[res_SimulationResult] := Module[{dim},
	dim = Dimensions[YSeries[res]];	
	Which[
		Length[XSeries[res]] != dim[[2]],
			Throw["DynamicSystem: XSeries and YSeries are not compatible"],
		Length[YNames[res]] != dim[[1]],
			Throw["DynamicSystem: YNames and YSeries are not compatible"]
	];
	res
]

MakeSimulationResult[
	x_,
	xName_String,
	series_,
	names_List] := checkConsistence[SimulationResult[x, xName, series, names]] /;
	(VectorQ[x] && MatrixQ[series])

XSeries[res_SimulationResult] := res[[1]]

XName[res_SimulationResult] := res[[2]]

YSeries[res_SimulationResult] := res[[3]]

YNames[res_SimulationResult] := res[[4]]

Options[TransformResult] = {TransformFunction->Automatic};


TransformResult[res:{___SimulationResult}, names_List, ops___] := Map[
	TransformResult[#, names, ops]&
, res]

TransformResult[res_SimulationResult, names_List, ops___] := Module[{ser, nam, pos, flist, op},
	op = TransformFunction /. {ops} /. Options[TransformResult];
	If[VectorQ[op] && Dimensions[op]==={2}, op = {op}];
	flist = Which[
		op === Automatic,
			Table[#1&, {Length[names]}],
		MatrixQ[op] && Dimensions[op][[2]] == 2,
			Map[
			If[(pos = Position[Transpose[op][[1]], #, 1, 1]) == {},
				#1&
			,
				op[[pos[[1, 1]], 2]]
			]&
			,names],
		True,
			Table[op, {Length[names]}]
	];	
	ser = {};
	nam = {};
	MapThread[Function[{ni, fi},
		If[(pos = Position[YNames[res], ni, 1, 1]) == {},
			Print["Series "<>ni<>" is not present: ignored"]
		,
			AppendTo[ser, MapThread[Function[{y, x}, fi[y, x]], {Flatten[Extract[YSeries[res], pos]], XSeries[res]}]];
			AppendTo[nam, ni]
		]
	], {names, flist}];

	If[ser == {}, Throw["FindResults: empty SimulationResult"]];
	MakeSimulationResult[
		XSeries[res],
		XName[res],
		ser,
		nam
	]
]

compatibleQ[res1_SimulationResult, res2_SimulationResult] := 
	XName[res1] === XName[res2] && YNames[res1] === YNames[res2]

CompatibleResultQ[res1_SimulationResult] := True 

CompatibleResultQ[res1_SimulationResult, res2__] := 
Apply[And, Map[compatibleQ[res1, #]&, {res2}]] /; VectorQ[{res2}, MatchQ[#, _SimulationResult]&]

CompatibleResultQ[list_List] := Apply[CompatibleResultQ, list]

Options[Difference] = {ErrorFunction->Subtract};

Difference[res1_SimulationResult, res2_SimulationResult, ops___] := 
	Difference[res1, {res2}, ops]

diff1[res1_, resi_, fun_] := MapThread[
	fun[#1, #2]&
, {YSeries[res1], YSeries[resi]}]

diff2[res1_, resi_, fun_, ops___] := MapThread[
	fun[#1, Interpolation[Transpose[{XSeries[resi], #2}], FilterOptions[Interpolation, {ops}], InterpolationOrder->1][XSeries[res1]]]&
, {YSeries[res1], YSeries[resi]}]

Difference[res1_SimulationResult, resList_List, ops___] := Module[{
	fun = ErrorFunction /. {ops} /. Options[Difference]},
	Map[
		MakeSimulationResult[
			XSeries[res1],
			XName[res1],
			If[XSeries[res1] == XSeries[#],
				diff1[res1, #, fun],
				diff2[res1, #, fun, ops]
			],
			YNames[res1]
		]&
	, resList]
] /; CompatibleResultQ[Prepend[res1, resList]]

TakeResult[res_SimulationResult, num_Integer] := 	MakeSimulationResult[
		Take[XSeries[res], num],
		XName[res],
		Take[YSeries[res], Length[YSeries[res]], num],
		YNames[res]
	]

ReadResult[file_String] := Module[{mat, names},
	mat = Chop[Import[file, "MTX"], $MinNumber];
	names = readNames[file<>".names"];
	MakeSimulationResult[
		mat[[1]],
		names[[1]],
		Delete[mat, 1],
		Delete[names, 1]		
	]
]

WriteResult[res_SimulationResult, file_String] := ( 
  	Export[file, Prepend[YSeries[res], XSeries[res]], "MTX"];
	  writeNames[file<>".names", Prepend[YNames[res], XName[res]]]
)

WriteResult[res_SimulationResult, file_String, "Table"] := (
		Export[file, Transpose[Prepend[YSeries[res], XSeries[res]]], "Table"]
)

(*to read old format, the treatment of complex numbers is for Sam*)

ReadResult[name_String, nameX_String] := Module[{in, ntime, time, series, names, node, ser},
	Off[Read::"readn"];
	in = OpenRead[name];
	ntime = Round[Read[in, Number]];
	time = ReadList[in, Number, ntime];
	names = {};
	series = {};
	While[
    (node = Read[in, Word, WordSeparators->{" ", "\t", "\n", FromCharacterCode[10], FromCharacterCode[13]}]) =!= EndOfFile,
    AppendTo[names, node];
		ser = ReadList[in, Number, ntime*2];
		Which[
			Length[ser] === ntime*2,
				ser = Map[(#[[1]]*Cos[#[[2]]*Degree] +
I*#[[1]]*Sin[#[[2]]*Degree])&, Partition[ser, 2]],
			Length[ser] === ntime,
				ser,
			True,
				ser = Delete[ser, -1]
		];
    AppendTo[series, ser]
  ];
	Close[in];
	On[Read::"readn"];
	MakeSimulationResult[
		time,
		nameX,
		series,
		names
	]
]

(*for Sam*)

formatNumber[x_] := ToString[NumberForm[CForm[x],25,NumberPadding->{" ",""},SignPadding->False,ExponentFunction -> Identity]]

WriteResultOld[res_SimulationResult, file_String] := Module[{of},
	of = OpenWrite[file, FormatType -> OutputForm];
	Write[of, ToString[Length[XSeries[res]]]];
	Scan[
		Write[of, formatNumber[#]]&
	,XSeries[res]];
	MapIndexed[(
		Write[of, #1];
		Scan[
			If [Head[#] === Complex,
				Write[of, formatNumber[Abs[#]]<>" "<>formatNumber[Arg[#]/Degree]]				
			,
				Write[of, formatNumber[#]]
			]&
		, YSeries[res][[#2[[1]]]]]
  )&, YNames[res]];
	Close[of]		
]

(*tuned for harmonic simulation*)
WriteResultAsDirichlet[res_SimulationResult, file_String] := 
Module[{of,val},
	of = OpenWrite[file, FormatType -> OutputForm];
	Write[of, "resume"];
	Write[of, "/solu"];
	Write[of, "allsel"];
	Write[of, "antype,3"];
	MapThread[Function[{xi, yi},
		Write[of, "harfrq,"<>ToString[CForm[xi]]];
		MapThread[(
			If[Head[#2] === Complex,
				val = ToString[CForm[Re[#2]]]<>","<>ToString[CForm[Im[#2]]]
			,
				val = ToString[CForm[#2]]
			];
			Write[of, "D,"<>StringReplace[#1, {"Node_" -> "", "_" -> ","}]<>","<>val];
  	)&, {YNames[res], yi}];
		Write[of, "solve"]
	], {XSeries[res], Transpose[YSeries[res]]}];
	Write[of, "fini"];
	Close[of]	
]

(*does not handle complex-valued numbers correctly*)
WriteResultAsDirichlet[res_SimulationResult, i_Integer, file_String] := 
Module[{of},
	If[i > Length[XSeries[res]] || i < 1, Throw["The index i is not valid"]];
	of = OpenWrite[file, FormatType -> OutputForm];
	MapIndexed[(
		Write[of, "D,"<>StringReplace[#1, {"Node_" -> "", "_" -> ","}]<>","<>ToString[CForm[YSeries[res][[#2[[1]], i]]]]];
  )&, YNames[res]];
	Close[of]	
]

defaultPlotOptions = {PlotRange->All, ImageSize->300, Frame->True, Axes->False};

Log10 := Log[10, #] &

Options[PlotResults] = {CommonTitle->"", FunctionX->Identity, FunctionY->Identity};

PlotResult[res_SimulationResult, ops___] := PlotResult[{res}, ops]

PlotResult[listres_List, ops___] := Module[{
		options = Join[{ops}, defaultPlotOptions],
		title = CommonTitle /. {ops} /. Options[PlotResults],	
		funx = FunctionX /. {ops} /. Options[PlotResults],
		funy = FunctionY /. {ops} /. Options[PlotResults]
	},
	MapIndexed[Function[{name, i},
		Apply[MultipleListPlot, Join[Map[Function[{res},
			Transpose[{funx[XSeries[res]], funy[YSeries[res][[i[[1]]]]]}]]
			, listres],
 			{FilterOptions[MultipleListPlot, options]},
			{PlotLabel->name<>": "<>title,
			PlotJoined->True,
			SymbolShape->None}]
		]
	],YNames[listres[[1]]]]
] /; CompatibleResultQ[listres]

makeInputs[inpFun_, n_] := Module[{inp},
	inp = Which[
	inpFun === Automatic,
		Table[1., {n}],
	Head[inpFun] === List && Length[inpFun] === n,
		inpFun,
	n === 1 && Head[inpFun] =!= List,
		{inpFun},
	True,
		Throw["Input functions are not compatible with the system"] 
	];
	If[VectorQ[inp, NumericQ],
		inp
	,
		Map[Function[{u},
			If[NumericQ[u[0, Table[0, {n}]]], 
				u
			, 
				If[NumericQ[(u&)[0, Table[0, {n}]]], 
					u&
				,
					Throw[{"Not numeric values of input function ", u}]
				]
			]
		], inp]
	]
]

Options[StationarySolution] = {InputFunction->Automatic};

StationarySolution[sys_DynamicSystem, ops___] := Module[{inpFun},
	inpFun = InputFunction /. {ops} /. Options[StationarySolution];
	inpFun = makeInputs[inpFun, Last[Dimensions[MatrixB[sys]]]];
	If[!VectorQ[inpFun, NumericQ], Throw["Input functions must be numeric vales"]]; 
	MatrixC[sys].LinearSolve[MatrixK[sys], MatrixB[sys].inpFun, FilterOptions[LinearSolve, ops]]]

harmonicLoop[list_, fun_, rhs_, ops___] := Map[
 LinearSolve[fun[#], rhs[#], FilterOptions[LinearSolve, ops]]&, 
2.*N[Pi]*list]

Options[HarmonicSolution] = {InputFunction->Automatic};

HarmonicSolution[freq_List, sys_DynamicSystem, ops___] := Module[{inpFun, tmp, f},
	inpFun = InputFunction /. {ops} /. Options[HarmonicSolution];
	inpFun = makeInputs[inpFun, Last[Dimensions[MatrixB[sys]]]];
	If[VectorQ[inpFun, NumericQ], 
		f[w_] := MatrixB[sys].inpFun;
		,
		f[w_] := MatrixB[sys].Map[Function[el, el[w]], inpFun];
	];
MakeSimulationResult[
	freq,
	"Frequency",
	MatrixC[sys].Transpose[Which[
	!MatrixMQ[sys],
		harmonicLoop[freq, (I*#*MatrixE[sys] + MatrixK[sys])&, 
		f, ops], 
	!MatrixEQ[sys],
		harmonicLoop[freq, (-#^2*MatrixM[sys] + MatrixK[sys])&, 
		f, ops], 
	True,
		harmonicLoop[freq, (-#^2*MatrixM[sys] + I*#*MatrixE[sys] + MatrixK[sys])&, 
		f, ops]
	]], 
	OutputNames[sys]
]] /; VectorQ[freq, NumericQ]

FrequencyConvergence[freq_, sys_DynamicSystem, 
start_Integer: 1, 
finish_: Infinity,
step_Integer: 1] := Module[{fin},
	fin = finish;
  If[fin > Length[MatrixK[sys]], fin = Length[MatrixK[sys]]];
  Map[
  (
    {#, Transpose[YSeries[HarmonicSolution[{freq}, TakeSystem[sys, #]]]][[1]]}
   ) &,
   Range[start, fin, step]
   ]
]/; NumericQ[freq]

Log10RelativeError[x_, y_] := Log10[Norm[x - y]/Norm[x]]

Options[LocalErrorIndicator] = {ErrorFunction->Log10RelativeError};

LocalError[list_List, exact_List, ops___] := Module[{err},
	err = ErrorFunction /. {ops} /. Options[LocalErrorIndicator];
	Map[{#[[1]], err[exact, #[[2]]]}&, list]
]

LocalErrorIndicator[list_List, ops___] := Module[{l, prev, p, err},
	err = ErrorFunction /. {ops} /. Options[LocalErrorIndicator];
	l1 = Delete[list, -1];
	l2 = Delete[list, 1];
	MapThread[({#1[[1]], err[#2[[2]], #1[[2]]]})&, {l1, l2}]
]

Options[TransientSolution] = {Verbose->False, InputFunction->Automatic, InitialState->Automatic, TINTP->{0.005,Null,Null,1}};

AnsysTransientSolution[time_List, sys_DynamicSystem, ops___] := Module[
  {init, inpFun, f, theta, dt, dtprev, un, vn, res, tprev, un1, tintp, factor},
	tintp = TINTP /. {ops} /. Options[TransientSolution];
  theta = tintp[[4]];
	init = InitialState /. {ops} /. Options[TransientSolution];
	If[init === Automatic,
		init = Table[0., {Length[MatrixK[sys]]}]];
	inpFun = InputFunction /. {ops} /. Options[TransientSolution];
	inpFun = makeInputs[inpFun, Last[Dimensions[MatrixB[sys]]]];
	If[VectorQ[inpFun, NumericQ], 
		f[tau_, x_] := MatrixB[sys].inpFun,
		f[tau_, x_] := MatrixB[sys].Map[Function[el, el[tau, x]], inpFun]
	];
	tprev = 0;
	dtprev = 0;
	un = init;
	vn = Table[0., {Length[MatrixK[sys]]}];
	res = Map[
		(
		dt = # - tprev;
		tprev = #;
		If[dt != 0,
			If[dt != dtprev,
				factor = LinearSolve[1./theta/dt*MatrixE[sys] + MatrixK[sys], FilterOptions[LinearSolve, ops]];
			];
 	  	un1 = factor[f[#, un] + MatrixE[sys].(1./theta/dt*un + (1 - theta)/theta*vn)];
 	  	vn = 1./theta/dt((un1-un) - (1 - theta)*dt*vn);
			dtprev = dt;
			un = un1
		,
			un
		]
		)&, time];
	MakeSimulationResult[
		time,
		"Time",
		MatrixC[sys].Transpose[res], 
		OutputNames[sys]
	]
]/; FirstOrderSystemQ[sys]

AnsysTransientSolution[time_List, sys_DynamicSystem, ops___] := Module[
  {init, inpFun, f, gamma, alpha, beta, dt, dtprev, un, vn, an, res, tprev, a0, a1, a2, a3, a4, a5, a6, a7, vn1, un1, an1, tintp, factor},
	tintp = TINTP /. {ops} /. Options[TransientSolution];
	If[tintp[[1]] =!= Null,
	  gamma = tintp[[1]];
		alpha = 0.25*(1 + gamma)^2;
		delta = 0.5 + gamma
	,
		alpha = tintp[[2]];
		beta = tintp[[3]]
	];
	init = InitialState /. {ops} /. Options[TransientSolution];
	If[init === Automatic,
		init = Table[0., {Length[MatrixK[sys]]}]];
	inpFun = InputFunction /. {ops} /. Options[TransientSolution];
	inpFun = makeInputs[inpFun, Last[Dimensions[MatrixB[sys]]]];
	If[VectorQ[inpFun, NumericQ], 
		f[tau_, x_] := MatrixB[sys].inpFun,
		f[tau_, x_] := MatrixB[sys].Map[Function[el, el[tau, x]], inpFun]
	];

	tprev = 0;
	dtprev = 0;
	un = init;
  vn = Table[0., {Length[MatrixK[sys]]}];
	an = Table[0., {Length[MatrixK[sys]]}];
	res = Map[
		(
		dt = # - tprev;
		tprev = #;
		If[dt != 0,
			a0 = 1./alpha/dt^2;
			a1 = delta/alpha/dt;
 	  	a2 = 1./alpha/dt;
	    a3 = 1./2/alpha - 1;
	  	a4 = delta/alpha - 1.;
	    a5 = dt/2*(delta/alpha - 2);
	    a6 = dt*(1 - delta);
	    a7 = delta*dt;
			If[dt != dtprev,
	 			factor = LinearSolve[a0*MatrixM[sys] + a1*MatrixE[sys] + MatrixK[sys], FilterOptions[LinearSolve, ops]]
			];
	    un1 = factor[f[#, un] + MatrixM[sys].(a0*un + a2*vn + a3*an) + MatrixE[sys].(a1*un + a4*vn + a5*an)];
			an1 = a0*(un1 - un) - a2*vn - a3*an;
	    vn1 = vn + a6*an + a7*an1;
			vn = vn1;
			an = an1;
			dtprev = dt;
			un = un1
		,
			un
		]
		)&, time];
	MakeSimulationResult[
		time,
		"Time",
		MatrixC[sys].Transpose[res], 
		OutputNames[sys]
	]
]/; SecondOrderSystemQ[sys]

TransientSolution[time_List, sys_DynamicSystem, ops___] := Module[{sol, tim, ver, inpFun, f1, init, init2, initv},
	ver = Verbose /. {ops} /. Options[TransientSolution];
	init = InitialState /. {ops} /. Options[TransientSolution];
	inpFun = InputFunction /. {ops} /. Options[TransientSolution];
	inpFun = makeInputs[inpFun, Last[Dimensions[MatrixB[sys]]]];
	If[VectorQ[inpFun, NumericQ], 
		f1[tau_, x_?VectorQ] := -MatrixK[sys].x + MatrixB[sys].inpFun,
		f1[tau_, x_?VectorQ] := -MatrixK[sys].x + MatrixB[sys].Map[Function[el, el[tau, x]], inpFun]
	];
	If[init === Automatic,
		init = Table[0., {Length[MatrixK[sys]]}]];
	If[Length[init] != Length[MatrixK[sys]], Throw["Initial State is not compatible"]];
	tim = AbsoluteTiming[sol = Which[
	ExplicitSystemQ[sys],
		NDSolve[{T'[tau] == f1[tau, T[tau]], 
			T[0] == init}, T, {tau, 0, Last[time]}, FilterOptions[NDSolve, ops]],
	FirstOrderSystemQ[sys],
		init2 = LinearSolve[MatrixE[sys], f1[0, init]];
		NDSolve[{(MatrixE[sys].#)&[T'[tau]] == f1[tau, T[tau]], 
			T[0] == init, T'[0] == init2}, T, {tau, 0, Last[time]}, SolveDelayed->True, FilterOptions[NDSolve, ops]],
	True,
		initv = Table[0., {Length[MatrixK[sys]]}];
		init2 = LinearSolve[MatrixM[sys], f1[0, init] - MatrixE[sys].initv];
		NDSolve[{(MatrixM[sys].#)&[T''[tau]] + (MatrixE[sys].#)&[T'[tau]] == f1[tau, T[tau]], 
			T[0] == init, T'[0] == initv, T''[0] == init2}, T, {tau, 0, Last[time]}, SolveDelayed->True, FilterOptions[NDSolve, ops]]
	];];
If[ver, Print["NDSolve has made ", Length[sol[[1, 1, 2, 3, 1]]], " steps for ", tim[[1]]]];
MakeSimulationResult[
	time,
	"Time",
	MatrixC[sys].Transpose[T[time] /. First[sol]], 
	OutputNames[sys]
]
]	/; VectorQ[time, NumericQ]

End[];

EndPackage[];

