Tuesday, October 27, 2026

What is RREF AND MATRIX TRANSFORMATIONS

 

1. What is RREF?

RREF stands for Reduced Row Echelon Form.

A matrix is in RREF when its entries follow four specific rules. These rules make the matrix easier to interpret and help us solve systems of linear equations.

First, remind students that a matrix is a rectangular arrangement of numbers in rows and columns.

For example:


Explain that we must examine the matrix row by row and column by column to decide whether it is in RREF.

2. Why do we need RREF?

Connect this topic to the previous lesson on systems of linear equations.


RREF helps us organize equations systematically, identify leading variables, and determine whether a system has a unique solution, no solution, or infinitely many solutions.

Important teaching point: The matrix above is in row echelon form, but it is not in RREF because the leading 1 in column 3 has nonzero entries above it.

This distinction is central to the slide.

3. How to teach the four conditions

Teach one rule at a time. Ask students to check the rule against the examples at the bottom of the slide.

Rule 1: All zero rows must be at the bottom

A zero row contains only zeros.

Example:


This condition is satisfied because the row of zeros is at the bottom.

But this matrix violates the condition:


The zero row appears before a nonzero row.

Ask students: Can a matrix have no zero rows?

Yes. This rule applies only if zero rows exist.

Rule 2: The first nonzero entry in each nonzero row must be 1

This first nonzero entry is called the leading 1 or pivot 1.

Example:


Each nonzero row begins with a leading 1.

Compare it with:


The first row begins with 2, not 1, so the matrix is not in RREF.

Rule 3: Each leading 1 must be to the right of the leading 1 in the row above

This creates a staircase pattern.


The second row's leading 1 is to the left of the first row's leading 1.

Ask students: Must the leading 1 in the first row be in column 1?

No. It can appear in a later column, provided the staircase pattern is maintained.

Rule 4: Each leading 1 must be the only nonzero entry in its column

This is the rule that distinguishes reduced row echelon form from ordinary row echelon form.


This is not in RREF because the pivot in column 2 has a nonzero entry (2) above it.

Important: Non-pivot columns may contain nonzero entries. Only the columns containing leading 1s must have zeros everywhere else.

4. Explain the three examples on your slide

This is a useful activity because the slide already provides three matrices.

Example 1


RREF
  • Leading 1s occur in columns 1, 2, and 3.

  • Each pivot column contains zeros everywhere else.

  • The pivots move to the right.

  • There are no zero rows, so the zero-row condition is satisfied.

Example 2



RREF
  • Leading 1s occur in columns 1, 2, and 4.

  • The pivot columns have zeros in all other positions.

  • The zero row is at the bottom.

  • Column 3 contains 7 and 4, which is allowed because it is not a pivot column.

Example 3



RREF
  • Leading 1s occur in columns 2, 4, and 6.

  • Their columns contain zeros everywhere else.

  • The pivots form the correct staircase.

  • The zero row is at the bottom.

5. RREF versus row echelon form

Students commonly confuse these two forms. Explain that RREF has all the requirements of row echelon form plus the additional requirement that each pivot column has zeros above and below its leading 1.

Property

Row echelon form (REF)

Reduced row echelon form (RREF)

Zero rows at bottom

Yes

Yes

Leading entries are 1

Not necessarily

Yes

Pivots move right down the rows

Yes

Yes

Zeros below each pivot

Yes

Yes

Zeros above each pivot

Not required

Yes

Pivot is the only nonzero entry in its column

Not required

Yes

Explanation of the Three Matrix Transformations

These three examples demonstrate an important concept in your Linear Algebra syllabus: how to convert a matrix into Reduced Row Echelon Form (RREF).

The main idea is to make each leading 1 the only nonzero entry in its column, while maintaining the correct row order.



Example 1: Eliminate the 3 above a pivot

Left matrix → Right matrix



What is happening?

The leading 1 in row 2, column 2 is already in the correct position. However, there is a 3 above it in row 1, column 2.

