Mainly Tech projects on Python and Electronic Design Automation.

Wednesday, January 01, 2025

Me, Chatgpt, copilot, gemini, and google search classify quadrilaterals

 (Best viewed on a larger than phone screen)

I decided to use AI more in coding, so found some free tools and incorporated them into my coding experience.

My initial goal

Someone on Linkedin posted their classification of types of quadrilaterals with a view to how they would implement each of them using OO principles. I commented on it, but was more interested in creating a program to state what type of quadrilateral it is given.

Starting off

I won't go through all the many questions I put to the AI's but you ask something of the AI, do background searches on results, revise a question, think of implementation, revise the questions to the AI again, ....

Early on I thought the best way to input a quadrilateral would be by entering four ordered points that traced their way around the periphery of the shape so after chatgpt had given a different definition of a quadrilateral I asked:

Is that the same as defining a quadrilateral as "A closed figure constructed from tracing four ordered, unequal, noncollinear points closing by tracing from the last to the first point"

 Because their were crucial occasions were lengths were computed and compared as floating point values I got the AI to align all input points onto a grid. After a while, I had to weigh how precise I would need to be to make specific changes to parts of the source in the AI, (and keeping track of those changes), versus just fixing my copy of some function and then just using select parts of AI generated code - with edits if it was from a different AI or something from a search.

Testing

I was using Vscode as my Python editor, I was not writing a Jupyter notebook, but I started using 

# %%

comments to split my source into cells that I could easily run/rerun in its interactive and graphical interpreter.

I needed some test data so, with the aid of image searches for types of quadrilaterals, I defined my type of quadrilaterals, and stated what features they have as:

test_cases = [  # ([points], "classification", "description")
    ([(1, 1), (7, 3), (6, 6), (4, 9)], "Convex", "Non crossing, all angles <180"),
    ([(1, 1), (7, 1), (7, 4), (1, 4)], "Rectangle1", "Non-crossing, all ang == 90, opp sides =="),
    ([(1, 1), (1, 7), (4, 7), (4, 1)], "Rectangle2", "Non-crossing, all ang == 90, all sides =="),
    ([(0, 3), (3, 0), (9, 6), (6, 9)], "Rectangle3", "Non-crossing, all ang == 90, opp sides ==, rotated"),
    ([(1, 1), (7, 1), (7, 7), (1, 7)], "Square1", "Non-crossing, all ang == 90, opp sides =="),
    ([(1, 5), (4, 1), (8, 4), (5, 8)], "Square2", "Non-crossing, all ang == 90, all sides ==, rotated"),
    ([(1, 1), (6, 1), (9, 5), (4, 5)], "Rhombus", "Non-crossing, ang !=90, opp sides || and all sides =="),
    ([(1, 1), (6, 1), (9, 10), (4, 10)], "Parallelogram", "Non-crossing, opp sides || and opp sides =="),
    ([(1, 1), (7, 1), (5, 5), (2, 5)], "Trapezium1", "Non-crossing, at least one set of opposite || sides"),
    ([(1, 1), (5, 3), (5, 6), (1, 7)], "Trapezium2", "Non-crossing, at least one set of opposite || sides"),
    ([(1, 4), (5, 1), (7, 4), (5, 7)], "Kite1", "Non crossing, No interior ang >180, two non== sets of == lines that touch"),
    ([(4, 1), (1, 5), (4, 11), (7, 5)], "Kite2", "Non crossing, No interior ang >180, two non== sets of == lines that touch"),
    ([(1, 1), (5, 5), (7, 4), (4, 8)], "Reflex", "Non crossing, one interior ang >180"),
    ([(1, 4), (7, 1), (4, 4), (7, 7)], "Dart1", "Non crossing, one interior ang >180, two non== sets of == lines that touch"),
    ([(4, 1), (1, 7), (4, 4), (7, 7)], "Dart2", "Non crossing, one interior ang >180, two non== sets of == lines that touch"),
    ([(1, 1), (6, 6), (7, 2), (3, 9)], "Crossed", "Crossing"),
    ([(1, 1), (7, 7), (7, 3), (1, 9)], "Truss1", "Crossing, crossing lines ==, none-crossing lines ||"),
    ([(1, 1), (7, 7), (3, 7), (9, 1)], "Truss2", "Crossing, crossing lines ==, none-crossing lines ||"),
    ([(1, 1), (7, 9), (7, 1), (1, 9)], "Bowtie1", "Crossing, crossing lines ==, none-crossing lines || and =="),
    ([(1, 1), (9, 7), (1, 7), (9, 1)], "Bowtie2", "Crossing, crossing lines ==, none-crossing lines || and =="),
    ([(0, 3), (9, 6), (6, 9), (3, 0)], "Bowtie3", "Crossing, crossing lines ==, none-crossing lines || and ==, rotated"),
]

