From 9853e073405f93b621fed1140722bd3aff20c618 Mon Sep 17 00:00:00 2001
From: Tomas Pecka <peckato1@fit.cvut.cz>
Date: Sat, 22 Nov 2014 19:31:38 +0100
Subject: [PATCH] algo: concat, union+concat tests

---
 alangop2/src/alangop.cpp                      |   5 +
 .../transform/AutomataConcatenation.cpp       | 207 ++++++++++++++++++
 .../transform/AutomataConcatenation.h         |  59 +++++
 .../AutomataIntersectionCartesianProduct.cpp  |   2 -
 .../AutomataUnionCartesianProduct.cpp         |  38 +++-
 .../transform/AutomataConcatenationTest.cpp   |  93 ++++++++
 .../transform/AutomataConcatenationTest.h     |  19 ++
 .../automaton/transform/AutomataUnionTest.cpp |  76 +++++++
 .../automaton/transform/AutomataUnionTest.h   |  19 ++
 9 files changed, 506 insertions(+), 12 deletions(-)
 create mode 100644 alib2algo/src/automaton/transform/AutomataConcatenation.cpp
 create mode 100644 alib2algo/src/automaton/transform/AutomataConcatenation.h
 create mode 100644 alib2algo/test-src/automaton/transform/AutomataConcatenationTest.cpp
 create mode 100644 alib2algo/test-src/automaton/transform/AutomataConcatenationTest.h
 create mode 100644 alib2algo/test-src/automaton/transform/AutomataUnionTest.cpp
 create mode 100644 alib2algo/test-src/automaton/transform/AutomataUnionTest.h

diff --git a/alangop2/src/alangop.cpp b/alangop2/src/alangop.cpp
index c80ef1e072..3eeb3c533b 100644
--- a/alangop2/src/alangop.cpp
+++ b/alangop2/src/alangop.cpp
@@ -13,6 +13,7 @@
 #include <automaton/Automaton.h>
 
 #include <automaton/transform/AutomataConcatenationEpsilonTransition.h>
+#include <automaton/transform/AutomataConcatenation.h>
 #include <automaton/transform/AutomataIntersectionCartesianProduct.h>
 #include <automaton/transform/AutomataUnionCartesianProduct.h>
 #include <automaton/transform/AutomataUnionEpsilonTransition.h>