For RREF, the pivot column must contain zeros everywhere except at the leading 1.

How do we remove the 3?

Perform the row operation:



Here, \(R_1\) means row 1 and \(R_2\) means row 2.

Calculate the new first row:



The other rows remain unchanged.

Point: Subtracting three times row 2 from row 1 eliminates the 3 without changing the leading 1 in row 1.

Example 2: Move the pivot row upward



What is happening?

The leading 1 in row 3 is in column 2, but row 2 already has its leading 1 in column 2.

This violates the staircase condition: the leading 1 of a lower row must be to the right of the leading 1 of the row above.

How do we correct it?

Subtract row 2 from row 3:



Calculate:

 [0,1,4,1]-[0,1,4,0] =[0,0,0,1] 

Therefore, the new third row becomes:

[0,0,0,1] 

The leading 1 now appears in column 4, which is to the right of the pivots in columns 1 and 2.

Point: When two rows have their leading 1s in the same column, use row operations to eliminate the lower leading entry and create the correct staircase pattern.

Example 3: Move a nonzero row above a zero row

The left matrix is:



The third row is a zero row, but the fourth row is nonzero.

What is wrong?

A matrix in RREF must have all zero rows at the bottom. The nonzero fourth row must therefore be moved above the zero row.

How do we correct it?

Interchange rows 3 and 4:



The result is:



Now the nonzero row is above the zero row, and the leading 1s occur in columns 1, 4, and 6. The pivot columns contain zeros everywhere else.

Point: A row interchange changes the order of the rows without changing the solution set of the corresponding system.

Summary for students

Example

Problem

Row operation

1

Nonzero entry above a pivot

\(R_1\rightarrow R_1-3R_2\)

2

Duplicate pivot column in consecutive rows

\(R_3\rightarrow R_3-R_2\)

3

Zero row above a nonzero row

\(R_3\leftrightarrow R_4\)

Remember the three basic elementary row operations:

  1. Row interchange: Swap two rows, \(R_i\leftrightarrow R_j\).

  2. Row scaling: Multiply a row by a nonzero constant, \(R_i\rightarrow kR_i\).

  3. Row replacement: Add a multiple of one row to another, \(R_i\rightarrow R_i+kR_j\).

These operations are used to transform a matrix into RREF without changing the solution set of its system of linear equations

MATRIX - BASIC OPERATIONS

 

1: Matrices and Basic Operations

What: A matrix is a rectangular array of numbers with m rows and n columns (an m×n matrix). The entry in row i, column j is written aᵢⱼ.

Why: Matrices organize data and represent linear transformations (rotations, scaling) and systems of equations compactly. Computer graphics, machine learning, engineering, and economics all rely on them.

How:

  • Addition/subtraction: same size only, entry by entry.
  • Scalar multiplication: multiply every entry.
  • Matrix multiplication: (AB)ᵢⱼ = row i of A · column j of B. This needs columns of A = rows of B.
  • Transpose Aᵀ: swap rows and columns.

Worked example:
A = [[1, 2], [3, 4]], B = [[0, 1], [1, 0]]

AB = [[1·0+2·1, 1·1+2·0], [3·0+4·1, 3·1+4·0]] = [[2, 1], [4, 3]]
BA = [[3, 4], [1, 2]]

So AB ≠ BA. Matrix multiplication is generally not commutative. Students must understand this early.

Common mistakes: multiplying entry by entry, ignoring size compatibility, assuming AB = BA.

Examples of the Four Basic Operations

1. Addition and Subtraction (same size only)

Add or subtract the entries in the same position.

A = [1  2]     B = [5  6]
    [3  4]         [7  8]

A + B = [1+5  2+6]  =  [ 6   8]
        [3+7  4+8]     [10  12]

A − B = [1−5  2−6]  =  [−4  −4]
        [3−7  4−8]     [−4  −4]

Not allowed: a 2×2 matrix plus a 2×3 matrix. The sizes differ, so some entries have no partner. Show this to your student as a “defined or undefined?” question.


