(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