// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2020 James D. Mitchell
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
//
// This file is the fourth of six that contains tests for the KnuthBendix
// classes. In a mostly vain attempt to speed up compilation the tests are
// split across 6 files as follows:
//
// 1: contains quick tests for fpsemigroup::KnuthBendix created from rules and
// all commented out tests.
//
// 2: contains more quick tests for fpsemigroup::KnuthBendix created from rules
//
// 3: contains yet more quick tests for fpsemigroup::KnuthBendix created from
// rules
//
// 4: contains standard and extreme test for fpsemigroup::KnuthBendix created
// from rules
//
// 5: contains tests for fpsemigroup::KnuthBendix created from FroidurePin
// instances
//
// 6: contains tests for congruence::KnuthBendix.
#define CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS
#include <iostream>
#include <string> // for string
#include <vector> // for vector
#include "catch.hpp" // for REQUIRE, REQUIRE_NOTHROW, REQUIRE_THROWS_AS
#include "test-main.hpp" // for LIBSEMIGROUPS_TEST_CASE
#include "libsemigroups/constants.hpp" // for POSITIVE_INFINITY
#include "libsemigroups/knuth-bendix.hpp" // for KnuthBendix, operator<<
#include "libsemigroups/report.hpp" // for ReportGuard
#include "libsemigroups/siso.hpp" // for XXX
#include "libsemigroups/stl.hpp" // for XXX
#include "libsemigroups/string.hpp" // for random_string
namespace libsemigroups {
constexpr bool REPORT = false;
using rule_type = fpsemigroup::KnuthBendix::rule_type;
namespace fpsemigroup {
////////////////////////////////////////////////////////////////////////
// Standard tests
////////////////////////////////////////////////////////////////////////
// Takes approx. 2s
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"084",
"(fpsemi) Example 6.6 in Sims (with limited "
"overlap lengths)",
"[standard][knuth-bendix][fpsemigroup][fpsemi]") {
auto rg = ReportGuard(REPORT);
KnuthBendix kb;
kb.set_alphabet( "abc");
kb.add_rule( "aa", "");
kb.add_rule( "bc", "");
kb.add_rule( "bbb", "");
kb.add_rule( "ababababababab", "");
kb.add_rule( "abacabacabacabacabacabacabacabac", "");
REQUIRE(!kb.confluent());
// In Sims it says to use 44 here, but that doesn't seem to work.
kb.max_overlap( 45);
// Avoid checking confluence since this is very slow, essentially takes
// the same amount of time as running Knuth-Bendix (fpsemi) 13.
kb.check_confluence_interval(LIMIT_MAX);
kb.run();
REQUIRE(kb.number_of_active_rules() == 1026);
// REQUIRE(kb.confluent());
// REQUIRE(kb.size() == 10752);
}
// Takes approx. 2s
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"085",
"(from kbmag/standalone/kb_data/funny3)",
"[standard][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard(REPORT);
KnuthBendix kb;
kb.set_alphabet( "aAbBcC");
kb.set_identity( "");
kb.set_inverses( "AaBbCc");
kb.add_rule( "aaa", "");
kb.add_rule( "bbb", "");
kb.add_rule( "ccc", "");
kb.add_rule( "ABa", "BaB");
kb.add_rule( "bcB", "cBc");
kb.add_rule( "caC", "aCa");
kb.add_rule( "abcABCabcABCabcABC", "");
kb.add_rule( "BcabCABcabCABcabCA", "");
kb.add_rule( "cbACBacbACBacbACBa", "");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
// kb.run() // also works, but is slower
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 8);
REQUIRE(kb.size() == 3);
REQUIRE(
std::vector<std::string>(kb.cbegin_normal_forms( 0, POSITIVE_INFINITY),
kb.cend_normal_forms())
== std::vector<std::string>({ "", "a", "A"}));
}
////////////////////////////////////////////////////////////////////////
// Extreme tests
////////////////////////////////////////////////////////////////////////
// Fibonacci group F(2,7) - order 29 - works better with largish tidyint
// Takes approx. 50s
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"086",
"(fpsemi) (from kbmag/standalone/kb_data/f27) (finite) (2 / 2)",
"[extreme][knuth-bendix][fpsemigroup][fpsemi][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "aAbBcCdDyYfFgG");
kb.set_identity( "");
kb.set_inverses( "AaBbCcDdYyFfGg");
kb.add_rule( "ab", "c");
kb.add_rule( "bc", "d");
kb.add_rule( "cd", "y");
kb.add_rule( "dy", "f");
kb.add_rule( "yf", "g");
kb.add_rule( "fg", "a");
kb.add_rule( "ga", "b");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 194);
REQUIRE(kb.size() == 29);
}
// Mathieu group M_11
// Takes approx. 2m9s (majority in checking confluence)
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"087",
"(from kbmag/standalone/kb_data/m11)",
"[extreme][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "abB");
kb.set_identity( "");
kb.set_inverses( "aBb");
kb.add_rule( "BB", "bb");
kb.add_rule( "BaBaBaBaBaB", "abababababa");
kb.add_rule( "bbabbabba", "abbabbabb");
kb.add_rule( "aBaBababaBabaBBaBab", "");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 1731);
REQUIRE(kb.size() == 7920);
}
// Weyl group E8 (all gens involutory).
// Takes approx. 8s for KnuthBendix
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"088",
"(from kbmag/standalone/kb_data/e8)",
"[extreme][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "abcdefgh");
kb.set_identity( "");
kb.set_inverses( "abcdefgh");
kb.add_rule( "bab", "aba");
kb.add_rule( "ca", "ac");
kb.add_rule( "da", "ad");
kb.add_rule( "ea", "ae");
kb.add_rule( "fa", "af");
kb.add_rule( "ga", "ag");
kb.add_rule( "ha", "ah");
kb.add_rule( "cbc", "bcb");
kb.add_rule( "db", "bd");
kb.add_rule( "eb", "be");
kb.add_rule( "fb", "bf");
kb.add_rule( "gb", "bg");
kb.add_rule( "hb", "bh");
kb.add_rule( "dcd", "cdc");
kb.add_rule( "ece", "cec");
kb.add_rule( "fc", "cf");
kb.add_rule( "gc", "cg");
kb.add_rule( "hc", "ch");
kb.add_rule( "ed", "de");
kb.add_rule( "fd", "df");
kb.add_rule( "gd", "dg");
kb.add_rule( "hd", "dh");
kb.add_rule( "fef", "efe");
kb.add_rule( "ge", "eg");
kb.add_rule( "he", "eh");
kb.add_rule( "gfg", "fgf");
kb.add_rule( "hf", "fh");
kb.add_rule( "hgh", "ghg");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 192);
REQUIRE(kb.gilman_digraph().number_of_nodes() == 332);
REQUIRE(kb.gilman_digraph().number_of_edges() == 533);
REQUIRE(kb.size() == 696729600);
}
// Second of BHN's series of increasingly complicated presentations of 1.
// Works quickest with large value of tidyint
// Takes > 1m (knuth_bendix), didn't run to the end
// Takes approx. 9s (knuth_bendix_by_overlap_length)
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"089",
"(from kbmag/standalone/kb_data/degen4b)",
"[extreme][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "aAbBcC");
kb.set_identity( "");
kb.set_inverses( "AaBbCc");
kb.add_rule( "bbABaBcbCCAbaBBccBCbccBCb", "");
kb.add_rule( "ccBCbCacAABcbCCaaCAcaaCAc", "");
kb.add_rule( "aaCAcAbaBBCacAAbbABabbABa", "");
REQUIRE(!kb.confluent());
// kb.run();
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 6);
REQUIRE(kb.size() == 1);
}
// Two generator presentation of Fibonacci group F(2,7) - order 29. Large
// value of tidyint works better.
// Takes approx. 12s (knuth_bendix_by_overlap_length)
// Takes > 19s (knuth_bendix), didn't run to the end
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"090",
"(from kbmag/standalone/kb_data/f27_2gen)",
"[extreme][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "aAbB");
kb.set_identity( "");
kb.set_inverses( "AaBb");
kb.add_rule( "bababbababbabbababbab", "a");
kb.add_rule( "abbabbababbaba", "b");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 19);
REQUIRE(kb.size() == 29);
}
// Takes approx. 1m8s
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"091",
"(fpsemi) Example 6.6 in Sims",
"[extreme][knuth-bendix][fpsemigroup][fpsemi]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "abc");
kb.add_rule( "aa", "");
kb.add_rule( "bc", "");
kb.add_rule( "bbb", "");
kb.add_rule( "ababababababab", "");
kb.add_rule( "abacabacabacabacabacabacabacabac", "");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.number_of_active_rules() == 1026);
REQUIRE(kb.confluent());
REQUIRE(kb.size() == 10752);
}
// Fibonacci group F(2,7) - without inverses
// Takes approx. 13s
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"092",
"(fpsemi) (from kbmag/standalone/kb_data/f27) (infinite) (1 / 2)",
"[extreme][knuth-bendix][fpsemigroup][fpsemi][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "aAbBcCdDyYfFgG");
kb.add_rule( "ab", "c");
kb.add_rule( "bc", "d");
kb.add_rule( "cd", "y");
kb.add_rule( "dy", "f");
kb.add_rule( "yf", "g");
kb.add_rule( "fg", "a");
kb.add_rule( "ga", "b");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
// Fails to terminate, or is very slow, with knuth_bendix
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 47);
REQUIRE(kb.size() == POSITIVE_INFINITY);
}
// An extension of 2^6 be L32
// Takes approx. 1m7s
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"093",
"(fpsemi) (from kbmag/standalone/kb_data/l32ext)",
"[extreme][knuth-bendix][fpsemigroup][fpsemi][kbmag][shortlex]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "abB");
kb.set_identity( "");
kb.set_inverses( "aBb");
kb.add_rule( "aa", "");
kb.add_rule( "BB", "b");
kb.add_rule( "BaBaBaB", "abababa");
kb.add_rule( "aBabaBabaBabaBab", "BabaBabaBabaBaba");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_active_rules() == 1026);
REQUIRE(kb.size() == 10752);
auto& ad = kb.gilman_digraph();
REQUIRE(ad.number_of_nodes() == 6021);
REQUIRE(ad.number_of_edges() == 7435);
REQUIRE(action_digraph_helper::is_acyclic(ad));
REQUIRE(ad.number_of_paths( 0, 0, 100) == 10752);
}
////////////////////////////////////////////////////////////////////////
// Tests that fail
////////////////////////////////////////////////////////////////////////
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"094",
"Ceitin's undecidable word problem example",
"[fail][knuth-bendix]") {
auto rg = ReportGuard( true);
KnuthBendix kb;
kb.set_alphabet( "abcde");
kb.add_rule( "ac", "ca");
kb.add_rule( "ad", "da");
kb.add_rule( "bc", "cb");
kb.add_rule( "bd", "db");
kb.add_rule( "eca", "ce");
kb.add_rule( "edb", "de");
kb.add_rule( "cca", "ccae");
kb.run(); // I guess this shouldn't work, and indeed it doesn't!
}
// kbmag/standalone/kb_data/verifynilp
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"095",
"(KnuthBendix 050 again) (from kbmag/standalone/kb_data/verifynilp)",
"[fail][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard();
std::string lphbt = "hHgGfFyYdDcCbBaA";
std::string invrs = "HhGgFfYyDdCcBbAa";
std::vector<size_t> perm(lphbt.size(), 0);
std::iota(perm.begin(), perm.end(), 0);
do {
detail::apply_permutation(lphbt, invrs, perm);
// std::cout << "Alphabet is " << lphbt << std::endl;
// std::cout << std::string(72, '#') << std::endl;
KnuthBendix kb;
kb.set_alphabet(lphbt);
kb.set_identity( "");
kb.set_inverses(invrs);
kb.add_rule( "BAba", "c");
kb.add_rule( "CAca", "d");
kb.add_rule( "DAda", "y");
kb.add_rule( "YByb", "f");
kb.add_rule( "FAfa", "g");
kb.add_rule( "ga", "ag");
kb.add_rule( "GBgb", "h");
kb.add_rule( "cb", "bc");
kb.add_rule( "ya", "ay");
REQUIRE(!kb.confluent());
kb.knuth_bendix_by_overlap_length();
if (kb.confluent()) {
REQUIRE(kb.number_of_active_rules() == 0);
REQUIRE(kb.size() == 0);
break;
}
} while (std::next_permutation(perm.begin(), perm.end()));
}
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"096",
"Sorouhesh",
"[quick][knuth-bendix][kbmag][shortlex]") {
auto rg = ReportGuard(REPORT);
size_t const n = 2;
size_t const p = 11;
KnuthBendix kb;
kb.set_alphabet( "ab");
kb.add_rule(std::string(size_t(std::pow( 5, n)), 'a'), "a");
kb.add_rule( "aba", "b");
kb.add_rule( "ab", std::string(p, 'b') + "a");
REQUIRE(!kb.confluent());
kb.run();
REQUIRE(kb.number_of_active_rules() == 7);
REQUIRE(kb.size() == size_t(std::pow( 5, n)) + 4 * p - 5);
REQUIRE(kb.normal_form( "aabb") == "aabb");
REQUIRE(kb.normal_form( "aabbaabb") == "bbbb");
REQUIRE(kb.normal_form( "aabbaabbaabb") == "aabbbbbb");
REQUIRE(kb.normal_form( "aabbaabbaabbaabb") == "bbbbbbbb");
REQUIRE(kb.normal_form( "aabbaabbaabbaabbaabb") == "aabbbbbbbbbb");
REQUIRE(
std::vector<std::string>(kb.cbegin_normal_forms( 0, POSITIVE_INFINITY),
kb.cend_normal_forms())
== std::vector<std::string>({ "a",
"b",
"aa",
"ab",
"ba",
"bb",
"aaa",
"aab",
"abb",
"bab",
"bbb",
"aaaa",
"aabb",
"abbb",
"babb",
"bbbb",
"aaaaa",
"aabbb",
"abbbb",
"babbb",
"bbbbb",
"aaaaaa",
"aabbbb",
"abbbbb",
"babbbb",
"bbbbbb",
"aaaaaaa",
"aabbbbb",
"abbbbbb",
"babbbbb",
"bbbbbbb",
"aaaaaaaa",
"aabbbbbb",
"abbbbbbb",
"babbbbbb",
"bbbbbbbb",
"aaaaaaaaa",
"aabbbbbbb",
"abbbbbbbb",
"babbbbbbb",
"bbbbbbbbb",
"aaaaaaaaaa",
"aabbbbbbbb",
"abbbbbbbbb",
"babbbbbbbb",
"bbbbbbbbbb",
"aaaaaaaaaaa",
"aabbbbbbbbb",
"abbbbbbbbbb",
"babbbbbbbbb",
"aaaaaaaaaaaa",
"aabbbbbbbbbb",
"aaaaaaaaaaaaa",
"aaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaa"}));
}
namespace {
std::string swap_a_and_b(std::string const& w) {
std::string result;
for ( auto l : w) {
if (l == 'a') {
result += "b";
} else {
result += "a";
}
}
return result;
}
std::unordered_set<std::string>& get_set() {
static std::unordered_set<std::string> set;
return set;
}
template < typename T>
void register_relation(T const& it1, T const& it2, size_t& nr) {
auto tmp = *it1 + "#" + *it2;
auto u = swap_a_and_b(*it1);
auto v = swap_a_and_b(*it2);
if (shortlex_compare(u, v)) {
get_set().insert(u + "#" + v);
} else {
get_set().insert(v + "#" + u);
}
std::cout << *it1 << " = " << *it2 << std::endl;
nr++;
}
} // namespace
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"015",
"(fpsemi) all 2-generated 1-relation semigroups 1 to 10",
"[extreme][kambites][fpsemigroup][fpsemi][xxx]") {
auto rg = ReportGuard( false);
auto first
= cbegin_sislo( "ab", std::string( 1, 'a'), std::string( 11, 'a'));
auto last = cbegin_sislo( "ab", std::string( 1, 'a'), std::string( 11, 'a'));
size_t N = std::distance(
first, cend_sislo( "ab", std::string( 1, 'a'), std::string( 11, 'a')));
REQUIRE(N == 2046);
std::advance(last, N - 1);
size_t total_c4 = 0;
size_t total = 0;
auto llast = last;
++llast;
for ( auto it1 = first; it1 != last; ++it1) {
auto it2 = it1;
++it2;
for (; it2 != llast; ++it2) {
auto tmp = *it1 + "#" + *it2;
if (get_set().insert(tmp).second) {
bool try_again = false;
{
KnuthBendix k;
k.set_alphabet( "ab");
k.add_rule(*it1, *it2);
k.run_for(std::chrono::milliseconds( 10));
if (k.confluent()) {
register_relation(it1, it2, total_c4);
} else {
try_again = true;
}
}
if (try_again) {
KnuthBendix k;
k.set_alphabet( "ba");
k.add_rule(*it1, *it2);
k.run_for(std::chrono::milliseconds( 10));
if (k.confluent()) {
register_relation(it1, it2, total_c4);
}
}
}
}
}
REQUIRE(total_c4 == 471479);
REQUIRE(total == 2092035);
}
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"016",
"(fpsemi) hard 2-generated 1-relation semigroups 1 to 10",
"[extreme][kambites][fpsemigroup][fpsemi][xxx2]") {
KnuthBendix k;
k.set_alphabet( "bac");
k.add_rule( "a", "cc");
k.add_rule( "c", "bab");
k.knuth_bendix_by_overlap_length();
std::cout << k.active_rules() << std::endl;
REQUIRE(k.active_rules()[ 0] == rule_type({ "", ""}));
}
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"017",
"(fpsemi) Konovalov",
"[extreme][knuthbendix][fpsemigroup][fpsemi][xxx3]") {
KnuthBendix k;
k.set_alphabet( "abAB");
k.add_rule( "Abba", "BB");
k.add_rule( "Baab", "AA");
k.run();
REQUIRE(k.size() == POSITIVE_INFINITY);
}
LIBSEMIGROUPS_TEST_CASE(
"KnuthBendix",
"018",
"(fpsemi) https://math.stackexchange.com/questions/2649807",
"[knuthbendix][fpsemigroup][fpsemi][fail]") {
do {
std::string lphbt = "abcABC";
std::string invrs = "ABCabc";
std::cout << std::string( 72, '#') << std::endl;
KnuthBendix k;
k.set_alphabet(lphbt);
k.set_identity( "");
k.set_inverses(invrs);
k.add_rule( "aa", "");
k.add_rule( "bbbbbbbbbbb", "");
k.add_rule( "cc", "");
k.add_rule( "abababab", "");
k.add_rule( "abbabbabbabbabbabb", "");
k.add_rule( "abbabaBabaBBabbaB", "");
k.add_rule( "acacac", "");
k.add_rule( "bcbc", "");
for (size_t i = 1; i < 3; ++i) {
auto lhs = detail::random_string(lphbt, 100);
auto rhs = detail::random_string(lphbt, 100);
k.add_rule(lhs, rhs);
std::cout << "trying rule " << lhs << " -> " << rhs << std::endl;
}
k.run_for(std::chrono::seconds( 1));
if (k.confluent()) {
size_t const N = k.size();
std::cout << "k.size() == " << N << std::endl;
if (k.size() == POSITIVE_INFINITY) {
break;
}
}
} while ( true);
}
LIBSEMIGROUPS_TEST_CASE( "KnuthBendix",
"120",
"example with undecidable word problem",
"[knuthbendix][fail]") {
KnuthBendix k;
k.set_alphabet( "ab");
k.add_rule( "abaabb", "bbaaba");
k.add_rule( "aababba", "bbaaaba");
k.add_rule( "abaaabb", "abbabaa");
k.add_rule( "bbbaabbaaba", "bbbaabbaaaa");
k.add_rule( "aaaabbaaba", "bbaaaa");
k.run_for(std::chrono::seconds( 10));
REQUIRE(k.finished());
}
} // namespace fpsemigroup
} // namespace libsemigroups
¤ Dauer der Verarbeitung: 0.8 Sekunden
¤
*© Formatika GbR, Deutschland
|
|