Module paroxython.assess_costs
Assess the learning cost associated with the introduction of the given programs.
Each program has been previously found to implement a number of notions represented by a list of
taxa of the form: \textrm{edge}_0/\textrm{edge}_1/.../\textrm{edge}_n, e.g.,
flow/loop/exit/early/break
.
Furthermore, the notions already imparted may cover a certain prefix of such a taxon, here, e.g.
flow/loop
. The learning cost should therefore not take this into account.
The cost of the remaining edges, here exit/early/break
, is approximated by a function taking
two zero-based indexes start
(inclusive) and stop
(exclusive), and returning a floating number. Currently, only two such functions are provided:
range_to_cost_linear()
, which simply counts the number of edges;range_to_cost_zeno()
, which calculates the sum of a decreasing function of the positions (with the assumption that the introduction of a sub-concept costs half that of its parent concept).
Expand source code Browse GitHub
from functools import lru_cache
from .user_types import (
AssessedPrograms,
AssessmentStrategy,
ProgramInfos,
ProgramPathSet,
TaxonName,
TaxonNameSet,
)
__pdoc__ = {
"LearningCostAssessor.__call__": True,
"LearningCostAssessor.__init__": True,
"LearningCostAssessor": "",
}
@lru_cache(maxsize=None) # NB: memoization needed for consistency with mypy's typing
def range_to_cost_linear(start: int, stop: int) -> float:
return float(stop - start)
@lru_cache(maxsize=None)
def range_to_cost_zeno(start: int, stop: int) -> float:
return sum(2 ** ~i for i in range(start, stop))
class LearningCostAssessor:
def __init__(self, programs: ProgramInfos, assessment_strategy: AssessmentStrategy = "zeno"):
self.programs = programs
if assessment_strategy.lower() == "zeno":
self.range_to_cost = range_to_cost_zeno
elif assessment_strategy == "linear":
self.range_to_cost = range_to_cost_linear
else:
raise NotImplementedError
self.range_to_cost.cache_clear()
self.taxon_cost.cache_clear()
def set_imparted_knowledge(self, imparted_knowledge: TaxonNameSet) -> None:
self.imparted_knowledge = imparted_knowledge
@lru_cache(maxsize=None)
def taxon_cost(self, taxon: TaxonName) -> float:
if taxon.startswith("meta/"):
return 0
(start, stop) = (0, 0)
if taxon not in self.imparted_knowledge:
edges = taxon.split("/")
stop = len(edges)
for start in range(stop - 1, -1, -1):
if "/".join(edges[:start]) in self.imparted_knowledge:
break
return self.range_to_cost(start, stop)
def __call__(self, selected_programs: ProgramPathSet) -> AssessedPrograms:
result = []
for program_path in selected_programs:
total_cost = 0.0
for taxon_name in self.programs[program_path]["taxa"]:
total_cost += self.taxon_cost(taxon_name)
result.append((total_cost, program_path))
return sorted(result)
Functions
def range_to_cost_linear(start: int,
stop: int
) ‑> float-
Return the length of the slice between
start
(inclusive) andstop
(exclusive).Expand source code Browse GitHub
@lru_cache(maxsize=None) # NB: memoization needed for consistency with mypy's typing def range_to_cost_linear(start: int, stop: int) -> float: return float(stop - start)
def range_to_cost_zeno(start: int,
stop: int
) ‑> float-
Calculate the sum of the given slice of a Zeno's geometric series.
Specifically: \sum_{i=\textrm{start}}^{\textrm{stop}-1}{\frac{1}{2^{i+1}}}
Since it converges absolutely towards 1, the returned value is in the interval [0, 1[.
Example
If
flow/loop
is already imparted, the learning cost offlow/loop/exit/early/break
will be:>>> range_to_cost_zeno(2, 5) 0.21875
Indeed, \frac{1}{2^3}+\frac{1}{2^4}+\frac{1}{2^5}=\frac{1}{8}+\frac{1}{16}+\frac{1}{32}=0.21875.
Expand source code Browse GitHub
@lru_cache(maxsize=None) def range_to_cost_zeno(start: int, stop: int) -> float: return sum(2 ** ~i for i in range(start, stop))
Classes
class LearningCostAssessor
-
Expand source code Browse GitHub
class LearningCostAssessor: def __init__(self, programs: ProgramInfos, assessment_strategy: AssessmentStrategy = "zeno"): self.programs = programs if assessment_strategy.lower() == "zeno": self.range_to_cost = range_to_cost_zeno elif assessment_strategy == "linear": self.range_to_cost = range_to_cost_linear else: raise NotImplementedError self.range_to_cost.cache_clear() self.taxon_cost.cache_clear() def set_imparted_knowledge(self, imparted_knowledge: TaxonNameSet) -> None: self.imparted_knowledge = imparted_knowledge @lru_cache(maxsize=None) def taxon_cost(self, taxon: TaxonName) -> float: if taxon.startswith("meta/"): return 0 (start, stop) = (0, 0) if taxon not in self.imparted_knowledge: edges = taxon.split("/") stop = len(edges) for start in range(stop - 1, -1, -1): if "/".join(edges[:start]) in self.imparted_knowledge: break return self.range_to_cost(start, stop) def __call__(self, selected_programs: ProgramPathSet) -> AssessedPrograms: result = [] for program_path in selected_programs: total_cost = 0.0 for taxon_name in self.programs[program_path]["taxa"]: total_cost += self.taxon_cost(taxon_name) result.append((total_cost, program_path)) return sorted(result)
Methods
def __init__(self, programs: Dict[ProgramPath, ProgramRecord], assessment_strategy: Literal['zeno', 'linear'] = 'zeno')
-
Set the programs and the function to be used for the learning cost calculation.
Args
assessment_strategy
:str
, optional-
Either:
"zeno"
:range_to_cost_zeno()
(default)."linear"
:range_to_cost_linear()
.
Raises
NotImplementedError
- Raised in case of unknown
assessment_strategy
.
Expand source code Browse GitHub
def __init__(self, programs: ProgramInfos, assessment_strategy: AssessmentStrategy = "zeno"): self.programs = programs if assessment_strategy.lower() == "zeno": self.range_to_cost = range_to_cost_zeno elif assessment_strategy == "linear": self.range_to_cost = range_to_cost_linear else: raise NotImplementedError self.range_to_cost.cache_clear() self.taxon_cost.cache_clear()
def set_imparted_knowledge(self,
imparted_knowledge: Set[TaxonName]
) ‑> NoneType-
Expand source code Browse GitHub
def set_imparted_knowledge(self, imparted_knowledge: TaxonNameSet) -> None: self.imparted_knowledge = imparted_knowledge
def taxon_cost(self,
taxon: TaxonName
) ‑> float-
Evaluate the learning cost of a given taxon.
The bounds of the not-yet-imparted suffix of the taxon are extracted and passed to the cost assessment function.
Note
The learning cost of a taxon prefixed by
"meta/"
is assumed to be zero.Expand source code Browse GitHub
@lru_cache(maxsize=None) def taxon_cost(self, taxon: TaxonName) -> float: if taxon.startswith("meta/"): return 0 (start, stop) = (0, 0) if taxon not in self.imparted_knowledge: edges = taxon.split("/") stop = len(edges) for start in range(stop - 1, -1, -1): if "/".join(edges[:start]) in self.imparted_knowledge: break return self.range_to_cost(start, stop)
def __call__(self,
selected_programs: Set[ProgramPath]
) ‑> List[Tuple[float, ProgramPath]]-
Associate the given selected programs with the total learning cost of their taxa.
Args
selected_programs
:ProgramPathSet
- A set of program paths.
Returns
AssessedPrograms
- A list of tuples
(total_cost, ProgramPath)
sorted by increasing cost.
Expand source code Browse GitHub
def __call__(self, selected_programs: ProgramPathSet) -> AssessedPrograms: result = [] for program_path in selected_programs: total_cost = 0.0 for taxon_name in self.programs[program_path]["taxa"]: total_cost += self.taxon_cost(taxon_name) result.append((total_cost, program_path)) return sorted(result)