2. Scalar Multiplication

Multiply every entry by the number.

3A = 3 [1  2]  =  [3·1  3·2]  =  [3   6]
       [3  4]     [3·3  3·4]     [9  12]

−2B = −2 [5  6]  =  [−10  −12]
         [7  8]     [−14  −16]

A useful extension: 3A − 2B combines both operations, entry by entry.


3. Matrix Multiplication

Rule: the entry in row i, column j of AB is (row i of A) · (column j of B). Multiply matching entries, then add.

Size check first: (m×n)(n×p) gives an m×p result. The two inner numbers must match.

A = [1  2  3]  (2×3)      B = [ 7   8]  (3×2)
    [4  5  6]                 [ 9  10]
                              [11  12]

Inner numbers are 3 and 3, so AB is defined, and the result is 2×2.

Row 1 · Col 1:  1·7 + 2·9 + 3·11  =  7 + 18 + 33  =  58
Row 1 · Col 2:  1·8 + 2·10 + 3·12 =  8 + 20 + 36  =  64
Row 2 · Col 1:  4·7 + 5·9 + 6·11  = 28 + 45 + 66  = 139
Row 2 · Col 2:  4·8 + 5·10 + 6·12 = 32 + 50 + 72  = 154

AB = [ 58   64]
     [139  154]

Now try BA. This is (3×2)(2×3), which is also defined, but the result is 3×3:

BA = [39  54   69]
     [49  68   87]
     [59  82  105]

This is a powerful lesson: AB and BA can even have different sizes, so they cannot be equal.

Not allowed: (2×3)(2×3). The inner numbers are 3 and 2, which do not match.


4. Transpose

Rows become columns. Row 1 becomes column 1, row 2 becomes column 2, and so on.

C = [1  2  3]   (2×3)
    [4  5  6]

Cᵀ = [1  4]     (3×2)
     [2  5]
     [3  6]

Notice the size flips from 2×3 to 3×2. For a square matrix:

D = [1  2]     Dᵀ = [1  3]
    [3  4]          [2  4]

The diagonal stays fixed, and the other entries swap across it.

Rule to teach next: (AB)ᵀ = BᵀAᵀ. The order reverses, just like with inverses.

Saturday, October 24, 2026

JWT WITH SIMPLEJWT- djangorestframework-simplejwt

 

JWT with djangorestframework-simplejwt

What changes compared with the PyJWT version

Before (PyJWT)Now (simplejwt)
jwt_utils.py: you wrote create/decode by handDeleted. simplejwt builds and verifies tokens
authentication.py: you parsed the header, handled expiry and errorsA short subclass that only says how to find your User
One access tokenAccess token and refresh token, with a ready-made expiry system
Manual expiry settingSIMPLE_JWT settings dict

Roles, permissions, serializers and ViewSets stay almost the same.

Why a subclass is needed: simplejwt assumes Django’s user model (AUTH_USER_MODEL). Your User is a separate table (UserId, Username, Password, RoleId), and your project already has migrations and an admin superuser, so switching AUTH_USER_MODEL now would break migrations. The subclass keeps your model and still uses simplejwt for everything else.

Step 0: Install

bash
pip install djangorestframework-simplejwt

You can remove PyJWT usage. simplejwt installs its own dependency.

Step 1: Models (models.py, unchanged)

What: Role and User. Why: each user has one role, and passwords are stored only as hashes.

python
from django.contrib.auth.hashers import make_password, check_password

class Role(models.Model):
    RoleId = models.AutoField(primary_key=True)
    RoleName = models.CharField(max_length=30, unique=True)   # Admin, Manager, Employee

    def __str__(self):
        return self.RoleName