It took some time working out a few of the points necessary as the AI's "can't count" very well so most were done by my hand calculations after giving up on AI help for this. (One gets tired of the confident but wrong replies).

You may not see the truss type of quadrilateral elsewhere, Bowties, to me always have that extra symmetry, but I needed a name for the category so chose truss.

Somewhere, there is a cell defining a function that visualises all the quadrilaterals. I started with chatgpt's help in starting function visualise_test_cases then modified it by hand, as necessary. The picture at the start of the blog is the final matplotlib output. Note: I used several versions of Python in development and some did not have matplotlib so used # %% cell shenanigans, and if 0: blocks so that the matplotlib stuff only runs in the vscode interpreter).

With testing, and debugging came another lot of AI and google searches, and AI describing some of the algorithms used. A google search lead to a StackOverflow q&A's where the second most used answer for finding the angles between two lines used atan2, and with an explanation of its benefits. I decided to use it and asked chatgpt to do this:

in _angle() is it better to use atan2?

 That was most of what was needed to update def _angle() to that shown. Because float angles are also being compared, I wrote _round_angle myself and applied it appropriately.

_classify becomes _categorise 

I did not like the first, "default", method of classification from chatgpt, which was refined as function _classify which returned only one type of quadrilateral for the given points, and was  a bit untidy in how it got there.. I asked several questions to get chatgpt to first calculate properties of  the quadrilateral, and refined it to form function _categorise. Putting my tests through it I got all the properties for my named quadrilaterals and pared them and reordered them to form dict quad2features. If the quadrilaterals features are a superset of those shown in quad2features then the quadrilateral is of the type in the key.

OK output

I started to get output that looked correct:


