@@ -93,8 +93,8 @@ representing groups, and objects representing elements. We'll lay out one
93
93
possible configuration, which helpfully involves both inheritance and
94
94
composition, as well as parametrisation of objects and delegation of methods.
95
95
96
- Basic design considerations
97
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
96
+ Cyclic groups
97
+ ~~~~~~~~~~~~~
98
98
99
99
Let's start with the cyclic groups of order :math: `n`. These are isomorphic to
100
100
the integers modulo :math: `n`, a property which we can use to create our
@@ -186,7 +186,134 @@ minimal characterisation of a group will suffice.
186
186
187
187
:numref: `cyclic_group ` shows an implementation of our minimal conception of
188
188
cyclic groups. Before considering it in any detail let's try it out to observe
189
- the concrete effects of the classes.
189
+ the concrete effects of the classes:
190
+
191
+ .. code-block :: ipython3
192
+
193
+ In [1]: from example_code.groups_basic import CyclicGroup
194
+
195
+ In [2]: C = CyclicGroup(5)
196
+
197
+ In [3]: print(C(3) * C(4))
198
+ 2_C5
199
+
200
+ We observe that we are able to create the cyclic group of order 5. Due to the
201
+ definition of the :meth: `__call__ ` :term: `special method ` at line 35, we are
202
+ then able to create elements of the group by calling the group object. The group
203
+ operation then has the expected effect:
204
+
205
+ .. math ::
206
+
207
+ 3 _{C_5 } \cdot 4 _{C_5 } &\equiv (3 + 4 ) \operatorname {mod} 5 \\
208
+ &= 2 \\
209
+ &\equiv 2 _{C_5 }
210
+
211
+ Finally, if we attempt to make a group element with a value which is not an
212
+ integer between 0 and 5, an exception is raised.
213
+
214
+ .. code-block :: ipython3
215
+
216
+ In [4]: C(1.5)
217
+ ---------------------------------------------------------------------------
218
+ ValueError Traceback (most recent call last)
219
+ <ipython-input-4-a5d8472d4486> in <module>
220
+ ----> 1 C(1.5)
221
+
222
+ ~/docs/principles_of_programming/object-oriented-programming/example_code/groups_basic.py in __call__(self, value)
223
+ 38 def __call__(self, value):
224
+ 39 '''Provide a convenient way to create elements of this group.'''
225
+ ---> 40 return Element(self, value)
226
+ 41
227
+ 42 def __str__(self):
228
+
229
+ ~/docs/principles_of_programming/object-oriented-programming/example_code/groups_basic.py in __init__(self, group, value)
230
+ 4 class Element:
231
+ 5 def __init__(self, group, value):
232
+ ----> 6 group._validate(value)
233
+ 7 self.group = group
234
+ 8 self.value = value
235
+
236
+ ~/docs/principles_of_programming/object-oriented-programming/example_code/groups_basic.py in _validate(self, value)
237
+ 30 '''Ensure that value is a legitimate element value in this group.'''
238
+ 31 if not (isinstance(value, Integral) and 0 <= value < self.order):
239
+ ---> 32 raise ValueError("Element value must be an integer"
240
+ 33 f" in the range [0, {self.order})")
241
+ 34
242
+
243
+ ValueError: Element value must be an integer in the range [0, 5)
244
+
245
+ We've seen :term: `composition ` here: on line 4
246
+ :class: `~example_code.groups_basic.Element `, is associated with a group object.
247
+ This is a classic *has a * relationship: an element has a group. We might have
248
+ attempted to construct this the other way around with classes having elements,
249
+ however this would have immediately hit the issue that elements have exactly one
250
+ group, while a group might have an unlimited number of elements. Object
251
+ composition is typically most successful when the relationship is uniquely
252
+ defined.
253
+
254
+ General linear groups
255
+ ~~~~~~~~~~~~~~~~~~~~~
256
+
257
+ We still haven't encountered inheritance, though. Where does that come into the
258
+ story? Well first we'll need to introduce at least one more family of groups.
259
+ For no other reason than convenience, let's choose :math: `G_n`, the general
260
+ linear group of degree :math: `n`. The elements of this group can be
261
+ represented as :math: `n\times n` invertible square matrices. At least to the
262
+ extent that real numbers can be represented on a computer, we can implement this
263
+ group as follows:
264
+
265
+ .. code-block :: python3
266
+ :caption: A basic implementation of the general linear group of a given
267
+ degree.
268
+ :name: general_linear_group
269
+ :linenos:
270
+
271
+ class GeneralLinearGroup:
272
+ '''The general linear group represented by degree x degree matrices.'''
273
+ def __init__(self, degree):
274
+ self.degree = degree
275
+
276
+ def _validate(self, value):
277
+ '''Ensure that value is a legitimate element value in this group.'''
278
+ value = np.asarray(value)
279
+ if not (value.shape == (self.degree, self.degree)):
280
+ raise ValueError("Element value must be a "
281
+ f"{self.degree} x {self.degree}"
282
+ "square array.")
283
+
284
+ def operation(self, a, b):
285
+ return a @ b
286
+
287
+ def __call__(self, value):
288
+ '''Provide a convenient way to create elements of this group.'''
289
+ return Element(self, value)
290
+
291
+ def __str__(self):
292
+ return f"G{self.degree}"
293
+
294
+ def __repr__(self):
295
+ return f"{self.__class__.__name__}({repr(self.degree)})"
296
+
297
+ We won't illustrate the operation of this class, though the reader is welcome to
298
+ :keyword: `import ` the :mod: `example_code.groups_basic ` module and experiment.
299
+ Instead, we simply note that this code is very, very similar to the
300
+ implementation of :class: `~example_code.groups_basic.CyclicGroup ` in
301
+ :numref: `cyclic_group `. The only functionally important differences are the
302
+ definitions of the :meth: `_validate ` and :meth: `operation ` methods.
303
+ `self.order ` is also renamed as `self.degree `, and `C ` is replaced by `G ` in the
304
+ string representation. It remains the case that there is a large amount of
305
+ code repetition between classes. For the reasons we touched on in
306
+ :numref: `repetition `, this is a highly undesirable state of affairs.
307
+
308
+ Inheritance
309
+ -----------
310
+
311
+ Suppose, instead of copying much of the same code, we had a prototype
312
+ :class: `Group ` class, and :class: `CyclicGroup ` and :class: `GeneralLinearGroup `
313
+ simply specified the ways in which they differ from the prototype. This would
314
+ avoid the issues associated with repeating code, and would make it obvious how
315
+ the different group implementations differ. This is exactly what inheritance
316
+ does.
190
317
191
318
Glossary
192
319
--------
@@ -215,7 +342,7 @@ Glossary
215
342
classes.
216
343
217
344
parent class
218
- A class from which another class, referred to as a :term: `child class `
345
+ A class from which another class, referred to as a :term: `child class `,
219
346
inherits.
220
347
221
348
subclass
0 commit comments