# Triangle area computation and linear transformations

I have a task to learn how to write clear and understandable documentation of written code in python. Please, can you give me a feedback to attached code below? I need to know if the code and comments explains the functionality of code properly.

# math operations library
import numpy as np

# for testing of classes
import unittest

# ========================== NODE ==================================== #
#Class representing geometric node in 3D
class Node:
def __init__(self, x=0, y=0, z=0):
self.X = x
self.Y = y
self.Z = z

# =========================== TRIANGLE =============================== #
# class representing triangle ABC
class Triangle:
def __init__(self, a, b, c):
self.A = a
self.B = b
self.C = c

# returns area of triangle in 2D
def Area(self):
return (
np.absolute(
self.A.X * (self.B.Y - self.C.Y)
+ self.B.X * (self.C.Y - self.A.Y)
+ self.C.X * (self.A.Y - self.B.Y)
) / 2.0
)

# unittests for triangle object
class TriangleTest(unittest.TestCase):
def test_area(self):
# triangle node
A = Node(0,0)
B = Node(1,0)
C = Node(0,1)

T = Triangle(A,B,C)
self.assertEquals(T.Area(), 0.5)

# ============================ Transformation ======================== #

"""
class representing transformation from reference triangle
with: A = [0,0],
B = [1,0],
C = [0,1].
"""
class Transformation:
def __init__(self, triangle):
# Base node - A node of triangle
self.A = triangle.A

# Matrix B(2x2) representing transformation operator
self.B11 = triangle.B.X - triangle.A.X
self.B12 = triangle.C.X - triangle.A.X
self.B21 = triangle.B.Y - triangle.A.Y
self.B22 = triangle.C.Y - triangle.A.Y

# determinant of transformation = B matrix (2x2)
self.Jacobian = (
(self.B11 * self.B22) - (self.B21 * self.B12)
)

# Inverse of transformation operator = invB matrix (2x2)
self.invB11 = 1 / self.Jacobian * (self.B22)
self.invB12 = - 1 / self.Jacobian * (self.B12)
self.invB21 = - 1 / self.Jacobian * (self.B21)
self.invB22 = 1 / self.Jacobian * (self.B11)

def TransformNode(self, node):
"""
This function returns coordinates of node, that is transformed
from referance coordinates system to general coordinate system
of given 2D triangle.

Args:

* node - geometrical node with coordinates in reference system

Returns:

* node with transformed coordinates into general coordinates
system of given 2D triangle
"""

return Node(
self.A.X + (self.B11 * node.X) + (self.B12 * node.Y),
self.A.Y + (self.B21 * node.X) + (self.B22 * node.Y)
)

def InvTransformNode(self, node):
"""
This function returns coordinates of node, thah is transformed
from general coordinate system of given 2D triangle to refere-
nce coordinate system.

Args:

* node - geometrical node with coordinates in general system

Returns:

* node with transformed coordinates to reference coordinate
system
"""

return Node(
self.invB11 * (node.X - self.A.X)
+ self.invB12 * (node.Y - self.A.Y),
self.invB21 * (node.X - self.A.X)
+ self.invB22 * (node.Y - self.A.Y)
)

# unittests for transformation object
class TransformationTest(unittest.TestCase):
def test_NodeTransformation(self):
"""
Checking if node D is transformed to reference system and back
correctly
"""

# triangle nodes
A = Node(1,1)
B = Node(3,1)
C = Node(4,2)

# transformed node
D = Node(2,3)

T = Triangle(A,B,C)
Trans = Transformation(T)

# retransformed node D
resD = Trans.TransformNode(Trans.InvTransformNode(D))

# compare result coordinates
self.assertEquals(resD.X, D.X)
self.assertEquals(resD.Y, D.Y)

# ============================== testing ============================= #
# Test Triangle object
unittest.TextTestRunner(verbosity=3).run(suite1)

# Test Transformation object
unittest.TextTestRunner(verbosity=3).run(suite2)

• (Welcome to CR!) Do you know docstrings? That is one thing python got right: documentation amidst essential parts of code. This still gets out of sync, but separating it from the code would require extra/criminal energy. – greybeard Dec 3 '17 at 23:03
• Many thanks, it seem to be interesting, I will check it. – VaPo Dec 3 '17 at 23:05
• @VaPo not a large enough suggestion to be it's own answer, but use a namedtuple for the Node type - as in, Node = namedtuple('Node', ['X', 'Y', 'Z']). It's shorter, clearer, and will provide you with some useful debugging stuff as well. – naiveai Dec 4 '17 at 3:31
• Even when it works for your example numbers, you should not check if a float is 0.5, but if abs(x-0.5) < eps, e.g. with eps=1e-14. Else there will be cases where floating point arithmetic leads to small rounding errors even when you think your numbers are even. I.e. you have 0.1+0.2 == 0.30000000000000004 in python. – allo Dec 4 '17 at 14:13
• Thank you for your time and answers, I really appreciate it. – VaPo Dec 9 '17 at 11:49

Here are a few of the high- and low-level ideas:

• too many comments. Remember, comments tend to age and outdate, they need to be maintained as the code changes. And, if they are over-used, they hurt readability, not improve it. See more at Coding Without Comments
• convert some of the comments preceding the functions and methods to proper documentation strings
• there are some PEP8 naming violations - like variable and function names that start with an upper case letter
• see if you can separate code from the tests into different files/modules
• if performance or/and memory usage is a concern, you may use __slots__ for attribute definitions
• there is also this awesome attrs library that may help with some class-attrs related boilerplate code
• +1 on this. Instead of those huge distracting banner comments, just put every class in it's own file. – Qwertie Dec 4 '17 at 2:57

The code is very readable and the comments are nice. Just a few remarks on some comments:

• Why don't Node and Triangle have docstring comments?
• Importing libraries doesn't need commentary.
• Having Node and Triangle docstrings seems a bit meaningless since they're such obvious classes - you can digest the code in 3 seconds flat. – naiveai Dec 4 '17 at 3:29
• You could make it a one line docstring. I’ve seen getter functions with one line docstrings. – Benjamin Philippe Dec 4 '17 at 14:18
• Well of course you could, but I don't really see the point of that. – naiveai Dec 5 '17 at 12:22
• I think just to have it in the documentation. – Benjamin Philippe Dec 5 '17 at 12:26
• Mindlessly documenting doesn't do much good. – naiveai Dec 5 '17 at 12:30