Convex
Non crossing, all angles <180 [(1, 1), (7, 3), (6, 6), (4, 9)] QuadrilateralClassifier(points).classify() =['Convex'] Rectangle1 Non-crossing, all ang == 90, opp sides == [(1, 1), (7, 1), (7, 4), (1, 4)] QuadrilateralClassifier(points).classify() =['Convex', 'Parallelogram', 'Rectangle'] Rectangle2 Non-crossing, all ang == 90, all sides == [(1, 1), (1, 7), (4, 7), (4, 1)] QuadrilateralClassifier(points).classify() =['Convex', 'Parallelogram', 'Rectangle'] Rectangle3 Non-crossing, all ang == 90, opp sides ==, rotated [(0, 3), (3, 0), (9, 6), (6, 9)] QuadrilateralClassifier(points).classify() =['Convex', 'Parallelogram', 'Rectangle'] Square1 Non-crossing, all ang == 90, opp sides == [(1, 1), (7, 1), (7, 7), (1, 7)] QuadrilateralClassifier(points).classify() =['Convex', 'Rhombus', 'Square'] Square2 Non-crossing, all ang == 90, all sides ==, rotated [(1, 5), (4, 1), (8, 4), (5, 8)] QuadrilateralClassifier(points).classify() =['Convex', 'Rhombus', 'Square'] Rhombus Non-crossing, ang !=90, opp sides || and all sides == [(1, 1), (6, 1), (9, 5), (4, 5)] QuadrilateralClassifier(points).classify() =['Convex', 'Rhombus'] Parallelogram Non-crossing, opp sides || and opp sides == [(1, 1), (6, 1), (9, 10), (4, 10)] QuadrilateralClassifier(points).classify() =['Convex', 'Parallelogram'] Trapezium1 Non-crossing, at least one set of opposite || sides [(1, 1), (7, 1), (5, 5), (2, 5)] QuadrilateralClassifier(points).classify() =['Convex', 'Trapezium'] Trapezium2 Non-crossing, at least one set of opposite || sides [(1, 1), (5, 3), (5, 6), (1, 7)] QuadrilateralClassifier(points).classify() =['Convex', 'Trapezium'] Kite1 Non crossing, No interior ang >180, two non== sets of == lines that touch [(1, 4), (5, 1), (7, 4), (5, 7)] QuadrilateralClassifier(points).classify() =['Convex', 'Kite'] Kite2 Non crossing, No interior ang >180, two non== sets of == lines that touch [(4, 1), (1, 5), (4, 11), (7, 5)] QuadrilateralClassifier(points).classify() =['Convex', 'Kite'] Reflex Non crossing, one interior ang >180 [(1, 1), (5, 5), (7, 4), (4, 8)] QuadrilateralClassifier(points).classify() =['Reflex'] Dart1 Non crossing, one interior ang >180, two non== sets of == lines that touch [(1, 4), (7, 1), (4, 4), (7, 7)] QuadrilateralClassifier(points).classify() =['Reflex', 'Dart'] Dart2 Non crossing, one interior ang >180, two non== sets of == lines that touch [(4, 1), (1, 7), (4, 4), (7, 7)] QuadrilateralClassifier(points).classify() =['Reflex', 'Dart'] Crossed Crossing [(1, 1), (6, 6), (7, 2), (3, 9)] QuadrilateralClassifier(points).classify() =['Reflex', 'Crossed'] Truss1 Crossing, crossing lines ==, none-crossing lines || [(1, 1), (7, 7), (7, 3), (1, 9)] QuadrilateralClassifier(points).classify() =['Reflex', 'Crossed', 'Truss'] Truss2 Crossing, crossing lines ==, none-crossing lines || [(1, 1), (7, 7), (3, 7), (9, 1)] QuadrilateralClassifier(points).classify() =['Reflex', 'Crossed', 'Truss'] Bowtie1 Crossing, crossing lines ==, none-crossing lines || and == [(1, 1), (7, 9), (7, 1), (1, 9)] QuadrilateralClassifier(points).classify() =['Reflex', 'Crossed', 'Truss', 'Bowtie'] Bowtie2 Crossing, crossing lines ==, none-crossing lines || and == [(1, 1), (9, 7), (1, 7), (9, 1)] QuadrilateralClassifier(points).classify() =['Reflex', 'Crossed', 'Truss', 'Bowtie'] Bowtie3 Crossing, crossing lines ==, none-crossing lines || and ==, rotated [(0, 3), (9, 6), (6, 9), (3, 0)] QuadrilateralClassifier(points).classify() =['Reflex', 'Crossed', 'Truss', 'Bowtie']

The code

Remember, my goal was to learn more about coding with added AI assistance - not to create production ready code.

#!/bin/env python3.11

# %%
"""
Quadrilateral taxonomy

Classification of quadrilaterals defined by a closed figure constructed by
tracing four ordered, distinct, and noncollinear points, connecting consecutive
oints with straight lines, and closing the shape by connecting the last point
to the first.

"""

import math

