""" demo_rome.py — Demo aggiornato: TouristProfile + fix ristoranti + tempi transit realistici. """ import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.models import PoI, PoICategory, TimeWindow from core.distance import DistanceMatrix from core.profile import ( profile_cultural_walker, profile_foodie_transit, profile_family_mixed, profile_art_lover_car, TouristProfile, TransportMode, MobilityLevel, ) from solver import NSGA2Solver, SolverConfig ROME_POIS = [ # Monumenti e luoghi PoI("colosseo", "Colosseo", 41.8902,12.4922,0.98,120,TimeWindow(540,1110),PoICategory.MONUMENT, ["antico","unesco"]), PoI("foro", "Foro Romano", 41.8925,12.4853,0.90, 90,TimeWindow(540,1110),PoICategory.MONUMENT, ["antico"]), PoI("vaticano", "Musei Vaticani", 41.9065,12.4534,0.97,180,TimeWindow(540,1080),PoICategory.MUSEUM, ["arte","unesco"]), PoI("sistina", "Cappella Sistina", 41.9029,12.4545,0.96, 60,TimeWindow(540,1080),PoICategory.MUSEUM, ["arte","rinascimento"]), PoI("pantheon", "Pantheon", 41.8986,12.4769,0.93, 60,TimeWindow(540,1140),PoICategory.MONUMENT, ["antico","architettura"]), PoI("trevi", "Fontana di Trevi", 41.9009,12.4833,0.88, 30,TimeWindow(0, 1440),PoICategory.MONUMENT, ["barocco","fotogenico"]), PoI("spagna", "Piazza di Spagna", 41.9059,12.4823,0.80, 30,TimeWindow(0, 1440),PoICategory.VIEWPOINT, ["shopping","fotogenico"]), PoI("borghese", "Galleria Borghese", 41.9143,12.4923,0.92,120,TimeWindow(540,1140),PoICategory.MUSEUM, ["arte","scultura"]), PoI("navona", "Piazza Navona", 41.8992,12.4731,0.85, 45,TimeWindow(0, 1440),PoICategory.VIEWPOINT, ["barocco","fotogenico"]), PoI("trastevere", "Trastevere", 41.8897,12.4703,0.82, 90,TimeWindow(600,1380),PoICategory.VIEWPOINT, ["quartiere","fotogenico"]), PoI("castel", "Castel Sant'Angelo", 41.9031,12.4663,0.83, 90,TimeWindow(540,1080),PoICategory.MONUMENT, ["medievale","panorama"]), PoI("aventino", "Giardino degli Aranci", 41.8837,12.4793,0.75, 40,TimeWindow(480,1200),PoICategory.PARK, ["panorama","fotogenico"]), PoI("terme", "Terme di Caracalla", 41.8788,12.4924,0.78, 75,TimeWindow(540,1080),PoICategory.MONUMENT, ["antico"]), # Ristoranti: solo per pranzo (TW 12-15) o cena (TW 19-22) PoI("rist_rione", "Osteria del Rione", 41.8962,12.4751,0.74, 60,TimeWindow(720, 900),PoICategory.RESTAURANT,["cucina_romana"]), PoI("rist_prati", "Trattoria Prati", 41.9042,12.4601,0.70, 75,TimeWindow(720, 900),PoICategory.RESTAURANT,["cucina_romana"]), PoI("rist_testac","Testaccio da Mario", 41.8792,12.4770,0.76, 70,TimeWindow(720, 900),PoICategory.RESTAURANT,["offal","cucina_romana"]), PoI("rist_cena1", "Ristorante San Pietro", 41.9050,12.4580,0.72, 80,TimeWindow(1140,1320),PoICategory.RESTAURANT,["cucina_romana"]), PoI("rist_cena2", "Da Enzo al 29", 41.8891,12.4697,0.78, 90,TimeWindow(1140,1320),PoICategory.RESTAURANT,["cucina_romana","trastevere"]), # Bar e caffè: colazione o pausa (TW ampia) PoI("bar_greco", "Caffè Greco", 41.9057,12.4818,0.72, 20,TimeWindow(480,1320),PoICategory.BAR, ["storico","caffe"]), PoI("bar_sant", "Sant'Eustachio il Caffè",41.8990,12.4752,0.75, 20,TimeWindow(480,1380),PoICategory.BAR, ["caffe","storico"]), PoI("bar_campo", "Bar del Fico", 41.8968,12.4720,0.65, 25,TimeWindow(600,1380),PoICategory.BAR, ["aperitivo","vivace"]), # Gelaterie: pomeriggio (TW 11-20) PoI("gel_fatamorgana","Fatamorgana", 41.8993,12.4729,0.70, 20,TimeWindow(660,1200),PoICategory.GELATERIA, ["artigianale","insolito"]), PoI("gel_giolitti", "Giolitti", 41.9003,12.4765,0.68, 20,TimeWindow(660,1260),PoICategory.GELATERIA, ["storico","classico"]), PoI("gel_prati", "Fatamorgana Prati", 41.9090,12.4626,0.66, 20,TimeWindow(660,1260),PoICategory.GELATERIA, ["artigianale"]), ] def profile_foodie_transit_updated() -> TouristProfile: """Gastronomico con mezzi: pranzo + cena, bar e gelateria nel pomeriggio.""" return TouristProfile( transport_mode = TransportMode.TRANSIT, mobility = MobilityLevel.NORMAL, allowed_categories = ["restaurant", "bar", "gelateria", "monument", "viewpoint", "park"], want_lunch = True, want_dinner = True, lunch_time = 720, # 12:00 dinner_time = 1200, # 20:00 meal_window = 60, tag_weights = {"cucina_romana": 1.6, "offal": 0.5, "vivace": 1.2, "caffe": 1.1}, ) def run_profile(name, profile, config, dm): print(f"\n{'━'*60}") print(f" Profilo: {name}") print(f"{'━'*60}") print(profile.summary()) dm.profile = profile solver = NSGA2Solver(ROME_POIS, dm, config, profile=profile) def cb(gen, pareto, stats): if gen % 30 == 0 or gen == 1: print(f" gen {gen:3d} | pareto={stats['pareto_size']:2d} | " f"best={stats['best_scalar']:.4f} | feasible={stats['feasible_pct']:.0f}%") front = solver.solve(callback=cb) feasible = [x for x in front if x.fitness.is_feasible] or front if not feasible: print(" Nessuna soluzione trovata.") return best = max(feasible, key=lambda x: x.fitness.scalar) sched = solver.evaluator.decode(best) # Conta categorie per verifica cats = {} for stop in sched.stops: k = stop.poi.category.value cats[k] = cats.get(k, 0) + 1 print(f"\n ★ Tour: {len(best.genes)} PoI | score={best.fitness.total_score:.2f} | " f"{best.fitness.total_distance:.1f}km | {best.fitness.total_time}min") print(f" Composizione: {', '.join(f'{v}×{k}' for k,v in sorted(cats.items()))}") print() if sched: print(sched.summary()) def main(): print("Costruzione matrice distanze...") dm = DistanceMatrix(ROME_POIS) dm.build() config = SolverConfig( pop_size = 60, max_generations = 200, budget = 660, # 11 ore (09:30–20:30) start_time = 570, # 09:30 start_lat = 41.896, start_lon = 12.484, stagnation_limit = 25, ) profiles = [ ("Gastronomico con mezzi (aggiornato)", profile_foodie_transit_updated()), ("Culturale a piedi", profile_cultural_walker()), ("Gastronomico con mezzi (standard)", profile_foodie_transit()), ("Family: misto", profile_family_mixed()), ("Art Lover: con auto", profile_art_lover_car()), ("Custom: solo viste, no pasti", TouristProfile( transport_mode=TransportMode.WALK, allowed_categories=["monument","viewpoint","park","bar","gelateria"], want_lunch=False, want_dinner=False, tag_weights={"fotogenico":1.5,"panorama":1.4,"caffe":1.1}, )), ] for name, profile in profiles: run_profile(name, profile, config, dm) print(f"\n{'━'*60}\n Completato.\n") if __name__ == "__main__": main()