Annotazioni relazionali
Il tipo di annotazione più semplice in PyBryt è l'annotazione del valore, che asserisce la presenza di un valore nel footprint della memoria dello studente. Tuttavia, queste annotazioni non coprono tutti i casi; cosa accade se si desidera verificare la presenza di due possibili rappresentazioni di un valore o si desidera esaminare l'ordinamento dei valori? In queste situazioni entrano in gioco le annotazioni relazionali.
Le annotazioni relazionali definiscono un tipo di relazione tra altre annotazioni. È possibile considerare le annotazioni relazionali come annotazioni che operano su altre annotazioni, asserendo condizioni su come le annotazioni vengono soddisfatte. Nell'ultimo modulo sono state illustrate le annotazioni della raccolta e come è possibile utilizzarle per applicare l'ordinamento delle annotazioni. Le annotazioni relazionali sono simili e in effetti le raccolte possono essere considerate come un tipo di annotazione relazionale.
Attualmente PyBryt supporta due tipi di annotazioni relazionali: annotazioni temporali e annotazioni logiche.
Annotazioni temporali
Le annotazioni temporali descrivono una relazione temporale tra annotazioni diverse. Vengono soddisfatte quando tutte le annotazioni figlio sono soddisfatte e i timestamp dei valori che soddisfano tali annotazioni si verificano in un ordine specifico.
PyBryt ha un solo tipo di annotazione temporale, BeforeAnnotation
, che afferma che i timestamp soddisfacenti delle annotazioni figlio si verificano in ordine non decrescente. Queste annotazioni possono essere create direttamente usando il costruttore come qualsiasi altra annotazione, ma tutte le annotazioni hanno anche un metodo before
e after
che può essere usato per costruire queste annotazioni in modo più semantico:
>>> a1 = pybryt.Value(1)
>>> a2 = pybryt.Value(2)
>>> a1.before(a2), a1.after(a2)
(pybryt.BeforeAnnotation, pybryt.BeforeAnnotation)
Come si può notare, sia Annotation.before
che Annotation.after
restituiscono BeforeAnnotation
, ma l'ordinamento delle annotazioni viene invertito nell'annotazione restituita da after
.
Quando si crea un'annotazione relazionale, è possibile aggiornare i campi per ognuna delle opzioni di annotazione in base alle esigenze oppure passare le opzioni come argomenti di parola chiave al metodo before
o after
:
a1_before_a2 = a1.before(
a2,
success_message="a1 is before a2",
failure_message="a1 is not before a2",
)
# or:
a1_before_a2 = a1.before(a2)
a1_before_a2.success_message = "a1 is before a2"
a1_before_a2.failure_message = "a1 is not before a2"
Con un footprint fittizio della memoria, è possibile vedere come BeforeAnnotation
viene soddisfatto. Nell'esempio seguente viene creato un footprint di questo tipo usando il metodo pybryt.MemoryFootprint.from_values
, che accetta valori alternati e timestamp:
pybryt.MemoryFootprint.from_values(val1, ts1, val2, ts2, val3, ts3, ...)
Si noti che il risultato dell'annotazione relazionale cambia man mano che si modificano i valori nel footprint e i relativi timestamp.
>>> ref = pybryt.ReferenceImplementation("temporal-annotations", [a1_before_a2])
>>> # the values in the correct order
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 1, 2, 2))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: True
MESSAGES:
- a1 is before a2
>>> # put both values at the same timestamp
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 1, 2, 1))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: True
MESSAGES:
- a1 is before a2
>>> # put the timestamp of 1 after the timestamp of 2
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 2, 2, 1))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: False
MESSAGES:
- a1 is not before a2
>>> # don't satisfy the second annotation
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 1))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: False
MESSAGES:
- a1 is not before a2
Annotazioni logiche
Le annotazioni logiche non riguardano la temporalità di quando le annotazioni sono soddisfatte, ma operano invece sul fatto che le annotazioni siano soddisfatte. Esse asseriscono condizioni per verificare se le annotazioni figlio sono soddisfatte, consentendo di costruire una logica booleana complessa all'interno dei riferimenti per far sì che più percorsi arrivino alla stessa soluzione.
Per creare un'annotazione logica, usare gli operatori logici bit per bit di Python in qualsiasi annotazione:
>>> a1 & a2, a1 | a2, a1 ^ a2
(pybryt.AndAnnotation, pybryt.OrAnnotation, pybryt.XorAnnotation)
Per creare condizioni che coinvolgono più di due annotazioni, è possibile concatenare gli operatori o creare un'istanza delle annotazioni direttamente con le annotazioni figlio. Analogamente alle annotazioni temporali, le opzioni per le annotazioni logiche possono essere impostate aggiornando l'attributo corrispondente nell'oggetto annotazione.
a3 = pybryt.Value(3)
all_anns = a1 & a2 & a3
all_anns.success_message = "Found a1, a2, and a3"
all_anns.failure_message = "Did not find a1, a2, and a3"
any_anns = a1 | a2 | a3
any_anns.success_message = "Found a1, a2, or a3"
any_anns.failure_message = "Did not find a1, a2, or a3"
one_ann = a1 ^ a2 ^ a3
one_ann.success_message = "Found exactly of a1, a2, or a3"
one_ann.failure_message = "Did not find exactly one of a1, a2, or a3"
PyBryt supporta anche l'operatore not (~
) per produrre annotazioni soddisfatte solo se l'annotazione figlio non è soddisfatta. Ad esempio, queste annotazioni possono essere usate per inviare un messaggio agli studenti se è presente un valore specifico nel footprint della memoria che non deve essere presente:
not_lst = ~pybryt.Value(lst)
not_lst.failure_message = "Found an incorrect value in your submission; " + \
"please double-check your implementation."
L'annotazione precedente fornisce un messaggio agli studenti se trova il valore di lst
nel footprint della memoria. Lo stesso effetto può essere ottenuto impostando success_message
nel costruttore pybryt.Value
.