class QuadrilateralClassifier:

    # Features of quadrilaterals
    quad2features = {
        'Convex':        {'Convex',},
        'Kite':          {'Convex', 'No Parallel Sides', 'Two Touching Equal Sides'},
        'Trapezium':     {'Convex', 'One Pair of Parallel Sides'},
        'Parallelogram': {'Convex', 'Two Pairs of Parallel Sides', 'Two Pairs of Equal Sides'},
        'Rhombus':       {'Convex', 'Two Pairs of Parallel Sides', 'Four Equal Sides'},
        'Rectangle':     {'Convex', 'Two Pairs of Parallel Sides', 'Two Pairs of Equal Sides', '4 Right Angles'},
        'Square':        {'Convex', 'Two Pairs of Parallel Sides', 'Four Equal Sides', '4 Right Angles'},
        'Reflex':        {'Concave'},
        'Dart':          {'Concave', 'No Parallel Sides', 'Two Touching Equal Sides'},
        'Crossed':       {'Crossed'},
        'Truss':         {'Crossed', 'One Pair of Parallel Sides'},
        'Bowtie':        {'Crossed', 'One Pair of Parallel Sides', 'Two Pairs of Equal Sides'},
    }

    def __init__(self, points, grid_size=0.01):
        """
        Initializes the QuadrilateralClassifier with points and grid size.

        Parameters:
            points (list of tuples): List of four ordered points (x, y).
            grid_size (int): Size of the grid to which points will be aligned.
        """
        if len(points) != 4:
            raise ValueError("Exactly four points are required to define a quadrilateral.")

        self.original_points = points
        self.grid_size = grid_size
        self.points = self._align_to_grid(points)

        # Check if points remain four distinct and noncollinear after alignment
        if len(set(self.points)) != 4:
            raise ValueError("Aligned points are not four distinct points.")
        if not self._is_noncollinear():
            raise ValueError("Aligned points are collinear, not forming a quadrilateral.")

    def _align_to_grid(self, points):
        """
        Aligns points to the nearest grid size.

        Parameters:
            points (list of tuples): List of points to align.

        Returns:
            list of tuples: List of aligned points.
        """
        return [(round(x / self.grid_size) * self.grid_size, round(y / self.grid_size) * self.grid_size) for x, y in points]

    def _round_length(self, length: float | int) -> float | int:
        """
        Rounds length to grid/10 size.

        Parameters:
            length: distance to round.

        Returns:
            Rounded length.
        """
        grid = self.grid_size / 10
        return round(length / grid) * grid

    def _round_angle(self, angle: float | int, _unit=0.01) -> float | int:
        """
        Rounds angle to multiples of _unit size.

        Parameters:
            angle: Angle to round.

        Returns:
            Rounded angle.
        """
        return round(angle / _unit) * _unit

    def _is_noncollinear(self):
        """
        Checks if the points are noncollinear.

        Returns:
            bool: True if points are noncollinear, False otherwise.
        """
        def are_points_collinear(p1, p2, p3):
            """
            Checks if three points are collinear.
            """
            x1, y1 = p1
            x2, y2 = p2
            x3, y3 = p3
            return (x2 - x1) * (y3 - y1) == (y2 - y1) * (x3 - x1)

        # Check all combinations of three points
        return not (
            are_points_collinear(self.points[0], self.points[1], self.points[2]) or
            are_points_collinear(self.points[0], self.points[1], self.points[3]) or
            are_points_collinear(self.points[0], self.points[2], self.points[3]) or
            are_points_collinear(self.points[1], self.points[2], self.points[3])
        )

    def _distance(self, p1, p2):
        """
        Calculates the Euclidean distance between two points.

        Parameters:
            p1, p2 (tuple): Two points.

        Returns:
            float: Distance between the points.
        """
        return self._round_length(math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2))

    def _angle(self, p1, p2, p3):
        """
        Calculates the angle (in degrees) at p2 formed by segments (p1, p2) and (p2, p3).

        Parameters:
            p1, p2, p3 (tuple): Three points.

        Returns:
            float: Angle in degrees.
        """
        dx1, dy1 = p1[0] - p2[0], p1[1] - p2[1]
        dx2, dy2 = p3[0] - p2[0], p3[1] - p2[1]
        angle1 = math.atan2(dy1, dx1)
        angle2 = math.atan2(dy2, dx2)
        angle = math.degrees((angle2 - angle1) % (2 * math.pi))
        if angle > 180:
            angle = 360 - angle
        return self._round_angle(angle)
   
    def _is_convex(self):
        """
        Determines if the quadrilateral is convex by analyzing the cross product of vectors formed by consecutive points.
        A quadrilateral is convex if all cross products have the same sign, indicating consistent rotational direction.

        Returns:
            bool: True if the quadrilateral is convex, False otherwise.
        """
        def cross_product_sign(p1, p2, p3):
            """
            Computes the sign of the cross product of vectors (p1->p2) and (p2->p3).
            """
            return (p2[0] - p1[0]) * (p3[1] - p2[1]) - (p2[1] - p1[1]) * (p3[0] - p2[0])

        signs = []
        for i in range(4):
            p1 = self.points[i]
            p2 = self.points[(i + 1) % 4]
            p3 = self.points[(i + 2) % 4]
            signs.append(cross_product_sign(p1, p2, p3))

        return all(s > 0 for s in signs) or all(s < 0 for s in signs)

    def _is_crossed(self):
        """
        Checks if the quadrilateral is crossed (self-intersecting).

        Returns:
            bool: True if the quadrilateral is crossed, False otherwise.
        """
        def do_segments_intersect(p1, p2, p3, p4):
            """
            Checks if segments (p1, p2) and (p3, p4) intersect.

            This is determined using orientation tests to check if the endpoints of each segment lie on opposite sides of the other segment.
            If the segments intersect, the intersection point lies within the bounds of each segment.

            Parameters:
                p1, p2 (tuple): Endpoints of the first segment.
                p3, p4 (tuple): Endpoints of the second segment.

            Returns:
                bool: True if the segments intersect, False otherwise.
            """
            def orientation(a, b, c):
                val = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1])
                if math.fabs(val) < 0.000001:
                    return 0
                if val == 0:
                    return 0  # Collinear
                return 1 if val > 0 else -1

            o1 = orientation(p1, p2, p3)
            o2 = orientation(p1, p2, p4)
            o3 = orientation(p3, p4, p1)
            o4 = orientation(p3, p4, p2)

            # General case
            if o1 != o2 and o3 != o4:
                return True

            return False

        def has_crossed_lines(points):
            """Check if a quadrilateral has crossed lines."""
            p1, p2, p3, p4 = points
            return (
                do_segments_intersect(p1, p2, p3, p4) or  # Check if line 1-2 intersects with line 3-4
                do_segments_intersect(p2, p3, p4, p1)     # Check if line 2-3 intersects with line 4-1
            )

        return has_crossed_lines(self.points)

    def _parallel_sides(self):
        """
        Counts the number of pairs of parallel sides.

        Returns:
            int: Number of pairs of parallel sides (0, 1, or 2).
        """
        def is_parallel(p1, p2, p3, p4):
            """
            Checks if lines (p1, p2) and (p3, p4) are parallel.
            """
            return (p2[1] - p1[1]) * (p4[0] - p3[0]) == (p2[0] - p1[0]) * (p4[1] - p3[1])

        parallel_count = 0
        if is_parallel(self.points[0], self.points[1], self.points[2], self.points[3]):
            parallel_count += 1
        if is_parallel(self.points[1], self.points[2], self.points[3], self.points[0]):
            parallel_count += 1
        return parallel_count

    def _classify(self):
        """
        Classifies the type of quadrilateral.

        Returns:
            str: Type of quadrilateral.
        """
        if not self._is_noncollinear():
            return "Not a quadrilateral (points are collinear)"

        # Precompute distances
        d = [
            self._distance(self.points[i], self.points[(i + 1) % 4])
            for i in range(4)
        ]
        diag1 = self._distance(self.points[0], self.points[2])
        diag2 = self._distance(self.points[1], self.points[3])

        # Check if crossed
        if self._is_crossed():
            # Check for bowtie or hourglass
            if d[0] == d[2] and d[1] == d[3]:
                return "Bowtie (Crossed Quadrilateral)"
            if d[0] != d[2] and d[1] != d[3]:
                return "Hourglass (Crossed Quadrilateral)"
            return "Crossed Quadrilateral"

        # Check convexity
        is_convex = self._is_convex()
        if not is_convex:
            # Check for dart
            if d[0] == d[3] and d[1] == d[2]:
                return "Dart (Concave Quadrilateral)"
            return "Concave Quadrilateral"

        # Check properties
        if d[0] == d[2] and d[1] == d[3]:  # Opposite sides equal
            if diag1 == diag2:  # Diagonals equal
                if d[0] == d[1]:
                    return "Square (Convex)"
                return "Rectangle (Convex)"
            if d[0] == d[1]:
                return "Rhombus (Convex)"
            return "Parallelogram (Convex)"
        elif (d[0] == d[2] or d[1] == d[3]) and not (d[0] == d[1] == d[2] == d[3]):
            return "Trapezium (Convex)"
        elif d[0] == d[1] == d[2] == d[3]:
            return "Rhombus (Convex, Degenerate as Square)"

        return "Irregular Quadrilateral (Convex)"


    def _categorise(self):
        """
        Categorises the quadrilaterals properties.

        Returns:
            set of str: Properties of the quadrilateral.
        """
        types = []

        # Check convexity
        is_convex = self._is_convex()
        if is_convex:
            types.append("Convex")
        else:
            types.append("Concave")

        # Check if crossed
        if self._is_crossed():
            types.append("Crossed")

        # Check for angles > 180°
        reflex_angles = [
            self._angle(self.points[i], self.points[(i + 1) % 4], self.points[(i + 2) % 4]) > 180
            for i in range(4)
        ]
        if any(reflex_angles):
            types.append("Reflex")

        # Check number of right angles
        right_angle_count = sum(
            abs(self._angle(self.points[i], self.points[(i + 1) % 4], self.points[(i + 2) % 4]) - 90) < 1e-6
            for i in range(4)
        )
        types.append(f"{right_angle_count} Right Angles")

        # Check number of equal sides
        d = [
            self._distance(self.points[i], self.points[(i + 1) % 4])
            for i in range(4)
        ]
        unique_lengths = set(d)
        if len(unique_lengths) == 1:
            types.append("Four Equal Sides")
        elif len(unique_lengths) == 2:
            if d[0] == d[2] and d[1] == d[3]:
                types.append("Two Pairs of Equal Sides")
            else:
                types.append("Two Touching Equal Sides")

        # Check number of parallel sides
        parallel_sides = self._parallel_sides()
        if parallel_sides == 2:
            types.append("Two Pairs of Parallel Sides")
        elif parallel_sides == 1:
            types.append("One Pair of Parallel Sides")
        elif parallel_sides == 0:
            types.append("No Parallel Sides")

        return set(types)


    def classify(self):
        """
        Aligns points, validates them, and classifies the quadrilateral.

        Returns:
            str: Classification of the quadrilateral.
        """
        features = self._categorise()
        quads = [name
                 for name, needed in self.quad2features.items()
                 if needed.issubset(features)]
        return quads


