Course-dependent skill structures

SkillAssignment links learning objects (lessons, exercises or activities) to the skills they teach and require. It implements a CDSS-style workflow with explicit labels and alternative prerequisite sets. The source workflow is described in Hockemeyer’s CDSS vignette (CDSS 0.3-1, 2026). The Python algorithms are independent implementations using the attribution semantics in Falmagne & Doignon (2011), Equation 5.2.

Build and inspect a course

from knowledgespaces import LearningObject, SkillAssignment

course = SkillAssignment([
    LearningObject("basics", frozenset({"a"})),
    LearningObject("alternative-basics", frozenset({"b"})),
    LearningObject("route-a", frozenset({"c"}), frozenset({"a"})),
    LearningObject("route-b", frozenset({"c"}), frozenset({"b"})),
    LearningObject("application", frozenset({"d", "e"}), frozenset({"c"})),
])
assert course.diagnostics().compliant
result = course.derive()
print(result.completed.requirements_for("application"))
# Two alternatives: {a, c} or {b, c}.
skill_space = result.skill_function.to_knowledge_space()
object_space = result.object_function.to_knowledge_space()

Within one record, all taught skills are acquired together and all required skills are jointly required. Repeated records with the same identifier express alternative requirements for one learning object; their taught sets must agree. This differs from having two distinct teaching activities. from_pairs(taught, required) accepts two (object, skill) tables. from_matrices(objects, skills, taught, required) accepts aligned binary matrices for single assignments. Explicit domains retain unused skills and nonteaching objects for diagnosis.

diagnostics() reports untaught skills, nonteaching objects and overlaps between taught and required skills. These semantic checks follow the three CDSS compliance conditions. They do not establish acyclicity or empirical validity. Derivation requires compliance; importing and inspecting an incomplete course does not.

What is derived

Write \(T_l\) for an object’s taught skills and \(\mathcal R_l\) for its alternative required sets. The skill attribution assigns to a skill \(s\) all sets \(T_l\cup R\) with \(s\in T_l\) and \(R\in\mathcal R_l\). A set \(K\) is admitted exactly when every \(s\in K\) has such a set contained in \(K\). The object attribution admits a set \(M\) exactly when each \(l\in M\) has some \(R\in\mathcal R_l\) contained in \(\bigcup_{j\in M}T_j\). Both constructions produce union-closed families; their canonical surmise functions are obtained by the attribution algorithm.

Strict complete() replaces each original requirement alternative by its inclusion-minimal containing skill states. It removes dominated alternatives. An overlap with the object’s own taught skills raises CurriculumCompletionError. Such a conflict can reflect a prerequisite cycle or redundant teaching of skills that are acquired together. It is not, by itself, a graph-cycle diagnosis.

complete(allow_cycles=True) instead completes each \(T_l\cup R\) jointly and records the resulting minimal states after removing \(T_l\). This option has an explicit static semantics and preserves the original skill space. It is not a literal reproduction of CDSS’s allowcycles output, whose source documentation leaves problematic cyclic results undefined.

derive() returns the original and completed assignment, canonical skill and object surmise functions, and a relation for each function that is quasi-ordinal. Relation pairs use (prerequisite, dependent) orientation. Exactly one teaching object per skill is sufficient in a single assignment; requirement alternatives can invalidate that shortcut. The implementation checks quasi-ordinality of the resulting functions directly.

The object function closes the original object attribution, retaining the original object domain. Completion alternatives do not create extra objects. CDSS 0.3-1’s cdss_lo_csma2sf duplicates object columns in some such cases; that behavior is deliberately not reproduced. Skills acquired together may be equivalent in the derived space. Use quotient() to inspect these classes and the refinement operations to introduce a justified finer structure.

Static compatibility and executable lessons

progress = course.reachability()
assert not progress.blocked_objects
print(progress.sequence)

reachability(initial_skills=()) tests prerequisites before adding the skills taught by each object. It returns one deterministic feasible sequence, all reachable skills and blocked objects. It does not optimize lesson cost, model forgetting or estimate success probabilities.

For example, an activity teaching a while requiring b, paired with an activity teaching b while requiring a, admits the static state {a, b}. Neither activity is executable from the empty skill set. The joint completion option preserves that distinction; it cannot invent an initial learning step.

Interchange and computational limits

read_assignment_csv / write_assignment_csv use two CDSS-style tables with object and skill columns. JSON read/write functions also preserve alternative requirements and absent domain labels. read_assignment and write_assignment also support native XLSX/ODS workbooks; see spreadsheet interchange. Pair-table export rejects cases it cannot represent without loss; see I/O.

Derivation searches minimal clause combinations without materializing all states. The number of alternatives can still grow exponentially. max_candidates bounds intermediate candidate families and raises rather than truncating the result. Subsequent state enumeration has its own size guards. Neither resource limits nor successful derivation establish that an expert-specified course accurately represents observed learning.

Run python cookbook/10_curriculum.py for a complete executable example.