-
-
-
-
-
+
\ No newline at end of file
diff --git a/deimos/views.py b/deimos/views.py
index dc933355..dcb713c7 100644
--- a/deimos/views.py
+++ b/deimos/views.py
@@ -247,7 +247,9 @@ def answer_question(request, question_id, assignment_id, course_id, student_id=N
'last_attempt_content':last_attempt.submitted_answer if last_attempt else '',
'last_attempt':last_attempt,
'units_too_many_attempts':units_too_many_attempts,
- 'passed_pairs':answers_c
+ 'passed_pairs':answers_c,
+ 'potential':round(question_student.get_potential() * 100, 1),
+ 'num_points':round(question_student.get_num_points(),2)
}
questions_dictionary[index] = context
return render(request, 'deimos/answer_question.html',
@@ -279,32 +281,23 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
question = Question.objects.get(pk=question_id)
feedback_data = ''
return_sp = None
- try:
- days_overdue = max(0, (date.today() - question.assignment.due_date.date()).days)
- overall_percentage = max(question.assignment.grading_scheme.floor_percentage, \
- 1 - days_overdue * question.assignment.grading_scheme.late_sub_deduct)
- except:
- overall_percentage = 1
- # Use get_or_create() to avoid duplicating QuestionStudent instances
- # Normally, we should just use get() because QuestionStudent object is already created
- # whenever the user opens a question for the first time, but just to be safe.
+
question_student, created = QuestionStudent.objects.get_or_create(student=student, question=question)
+ current_potential = question_student.get_potential()
num_attempts = question_student.get_num_attempts()
if data["questionType"].startswith('structural'):
- too_many_attempts = num_attempts >= question.struct_settings.max_num_attempts
- if question_student.num_units_attempts:
- units_too_many_attempts = question_student.num_units_attempts >= question.struct_settings.units_num_attempts
- else:
- units_too_many_attempts = False
+ too_many_attempts = num_attempts == question.struct_settings.max_num_attempts
+ units_too_many_attempts = question_student.num_units_attempts == question.struct_settings.units_num_attempts
+
elif data["questionType"].startswith('mcq') or data["questionType"] == 'mp':
too_many_attempts = num_attempts >= question.mcq_settings.mcq_max_num_attempts
- units_too_many_attempts = True
+ units_too_many_attempts = True # So that the light turns red?
correct = question_student.success
- if ( not (question_student.success)):
+ if ( not (question_student.success)):
last_attempt = QuestionAttempt.objects.filter(question_student=question_student).last()
prev_success = last_attempt.success if last_attempt else False
- correct = prev_success
- prev_units_success = last_attempt.units_success if last_attempt else False
+ correct = prev_success # checking if succeeded sheer answer (and failed units)
+ prev_units_success = last_attempt.units_success if last_attempt else False # checking if succeeded units (and failed sheer answer)
units_correct = prev_units_success
units_too_many_attempts = False if prev_units_success else units_too_many_attempts
if not (too_many_attempts or prev_success):
@@ -315,59 +308,73 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
if compare_expressions(previous_attempt.content, simplified_answer):
previously_submitted = True
return JsonResponse({'previously_submitted': previously_submitted})
- attempt = QuestionAttempt.objects.create(question_student=question_student)
- attempt.content = simplified_answer
- attempt.submitted_answer = submitted_answer
+ # Recording attempt
+ attempt = QuestionAttempt.objects.create(question_student=question_student,\
+ content=simplified_answer,\
+ submitted_answer=submitted_answer)
+ # Getting correct answer
answer_content, units = question.expression_answer.content, question.expression_answer.answer_unit
+ # Comparing correct answer with simplified submitted answer
correct = compare_expressions(answer_content, simplified_answer)
elif question.answer_type in [QuestionChoices.STRUCTURAL_FLOAT, QuestionChoices.STRUCTURAL_VARIABLE_FLOAT]:
if question.answer_type == QuestionChoices.STRUCTURAL_FLOAT:
+ # Structural float with no variable.
answer_content, units = question.float_answer.content, question.float_answer.answer_unit
else:
+ # Variable structural float
answer_content, units = question_student.compute_structural_answer(), question.variable_float_answer.answer_unit
try:
+ # Will sometimes return a value error if answers do not match
correct, feedback_data = compare_floats(answer_content, simplified_answer, question.struct_settings.margin_error)
except ValueError:
correct = False
+
+ # Checking previous submissions before recording attempt
if not correct:
for prev_attempt in question_student.attempts.all():
answers_are_the_same, _ = compare_floats(prev_attempt.content, simplified_answer, margin_error=0.02, get_feedback=False)
if answers_are_the_same:
return JsonResponse({'previously_submitted': True})
- attempt = QuestionAttempt.objects.create(question_student=question_student)
- attempt.content = simplified_answer
- attempt.submitted_answer = submitted_answer
+ attempt = QuestionAttempt.objects.create(question_student=question_student,\
+ content=simplified_answer,
+ submitted_answer=submitted_answer)
else:
raise ValueError(f'Expected either STRUCT FLOAT OR VAR STRUCT FLOAT, but got {question.answer_type}')
- if correct:
+
+ # Recording status (whether attempt was succesful or not)
+ # then calculating the number of points
+ if correct: # Handling correct case for structural questions.
attempt.success = True
- if question.answer_type in [QuestionChoices.STRUCTURAL_FLOAT, \
- QuestionChoices.STRUCTURAL_VARIABLE_FLOAT, QuestionChoices.STRUCTURAL_EXPRESSION] and units:
- attempt.num_points = overall_percentage * max(0, question.struct_settings.num_points * (1 - question.struct_settings.percentage_pts_units)\
- * (1 - question.struct_settings.deduct_per_attempt *
- max(0, question_student.get_num_attempts() - 1)))
+ attempt_potential = current_potential
+ if units: # Will just score the attempt, without considering the points for units.
+ # Units are taken care of in the last block in this function.
if prev_units_success:
- question_student.success = True
+ question_student.success, question_student.is_complete = (True, True)
+ elif not units_too_many_attempts: # get_potential() will return
+ # a percentage where the units are already deducted
+ # if units_too_many_attempts == True
+ attempt_potential -= question.struct_settings.percentage_pts_units
else:
- question_student.success = True
- attempt.num_points = overall_percentage * max(0, question.struct_settings.num_points * (1 - question.struct_settings.deduct_per_attempt *
- max(0, question_student.get_num_attempts() - 1)))
+ question_student.success, question_student.is_complete = (True, True)
+ attempt.num_points = attempt_potential * question.struct_settings.num_points
+
elif data["questionType"] == 'mcq':
# retrieve list of 'true' mcq options
# !important: mcq answers of different type may have the same primary key.
+ # That is the reason why the type is taken into consideration when doing comparisons.
# checking previous attempts
for previous_attempt in question_student.attempts.all():
- # print(f"submitted answer set: {(set(simplified_answer))},prevsious answer content set: {set(eval(previous_attempt.content))}")
+ # previous_attempt.content is a string, so we use eval() to convert
+ # to list, before converting to set and doing the comparison.
if set(simplified_answer) == set(eval(previous_attempt.content)):
previously_submitted = True
return JsonResponse({'previously_submitted': previously_submitted})
- attempt = QuestionAttempt.objects.create(question_student=question_student)
- attempt.content = str(simplified_answer)
-
+ attempt = QuestionAttempt.objects.create(question_student=question_student, \
+ content=str(simplified_answer))
answers = []
# Loop through each answer type and process accordingly
@@ -378,21 +385,22 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
answer_pks = list(answer_queryset.filter(is_answer=True).values_list('pk', flat=True))
# Convert primary keys to the desired string format and extend the answers list
answers.extend([str(pk) + str(answer_field[1]) for pk in answer_pks])
- # print(f'Submitted answer set: {(set(simplified_answer))}, answers set: {set(answers)}')
+
+ # Validating mcq submission
if len(simplified_answer) == len(answers):
s1, s2 = set(simplified_answer), set(answers)
if s1 == s2:
correct = True
- percentage_gain = (1 - question.mcq_settings.mcq_deduct_per_attempt *
- max(0, question_student.get_num_attempts() - 1))
- attempt.num_points = overall_percentage * max(0, question.mcq_settings.num_points * percentage_gain)
- question_student.success = True
-
+ attempt.num_points = current_potential * question.mcq_settings.num_points
+ question_student.success, question_student.is_complete = (True, True)
attempt.success = True
+
elif data["questionType"] == 'mp':
success_pairs_strings = []
attempt_pairs = []
-
+ # Expecting to receive a dictionary(JS object) from the front end
+ # with keys being the primary keys, and values being the encrypted primary keys
+ # So we decrypt the values and see if they are the same with the key.
for part_A_pk, encrypted_B_pk in submitted_answer.items():
decrypted_b = decrypt_integer(int(encrypted_B_pk))
attempt_pairs.append(f'{part_A_pk}-{decrypted_b}')
@@ -409,25 +417,22 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
sp = previous_a.success_pairs.pairs.split('&')
success_pairs_strings = list(set(success_pairs_strings) - set(sp))
frac = (num_of_correct/total_num_of_pairs)
- if frac == 1 or num_of_correct == len(attempt_pairs):
- question_student.success = True
+ if frac == 1 or num_of_correct == len(attempt_pairs): # we technically do not need frac in this condition.
+ question_student.success, question_student.is_complete = (True, True)
correct = True
attempt_pairs = "&".join(attempt_pairs)
attempt = QuestionAttempt.objects.create(question_student=question_student)
attempt.content = attempt_pairs
- attempt.submitted_answer = attempt_pairs
- attempt.num_points = frac * \
- overall_percentage * max(0, question.mcq_settings.num_points * (1 - question.mcq_settings.mcq_deduct_per_attempt *
- max(0, question_student.get_num_attempts() - 1)))
- return_sp = success_pairs_strings
+ attempt.submitted_answer = attempt_pairs # useless. Using twice the size of memory.
+ attempt.num_points = frac * current_potential * question.mcq_settings.num_points
+ return_sp = success_pairs_strings # will return the successful pairs so the front end can be updated.
success_pairs_strings = "&".join(success_pairs_strings)
- attempt.save()
- success_pairs = QASuccessPairs.objects.create(pairs=success_pairs_strings, question_attempt=attempt)
+ # attempt.save() # probably not needed here?
+ success_pairs = QASuccessPairs.objects.create(question_attempt=attempt,pairs=success_pairs_strings)
success_pairs.save()
question_student.save()
attempt.save()
- # print(f'Too many units attempts: {units_too_many_attempts} previous units success: {prev_units_success}')
- # print(f'previously passed units: {prev_units_success} structural too many attempts: {too_many_attempts}')
+
if not (units_too_many_attempts or prev_units_success):
answer_types_with_units = {QuestionChoices.STRUCTURAL_FLOAT: 'float_answer',\
QuestionChoices.STRUCTURAL_EXPRESSION:'expression_answer',\
@@ -436,28 +441,26 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
units = getattr(question, answer_types_with_units[question.answer_type]).answer_unit
# the following line will retrieve the most recent attempt, which is either the attempt that has just
# been submitted or the last attempt before it exceeded the number of permitted attempts.
- last_attempt = QuestionAttempt.objects.filter(question_student=question_student).last()
-
+ last_attempt = attempt if attempt else last_attempt
# the instructor may mistakenly set a maximum number of attempts to a question that doesn't even have
# units, so we check if units exist in the first place.
- if units:
+ if units:
submitted_units = data["submitted_units"]
# print(f"Correct units: {units}, Submitted: {submitted_units}")
units_correct = compare_units(units, submitted_units)
last_attempt.units_success = units_correct
last_attempt.submitted_units = submitted_units
+ question_student.num_units_attempts += 1
# Update the number of points for this attempt
if units_correct:
last_attempt.num_points += question.struct_settings.num_points * question.struct_settings.percentage_pts_units
last_attempt.save()
- if question_student.num_units_attempts:
- question_student.num_units_attempts += 1
- else:
- question_student.num_units_attempts = 1
+
+ # Updating question status
if units_correct and last_attempt.success:
- question_student.success = True
-
+ question_student.success = True
question_student.save()
+
elif(prev_units_success and not too_many_attempts): # that is if a new attempt has been created.
unit_points = question.struct_settings.num_points * question.struct_settings.percentage_pts_units
attempt.num_points += unit_points
@@ -470,10 +473,12 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
last_attempt.num_points = 0
attempt.save()
last_attempt.save()
- # print(f"Correct:{correct}. Units too may attempts: {units_too_many_attempts}")
+
+
+ # Some info that will be used to update the front end.
if(not correct and (data["questionType"].startswith('structural'))):
- too_many_attempts = num_attempts + 1 >= question.struct_settings.max_num_attempts
- if question_student.num_units_attempts and not units_correct:
+ too_many_attempts = num_attempts + 1 == question.struct_settings.max_num_attempts
+ if not units_correct:
units_too_many_attempts = question_student.num_units_attempts >= question.struct_settings.units_num_attempts
# Return a JsonResponse
return JsonResponse({
@@ -483,8 +488,13 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id=
'feedback_data': feedback_data,
'units_correct': units_correct,
'units_too_many_attempts':units_too_many_attempts,
- 'success_pairs': return_sp if return_sp else None
+ 'success_pairs': return_sp if return_sp else None,
+ 'potential':round(question_student.get_potential() * 100),
+ 'grade':round(question_student.get_num_points(), 1),
+ 'numAttempts':question_student.get_num_attempts(),
+ 'unitsNumAttempts': question_student.num_units_attempts
})
+
def login_view(request):
diff --git a/media/kilua.webp b/media/kilua.webp
new file mode 100644
index 00000000..729244a9
Binary files /dev/null and b/media/kilua.webp differ
diff --git a/media/phobos/images/question_images/newton_XZvHgpQ.jpg b/media/phobos/images/question_images/newton_XZvHgpQ.jpg
new file mode 100644
index 00000000..3c33d1f3
Binary files /dev/null and b/media/phobos/images/question_images/newton_XZvHgpQ.jpg differ
diff --git a/phobos/__pycache__/models.cpython-311.pyc b/phobos/__pycache__/models.cpython-311.pyc
index 09a8c354..704c8fcc 100644
Binary files a/phobos/__pycache__/models.cpython-311.pyc and b/phobos/__pycache__/models.cpython-311.pyc differ
diff --git a/phobos/__pycache__/utils.cpython-311.pyc b/phobos/__pycache__/utils.cpython-311.pyc
index f206604f..d2898bc3 100644
Binary files a/phobos/__pycache__/utils.cpython-311.pyc and b/phobos/__pycache__/utils.cpython-311.pyc differ
diff --git a/phobos/__pycache__/views.cpython-311.pyc b/phobos/__pycache__/views.cpython-311.pyc
index 4bd59d62..8fbfc1bc 100644
Binary files a/phobos/__pycache__/views.cpython-311.pyc and b/phobos/__pycache__/views.cpython-311.pyc differ
diff --git a/phobos/migrations/0049_remove_question_category_unit_questioncategory.py b/phobos/migrations/0049_remove_question_category_unit_questioncategory.py
new file mode 100644
index 00000000..7b5b7f01
--- /dev/null
+++ b/phobos/migrations/0049_remove_question_category_unit_questioncategory.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.2.3 on 2023-11-10 23:36
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('phobos', '0048_remove_question_num_points_alter_courseinfo_course'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='question',
+ name='category',
+ ),
+ migrations.CreateModel(
+ name='Unit',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('subtopic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='units', to='phobos.subtopic')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='QuestionCategory',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('question', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='category', to='phobos.question')),
+ ('subtopic', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='phobos.subtopic')),
+ ('topic', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='phobos.topic')),
+ ('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='phobos.unit')),
+ ],
+ ),
+ ]
diff --git a/phobos/migrations/__pycache__/0049_remove_question_category_unit_questioncategory.cpython-311.pyc b/phobos/migrations/__pycache__/0049_remove_question_category_unit_questioncategory.cpython-311.pyc
new file mode 100644
index 00000000..958ec4cb
Binary files /dev/null and b/phobos/migrations/__pycache__/0049_remove_question_category_unit_questioncategory.cpython-311.pyc differ
diff --git a/phobos/models.py b/phobos/models.py
index bfcef3df..f900e613 100644
--- a/phobos/models.py
+++ b/phobos/models.py
@@ -4,21 +4,7 @@
#from django.contrib.postgres.fields import ArrayField
from django.core.validators import MaxValueValidator, MinValueValidator
import random
-from Dr_R.settings import BERT_TOKENIZER, BERT_MODEL
-import torch
-
-def attention_pooling(hidden_states, attention_mask):
- # Apply attention mask to hidden states
- attention_mask_expanded = attention_mask.unsqueeze(-1).expand(hidden_states.size())
- masked_hidden_states = hidden_states * attention_mask_expanded
-
- # Calculate attention scores and apply softmax
- attention_scores = torch.nn.functional.softmax(masked_hidden_states, dim=1)
-
- # Weighted sum using attention scores
- pooled_output = (masked_hidden_states * attention_scores).sum(dim=1)
- return pooled_output
-
+from .utils import *
class DifficultyChoices(models.TextChoices):
EASY = 'EASY', 'Easy'
MEDIUM = 'MEDIUM', 'Medium'
@@ -149,6 +135,17 @@ class SubTopic(models.Model):
def __str__(self):
return self.name
+
+class Unit(models.Model):
+ """
+ A subtopic may have many units under it. For example, the subtopic Collision, under the topic
+ Mechanics, has units 1D collision, 2D collision, conservation of linear momentum, etc.
+ """
+ subtopic = models.ForeignKey(SubTopic, on_delete=models.CASCADE, related_name='units')
+ name = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name
class Assignment(models.Model):
"""
@@ -196,25 +193,11 @@ class Question(models.Model):
The weight attribute is used to compute a student's grade for an Assignment.
So all the questions in an assignment may have the same weight (uniform distribution),
or different weights based on the instructor's input or automated (based on difficulty)
-
- # TODO: !Important
- Though the category input from the instructor at the time of creation of the question
- will be optional, we may use embeddings to determine the similarity with other questions
- and assign the same category as the closest question.
-
- The vector or matrix representation of the question should be computed at the time of
- creation or later(automatically - it may also be updated as more questions come in).
- That will be used for category assignment described above, and most importantly for
- the search engine.
-
- # Define choices for the topic
-
"""
number = models.CharField(blank=False, null=False, max_length=5)
text = models.TextField(max_length= 2000, null=False, blank=False)
assignment = models.ForeignKey(Assignment, null=True, on_delete=models.CASCADE, \
related_name="questions")
- category = models.CharField(max_length=50, null=True, blank=True)
topic = models.ForeignKey(Topic, on_delete=models.SET_NULL, null=True, blank=True)
sub_topic = models.ForeignKey(SubTopic, on_delete=models.SET_NULL, null=True, blank=True)
parent_question = models.ForeignKey('self', on_delete=models.CASCADE, null=True, \
@@ -241,18 +224,21 @@ def save(self, *args, **kwargs):
settings, created = StructuralQuestionSettings.objects.get_or_create(question=self)
settings.due_date = self.default_due_date()
settings.save()
- if not self.embedding:
- question_tokens = BERT_TOKENIZER.encode(self.text, add_special_tokens=True)
- with torch.no_grad():
- question_tensor = torch.tensor([question_tokens])
- question_attention_mask = (question_tensor != 0).float() # Create attention mask
- question_encoded_output = BERT_MODEL(question_tensor, attention_mask=question_attention_mask)[0]
+ if not self.embedding:
+ # Encode the question text, ensuring the sequence is within the max length limit
+ max_length = 512 # BERT's maximum sequence length
+ question_tokens = BERT_TOKENIZER.encode(self.text, add_special_tokens=True, max_length=max_length, truncation=True, padding='max_length')
- # Apply attention-based pooling to question encoded output
- question_encoded_output_pooled = attention_pooling(question_encoded_output, question_attention_mask)
+ with torch.no_grad():
+ question_tensor = torch.tensor([question_tokens])
+ question_attention_mask = (question_tensor != 0).float() # Create attention mask
+ question_encoded_output = BERT_MODEL(question_tensor, attention_mask=question_attention_mask)[0]
- # Save the encoded output to the question object
- self.embedding = question_encoded_output_pooled.tolist()
+ # Apply attention-based pooling to question encoded output
+ question_encoded_output_pooled = attention_pooling(question_encoded_output, question_attention_mask)
+
+ # Save the encoded output to the question object
+ self.embedding = question_encoded_output_pooled.tolist()
super(Question, self).save(*args, **kwargs)
@@ -266,6 +252,15 @@ def get_num_points(self):
def __str__(self):
return f"Question {self.number} for {self.assignment}"
+class QuestionCategory(models.Model):
+ """
+ Information about the topic, subtopic, and unit of a question
+ """
+ question = models.OneToOneField(Question, on_delete=models.CASCADE, related_name='category')
+ topic = models.ForeignKey(Topic, on_delete=models.SET_NULL, null=True, blank=True)
+ subtopic = models.ForeignKey(SubTopic, on_delete=models.SET_NULL, null=True, blank=True)
+ unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
+
class BaseQuestionSettings(models.Model):
"""
Base settings for a `Question`.
diff --git a/phobos/scripts/topics_subtopics.py b/phobos/scripts/topics_subtopics.py
index 7ee4e743..079f557d 100644
--- a/phobos/scripts/topics_subtopics.py
+++ b/phobos/scripts/topics_subtopics.py
@@ -1,205 +1,300 @@
-from phobos.models import Topic, SubTopic, Subject
+from phobos.models import Topic, SubTopic, Subject, Unit
def run():
- # Define a dictionary with topics and their subtopics
+
+ subjects = ['PHYSICS']
+ # Define a dictionary with topics, subtopics, and units
+ topics_subtopics_units_list = [
+ {
+ 'Mechanics': {
+ 'Kinematics': [
+ 'Motion in One Dimension',
+ 'Motion in Two Dimensions',
+ 'Projectile Motion',
+ 'Uniform Circular Motion',
+ ],
+ 'Newton\'s Laws of Motion': [
+ 'Force and Inertia',
+ 'Applications of Newton\'s Laws',
+ 'Frictional Forces',
+ 'Non-inertial Frames and Pseudo Forces',
+ ],
+ 'Work, Energy, and Power': [
+ 'Work Done by a Constant Force',
+ 'Work Done by a Variable Force',
+ 'Kinetic and Potential Energy',
+ 'Conservation of Energy',
+ 'Power',
+ ],
+ 'Momentum and Collisions': [
+ 'Linear Momentum and Impulse',
+ 'Conservation of Linear Momentum',
+ 'Collisions in One Dimension',
+ 'Collisions in Two Dimensions',
+ 'Center of Mass',
+ ],
+ 'Circular and Rotational Motion': [
+ 'Rotational Kinematics',
+ 'Rotational Dynamics',
+ 'Angular Momentum and Its Conservation',
+ 'Gyroscopic Effects',
+ ],
+ 'Gravitation': [
+ 'Newton\'s Law of Universal Gravitation',
+ 'Gravitational Potential Energy',
+ 'Orbits of Planets and Satellites',
+ 'Gravitational Fields',
+ ],
+ 'Statics of Rigid Bodies': [
+ 'Equilibrium of Rigid Bodies',
+ 'Statics of Structures',
+ 'Centroids and Centers of Gravity',
+ 'Stress and Strain',
+ ],
+ 'Dynamics of Rigid Bodies': [
+ 'Torque and Angular Acceleration',
+ 'Rotational Work and Energy',
+ 'Rotational Collisions',
+ 'Dynamics of Rotational Motion',
+ ],
+ },
+ 'Thermodynamics': {
+ 'Thermal Properties of Matter': [
+ 'Temperature and Heat',
+ 'Thermal Expansion',
+ 'Heat Capacity',
+ 'Phase Transitions',
+ ],
+ 'Thermal Equilibrium and Temperature': [
+ 'Zeroth Law of Thermodynamics',
+ 'Thermometers and Temperature Scales',
+ 'Thermal Contact and Equilibrium',
+ ],
+ 'First Law of Thermodynamics': [
+ 'Internal Energy',
+ 'Heat and Work',
+ 'Thermodynamic Processes',
+ ],
+ 'Second Law of Thermodynamics': [
+ 'Heat Engines',
+ 'Refrigerators and Heat Pumps',
+ 'Entropy',
+ 'Carnot Cycle',
+ ],
+ 'Heat Transfer Methods': [
+ 'Conduction',
+ 'Convection',
+ 'Radiation',
+ ],
+ 'Thermodynamic Processes': [
+ 'Isothermal Process',
+ 'Adiabatic Process',
+ 'Isobaric Process',
+ 'Isochoric Process',
+ ],
+ 'Entropy and Carnot Cycle': [
+ 'Statistical Interpretation of Entropy',
+ 'Entropy Changes and Processes',
+ 'The Carnot Engine',
+ 'Efficiency of Heat Engines',
+ ],
+ 'Heat Engines and Refrigerators': [
+ 'Otto Cycle',
+ 'Diesel Cycle',
+ 'Refrigeration Cycles',
+ ],
+ },
- subjects = ['PHYSICS', 'MATHS', 'COMPUTER_SCIENCE']
+ 'Electricity and Magnetism': {
+ 'Electrostatics': [
+ 'Charge and Coulomb\'s Law',
+ 'Electric Field',
+ 'Electric Potential',
+ 'Capacitors and Dielectrics',
+ ],
+ 'Electric Potential and Capacitance': [
+ 'Potential Energy in Electric Fields',
+ 'Equipotential Surfaces',
+ 'Capacitance',
+ 'Energy Stored in Capacitors',
+ ],
+ 'Current Electricity': [
+ 'Electric Current and Drift Speed',
+ 'Resistance and Ohm\'s Law',
+ 'Electrical Power',
+ 'Direct and Alternating Current Circuits',
+ ],
+ 'Magnetic Effects of Current': [
+ 'Magnetic Fields and Forces',
+ 'Biot-Savart Law',
+ 'Ampère\'s Law',
+ 'Electromagnetic Induction',
+ ],
+ 'Electromagnetic Induction and AC': [
+ 'Faraday\'s Law',
+ 'Lenz\'s Law',
+ 'AC Generators',
+ 'Transformers',
+ ],
+ 'Electromagnetic Waves': [
+ 'Wave Equation',
+ 'Energy in Electromagnetic Waves',
+ 'Propagation of Electromagnetic Waves',
+ 'Polarization of Light',
+ ],
+ 'Maxwell\'s Equations': [
+ 'Gauss\'s Law for Electricity',
+ 'Gauss\'s Law for Magnetism',
+ 'Faraday\'s Law of Induction',
+ 'Ampère-Maxwell Law',
+ ],
+ },
- topics_subtopics_list = [{
- 'Mechanics': [
- 'Kinematics',
- 'Newton\'s Laws of Motion',
- 'Work, Energy, and Power',
- 'Momentum and Collisions',
- 'Circular and Rotational Motion',
- 'Gravitation',
- 'Statics of Rigid Bodies',
- 'Dynamics of Rigid Bodies',
- ],
- 'Thermodynamics': [
- 'Thermal Properties of Matter',
- 'Thermal Equilibrium and Temperature',
- 'First Law of Thermodynamics',
- 'Second Law of Thermodynamics',
- 'Heat Transfer Methods',
- 'Thermodynamic Processes',
- 'Entropy and Carnot Cycle',
- 'Heat Engines and Refrigerators',
- ],
- 'Electricity and Magnetism': [
- 'Electrostatics',
- 'Electric Potential and Capacitance',
- 'Current Electricity',
- 'Magnetic Effects of Current',
- 'Electromagnetic Induction and AC',
- 'Electromagnetic Waves',
- 'Maxwell\'s Equations',
- ],
- 'Waves': [
- 'Wave Basics and Types',
- 'Wave Properties and Behavior',
- 'Superposition and Standing Waves',
- 'Sound Waves',
- 'Doppler Effect',
- 'Resonance',
- ],
- 'Optics': [
- 'Reflection and Refraction',
- 'Lens, Mirrors, and Optical Instruments',
- 'Wave Optics: Interference and Diffraction',
- 'Polarization',
- 'Dispersion and Spectra',
- 'Optical Phenomena in Nature',
- ],
- 'Modern Physics': [
- 'Quantum Physics and Quantum Mechanics',
- 'Dual Nature of Matter and Radiation',
- 'Atomic and Nuclear Physics',
- 'Special and General Relativity',
- 'Particle Physics and Standard Model',
- 'Cosmology and Universe',
- ],
- 'Fluid Mechanics': [
- 'Fluid Properties and Statics',
- 'Fluid Dynamics and Bernoulli\'s Principle',
- 'Viscosity and Laminar Flow',
- 'Surface Tension and Capillarity',
- 'Turbulence and Flow Patterns',
- ],
- },
- {
- 'Algebra': [
- 'Polynomials',
- 'Linear Equations',
- 'Quadratic Equations',
- 'Matrices and Determinants',
- 'Complex Numbers',
- 'Binomial Theorem',
- ],
- 'Calculus': [
- 'Limits and Continuity',
- 'Differential Calculus',
- 'Integral Calculus',
- 'Differential Equations',
- 'Applications of Derivatives',
- 'Applications of Integrals',
- ],
- 'Geometry': [
- 'Coordinate Geometry',
- 'Lines and Angles',
- 'Triangles',
- 'Quadrilaterals and Polygons',
- 'Circles',
- '3D Geometry',
- ],
- 'Trigonometry': [
- 'Trigonometric Identities',
- 'Trigonometric Equations',
- 'Inverse Trigonometry',
- 'Height and Distances',
- ],
- 'Statistics and Probability': [
- 'Mean, Median, Mode',
- 'Variance and Standard Deviation',
- 'Permutations and Combinations',
- 'Probability',
- 'Probability Distributions',
- ],
- 'Vectors': [
- 'Vector Algebra',
- 'Scalar Product',
- 'Vector Product',
- 'Linear Independence',
- 'Applications in Geometry',
- ]
- },
- {
- 'Programming Fundamentals': [
- 'Syntax and Semantics',
- 'Variables and Data Types',
- 'Control Structures',
- 'Loops',
- 'Functions and Methods',
- 'Object-Oriented Programming',
- 'Recursion'
- ],
- 'Data Structures': [
- 'Arrays',
- 'Linked Lists',
- 'Stacks',
- 'Queues',
- 'Trees',
- 'Graphs',
- 'Hash Tables'
- ],
- 'Algorithms': [
- 'Sorting Algorithms',
- 'Searching Algorithms',
- 'Graph Algorithms',
- 'Dynamic Programming',
- 'Greedy Algorithms',
- 'Divide and Conquer',
- ],
- 'Operating Systems': [
- 'Processes and Threads',
- 'Memory Management',
- 'File Systems',
- 'Concurrency',
- 'Scheduling',
- 'I/O Management',
- ],
- 'Database Systems': [
- 'Relational Databases',
- 'SQL',
- 'Normalization',
- 'Transactions and Concurrency Control',
- 'NoSQL Databases',
- 'Database Indexing and Optimization',
- ],
- 'Computer Networks': [
- 'OSI and TCP/IP Models',
- 'Routing and Switching',
- 'Network Protocols',
- 'Wireless Networks',
- 'Security and Cryptography',
- ],
- 'Software Engineering': [
- 'Software Development Life Cycle',
- 'Software Testing',
- 'Software Design Patterns',
- 'Agile Development',
- 'Version Control',
- 'Continuous Integration and Continuous Deployment',
- ],
- 'Web Development': [
- 'HTML, CSS, JavaScript',
- 'Front-end Frameworks',
- 'Back-end Development',
- 'Web Security',
- 'APIs and Web Services',
- 'Responsive Design',
- ],
- 'Artificial Intelligence': [
- 'Machine Learning',
- 'Neural Networks',
- 'Natural Language Processing',
- 'Robotics',
- 'Expert Systems',
- 'Search Algorithms'
- ],
- 'Cybersecurity': [
- 'Network Security',
- 'Cryptography',
- 'Penetration Testing',
- 'Malware Analysis',
- 'Authentication and Authorization',
- 'Security Policies and Procedures',
- ]
- }]
- for index, topics_subtopics in enumerate(topics_subtopics_list):
+ 'Waves': {
+ 'Wave Basics and Types': [
+ 'Transverse and Longitudinal Waves',
+ 'Wave Parameters and Equations',
+ 'Wave Speed on a String',
+ ],
+ 'Wave Properties and Behavior': [
+ 'Reflection and Refraction of Waves',
+ 'Interference and Diffraction',
+ 'Standing Waves and Resonance',
+ ],
+ 'Superposition and Standing Waves': [
+ 'Principle of Superposition',
+ 'Formation of Standing Waves',
+ 'Harmonics and Overtones',
+ ],
+ 'Sound Waves': [
+ 'Characteristics of Sound',
+ 'The Doppler Effect',
+ 'Acoustics',
+ 'Ultrasound and Applications',
+ ],
+ 'Doppler Effect': [
+ 'Doppler Shift for Sound Waves',
+ 'Doppler Shift for Light Waves',
+ 'Applications in Astronomy and Radar',
+ ],
+ 'Resonance': [
+ 'Resonance in Strings and Air Columns',
+ 'Electrical Resonance in Circuits',
+ 'Resonance in Buildings and Bridges',
+ ],
+ },
+ 'Optics': {
+ 'Reflection and Refraction': [
+ 'Laws of Reflection',
+ 'Snell\'s Law',
+ 'Total Internal Reflection',
+ 'Fiber Optics',
+ ],
+ 'Lens, Mirrors, and Optical Instruments': [
+ 'Converging and Diverging Lenses',
+ 'Mirror Equations',
+ 'Microscopes and Telescopes',
+ 'Camera and Optical Sensors',
+ ],
+ 'Wave Optics: Interference and Diffraction': [
+ 'Young\'s Double-Slit Experiment',
+ 'Diffraction Grating',
+ 'X-ray Diffraction',
+ 'Holography',
+ ],
+ 'Polarization': [
+ 'Polarization by Reflection',
+ 'Polarization by Scattering',
+ 'Liquid Crystal Displays',
+ 'Optical Activity in Materials',
+ ],
+ 'Dispersion and Spectra': [
+ 'Prism and Rainbows',
+ 'Spectral Lines and Emission Spectra',
+ 'Absorption Spectra',
+ 'Spectroscopy',
+ ],
+ 'Optical Phenomena in Nature': [
+ 'Mirages',
+ 'Rainbows',
+ 'Auroras',
+ 'The Blue Sky and Sunset',
+ ],
+ },
+ 'Modern Physics': {
+ 'Quantum Physics and Quantum Mechanics': [
+ 'Photoelectric Effect',
+ 'Wave-Particle Duality',
+ 'Quantum Tunneling',
+ 'Quantum Entanglement',
+ ],
+ 'Dual Nature of Matter and Radiation': [
+ 'De Broglie Hypothesis',
+ 'Davisson-Germer Experiment',
+ 'Compton Scattering',
+ ],
+ 'Atomic and Nuclear Physics': [
+ 'Rutherford\'s Atomic Model',
+ 'Nuclear Reactions',
+ 'Radioactivity',
+ 'Nuclear Fission and Fusion',
+ ],
+ 'Special and General Relativity': [
+ 'Time Dilation and Length Contraction',
+ 'Mass-Energy Equivalence',
+ 'Gravitational Time Dilation',
+ 'Black Holes and Event Horizons',
+ ],
+ 'Particle Physics and Standard Model': [
+ 'Elementary Particles',
+ 'Forces and Mediator Particles',
+ 'Accelerators and Detectors',
+ 'Quarks and Leptons',
+ ],
+ 'Cosmology and Universe': [
+ 'Big Bang Theory',
+ 'Cosmic Microwave Background',
+ 'Dark Matter and Dark Energy',
+ 'Expansion of the Universe',
+ ],
+ },
+ 'Fluid Mechanics': {
+ 'Fluid Properties and Statics': [
+ 'Density and Pressure',
+ 'Buoyancy and Archimedes\' Principle',
+ 'Fluids in Motion',
+ 'Bernoulli\'s Equation',
+ ],
+ 'Fluid Dynamics and Bernoulli\'s Principle': [
+ 'Streamline Flow and Turbulence',
+ 'Viscosity and Reynolds Number',
+ 'Flow Rate and Continuity Equation',
+ 'Applications of Bernoulli\'s Principle',
+ ],
+ 'Viscosity and Laminar Flow': [
+ 'Viscous Forces and Poiseuille\'s Law',
+ 'Flow in Pipes and Channels',
+ 'Stokes\' Law and Terminal Velocity',
+ ],
+ 'Surface Tension and Capillarity': [
+ 'Cohesion and Adhesion',
+ 'Drops and Bubbles',
+ 'Capillary Action',
+ 'Detergents and Surfactants',
+ ],
+ 'Turbulence and Flow Patterns': [
+ 'Characterization of Turbulent Flow',
+ 'Vortices and Eddies',
+ 'Aerodynamics and Hydrodynamics',
+ 'Flow Measurement Techniques',
+ ],
+ },
+ },
+ ]
+ for index, topics_subtopics_units in enumerate(topics_subtopics_units_list):
subject, created = Subject.objects.get_or_create(name=subjects[index])
# Create instances of subtopics using nested for loops and the dictionary values
- for topic_name, subtopics in topics_subtopics.items():
+ for topic_name, subtopics_units_dict in topics_subtopics_units.items():
# Check if the topic already exists in the database
topic, created = Topic.objects.get_or_create(name=topic_name, subject=subject)
@@ -207,9 +302,15 @@ def run():
if created:
print(f"Created new topic: {topic_name}")
- for subtopic_name in subtopics:
- SubTopic.objects.create(topic=topic, name=subtopic_name)
+ for subtopic_name in subtopics_units_dict:
+ st, created = SubTopic.objects.get_or_create(topic=topic, name=subtopic_name)
+
+ for unit_name in subtopics_units_dict[subtopic_name]:
+ unit, created = Unit.objects.get_or_create(name=unit_name, subtopic=st)
# Verify the creation of objects
print(Topic.objects.all())
print(SubTopic.objects.all())
+ print(Unit.objects.all())
+
+
diff --git a/phobos/static/phobos/css/style.css b/phobos/static/phobos/css/style.css
index dd43976e..cbe30839 100644
--- a/phobos/static/phobos/css/style.css
+++ b/phobos/static/phobos/css/style.css
@@ -359,6 +359,7 @@ li{
.course-li {
/*font-family: 'Courier New', Courier, monospace;*/
+ display: flex;
font-family:'Nebula', 'Times New Roman', Times, serif;
flex: 1 0 calc(33.33% - 20px); /* Distribute the items equally in three columns */
height: 60vh;
@@ -367,8 +368,18 @@ li{
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
position:relative;
+ overflow: hidden;
+ color:black;
}
+.course-li a {
+ color: inherit;
+ width: 100%;
+}
+.course-li a:hover {
+ text-decoration: none;
+ color: inherit;
+}
/* Assignment displays need less height */
.assignment-li {
height: 25vh;
@@ -376,59 +387,35 @@ li{
div.course-section {
- position: relative;
- height: 70%;
+ max-height: 100%;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
}
/* Set the height of .image-container to 50% of the height of .course-li */
div.image-container {
- position: absolute;
- top: 0;
- left: 0;
- height: 85%;
+ height: 70%;
width: 100%; /* Make the image container full-width within .course-li */
- overflow: hidden; /* Ensure the image doesn't overflow the container */
+ /* overflow: hidden; Ensure the image doesn't overflow the container */
+}
+.c-text {
+ height: 30%;
+ overflow: hidden;
}
/* Style the image to fill the .image-container */
div.image-container img.course-image {
height: 100%;
width: 100%;
- /*max-height: 100%; Ensure the image height fits within the container */
- /*max-width: 100%; Ensure the image width fits within the container */
- object-fit: cover; /* Maintain aspect ratio and cover the container */
- border-radius: 10px 10px 10px 10px; /* Optional: Add border radius to the top corners */
-}
-div.c-text {
- position: absolute;
- top: 87%;
- height: 30%;
-}
-
-.a-text.c-text {
- top: 25%;
-}
-
-.edit-section {
- bottom: 5%;
- position: absolute;
-}
-.edit-btn {
- background-color: rgba(249, 105, 0, 1) !important;
- transition: background-color 0.2s !important;
- color: rgba(0,0,0,0.95) !important;
- font-weight:400 !important;
- font-family: 'Aileron', sans-serif;
-
-}
-.edit-btn:hover {
- background-color: rgba(249, 105, 0, .7) !important;
+ border-radius: 10px;
+ object-fit: cover;
}
-
/* -----------------Style for phobos:create_question.html--------- */
.mp-input-div {
display: flex;
@@ -866,9 +853,6 @@ more than assignment display */
.question-li {
height: 30vh;
}
- .a-text.c-text {
- top: 15%;
- }
.sf-container {
display: flex;
diff --git a/phobos/static/phobos/js/search_generic.js b/phobos/static/phobos/js/search_generic.js
index d14cbf52..718dd702 100644
--- a/phobos/static/phobos/js/search_generic.js
+++ b/phobos/static/phobos/js/search_generic.js
@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
parentDivName = 'course-li'; // most pages will have course-li
}
if(displayProperty == null){
- displayProperty = 'block';
+ displayProperty = 'flex';
} // corresponding to searchField.dataset.name
if(searchField != null){
// Add an event listener to the search field for the 'keydown' event
diff --git a/phobos/templates/phobos/assignment_display.html b/phobos/templates/phobos/assignment_display.html
index c85ee5fe..9dbda264 100644
--- a/phobos/templates/phobos/assignment_display.html
+++ b/phobos/templates/phobos/assignment_display.html
@@ -1,22 +1,18 @@
-
-
-
-
-
{{assignment.name | safe}}
-
Created on {{ assignment.timestamp|date:"m/d/Y" }}
- {% if assignment.assigned_date %}
-
Assigned on {{ assignment.assigned_date|date:"m/d/Y" }}
- {% endif %}
- {% if assignment.due_date %}
-
Due on {{ assignment.due_date|date:"m/d/Y" }}
- {% endif %}
-
-
-
+
+
+
+
{{assignment.name | safe}}
+
Created on {{ assignment.timestamp|date:"m/d/Y" }}
+ {% if assignment.assigned_date %}
+
Assigned on {{ assignment.assigned_date|date:"m/d/Y" }}
+ {% endif %}
+ {% if assignment.due_date %}
+
Due on {{ assignment.due_date|date:"m/d/Y" }}
+ {% endif %}
+
+
+
\ No newline at end of file
diff --git a/phobos/templates/phobos/course_display.html b/phobos/templates/phobos/course_display.html
index c557ff85..9af766f4 100644
--- a/phobos/templates/phobos/course_display.html
+++ b/phobos/templates/phobos/course_display.html
@@ -1,8 +1,21 @@
-
-
-
+
+
+
+ {% if course.image %}
+

+ {% endif %}
+
+
+
{{course.name}}
+
+ {{ course.description }}
+
Created on {{ course.timestamp|date:"m/d/Y" }}
+
+
+
+
-
-
-
-
- {% if course.image %}
-

- {% endif %}
-
-
-
{{course.name}}
-
{% if course.description|length > 90 %}
- {{ course.description|slice:":90" }}...
- {% else %}
- {{ course.description }}
- {% endif %}
-
-
Created on {{ course.timestamp|date:"m/d/Y" }}
-
-
-
-
\ No newline at end of file
diff --git a/phobos/templates/phobos/question_display.html b/phobos/templates/phobos/question_display.html
index 6c76982d..02f8ae75 100644
--- a/phobos/templates/phobos/question_display.html
+++ b/phobos/templates/phobos/question_display.html
@@ -1,24 +1,15 @@
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/phobos/utils.py b/phobos/utils.py
index 17e3ed23..016ba542 100644
--- a/phobos/utils.py
+++ b/phobos/utils.py
@@ -1,4 +1,19 @@
from urllib.parse import urlparse
+from Dr_R.settings import BERT_TOKENIZER, BERT_MODEL
+import torch
+
+def attention_pooling(hidden_states, attention_mask):
+ # Apply attention mask to hidden states
+ attention_mask_expanded = attention_mask.unsqueeze(-1).expand(hidden_states.size())
+ masked_hidden_states = hidden_states * attention_mask_expanded
+
+ # Calculate attention scores and apply softmax
+ attention_scores = torch.nn.functional.softmax(masked_hidden_states, dim=1)
+
+ # Weighted sum using attention scores
+ pooled_output = (masked_hidden_states * attention_scores).sum(dim=1)
+ return pooled_output
+
#--------------HELPER FUNCTIONS--------------------------------#
def replace_links_with_html(text):
# Find all URLs in the input text
diff --git a/phobos/views.py b/phobos/views.py
index 28758efa..6241b495 100644
--- a/phobos/views.py
+++ b/phobos/views.py
@@ -553,7 +553,7 @@ def create_question(request, assignment_id=None, question_nums_types=None):
answer = creation_func(new_question, question_answer,\
answer_unit, answer_preface, vars_dict)
if not vars_dict:
- new_question.answer_type = QuestionChoices.STRUCTURAL_FLOAT_FLOAT
+ new_question.answer_type = QuestionChoices.STRUCTURAL_FLOAT
elif answer_content.startswith('@{') and answer_content.endswith('}@'):
new_question.answer_type = QuestionChoices.STRUCTURAL_VARIABLE_FLOAT
else:
@@ -907,9 +907,9 @@ def modify_question_student_score(request,question_student_id,new_score,course_i
def search_question(request):
if request.method == 'POST':
input_text = request.POST.get('search_question','')
-
# Tokenize and encode the input text
- input_tokens = BERT_TOKENIZER.encode(input_text, add_special_tokens=True)
+ max_length = 512 # BERT's maximum sequence length
+ input_tokens = BERT_TOKENIZER.encode(input_text, add_special_tokens=True, max_length=max_length, truncation=True, padding='max_length')
with torch.no_grad():
input_tensor = torch.tensor([input_tokens])
attention_mask = (input_tensor != 0).float() # Create attention mask