class User(models.Model):
    UserId = models.AutoField(primary_key=True)
    Username = models.CharField(max_length=50, unique=True)
    Password = models.CharField(max_length=255)               # stores the HASH
    RoleId = models.ForeignKey(Role, on_delete=models.PROTECT)

    def set_password(self, raw_password):
        self.Password = make_password(raw_password)

    def check_password(self, raw_password):
        return check_password(raw_password, self.Password)

    # DRF checks request.user.is_authenticated, so our custom model must provide it
    @property
    def is_authenticated(self):
        return True

    def __str__(self):
        return self.Username
bash
python manage.py makemigrations
python manage.py migrate

If you already did this earlier, skip it.

Step 2: Create roles and the first admin (shell, unchanged)

python
from employees.models import Role, User

admin_role = Role.objects.create(RoleName='Admin')
Role.objects.create(RoleName='Manager')
Role.objects.create(RoleName='Employee')

u = User(Username='admin', RoleId=admin_role)
u.set_password('Admin@123')      # hashes the password
u.save()

Step 3: Settings (settings.py)

What: Tell simplejwt how long tokens live and which field identifies your user.
Why: USER_ID_FIELD defaults to id, but your primary key is UserId. Without this, token creation fails with an AttributeError.

python
from datetime import timedelta

REST_FRAMEWORK = {
    # Every request is checked by our subclass (Step 4)
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'employees.authentication.CustomJWTAuthentication',
    ],
    # Secure by default: login is required unless an endpoint opts out
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),   # short-lived: used on every request
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),      # long-lived: only used to get a new access token
    'USER_ID_FIELD': 'UserId',                        # your primary key column name
    'USER_ID_CLAIM': 'user_id',                       # name stored inside the token
    'AUTH_HEADER_TYPES': ('Bearer',),                 # Authorization: Bearer <token>
}

simplejwt signs tokens with your SECRET_KEY by default. You don’t need to add it to INSTALLED_APPS.

Step 4: Authentication class (employees/authentication.py)

What: Override one method, get_user.
Why: simplejwt already reads the header, checks the signature, and checks expiry. The only part it can’t know is where your users live.

python
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed
from rest_framework_simplejwt.settings import api_settings

from .models import User


class CustomJWTAuthentication(JWTAuthentication):

    # Called after the token is verified. Must return the user object,
    # which DRF puts in request.user.
    def get_user(self, validated_token):
        try:
            user_id = validated_token[api_settings.USER_ID_CLAIM]
        except KeyError:
            raise InvalidToken('Token has no user identification.')

        try:
            # select_related loads the role in the same query
            # (a deleted user or changed role takes effect immediately)
            return User.objects.select_related('RoleId').get(pk=user_id)
        except User.DoesNotExist:
            raise AuthenticationFailed('User not found.', code='user_not_found')

Compare with the 40-line PyJWT version. Header parsing, 401 responses, and expired/invalid token messages are all inherited.

Step 5: Role-based permissions (employees/permissions.py, unchanged)

What: Permission classes that check the user’s role name.
Why: simplejwt handles authentication (who are you). Roles are your own rule, so authorization stays custom.

python
from rest_framework.permissions import BasePermission, IsAuthenticated


def role_required(*allowed_roles):
    """Builds a permission class that allows only the given role names."""

    class RolePermission(BasePermission):
        message = f'Access denied. Required role: {", ".join(allowed_roles)}.'

        def has_permission(self, request, view):
            user = request.user
            return bool(
                user
                and user.is_authenticated
                and user.RoleId.RoleName in allowed_roles
            )

    return RolePermission


# Any logged-in user can READ; only listed roles can WRITE
class RoleBasedWriteMixin:
    write_roles = ('Admin',)             # override in each ViewSet

    def get_permissions(self):
        if self.action in ('list', 'retrieve'):
            return [IsAuthenticated()]
        return [role_required(*self.write_roles)()]

Step 6: Serializers (serializers.py, unchanged)

python
from .models import User

class LoginSerializer(serializers.Serializer):
    Username = serializers.CharField()
    Password = serializers.CharField(write_only=True)    # accepted, never returned