# %%
# Example usage of visualize_test_cases function
test_cases = [  # ([points], "classification", "description")
    ([(1, 1), (7, 3), (6, 6), (4, 9)], "Convex", "Non crossing, all angles <180"),
    ([(1, 1), (7, 1), (7, 4), (1, 4)], "Rectangle1", "Non-crossing, all ang == 90, opp sides =="),
    ([(1, 1), (1, 7), (4, 7), (4, 1)], "Rectangle2", "Non-crossing, all ang == 90, all sides =="),
    ([(0, 3), (3, 0), (9, 6), (6, 9)], "Rectangle3", "Non-crossing, all ang == 90, opp sides ==, rotated"),
    ([(1, 1), (7, 1), (7, 7), (1, 7)], "Square1", "Non-crossing, all ang == 90, opp sides =="),
    ([(1, 5), (4, 1), (8, 4), (5, 8)], "Square2", "Non-crossing, all ang == 90, all sides ==, rotated"),
    ([(1, 1), (6, 1), (9, 5), (4, 5)], "Rhombus", "Non-crossing, ang !=90, opp sides || and all sides =="),
    ([(1, 1), (6, 1), (9, 10), (4, 10)], "Parallelogram", "Non-crossing, opp sides || and opp sides =="),
    ([(1, 1), (7, 1), (5, 5), (2, 5)], "Trapezium1", "Non-crossing, at least one set of opposite || sides"),
    ([(1, 1), (5, 3), (5, 6), (1, 7)], "Trapezium2", "Non-crossing, at least one set of opposite || sides"),
    ([(1, 4), (5, 1), (7, 4), (5, 7)], "Kite1", "Non crossing, No interior ang >180, two non== sets of == lines that touch"),
    ([(4, 1), (1, 5), (4, 11), (7, 5)], "Kite2", "Non crossing, No interior ang >180, two non== sets of == lines that touch"),
    ([(1, 1), (5, 5), (7, 4), (4, 8)], "Reflex", "Non crossing, one interior ang >180"),
    ([(1, 4), (7, 1), (4, 4), (7, 7)], "Dart1", "Non crossing, one interior ang >180, two non== sets of == lines that touch"),
    ([(4, 1), (1, 7), (4, 4), (7, 7)], "Dart2", "Non crossing, one interior ang >180, two non== sets of == lines that touch"),
    ([(1, 1), (6, 6), (7, 2), (3, 9)], "Crossed", "Crossing"),
    ([(1, 1), (7, 7), (7, 3), (1, 9)], "Truss1", "Crossing, crossing lines ==, none-crossing lines ||"),
    ([(1, 1), (7, 7), (3, 7), (9, 1)], "Truss2", "Crossing, crossing lines ==, none-crossing lines ||"),
    ([(1, 1), (7, 9), (7, 1), (1, 9)], "Bowtie1", "Crossing, crossing lines ==, none-crossing lines || and =="),
    ([(1, 1), (9, 7), (1, 7), (9, 1)], "Bowtie2", "Crossing, crossing lines ==, none-crossing lines || and =="),
    ([(0, 3), (9, 6), (6, 9), (3, 0)], "Bowtie3", "Crossing, crossing lines ==, none-crossing lines || and ==, rotated"),
]

