This attendance model aims to foster consistent student engagement throughout the semester. Traditional systems often allow students to front-load their attendance early in the term, only to disengage when the material becomes more complex. This undermines active participation, which is crucial as the content progresses.
To address this, we use two metrics: the Aggregate Tracker (AT), which measures overall attendance, and the Consistency Tracker (CT), which focuses on the regularity of attendance. Both trackers are based on an RC circuit-inspired model, treating attendance as a dynamic process and modeling it as a charge in a capacitor. The goal for students is to increase their Aggregate Tracker Charge and Consistency Tracker Charge, both of which must surpass a predetermined threshold to earn the end-of-semester reward for achieving acceptable attendance.
At the start of the semester, students begin with a perfect CT charge. However, it drops rapidly with consecutive missed classes, with the decay slowing down as more classes are missed. To encourage students to break their streak of absences, CT charge increases quickly with the first few attendances, but the rate of growth slows as students settle into a more consistent attendance pattern.
In contrast, the AT charge starts at zero and accumulates as students attend class. By the end of the semester, the AT charge must meet a minimum threshold. This prevents "backloading" by measuring overall attendance, regardless of when it occurs, ensuring that students maintain a baseline level of engagement throughout the term.
We incorporate free passes in our model to account for the fact that life can interfere with attendance. These passes offer a grace period for occasional absences, preventing immediate penalties, while also discouraging extended disengagement.
Ultimately, this model goes beyond static attendance tracking to promote long-term learning outcomes by rewarding consistent engagement. It motivates students to stay involved throughout the semester, even when challenges arise, and supports their academic success.
Think of your attendance tracker as the charge on two capacitors: Aggregate Tracker (AT) and Consistency Tracker (CT), both with a capacitance of 1 Farad. Since the capacitance is sized at 1 unit, the charge and voltage are equivalent and used interchangeably throughout this document.
The figure below illustrates the overall model. The bottom portion shows the circuit that models your attendance. The battery at the center represents the "class," and you charge your capacitors by "Engaging" with the class. In the circuit, the Engagement switch is closed whenever you attend.
The left capacitor, Aggregate Tracker (AT), located in the green highlighted section, represents your overall attendance. It starts with zero charge at the beginning of the semester. As you attend class and the Engagement switch is closed, your AT charge increases. When you stop attending class, the Engagement switch opens, and there’s no decrease in charge. You can begin accumulating charge again when you return to class. The resistor for this part of the circuit is set to 30. Based on RC-circuit formulas, after attending 30 lectures, you’ll have achieved about 63% (63 units) of the total charge needed. This is above the threshold for the Aggregate Tracker, which is 60 units.
The right capacitor, Consistency Tracker (CT), located in the blue highlighted section, represents your consistency in attendance and works a bit differently. It starts fully charged at 100 units at the beginning of the semester. If you attend every class, the Engagement switch stays closed, and you maintain your 100 units, easily surpassing the threshold of 60. However, if you "disengage" from the class, the Engagement switch opens, and the Disengagement switch closes, causing your CT to rapidly discharge.
Disengaging from class isn't just about missing a single lecture. We understand that life can interfere, and there may be instances throughout the semester where you miss class due to valid reasons. To accommodate this, we allow for four free passes. If you miss a class but return to the next lecture, you lose one free pass but aren’t considered "disengaged" yet. Once all four free passes are used, missing any class will result in being marked as disengaged, and the Disengagement switch will close immediately. Even if you have free passes remaining, missing consecutive classes will trigger the Disengagement switch, quickly providing feedback that you need to re-engage with the class.
The top part of the figure below shows the flowchart that determines when the Engagement and Disengagement switches are open or closed. Together with the circuit, they provide a complete picture of how the model works.
The Consistency Tracker consists of a capacitor to store the charge and two resistors—one for charging and one for discharging. You can think of the battery as the energy source for attending class. When you engage with the course by attending classes, the left switch closes, and the right switch remains open. This allows the CT capacitor to charge, similar to how an RC circuit operates. The figure below illustrates the CT Tracker in "Engagement" mode, where E is 1 and D is 0.
When you disengage from the course by missing consecutive classes or too many sporadic classes, the Engagement switch opens, and the Disengagement switch closes. This causes the CT to discharge rapidly, similar to how an RC circuit operates. The figure below illustrates the CT circuitry in "Disengagement" mode, where E is 0 and D is 1.
As described in the overview, there is a grace period between Engagement and Disengagement Modes, referred to as the "Intermediate" Mode. This mode depends on whether you have any free passes remaining or if you're missing classes consecutively, even if you still have free passes. In Intermediate Mode, both the E and D switches are open, and your CT charge remains unchanged. The figure below shows the state of the Consistency Tracker in this mode when both Eand D are set to zero.
The graph below illustrates the Finite State Machine representing the three states of the Consistency Tracker. As described earlier, your state transitions from Engaged to Intermediate or Disengaged based on your attendance and the status of your free passes.
To get the governing formulas for the CT charge in the "Engagement" mode, using Kirchhoff's Voltage Law (KVL) around the loop on the left, we get:
Note that here, Vi is the battery (class) and Vc is your capacitor's charge (because the size of the capacitor is 1 Farad.) Then, we can do some basic algebra and calculus to obtain the solution for a differential equation modeling charging capacitance:
Note that, as it pertains to our purposes, t is the number of consecutive classes you have attended after a miss or a steak of misses. Thus, to calculate the growing exponential from any given point of your attendance streak, impose initial conditions at the start of the streak (t=0):
Yay! We now have a closed form solution to calculate your CT charge in the Engagement Mode:
Similarly, for the "Disengagement: Mode, we start by writing Kirchhoff's Current Law (KCL) at the node of the capacitor connected to the closed discharge switch:
This is a linear, constant-coefficient, time-invariant, ordinary, homogenous differential equation. So by inspection, we can see the solution takes the form:
Similar to charging, t is the number of consecutive classes you have missed after an attendance or a streak of attendances. And once again, imposing initial conditions, we get:
The "Intermediate" Mode and Free Passes are introduced because we understand that sometimes life can interfere with attendance. You can think of them as a delay in the process that triggers the drop in your CT charge. Each student is granted a total of four free passes throughout the semester. When you use a free pass, the decay curve is shifted to the right by one. This is shown in the below equation.
Even if you still have free passes remaining, if you miss another class and continue to miss classes consecutively, the discharge process will begin. This means the discharge does not occur immediately after the first missed class, but will continue for all consecutive absences until you attend class again. This delay in the discharge process will remain in effect as long as you have free passes left. Once your free passes are used up, any missed class—whether consecutive or not—will result in the discharge occurring immediately.
So, putting this all together, what we have is a set of functions that apply depending on your attendance record. In all cases, t=0 represents the time that the current function begins applying, so the overall score function is a piecewise.
In order to provide you with the ability to quickly calculate your grades, there are two options. You can use MATLAB or Python, the code for which is provided below. MATLAB is simple, as you simply only need to install MATLAB and run it. The downside is that the plot generated is not interactive. On the other hand, more advanced users can try setting up a Python environment, installing the dependencies specified by the import statements, and running the script. When run, a dash server will be served on localhost, which can be accessed (usually through http://127.0.0.1:8085) in the browser and you can interactively play with different scenarios to model your grade.
MATLAB:
close all
clear
%% Define Constants
engagement_tau = 12;
disengagement_tau = 6;
lowest_possible_engagement = 0;
highest_possible_engagement = 100;
aggregate_tau = 30;
hold_duration = 1;
num_lectures = 38;
lecture_numbers = 1:num_lectures;
%% Initialize variable data - REPLACE THE 1 CORRESPONDING WITH THE ABOVE LECTURE NUMBER WITH 0 FOR A MISSED LECTURE
lectures_attended = [
% 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1,...
... 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
];
if ~isequal(size(lectures_attended), [1, num_lectures])
error("The class attendance vector must be a row vector" + ...
"with %u elements", num_lectures)
end
consistency_result = zeros(1, num_lectures);
aggregate_result = zeros(1, num_lectures);
free_pass_count = 4;
prev_consistency_charge = highest_possible_engagement;
prev_aggregate_charge = 0;
prev_consistency_discharge = highest_possible_engagement;
consistency_en_volts = highest_possible_engagement;
aggregate_en_volts = 0;
attendance_streak = 0;
miss_streak = 0;
%% Calculate grades at each class
for idx = lecture_numbers
if logical(lectures_attended(idx))
attendance_streak = attendance_streak + 1;
consistency_en_volts = highest_possible_engagement + (prev_consistency_charge - highest_possible_engagement) * exp(-attendance_streak / engagement_tau);
aggregate_en_volts = highest_possible_engagement + (prev_aggregate_charge - highest_possible_engagement) * exp(-attendance_streak / aggregate_tau);
miss_streak = 0;
prev_consistency_discharge = consistency_en_volts;
if idx > 1 && ~(logical(lectures_attended(idx-1)))
free_pass_count = free_pass_count - 1;
end
else
miss_streak = miss_streak + 1;
if free_pass_count <= 0
consistency_en_volts = prev_consistency_discharge * exp(-(miss_streak) / disengagement_tau);
elseif miss_streak > hold_duration
consistency_en_volts = prev_consistency_discharge * exp(-(miss_streak - hold_duration) / disengagement_tau);
end
attendance_streak = 0;
prev_consistency_charge = consistency_en_volts;
prev_aggregate_charge = aggregate_en_volts;
end
consistency_result(idx) = consistency_en_volts;
aggregate_result(idx) = aggregate_en_volts;
end
figure
plot(lecture_numbers, consistency_result, 'b-o')
hold on
plot(lecture_numbers, aggregate_result, 'r-o')
legend('Consistency Attendance Tracker', 'Aggregate Attendance Tracker', 'Location', 'southoutside')
title("Student Engagement Over Time")
xlabel("Time (in terms of completed lectures)")
ylabel("Engagement (En-Volts)")
axis([0 num_lectures lowest_possible_engagement highest_possible_engagement])
xticks(lecture_numbers)
Python:
import dash
import numpy as np
import plotly.graph_objs as go
from dash import dcc, html
from dash.dependencies import Input, Output
class DashServer:
def __init__(self):
self.app = dash.Dash(__name__)
self.num_lectures = 38
self.lecture_numbers = np.arange(1, self.num_lectures + 1)
self.layout_app()
self.engagement_tau = 12
self.disengagement_tau = 6
self.maximum_en_volts = 100
self.aggregate_tau = 30
self.initial_en_volts = self.maximum_en_volts
self.initial_aggregate_en_volts = 0
self.hold_duration = 1
self.free_pass_count = 4
def layout_app(self):
self.app.layout = html.Div([
html.H1("RC-based Attendance Modeling Simulator"),
html.P(
"The simulator below allows you to observe the Aggregate and Consistency Trackers as described on the course website at the following link: "),
html.Label([html.A('https://sites.google.com/vt.edu/introduction-to-embeddedsystem/course-management/syllabus/rcat', href='https://sites.google.com/vt.edu/introduction-to-embeddedsystem/course-management/syllabus/rcat')]),
html.P(
"To simulate your Aggregate and Consistency Charges, simply select ‘0’ for any missed lectures and vice versa. The graph below will show how your trackers charge and discharge accordingly."),
html.H3("Click 0 for a Missed Lecture."),
html.Div([
html.Div([
html.H3(header),
dcc.RadioItems(
id=f"radio-{i}",
options=[{"label": "1", "value": 1}, {"label": "0", "value": 0}],
value=1, # Default value
)
]) for i, header in enumerate(self.lecture_numbers)
], style={"display": "flex", "flex-wrap": "wrap", "gap": "8px"}),
dcc.Graph(id='exp-plot'),
])
def piecewise_grader(vector):
free_pass_count = self.free_pass_count
consistency_result = np.zeros_like(vector, dtype=float)
aggregate_result = np.zeros_like(vector, dtype=float)
prev_consistency_charge = self.initial_en_volts
prev_aggregate_charge = self.initial_aggregate_en_volts
prev_consistency_discharge = self.initial_en_volts
consistency_en_volts = self.initial_en_volts
aggregate_en_volts = self.initial_aggregate_en_volts
attendance_streak = 0
miss_streak = 0
for lecture_num in range(len(vector)):
if vector[lecture_num] == 1:
attendance_streak += 1
consistency_en_volts = self.maximum_en_volts + (
prev_consistency_charge - self.maximum_en_volts) * np.exp(
-attendance_streak / self.engagement_tau)
aggregate_en_volts = self.maximum_en_volts + (
prev_aggregate_charge - self.maximum_en_volts) * np.exp(
-attendance_streak / self.aggregate_tau)
miss_streak = 0
prev_consistency_discharge = consistency_en_volts
if lecture_num > 0 and vector[lecture_num - 1] == 0:
free_pass_count -= 1
else:
miss_streak += 1
if free_pass_count <= 0:
consistency_en_volts = prev_consistency_discharge * np.exp(
-(miss_streak / self.disengagement_tau))
elif miss_streak > self.hold_duration:
consistency_en_volts = prev_consistency_discharge * np.exp(
-(miss_streak - self.hold_duration) / self.disengagement_tau)
attendance_streak = 0
prev_consistency_charge = consistency_en_volts
prev_aggregate_charge = aggregate_en_volts
consistency_result[lecture_num] = consistency_en_volts
aggregate_result[lecture_num] = aggregate_en_volts
return consistency_result, aggregate_result
@self.app.callback(
Output('exp-plot', 'figure'),
*[Input(f"radio-{i}", "value") for i in range(self.num_lectures)]
)
def graph_capacitance(*args):
engagement_fig = go.Figure()
lectures_attended = args[:self.num_lectures]
consistency_y, aggregate_y = piecewise_grader(lectures_attended)
y_min = -1
y_range = 101
engagement_fig.add_trace(go.Scatter(x=self.lecture_numbers, y=consistency_y, mode='lines+markers',
name='Consistency Attendance Tracker'))
engagement_fig.add_trace(go.Scatter(x=self.lecture_numbers, y=aggregate_y, mode='lines+markers',
name='Aggregate Attendance Tracker'))
engagement_fig.update_layout(
title='Student Engagement Over Time',
xaxis_title='Time (in terms of completed lectures)',
yaxis_title='Engagement (En-Volts)'
)
engagement_fig.update_yaxes(
range=[y_min - y_range * 0.1, 100 + y_range * 0.1],
tick0=0,
dtick=10
)
engagement_fig.update_xaxes(
range=[1, self.num_lectures],
tick0=1,
dtick=1
)
return engagement_fig
my_app = DashServer()
server = my_app.app.server
if __name__ == '__main__':
my_app.app.run_server(debug=True)
In the event that you do not have success downloading and setting up via source code, feel free to use the web-hosted version of our simulator at https://grade-capacitance.onrender.com/.