Annotazioni relazionali

Completato

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.

Verificare le conoscenze

1.

Quale delle espressioni seguenti crea un'annotazione che afferma che a1 o a2 sono soddisfatti?

2.

Quale delle chiamate seguenti crea un'annotazione che afferma che v2 viene rigorosamente dopov1. Ovvero i timestamp di v1 e v2 non possono essere gli stessi?

3.

Si supponga di creare un'annotazione ann con:

a1, a2, a3 = pybryt.Value(1), pybryt.Value(2), pybryt.Value(3)
ann = (a1 | a2) ^ a3

Quale dei set seguenti, se trasformati in footprint della memoria, soddisferebbe ann?