@@ -26,6 +27,7 @@ int main(int argc, char* argv[]) {
 		allowed.push_back("unionEpsilon");
 		allowed.push_back("unionCartesian");
 		allowed.push_back("concatenationEpsilon");
+		allowed.push_back("concatenation");
 		allowed.push_back("intersectionCartesian");
 		TCLAP::ValuesConstraint<std::string> allowedVals( allowed );
 
@@ -72,6 +74,9 @@ int main(int argc, char* argv[]) {
 		} else if( algorithm.getValue() == "concatenationEpsilon") {
 			alib::DataFactory::toStdout(automaton::transform::AutomataConcatenationEpsilonTransition::concatenation(automaton1, automaton2));;
 			return 0;
+		} else if( algorithm.getValue() == "concatenation") {
+			alib::DataFactory::toStdout(automaton::transform::AutomataConcatenation::concatenation(automaton1, automaton2));;
+			return 0;
 		} else if( algorithm.getValue() == "intersectionCartesian") {
 			alib::DataFactory::toStdout(automaton::transform::AutomataIntersectionCartesianProduct::intersection(automaton1, automaton2));;
 			return 0;
diff --git a/alib2algo/src/automaton/transform/AutomataConcatenation.cpp b/alib2algo/src/automaton/transform/AutomataConcatenation.cpp
new file mode 100644
index 0000000000..c8e3cdbec3
--- /dev/null
+++ b/alib2algo/src/automaton/transform/AutomataConcatenation.cpp
@@ -0,0 +1,207 @@
+/*
+ * AutomataConcatenation.cpp
+ *
+ *  Created on: 20. 11. 2014
+ *	  Author: Tomas Pecka
+ */
+
+#include "AutomataConcatenation.h"
+#include <exception/AlibException.h>
+#include <label/Label.h>
+#include "common/PairLabel.h"
+
+#define AUTOMATON_FIRST  (label::labelFrom(1))
+#define AUTOMATON_SECOND (label::labelFrom(2))
+
+namespace automaton
+{
+
+namespace transform
+{
+
+automaton::Automaton AutomataConcatenation::concatenation(const automaton::Automaton& first, const automaton::Automaton& second)
+{
+	AutomatonBase* out;
+	Accept((void*) &out, first.getData(), second.getData(), AutomataConcatenation::AUTOMATA_CONCATENATION);
+	automaton::Automaton res(*out);
+	delete out;
+	return res;
+}
+
+automaton::NFA AutomataConcatenation::concatenation(const automaton::NFA& first, const automaton::NFA& second)
+{
+	automaton::State q01q02(pairLabel(first.getInitialState(), second.getInitialState()));
+	automaton::NFA res(pairLabel(AUTOMATON_FIRST, first.getInitialState()));
+
+	for(const auto& q : first.getStates())
+		res.addState(pairLabel(AUTOMATON_FIRST, q));
+	for(const auto& q : second.getStates())
+		res.addState(pairLabel(AUTOMATON_SECOND, q));
+	res.addState(q01q02);
+
+	for(const auto& symbol : first.getInputAlphabet())
+		res.addInputSymbol(symbol);
+	for(const auto& symbol : second.getInputAlphabet())
+		res.addInputSymbol(symbol);
+
+	if(first.getFinalStates().count(first.getInitialState()) > 0)
+		res.setInitialState(q01q02);
+
+	for(const auto& t : first.getTransitions())
+	{
+		for(const auto& q : t.second)
+		{
+			res.addTransition(pairLabel(AUTOMATON_FIRST, t.first.first), t.first.second, pairLabel(AUTOMATON_FIRST, q));
+
+			if(first.getFinalStates().count(q) > 0)
+				res.addTransition(pairLabel(AUTOMATON_FIRST, t.first.first), t.first.second, pairLabel(AUTOMATON_SECOND, second.getInitialState()));
+		}
+	}
+	for(const auto& t : second.getTransitions())
+		for(const auto& q : t.second)
+			res.addTransition(pairLabel(AUTOMATON_SECOND, t.first.first), t.first.second, pairLabel(AUTOMATON_SECOND, q));
+
+	for(const auto& t : first.getTransitionsFromState(first.getInitialState()))
+		for(const auto& q : t.second)
+			res.addTransition(q01q02, t.first.second, pairLabel(AUTOMATON_FIRST, q));
+	for(const auto& t : second.getTransitionsFromState(second.getInitialState()))
+		for(const auto& q : t.second)
+			res.addTransition(q01q02, t.first.second, pairLabel(AUTOMATON_SECOND, q));
+
+	for(const auto& q : second.getFinalStates())
+		res.addFinalState(pairLabel(AUTOMATON_SECOND, q));
+	if(first.getFinalStates().count(first.getInitialState()) > 0)
+		res.addFinalState(q01q02);
+
+	return res;
+}
+
+automaton::NFA AutomataConcatenation::concatenation(const automaton::DFA& first, const automaton::DFA& second)
+{
+	automaton::State q01q02(pairLabel(first.getInitialState(), second.getInitialState()));
+	automaton::NFA res(pairLabel(AUTOMATON_FIRST, first.getInitialState()));
+
+	for(const auto& q : first.getStates())
+		res.addState(pairLabel(AUTOMATON_FIRST, q));
+	for(const auto& q : second.getStates())
+		res.addState(pairLabel(AUTOMATON_SECOND, q));
+	res.addState(q01q02);
+
+	for(const auto& symbol : first.getInputAlphabet())
+		res.addInputSymbol(symbol);
+	for(const auto& symbol : second.getInputAlphabet())
+		res.addInputSymbol(symbol);
+
+	if(first.getFinalStates().count(first.getInitialState()) > 0)
+		res.setInitialState(q01q02);
+
+	for(const auto& t : first.getTransitions())
+	{
+		res.addTransition(pairLabel(AUTOMATON_FIRST, t.first.first), t.first.second, pairLabel(AUTOMATON_FIRST, t.second));
+
+		if(first.getFinalStates().count(t.second) > 0)
+			res.addTransition(pairLabel(AUTOMATON_FIRST, t.first.first), t.first.second, pairLabel(AUTOMATON_SECOND, second.getInitialState()));
+	}
+	for(const auto& t : second.getTransitions())
+		res.addTransition(pairLabel(AUTOMATON_SECOND, t.first.first), t.first.second, pairLabel(AUTOMATON_SECOND, t.second));
+
+	for(const auto& t : first.getTransitionsFromState(first.getInitialState()))
+		res.addTransition(q01q02, t.first.second, pairLabel(AUTOMATON_FIRST, t.second));
+	for(const auto& t : second.getTransitionsFromState(second.getInitialState()))
+		res.addTransition(q01q02, t.first.second, pairLabel(AUTOMATON_SECOND, t.second));
+
+	for(const auto& q : second.getFinalStates())
+		res.addFinalState(pairLabel(AUTOMATON_SECOND, q));
+	if(first.getFinalStates().count(first.getInitialState()) > 0)
+		res.addFinalState(q01q02);
+
+	return res;
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::EpsilonNFA&, const automaton::EpsilonNFA&) const
+{
+	throw exception::AlibException("Unsupported automaton type EpsilonNFA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::CompactNFA&, const automaton::CompactNFA&) const
+{
+	throw exception::AlibException("Unsupported automaton type CompactNFA");
+}
+
+void AutomataConcatenation::Visit(void* data, const automaton::DFA& first, const automaton::DFA& second) const
+{
+	AutomatonBase* &ret = *(AutomatonBase**) data;
+	ret = std::move(AutomataConcatenation::concatenation(first, second)).plunder();
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::ExtendedNFA&, const automaton::ExtendedNFA&) const
+{
+	throw exception::AlibException("Unsupported automaton type ExtendedNFA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::MultiInitialStateNFA&, const automaton::MultiInitialStateNFA&) const
+{
+	throw exception::AlibException("Unsupported automaton type MultiInitialStateNFA");
+}
+
+void AutomataConcatenation::Visit(void* data, const automaton::NFA& first, const automaton::NFA& second) const
+{
+	AutomatonBase* &ret = *(AutomatonBase**) data;
+	ret = std::move(AutomataConcatenation::concatenation(first, second)).plunder();
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::DPDA&, const automaton::DPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type DPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::NPDA&, const automaton::NPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type NPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::InputDrivenNPDA&, const automaton::InputDrivenNPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type InputDrivenNPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::RealTimeHeightDeterministicDPDA&, const automaton::RealTimeHeightDeterministicDPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type RealTimeHeightDeterministicDPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::RealTimeHeightDeterministicNPDA&, const automaton::RealTimeHeightDeterministicNPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type RealTimeHeightDeterministicNPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::SinglePopNPDA&, const automaton::SinglePopNPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type SinglePopNPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::SinglePopDPDA&, const automaton::SinglePopDPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type SinglePopDPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::VisiblyPushdownDPDA&, const automaton::VisiblyPushdownDPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type VisiblyPushdownDPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::VisiblyPushdownNPDA&, const automaton::VisiblyPushdownNPDA&) const
+{
+	throw exception::AlibException("Unsupported automaton type VisiblyPushdownNPDA");
+}
+
+void AutomataConcatenation::Visit(void*, const automaton::OneTapeDTM&, const automaton::OneTapeDTM&) const
+{
+	throw exception::AlibException("Unsupported automaton type OneTapeDTM");
+}
+
+const AutomataConcatenation AutomataConcatenation::AUTOMATA_CONCATENATION;
+
+} /* namespace transform */
+
+} /* namespace automaton */
diff --git a/alib2algo/src/automaton/transform/AutomataConcatenation.h b/alib2algo/src/automaton/transform/AutomataConcatenation.h
new file mode 100644
index 0000000000..9ee1b5523e
--- /dev/null
+++ b/alib2algo/src/automaton/transform/AutomataConcatenation.h
@@ -0,0 +1,59 @@
+/*
+ * AutomataConcatenation.h
+ *
+ *  Created on: 20. 11. 2014
+ *	  Author: Tomas Pecka
+ */
+
+#ifndef AUTOMATA_CONCATENATION_H_
+#define AUTOMATA_CONCATENATION_H_
+
+#include <automaton/Automaton.h>
+#include <automaton/FSM/EpsilonNFA.h>
+
+namespace automaton
+{
+
+namespace transform
+{
+
+/**
+ * Concatenates two automata.
+ *  - For finite automata A1, A2, we create automaton L accepting L(A1).L(A2) (Melichar, 2.78)
+ */
+class AutomataConcatenation : public automaton::VisitableAutomatonBase::const_promoting_visitor_type
+{
+public:
+	static automaton::Automaton concatenation(const automaton::Automaton& first, const automaton::Automaton& second);
+
+	static automaton::NFA concatenation(const automaton::DFA& first, const automaton::DFA& second);
+	static automaton::NFA concatenation(const automaton::NFA& first, const automaton::NFA& second);
+
+private:
+	void Visit(void* data, const automaton::CompactNFA& first, const automaton::CompactNFA& second) const;
+	void Visit(void* data, const automaton::DFA& first, const automaton::DFA& second) const;
+	void Visit(void* data, const automaton::EpsilonNFA& first, const automaton::EpsilonNFA& second) const;
+	void Visit(void* data, const automaton::ExtendedNFA& first, const automaton::ExtendedNFA& second) const;
+	void Visit(void* data, const automaton::MultiInitialStateNFA& first, const automaton::MultiInitialStateNFA& second) const;
+	void Visit(void* data, const automaton::NFA& first, const automaton::NFA& second) const;
+
+	void Visit(void* data, const automaton::DPDA& first, const automaton::DPDA& second) const;
+	void Visit(void* data, const automaton::NPDA& first, const automaton::NPDA& second) const;
+	void Visit(void* data, const automaton::InputDrivenNPDA& first, const automaton::InputDrivenNPDA& second) const;
+	void Visit(void* data, const automaton::RealTimeHeightDeterministicDPDA& first, const automaton::RealTimeHeightDeterministicDPDA& second) const;
+	void Visit(void* data, const automaton::RealTimeHeightDeterministicNPDA& first, const automaton::RealTimeHeightDeterministicNPDA& second) const;
+	void Visit(void* data, const automaton::SinglePopNPDA& first, const automaton::SinglePopNPDA& second) const;
+	void Visit(void* data, const automaton::SinglePopDPDA& first, const automaton::SinglePopDPDA& second) const;
+	void Visit(void* data, const automaton::VisiblyPushdownDPDA& first, const automaton::VisiblyPushdownDPDA& second) const;
+	void Visit(void* data, const automaton::VisiblyPushdownNPDA& first, const automaton::VisiblyPushdownNPDA& second) const;
+
+	void Visit(void* data, const automaton::OneTapeDTM& first, const automaton::OneTapeDTM& second) const;
+
+	static const AutomataConcatenation AUTOMATA_CONCATENATION;
+};
+
+} /* namespace transform */
+
+} /* namespace automaton */
+
+#endif /* AUTOMATA_CONCATENATION_H_ */
diff --git a/alib2algo/src/automaton/transform/AutomataIntersectionCartesianProduct.cpp b/alib2algo/src/automaton/transform/AutomataIntersectionCartesianProduct.cpp
index 91a92f2964..a287a18b3c 100644
--- a/alib2algo/src/automaton/transform/AutomataIntersectionCartesianProduct.cpp
+++ b/alib2algo/src/automaton/transform/AutomataIntersectionCartesianProduct.cpp
@@ -82,8 +82,6 @@ automaton::NFA AutomataIntersectionCartesianProduct::intersection(const automato
 		const label::Label& label_p = static_cast<const label::LabelPairLabel&>(state.getName().getData()).getData().first;
 		const label::Label& label_q = static_cast<const label::LabelPairLabel&>(state.getName().getData()).getData().second;
 
-		// std::cout << label_p << ";" << label_q << std::endl;
-
 		for(const auto& tp : first.getTransitionsFromState(automaton::State(label_p)))
 			for(const auto& tq : second.getTransitionsFromState(automaton::State(label_q)))
 				if(tp.first.second == tq.first.second)
diff --git a/alib2algo/src/automaton/transform/AutomataUnionCartesianProduct.cpp b/alib2algo/src/automaton/transform/AutomataUnionCartesianProduct.cpp
index e5db1d4b20..2a03e1e1bf 100644
--- a/alib2algo/src/automaton/transform/AutomataUnionCartesianProduct.cpp
+++ b/alib2algo/src/automaton/transform/AutomataUnionCartesianProduct.cpp
@@ -29,6 +29,9 @@ automaton::Automaton AutomataUnionCartesianProduct::unification(const automaton:
 
 automaton::DFA AutomataUnionCartesianProduct::unification(const automaton::DFA& first, const automaton::DFA& second)
 {
+	if(!first.isTotal() || !second.isTotal())
+		throw exception::AlibException("Automata must be total to unify with cartesian product");
+
 	automaton::State q0(pairLabel(first.getInitialState(), second.getInitialState()));
 	automaton::DFA res(q0);
 
@@ -49,16 +52,25 @@ automaton::DFA AutomataUnionCartesianProduct::unification(const automaton::DFA&
 		for(const auto& q : second.getFinalStates())
 			res.addFinalState(automaton::State(pairLabel(p, q)));
 
-	for(const auto& tp : first.getTransitions())
-		for(const auto& tq : second.getTransitions())
-			if(tp.first.second == tq.first.second)
-				res.addTransition(automaton::State(pairLabel(tp.first.first, tq.first.first)), tp.first.second, automaton::State(pairLabel(tp.second, tq.second)));
+	for(const auto& state : res.getStates())
+	{
+		const label::Label& label_p = static_cast<const label::LabelPairLabel&>(state.getName().getData()).getData().first;
+		const label::Label& label_q = static_cast<const label::LabelPairLabel&>(state.getName().getData()).getData().second;
+
+		for(const auto& tp : first.getTransitionsFromState(automaton::State(label_p)))
+			for(const auto& tq : second.getTransitionsFromState(automaton::State(label_q)))
+				if(tp.first.second == tq.first.second)
+					res.addTransition(state, tp.first.second, automaton::State(pairLabel(tp.second, tq.second)));
+	}
 
 	return res;
 }
 
 automaton::NFA AutomataUnionCartesianProduct::unification(const automaton::NFA& first, const automaton::NFA& second)
 {
+	if(!first.isTotal() || !second.isTotal())
+		throw exception::AlibException("Automata must be total to unify with cartesian product");
+
 	automaton::State q0(pairLabel(first.getInitialState(), second.getInitialState()));
 	automaton::NFA res(q0);
 
@@ -79,12 +91,18 @@ automaton::NFA AutomataUnionCartesianProduct::unification(const automaton::NFA&
 		for(const auto& q : second.getFinalStates())
 			res.addFinalState(automaton::State(pairLabel(p, q)));
 
-	for(const auto& tp : first.getTransitions())
-		for(const auto& tq : second.getTransitions())
-			if(tp.first.second == tq.first.second)
-				for(const auto& p : tp.second)
-					for(const auto& q : tq.second)
-						res.addTransition(automaton::State(pairLabel(tp.first.first, tq.first.first)), tp.first.second, automaton::State(pairLabel(p, q)));
+	for(const auto& state : res.getStates())
+	{
+		const label::Label& label_p = static_cast<const label::LabelPairLabel&>(state.getName().getData()).getData().first;
+		const label::Label& label_q = static_cast<const label::LabelPairLabel&>(state.getName().getData()).getData().second;
+
+		for(const auto& tp : first.getTransitionsFromState(automaton::State(label_p)))
+			for(const auto& tq : second.getTransitionsFromState(automaton::State(label_q)))
+				if(tp.first.second == tq.first.second)
+					for(const auto& p : tp.second)
+						for(const auto& q : tq.second)
+							res.addTransition(state, tp.first.second, automaton::State(pairLabel(p, q)));
+	}
 
 	return res;
 }
diff --git a/alib2algo/test-src/automaton/transform/AutomataConcatenationTest.cpp b/alib2algo/test-src/automaton/transform/AutomataConcatenationTest.cpp
new file mode 100644
index 0000000000..70d2ef29af
--- /dev/null
+++ b/alib2algo/test-src/automaton/transform/AutomataConcatenationTest.cpp
@@ -0,0 +1,93 @@
+#include <list>
+#include "AutomataConcatenationTest.h"
+
+#include "automaton/transform/AutomataConcatenation.h"
+#include "automaton/transform/AutomataConcatenationEpsilonTransition.h"
+
+#include "automaton/simplify/MinimizeBrzozowski.h"
+#include "automaton/simplify/Normalize.h"
+#include "automaton/simplify/EpsilonRemover.h"
+#include "automaton/simplify/Trim.h"
+#include "automaton/simplify/Total.h"
+#include "automaton/determinize/Determinize.h"
+
+#include <factory/DataFactory.hpp>
+
+#define CPPUNIT_IMPLY(x, y) CPPUNIT_ASSERT(!(x) || (y))
+
+CPPUNIT_TEST_SUITE_REGISTRATION( AutomataConcatenationTest );
+
+void AutomataConcatenationTest::setUp() {
+}
+
+void AutomataConcatenationTest::tearDown() {
+}
+
+void AutomataConcatenationTest::testAutomataConcatenation() {
+	// based on Melichar, 2.79
+
+	automaton::State q1a("1"), q2a("2"), q0a("0"), q1b("1'"), q2b("2'"), q0b("0'");
+	automaton::State q0102("q0102");
+	alphabet::Symbol a(alphabet::symbolFrom('a')), b(alphabet::symbolFrom('b'));
+
+	automaton::DFA m1(q1a);
+	automaton::DFA m2(q1b);
+	automaton::NFA m3(q1a);
+
+	m1.setInputSymbols({a, b});
+	m1.setStates({q1a, q2a, q0a});
+	m1.addTransition(q1a, a, q2a);
+	m1.addTransition(q1a, b, q0a);
+	m1.addTransition(q2a, a, q2a);
+	m1.addTransition(q2a, b, q0a);
+	m1.addTransition(q0a, a, q0a);
+	m1.addTransition(q0a, b, q0a);
+	m1.addFinalState(q2a);
+
+	m2.setInputSymbols({a, b});
+	m2.setStates({q1b, q2b});
+	m2.addTransition(q1b, b, q2b);
+	m2.addTransition(q2b, b, q2b);
+	m2.addFinalState(q2b);
+
+	m3.setInputSymbols({a, b});
+	m3.setStates({q1a, q1b, q2a, q2b, q0a, q0b, q0102});
+	m3.addTransition(q1a, a, q2a);
+	m3.addTransition(q1a, a, q1b);
+	m3.addTransition(q1a, b, q0a);
+	m3.addTransition(q2a, a, q2a);
+	m3.addTransition(q2a, a, q1b);
+	m3.addTransition(q2a, b, q0a);
+	m3.addTransition(q0a, a, q0a);
+	m3.addTransition(q0a, b, q0a);
+	m3.addTransition(q1b, a, q0b);
+	m3.addTransition(q1b, b, q2b);
+	m3.addTransition(q2b, a, q0b);
+	m3.addTransition(q2b, b, q2b);
+	m3.addTransition(q0b, a, q0b);
+	m3.addTransition(q0b, b, q0b);
+	m3.setFinalStates({q2b});
+
+	auto u11 = automaton::transform::AutomataConcatenationEpsilonTransition::concatenation(automaton::Automaton(automaton::DFA(m1)), automaton::Automaton(automaton::NFA(m2)));
+	auto u12 = automaton::transform::AutomataConcatenationEpsilonTransition::concatenation(automaton::Automaton(automaton::DFA(m1)), automaton::Automaton(automaton::DFA(m2)));
+	auto u21 = automaton::transform::AutomataConcatenation::concatenation(automaton::Automaton(automaton::DFA(m1)), automaton::Automaton(automaton::NFA(m2)));
+	auto u22 = automaton::transform::AutomataConcatenation::concatenation(automaton::Automaton(automaton::DFA(m1)), automaton::Automaton(automaton::DFA(m2)));
+
+	automaton::Automaton umdfa  (automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(m3)))));
+	automaton::Automaton umdfa11(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(u11)))));
+	automaton::Automaton umdfa12(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(u12)))));
+	automaton::Automaton umdfa21(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(u21)))));
+	automaton::Automaton umdfa22(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(u22)))));
+	alib::DataFactory::toStdout(umdfa);
+	alib::DataFactory::toStdout(umdfa11);
+	alib::DataFactory::toStdout(umdfa12);
+	alib::DataFactory::toStdout(umdfa21);
+	alib::DataFactory::toStdout(umdfa22);
+
+
+	//CPPUNIT_ASSERT(umdfa11 == umdfa);
+	CPPUNIT_ASSERT(umdfa12 == umdfa);
+	//CPPUNIT_ASSERT(umdfa21 == umdfa);
+	CPPUNIT_ASSERT(umdfa22 == umdfa);
+
+}
diff --git a/alib2algo/test-src/automaton/transform/AutomataConcatenationTest.h b/alib2algo/test-src/automaton/transform/AutomataConcatenationTest.h
new file mode 100644
index 0000000000..aa7dbecb20
--- /dev/null
+++ b/alib2algo/test-src/automaton/transform/AutomataConcatenationTest.h
@@ -0,0 +1,19 @@
+#ifndef AUTOMATA_CONCAT_TEST_H_
+#define AUTOMATA_CONCAT_TEST_H_
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class AutomataConcatenationTest : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE( AutomataConcatenationTest );
+  CPPUNIT_TEST( testAutomataConcatenation );
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+  void setUp();
+  void tearDown();
+
+  void testAutomataConcatenation();
+};
+
+#endif /* AUTOMATA_CONCAT_TEST_H_ */
diff --git a/alib2algo/test-src/automaton/transform/AutomataUnionTest.cpp b/alib2algo/test-src/automaton/transform/AutomataUnionTest.cpp
new file mode 100644
index 0000000000..fe6d35161b
--- /dev/null
+++ b/alib2algo/test-src/automaton/transform/AutomataUnionTest.cpp
@@ -0,0 +1,76 @@
+#include <list>
+#include "AutomataUnionTest.h"
+
+#include "automaton/transform/AutomataUnionCartesianProduct.h"
+#include "automaton/transform/AutomataUnionEpsilonTransition.h"
+#include "automaton/simplify/MinimizeBrzozowski.h"
+#include "automaton/simplify/Normalize.h"
+#include "automaton/simplify/EpsilonRemover.h"
+#include "automaton/simplify/Trim.h"
+#include "automaton/simplify/Total.h"
+#include "automaton/determinize/Determinize.h"
+
+#include <factory/DataFactory.hpp>
+
+#define CPPUNIT_IMPLY(x, y) CPPUNIT_ASSERT(!(x) || (y))
+
+CPPUNIT_TEST_SUITE_REGISTRATION( AutomataUnionTest );
+
+void AutomataUnionTest::setUp() {
+}
+
+void AutomataUnionTest::tearDown() {
+}
+
+void AutomataUnionTest::testAutomataUnion() {
+	// based on Melichar, 2.72
+
+	automaton::State q1a("1"), q2a("2"), q0a("0"), q1b("1'"), q2b("2'"), q0b("0'");
+	automaton::State q11("11"), q20("20"), q00("00"), q02("02");
+	alphabet::Symbol a(alphabet::symbolFrom('a')), b(alphabet::symbolFrom('b'));
+
+	automaton::DFA m1(q1a);
+	automaton::DFA m2(q1b);
+	automaton::DFA m3(q11);
+
+	m1.setInputSymbols({a, b});
+	m1.setStates({q1a, q2a, q0a});
+	m1.addTransition(q1a, a, q2a);
+	m1.addTransition(q1a, b, q0a);
+	m1.addTransition(q2a, a, q2a);
+	m1.addTransition(q2a, b, q0a);
+	m1.addTransition(q0a, a, q0a);
+	m1.addTransition(q0a, b, q0a);
+	m1.addFinalState(q2a);
+
+	m2.setInputSymbols({a, b});
+	m2.setStates({q1b, q2b});
+	m2.addTransition(q1b, b, q2b);
+	m2.addTransition(q2b, b, q2b);
+	m2.addFinalState(q2b);
+
+	m3.setInputSymbols({a, b});
+	m3.setStates({q11, q20, q00, q02});
+	m3.addTransition(q11, a, q20);
+	m3.addTransition(q11, b, q02);
+	m3.addTransition(q20, a, q20);
+	m3.addTransition(q20, b, q00);
+	m3.addTransition(q00, a, q00);
+	m3.addTransition(q00, b, q00);
+	m3.addTransition(q02, a, q00);
+	m3.addTransition(q02, b, q02);
+	m3.setFinalStates({q20, q02});
+
+	auto u1 = automaton::transform::AutomataUnionEpsilonTransition::unification(automaton::Automaton(m1), automaton::Automaton(m2));
+	CPPUNIT_ASSERT_THROW(automaton::transform::AutomataUnionCartesianProduct::unification(automaton::Automaton(m1), automaton::Automaton(m2)), exception::AlibException);
+	CPPUNIT_ASSERT_THROW(automaton::transform::AutomataUnionCartesianProduct::unification(automaton::Automaton(automaton::NFA(m1)), automaton::Automaton(m2)), exception::AlibException);
+	auto u2 = automaton::transform::AutomataUnionEpsilonTransition::unification(automaton::Automaton(automaton::simplify::Total::total(m1)), automaton::Automaton(automaton::simplify::Total::total(m2)));
+
+	automaton::Automaton umdfa(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(m3)))));
+	automaton::Automaton umdfa1(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(u1)))));
+	automaton::Automaton umdfa2(automaton::simplify::Normalize::normalize(automaton::simplify::Trim::trim(automaton::simplify::MinimizeBrzozowski::minimize(automaton::simplify::EpsilonRemover::remove(u2)))));
+
+	CPPUNIT_ASSERT(umdfa1 == umdfa);
+	CPPUNIT_ASSERT(umdfa2 == umdfa);
+
+}
diff --git a/alib2algo/test-src/automaton/transform/AutomataUnionTest.h b/alib2algo/test-src/automaton/transform/AutomataUnionTest.h
new file mode 100644
index 0000000000..d45ef404d8
--- /dev/null
+++ b/alib2algo/test-src/automaton/transform/AutomataUnionTest.h
@@ -0,0 +1,19 @@
+#ifndef AUTOMATA_UNION_TEST_H_
+#define AUTOMATA_UNION_TEST_H_
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class AutomataUnionTest : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE( AutomataUnionTest );
+  CPPUNIT_TEST( testAutomataUnion );
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+  void setUp();
+  void tearDown();
+
+  void testAutomataUnion();
+};
+
+#endif /* AUTOMATA_UNION_TEST_H_ */
-- 
GitLab