# %%
if 0:
    ...
   
    # %%
    from  matplotlib import pyplot as plt

    def visualize_test_cases(test_cases):
        """
        Visualizes the test cases and labels them with their classification.

        Parameters:
            test_cases (list of tuples): List of tuples, each containing points and classification.
        """
        fig, axs = plt.subplots(3, 7, figsize=(15, 6))
        axs = axs.flatten()

        # Determine the global axis limits
        all_points = [point for points, __classification, _description in test_cases for point in points]
        xs, ys = zip(*all_points)
        x_min, x_max = min(xs), max(xs)
        y_min, y_max = min(ys), max(ys)

        for ax, (points, classification, _description) in zip(axs, test_cases):
            points = points[::]
            points.append(points[0])  # To complete the quadrilateral loop
            xs, ys = zip(*points)
            ax.plot(xs, ys, marker='o')

            ax.set_title(classification)
            ax.set_xticks([])
            ax.set_yticks([])
            ax.set_xlim(x_min - 1, x_max + 1)
            ax.set_ylim(y_min - 1, y_max + 1)

        plt.tight_layout()
        plt.show()

    visualize_test_cases(test_cases)

# %%
head = \
'NAME           CONVEX  REFLEX  CROSSING    >180DEG     =90DEG  SIDES==     PARALLEL'.strip().split()
body = [line.strip().split() for line in '''
Convex          True    False   False           0       -         -           -
Rectangle       True    False   False           0       4       2,pairs     2,pairs
Square          True    False   False           0       4       4           2,pairs
Rhombus         True    False   False           0       0       4           2,pairs
Parallelogram   True    False   False           0       0       2,pairs     2,pairs
Trapezium       True    False   False           -       -       -           1,pair
Kite            True    False   False           0       0       2,Tpairs    0
Reflex          False   True    False           1       0       -           -
Dart            False   True    False           1       0       2,Tpairs    0
Crossed         False   False   True            -       -       -           -
Truss           False   False   True            -       -       -           1,pair
Bowtie          False   False   True            -       -       2,pairs     1,pair

# KEY
#
# CONVEX        not REFLEX and not CROSSED
# REFLEX        One angle greater than 180 degrees
# CROSSING      two lines in figure cross over
# >180DEG       Interior angles greater than 180 degrees
# =90DEG        Interior right-angles
# SIDES==       Number of equal length sides
# PARALLEL      Number of parallel sides
#
# -             Don't care
# Tpairs        Pair of equal length sides Touching at one shared point

'''.strip().splitlines() if line.strip() and not line.startswith('#')]
# %%

