Skip to content

Commit c0ff15b

Browse files
committed
move to new pycommons with generator-based csv api
1 parent 64063ab commit c0ff15b

22 files changed

+2347
-275
lines changed

moptipy/evaluation/base.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Some internal helper functions and base classes."""
22

33
from dataclasses import dataclass
4-
from typing import Any, Callable, Final
4+
from typing import Any, Final, Iterable
55

66
from pycommons.io.path import Path
77
from pycommons.types import check_int_range, type_error
@@ -855,32 +855,31 @@ def sort_key(obj: PerRunData | MultiRunData) -> tuple[Any, ...]:
855855

856856

857857
def motipy_footer_bottom_comments(
858-
_: Any, dest: Callable[[str], Any],
859-
additional: str | None = None) -> None:
858+
_: Any, additional: str | None = None) -> Iterable[str]:
860859
"""
861860
Print the standard csv footer for moptipy.
862861
863862
:param _: the setup object, ignored
864863
:param dest: the destination callable
865-
:param dest: the destination to write to
866864
:param additional: any additional output string
865+
:returns: the iterable with the footer comments
867866
868-
>>> def __qpt(s: str):
867+
>>> for s in motipy_footer_bottom_comments(None, "bla"):
869868
... print(s[:49])
870-
>>> motipy_footer_bottom_comments(None, __qpt, "bla")
871869
This data has been generated with moptipy version
872870
bla
873871
You can find moptipy at https://thomasweise.githu
874872
875-
>>> motipy_footer_bottom_comments(None, __qpt, None)
873+
>>> for s in motipy_footer_bottom_comments(None, None):
874+
... print(s[:49])
876875
This data has been generated with moptipy version
877876
You can find moptipy at https://thomasweise.githu
878877
"""
879-
dest("This data has been generated with moptipy version "
880-
f"{moptipy_version}.")
878+
yield ("This data has been generated with moptipy version "
879+
f"{moptipy_version}.")
881880
if (additional is not None) and (str.__len__(additional) > 0):
882-
dest(additional)
883-
dest("You can find moptipy at https://thomasweise.github.io/mopitpy.")
881+
yield additional
882+
yield "You can find moptipy at https://thomasweise.github.io/mopitpy."
884883

885884

886885
#: a description of the algorithm field

moptipy/evaluation/end_results.py

+86-93
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import argparse
1717
from dataclasses import dataclass
1818
from math import inf, isfinite
19-
from typing import Any, Callable, Final, Iterable, cast
19+
from typing import Any, Callable, Final, Generator, Iterable, cast
2020

2121
from pycommons.io.console import logger
2222
from pycommons.io.csv import (
@@ -552,21 +552,23 @@ def to_csv(results: Iterable[EndResult], file: str) -> Path:
552552
logger(f"Writing end results to CSV file {path!r}.")
553553
path.ensure_parent_dir_exists()
554554
with path.open_for_write() as wt:
555-
csv_write(
556-
data=sorted(results), consumer=line_writer(wt),
557-
setup=CsvWriter().setup,
558-
get_column_titles=CsvWriter.get_column_titles,
559-
get_row=CsvWriter.get_row,
560-
get_header_comments=CsvWriter.get_header_comments,
561-
get_footer_comments=CsvWriter.get_footer_comments,
562-
get_footer_bottom_comments=CsvWriter.get_footer_bottom_comments)
555+
consumer: Final[Callable[[str], None]] = line_writer(wt)
556+
for r in csv_write(
557+
data=sorted(results),
558+
setup=CsvWriter().setup,
559+
column_titles=CsvWriter.get_column_titles,
560+
get_row=CsvWriter.get_row,
561+
header_comments=CsvWriter.get_header_comments,
562+
footer_comments=CsvWriter.get_footer_comments,
563+
footer_bottom_comments=CsvWriter.get_footer_bottom_comments):
564+
consumer(r)
563565
logger(f"Done writing end results to CSV file {path!r}.")
564566
return path
565567

566568

567-
def from_csv(file: str, consumer: Callable[[EndResult], Any],
569+
def from_csv(file: str,
568570
filterer: Callable[[EndResult], bool]
569-
= lambda x: True) -> None:
571+
= lambda x: True) -> Generator[EndResult, None, None]:
570572
"""
571573
Parse a given CSV file to get :class:`EndResult` Records.
572574
@@ -575,22 +577,14 @@ def from_csv(file: str, consumer: Callable[[EndResult], Any],
575577
:class:`list`
576578
:param filterer: an optional filter function
577579
"""
578-
if not callable(consumer):
579-
raise type_error(consumer, "consumer", call=True)
580580
path: Final[Path] = file_path(file)
581581
logger(f"Now reading CSV file {path!r}.")
582-
583-
def __cons(r: EndResult, __c=consumer, __f=filterer) -> None:
584-
"""Consume a record."""
585-
if __f(r):
586-
__c(r)
587-
588582
with path.open_for_read() as rd:
589-
csv_read(rows=rd,
590-
setup=CsvReader,
591-
parse_row=CsvReader.parse_row,
592-
consumer=__cons)
593-
583+
for r in csv_read(rows=rd,
584+
setup=CsvReader,
585+
parse_row=CsvReader.parse_row):
586+
if filterer(r):
587+
yield r
594588
logger(f"Done reading CSV file {path!r}.")
595589

596590

@@ -663,116 +657,115 @@ def setup(self, data: Iterable[EndResult]) -> "CsvWriter":
663657
return self
664658
return self
665659

666-
def get_column_titles(self, dest: Callable[[str], None]) -> None:
660+
def get_column_titles(self) -> Iterable[str]:
667661
"""
668662
Get the column titles.
669663
670-
:param dest: the destination string consumer
664+
:returns: the column titles
671665
"""
672666
p: Final[str] = self.scope
673-
dest(csv_scope(p, KEY_ALGORITHM))
674-
dest(csv_scope(p, KEY_INSTANCE))
675-
dest(csv_scope(p, KEY_OBJECTIVE_FUNCTION))
667+
data: list[str] = [
668+
KEY_ALGORITHM, KEY_INSTANCE, KEY_OBJECTIVE_FUNCTION]
676669
if self.__needs_encoding:
677-
dest(csv_scope(p, KEY_ENCODING))
678-
dest(csv_scope(p, KEY_RAND_SEED))
679-
dest(csv_scope(p, KEY_BEST_F))
680-
dest(csv_scope(p, KEY_LAST_IMPROVEMENT_FE))
681-
dest(csv_scope(p, KEY_LAST_IMPROVEMENT_TIME_MILLIS))
682-
dest(csv_scope(p, KEY_TOTAL_FES))
683-
dest(csv_scope(p, KEY_TOTAL_TIME_MILLIS))
670+
data.append(KEY_ENCODING)
671+
data.append(KEY_RAND_SEED)
672+
data.append(KEY_BEST_F)
673+
data.append(KEY_LAST_IMPROVEMENT_FE)
674+
data.append(KEY_LAST_IMPROVEMENT_TIME_MILLIS)
675+
data.append(KEY_TOTAL_FES)
676+
data.append(KEY_TOTAL_TIME_MILLIS)
684677

685678
if self.__needs_goal_f:
686-
dest(csv_scope(p, KEY_GOAL_F))
679+
data.append(KEY_GOAL_F)
687680
if self.__needs_max_fes:
688-
dest(csv_scope(p, KEY_MAX_FES))
681+
data.append(KEY_MAX_FES)
689682
if self.__needs_max_ms:
690-
dest(csv_scope(p, KEY_MAX_TIME_MILLIS))
683+
data.append(KEY_MAX_TIME_MILLIS)
684+
return (csv_scope(p, q) for q in data)
691685

692-
def get_row(self, data: EndResult,
693-
dest: Callable[[str], None]) -> None:
686+
def get_row(self, data: EndResult) -> Iterable[str]:
694687
"""
695688
Render a single end result record to a CSV row.
696689
697690
:param data: the end result record
698-
:param dest: the string consumer
691+
:returns: the row iterator
699692
"""
700-
dest(data.algorithm)
701-
dest(data.instance)
702-
dest(data.objective)
693+
yield data.algorithm
694+
yield data.instance
695+
yield data.objective
703696
if self.__needs_encoding:
704-
dest(data.encoding if data.encoding else "")
705-
dest(hex(data.rand_seed))
706-
dest(num_to_str(data.best_f))
707-
dest(str(data.last_improvement_fe))
708-
dest(str(data.last_improvement_time_millis))
709-
dest(str(data.total_fes))
710-
dest(str(data.total_time_millis))
697+
yield data.encoding if data.encoding else ""
698+
yield hex(data.rand_seed)
699+
yield num_to_str(data.best_f)
700+
yield str(data.last_improvement_fe)
701+
yield str(data.last_improvement_time_millis)
702+
yield str(data.total_fes)
703+
yield str(data.total_time_millis)
711704
if self.__needs_goal_f:
712-
dest(num_or_none_to_str(data.goal_f))
705+
yield num_or_none_to_str(data.goal_f)
713706
if self.__needs_max_fes:
714-
dest(int_or_none_to_str(data.max_fes))
707+
yield int_or_none_to_str(data.max_fes)
715708
if self.__needs_max_ms:
716-
dest(int_or_none_to_str(data.max_time_millis))
709+
yield int_or_none_to_str(data.max_time_millis)
717710

718-
def get_header_comments(self, dest: Callable[[str], None]) -> None:
711+
def get_header_comments(self) -> Iterable[str]:
719712
"""
720713
Get any possible header comments.
721714
722-
:param dest: the destination
715+
:returns: the header comments
723716
"""
724-
dest("Experiment End Results")
725-
dest("See the description at the bottom of the file.")
717+
return ("Experiment End Results",
718+
"See the description at the bottom of the file.")
726719

727-
def get_footer_comments(self, dest: Callable[[str], None]) -> None:
720+
def get_footer_comments(self) -> Iterable[str]:
728721
"""
729722
Get any possible footer comments.
730723
731-
:param dest: the destination
724+
:returns: the footer comments
732725
"""
733-
dest("")
726+
yield ""
734727
scope: Final[str | None] = self.scope
735-
dest("Records describing the end results of single runs ("
736-
"single executions) of algorithms applied to optimization "
737-
"problems.")
738-
dest("Each run is characterized by an algorithm setup, a problem "
739-
"instance, and a random seed.")
728+
yield ("Records describing the end results of single runs ("
729+
"single executions) of algorithms applied to optimization "
730+
"problems.")
731+
yield ("Each run is characterized by an algorithm setup, a problem "
732+
"instance, and a random seed.")
740733
if scope:
741-
dest("All end result records start with prefix "
742-
f"{scope}{SCOPE_SEPARATOR}.")
743-
dest(f"{csv_scope(scope, KEY_ALGORITHM)}: {DESC_ALGORITHM}")
744-
dest(f"{csv_scope(scope, KEY_INSTANCE)}: {DESC_INSTANCE}")
745-
dest(f"{csv_scope(scope, KEY_OBJECTIVE_FUNCTION)}:"
746-
f" {DESC_OBJECTIVE_FUNCTION}")
734+
yield ("All end result records start with prefix "
735+
f"{scope}{SCOPE_SEPARATOR}.")
736+
yield f"{csv_scope(scope, KEY_ALGORITHM)}: {DESC_ALGORITHM}"
737+
yield f"{csv_scope(scope, KEY_INSTANCE)}: {DESC_INSTANCE}"
738+
yield (f"{csv_scope(scope, KEY_OBJECTIVE_FUNCTION)}:"
739+
f" {DESC_OBJECTIVE_FUNCTION}")
747740
if self.__needs_encoding:
748-
dest(f"{csv_scope(scope, KEY_ENCODING)}: {DESC_ENCODING}")
749-
dest(f"{csv_scope(scope, KEY_RAND_SEED)}: {DESC_RAND_SEED}")
750-
dest(f"{csv_scope(scope, KEY_BEST_F)}: {DESC_BEST_F}")
751-
dest(f"{csv_scope(scope, KEY_LAST_IMPROVEMENT_FE)}: "
752-
f"{DESC_LAST_IMPROVEMENT_FE}")
753-
dest(f"{csv_scope(scope, KEY_LAST_IMPROVEMENT_TIME_MILLIS)}: "
754-
f"{DESC_LAST_IMPROVEMENT_TIME_MILLIS}")
755-
dest(f"{csv_scope(scope, KEY_TOTAL_FES)}: {DESC_TOTAL_FES}")
756-
dest(f"{csv_scope(scope, KEY_TOTAL_TIME_MILLIS)}: "
757-
f"{DESC_TOTAL_TIME_MILLIS}")
741+
yield (f"{csv_scope(scope, KEY_ENCODING)}: {DESC_ENCODING}")
742+
yield f"{csv_scope(scope, KEY_RAND_SEED)}: {DESC_RAND_SEED}"
743+
yield f"{csv_scope(scope, KEY_BEST_F)}: {DESC_BEST_F}"
744+
yield (f"{csv_scope(scope, KEY_LAST_IMPROVEMENT_FE)}: "
745+
f"{DESC_LAST_IMPROVEMENT_FE}")
746+
yield (f"{csv_scope(scope, KEY_LAST_IMPROVEMENT_TIME_MILLIS)}: "
747+
f"{DESC_LAST_IMPROVEMENT_TIME_MILLIS}")
748+
yield f"{csv_scope(scope, KEY_TOTAL_FES)}: {DESC_TOTAL_FES}"
749+
yield (f"{csv_scope(scope, KEY_TOTAL_TIME_MILLIS)}: "
750+
f"{DESC_TOTAL_TIME_MILLIS}")
758751
if self.__needs_goal_f:
759-
dest(f"{csv_scope(scope, KEY_GOAL_F)}: {DESC_GOAL_F}")
752+
yield f"{csv_scope(scope, KEY_GOAL_F)}: {DESC_GOAL_F}"
760753
if self.__needs_max_fes:
761-
dest(f"{csv_scope(scope, KEY_MAX_FES)}: {DESC_MAX_FES}")
754+
yield f"{csv_scope(scope, KEY_MAX_FES)}: {DESC_MAX_FES}"
762755
if self.__needs_max_ms:
763-
dest(f"{csv_scope(scope, KEY_MAX_TIME_MILLIS)}: "
764-
f"{DESC_MAX_TIME_MILLIS}")
756+
yield (f"{csv_scope(scope, KEY_MAX_TIME_MILLIS)}: "
757+
f"{DESC_MAX_TIME_MILLIS}")
765758

766-
def get_footer_bottom_comments(self, dest: Callable[[str], None]) -> None:
759+
def get_footer_bottom_comments(self) -> Iterable[str]:
767760
"""
768761
Get the footer bottom comments.
769762
770-
:param dest: the destination to write to.
763+
:returns: the footer comments
771764
"""
772-
motipy_footer_bottom_comments(
773-
self, dest, ("The end results data is produced using module "
774-
"moptipy.evaluation.end_results."))
775-
pycommons_footer_bottom_comments(self, dest)
765+
yield from motipy_footer_bottom_comments(
766+
self, ("The end results data is produced using module "
767+
"moptipy.evaluation.end_results."))
768+
yield from pycommons_footer_bottom_comments(self)
776769

777770

778771
class CsvReader:

0 commit comments

Comments
 (0)