Bessere Verteilung: Strafen/Mutation, plus Abstammungsbaum mit Id-Herkunft
Weniger vermeidbare Gruppentrennungen:
- strafen.cfg: Tischbesetzung bestraft jetzt gestaffelt nach freien
Plaetzen (teilbelegte Tische am teuersten, mit Fallback auf den
naechstkleineren Eintrag), Trennungsstrafen deutlich erhoeht - so
werden Gruppen nicht mehr getrennt, obwohl leere Tische danebenstehen
- gibPunkte('Tischbesetzung') mit Naechst-kleiner-Fallback
- mutieren() setzt geraeumte Gruppen groesste-zuerst neu
(GruppenSetzen(GrosseZuerst=True)), damit grosse Gruppen die frei
gewordenen ganzen Tische bekommen und wieder zusammenkommen
(uebrig bleiben nur physisch unvermeidbare Trennungen, Gruppe > Tisch)
Abstammungsbaum (neuer Schalter --show-lineage -> Abstammung.svg):
- neue Klasse ga.Abstammung zeichnet den vollen Mutationsbaum aller
Loesungen; der Ast der finalen besten Loesung ist hervorgehoben und
mit ihrer Id beschriftet
- hierarchische Loesungs-Ids (Farm._WurzelId/_KindId): Wurzel '7',
Mutationskind '7.2', dessen Kind '7.2.1' - die ganze Abstammung ist
direkt aus der Id ablesbar
Tests (15 gruen), README und CLAUDE.md aktualisiert; Beispiele mit den
neuen Strafen und beiden Grafiken neu berechnet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ import unittest
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'libs'))
|
||||
|
||||
from Strukturdaten import Gruppe, Gruppen, JSONConfig, Person, Sitzplatzverteilung, Strafliste, Tisch, Tische
|
||||
from ga import Entwicklung, Farm, Zyklus, _StringAusdruck
|
||||
from ga import Abstammung, Entwicklung, Farm, Zyklus, _StringAusdruck
|
||||
|
||||
IMAGES_DIR = os.path.join(os.path.dirname(__file__), '..', 'doc', 'images')
|
||||
|
||||
@@ -327,5 +327,84 @@ class TestAdaptiverZyklus(unittest.TestCase):
|
||||
% pop_pro_gen )
|
||||
|
||||
|
||||
class TestAbstammung(unittest.TestCase):
|
||||
"""Der Stammbaum vergibt hierarchische Ids, aus denen sich die Abstammung
|
||||
ablesen laesst, und zeichnet einen Baum."""
|
||||
|
||||
class _L:
|
||||
"""GA-Loesung, deren Mutation den Wert leicht verbessert (damit ueber
|
||||
Generationen ein mehrstufiger Ast entsteht)."""
|
||||
def __init__(self, *a, **k):
|
||||
self.value = -100
|
||||
def mutieren(self):
|
||||
self.value = self.value + 1
|
||||
return self
|
||||
def __eq__(self, other):
|
||||
return self.value == other.value
|
||||
def __lt__(self, other):
|
||||
return self.value < other.value
|
||||
|
||||
def _zyklus(self, **params):
|
||||
Z = Zyklus()
|
||||
Z.Modus = 'adaptiv'
|
||||
Z.Adaptiv = dict(Zyklus.AdaptivDefaults)
|
||||
Z.Adaptiv.update(params)
|
||||
return Z
|
||||
|
||||
def test_hierarchische_ids_kodieren_abstammung(self):
|
||||
Z = self._zyklus( Start=5, Schrumpfung=1.0, MinPopulation=5,
|
||||
MaxGenerationen=3, Geduld=99, Schwelle=0.0 )
|
||||
F = Farm( Z, self._L )
|
||||
Knoten = F.Abstammung.Knoten
|
||||
|
||||
# Wurzeln sind schlichte Zahlen ohne Punkt und haben keine Eltern
|
||||
wurzeln = [ Id for Id, K in Knoten.items() if K['eltern'] is None ]
|
||||
self.assertTrue( all( '.' not in Id for Id in wurzeln ) )
|
||||
self.assertEqual( len(wurzeln), 5 )
|
||||
|
||||
# jede Kind-Id ist Eltern-Id + '.' + index; die Eltern-Id ist damit
|
||||
# das Praefix bis zum letzten Punkt und existiert als Knoten
|
||||
for Id, K in Knoten.items():
|
||||
if K['eltern'] is None:
|
||||
continue
|
||||
self.assertIn( '.', Id )
|
||||
self.assertEqual( Id.rsplit('.', 1)[0], K['eltern'] )
|
||||
self.assertIn( K['eltern'], Knoten )
|
||||
|
||||
def test_ahnenkette_ist_aus_id_ablesbar(self):
|
||||
# Bei einem Kind '3.2.1' muessen '3.2' und '3' die Ahnen sein
|
||||
A = Abstammung()
|
||||
A.erfassen( '3', None, 0, -100, 'e' )
|
||||
A.erfassen( '3.2', '3', 1, -99, 'm' )
|
||||
A.erfassen( '3.2.1', '3.2', 2, -98, 'm' )
|
||||
# rueckwaerts ueber die Eltern-Verweise
|
||||
kette = []
|
||||
Id = '3.2.1'
|
||||
while Id is not None:
|
||||
kette.append(Id)
|
||||
Id = A.Knoten[Id]['eltern']
|
||||
self.assertEqual( kette, ['3.2.1', '3.2', '3'] )
|
||||
# und das laesst sich auch rein syntaktisch aus der Id ablesen
|
||||
self.assertEqual( '3.2.1'.split('.'), ['3', '2', '1'] )
|
||||
|
||||
def test_svg_hebt_beste_hervor(self):
|
||||
Z = self._zyklus( Start=4, Schrumpfung=1.0, MinPopulation=4,
|
||||
MaxGenerationen=2, Geduld=99, Schwelle=0.0 )
|
||||
F = Farm( Z, self._L )
|
||||
Bester = F.Bester()
|
||||
Pfad = os.path.join( IMAGES_DIR, 'test_abstammung.svg' )
|
||||
F.Abstammung.alsSVG( Pfad, HervorId=Bester._absid )
|
||||
with open(Pfad) as f:
|
||||
svg = f.read()
|
||||
self.assertIn( '<svg', svg )
|
||||
self.assertIn( 'Abstammung der Loesungen', svg )
|
||||
# die Id der besten Loesung ist als Beschriftung enthalten
|
||||
self.assertIn( '>%s</text>' % Bester._absid, svg )
|
||||
|
||||
def test_leere_abstammung_wirft_beim_svg(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Abstammung().alsSVG( os.path.join(IMAGES_DIR, 'leer_abst.svg') )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user