#for points, classification, description in test_cases[4:5]:
#for points, classification, description in test_cases[-1:]:
quad2features = {}
for points, classification, description in test_cases:
    quad2features[classification] = QuadrilateralClassifier(points).classify()
    print(f"\n{classification}\n  {description}\n  {points}")
    print(f"  {QuadrilateralClassifier(points).classify() =}")


IS AI Worth it?

I was surprised by just how much AI will do on early prompts, but days are then spent honing and checking, again and again.. Some things, like the has_crossed_lines function just could not be done correctly by chatgpt. It continuousely got the line segments and points of the line segments wrong. In the end I had to cut-n-paste the correct points andintersection checks into my query for it to then "generate" the right function. I had to know what I was doing to have first found the error.

One needs to know more, or find out more, than the AI to spot mistakes. The AI can help with this by asking it to explain parts of its code then searching for corroborating evidence, or for limitations in the algorithms it has chosen.

The AI's can't calculate worth a damn, and this can affect their orderings too. It helps if the AI interface can not only generate code, but also run it. Asking AI to generate points for various types of quadrilateral was error prone, leading me to do it without AI aid.

Free AI accounts have limits! I had to wait till the next day on several occasions after using up my free time.


In summary, I will carry on trying out AI, hopefully both it and I will get better!

No comments:

Post a Comment

Followers

Subscribe Now: google

Add to Google Reader or Homepage

Go deh too!

whos.amung.us

Blog Archive