class RegisterSerializer(serializers.ModelSerializer):
    Password = serializers.CharField(write_only=True, min_length=6)

    class Meta:
        model = User
        fields = ['UserId', 'Username', 'Password', 'RoleId']

    def create(self, validated_data):
        raw_password = validated_data.pop('Password')
        user = User(**validated_data)
        user.set_password(raw_password)      # hash, never save the raw value
        user.save()
        return user

Step 7: Views (views.py)

Imports:

python
from rest_framework import viewsets, status, filters
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
from rest_framework_simplejwt.settings import api_settings

from .models import Department, Employee, Project, User
from .serializers import (
    DepartmentSerializer, EmployeeSerializer, ProjectSerializer,
    LoginSerializer, RegisterSerializer,
)
from .permissions import role_required, RoleBasedWriteMixin

Login (public)

What: Check the password, then let simplejwt create the tokens.
Why: RefreshToken.for_user(user) reads user.UserId (from USER_ID_FIELD) and builds a signed token. Claims you add to the refresh token are copied into the access token, so the client can read the role without an extra call.

python
class LoginView(APIView):
    authentication_classes = []          # nobody has a token yet
    permission_classes = [AllowAny]      # public endpoint

    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        username = serializer.validated_data['Username']
        password = serializer.validated_data['Password']

        try:
            user = User.objects.select_related('RoleId').get(Username=username)
        except User.DoesNotExist:
            user = None

        # Same message for "no such user" and "wrong password",
        # so attackers can't discover which usernames exist.
        if user is None or not user.check_password(password):
            return Response({'detail': 'Invalid username or password.'},
                            status=status.HTTP_401_UNAUTHORIZED)

        refresh = RefreshToken.for_user(user)       # creates the token pair
        # Custom claims (readable by anyone, so never put secrets here)
        refresh['username'] = user.Username
        refresh['role_id'] = user.RoleId.RoleId
        refresh['role_name'] = user.RoleId.RoleName

        return Response({
            'roleid': user.RoleId.RoleId,
            'username': user.Username,
            'token': str(refresh.access_token),     # send this with every request
            'refresh': str(refresh),                # keep this to get a new access token
        })

Refresh (public)

What: Exchange a refresh token for a new access token.
Why: The access token expires quickly (60 minutes). This lets the user stay logged in without typing the password again.

simplejwt’s built-in TokenRefreshView looks the user up in Django’s user table, which doesn’t contain your users. So this small view replaces it.

python
class RefreshView(APIView):
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        try:
            refresh = RefreshToken(request.data.get('refresh'))   # verifies signature and expiry
        except TokenError as e:
            raise InvalidToken(e.args[0])

        # Make sure the user still exists
        if not User.objects.filter(pk=refresh[api_settings.USER_ID_CLAIM]).exists():
            return Response({'detail': 'User not found.'},
                            status=status.HTTP_401_UNAUTHORIZED)

        return Response({'token': str(refresh.access_token)})

Register (Admin only)

python
class RegisterView(APIView):
    # Without this, anyone could create an Admin account
    permission_classes = [role_required('Admin')]

    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)

Protect your ViewSets

python
class DepartmentViewSet(RoleBasedWriteMixin, viewsets.ModelViewSet):
    write_roles = ('Admin',)
    queryset = Department.objects.all()
    serializer_class = DepartmentSerializer


class EmployeeViewSet(RoleBasedWriteMixin, viewsets.ModelViewSet):
    write_roles = ('Admin', 'Manager')
    queryset = Employee.objects.select_related('DepartmentId').prefetch_related('projects')
    serializer_class = EmployeeSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ['Contact', 'Designation']


class ProjectViewSet(RoleBasedWriteMixin, viewsets.ModelViewSet):
    write_roles = ('Admin', 'Manager')
    queryset = Project.objects.prefetch_related('Employees')
    serializer_class = ProjectSerializer

Step 8: URLs (employees/urls.py)

python
from .views import (DepartmentViewSet, EmployeeViewSet, ProjectViewSet,
                    LoginView, RefreshView, RegisterView)

urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),
    path('token/refresh/', RefreshView.as_view(), name='token_refresh'),
    path('register/', RegisterView.as_view(), name='register'),
    path('', include(router.urls)),     # keep the router include last
]

Step 9: Test in Postman

1. Login: POST http://127.0.0.1:8000/api/login/

json
{"Username": "admin", "Password": "Admin@123"}

Response:

json
{
  "roleid": 1,
  "username": "admin",
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh": "eyJhbGciOiJIUzI1NiIs..."
}

2. Call a protected API: Authorization tab, type Bearer Token, paste token.

3. Refresh: POST /api/token/refresh/ with {"refresh": "<refresh value>"} returns a new token.

RequestResult
GET /api/employees/ without a token401
GET /api/employees/ with an expired or bad token401 with a message
GET /api/employees/?search=dev as Employee role200
POST /api/employees/ as Employee role403
POST /api/employees/ as Manager or Admin201
DELETE /api/departments/1/ as Manager403
POST /api/register/ as non-Admin403
POST /api/login/ with a wrong password401

Tip: paste the access token at jwt.io to see user_id, username, role_id, role_name, exp. It also proves the payload is readable, so never put passwords in it.

Creating users directly in MySQL FOR JWT

 

Creating users directly in MySQL

Yes, it works, with one catch: the Password column must contain a Django password hash, not the plain password. Your check_password() compares the typed password against a hash. If you insert Admin@123 as plain text, login always fails with “Invalid username or password.”

Step 1: Generate a hash

Run this in the Django shell, which only creates the hash and saves nothing:

bash
python manage.py shell
python
from django.contrib.auth.hashers import make_password
print(make_password('Admin@123'))

The output looks like this (yours will differ, because each hash has a random salt):

pbkdf2_sha256$870000$k3J9xQ2mP1aB$Zr8f0...long-string...=

Copy the whole line.

Step 2: Check the table and column names

sql
SHOW TABLES;
DESCRIBE employees_user;

Django names tables <app>_<model>, so yours should be employees_role and employees_user. The foreign key column is RoleId_id, because your field is called RoleId and Django adds _id.

Step 3: Insert the data

Roles first, because users reference them:

sql
INSERT INTO employees_role (RoleName) VALUES ('Admin'), ('Manager'), ('Employee');
SELECT * FROM employees_role;     -- note the RoleId values (usually 1, 2, 3)

Then the user, pasting your own hash:

sql
INSERT INTO employees_user (Username, Password, RoleId_id)
VALUES ('admin',
        'pbkdf2_sha256$870000$k3J9xQ2mP1aB$Zr8f0...paste-your-full-hash...=',
        1);                       -- 1 = the RoleId of Admin

Keep the hash inside single quotes. It contains $, which is fine in MySQL but can be mangled if you paste it into a bash command line.

Step 4: Test login

POST http://127.0.0.1:8000/api/login/
{"Username": "admin", "Password": "Admin@123"}

You should get roleid, username, token and refresh.

Common problems

SymptomCause
Always “Invalid username or password”Plain text stored instead of a hash, or the hash was cut off when pasting
Cannot add or update a child row: a foreign key constraint failsThe RoleId_id value doesn’t exist in employees_role
Unknown column 'RoleId'Use RoleId_id in the users table
Table doesn't existRun SHOW TABLES; and use the real names
Duplicate entryThe username is already taken (Username is unique)

Notes

  1. One hash, many users is fine. If several test users should share a password, reuse the same hash. Each real user should have their own.
  2. Don’t store plain text and “fix it later.” Passwords stored as plain text are a real security risk, and your login will never accept them anyway.
  3. Use SQL only for seeding test data. For normal user creation, use the /api/register/ endpoint (Admin only), which hashes the password for you.
  4. Don’t create users in the Django admin unless you register a custom form. The default admin form would save the typed password as plain text, which fails login for the same reason.