CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

| Download

GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it

Views: 418346
1
2
2 Implementing circle objects
3
4
In this chapter we explain how the GAP system may be extended with new
5
objects using the circle multiplication as an example. We follow the
6
guidelines given in the GAP Reference Manual (see 'Reference: Creating New
7
Objects' and subsequent chapters), to which we refer for more details.
8
9
10
2.1 First attempts
11
12
Of course, having two ring elements, you can straightforwardly compute their
13
circle product defined as r ⋅ s = r + s + rs. You can do this in a command
14
line, and it is a trivial task to write a simplest function of two arguments
15
that will do this:
16
17
 Example 
18

19
gap> CircleMultiplication := function(a,b)
20
>  return a+b+a*b;
21
>  end;
22
function( a, b ) ... end
23
gap> CircleMultiplication(2,3); 
24
11
25
gap> CircleMultiplication( ZmodnZObj(2,8), ZmodnZObj(5,8) ); 
26
ZmodnZObj( 1, 8 )
27

28

29
30
However, there is no check whether both arguments belong to the same ring
31
and whether they are ring elements at all, so it is easy to obtain some
32
meaningless results:
33
34
 Example 
35

36
gap> CircleMultiplication( 3, ZmodnZObj(3,8) );
37
ZmodnZObj( 7, 8 )
38
gap> CircleMultiplication( [1], [2,3] );
39
[ 5, 5 ]
40

41

42
43
You can include some tests for arguments, and maybe the best way of doing
44
this would be declaring a new operation for two ring elements, and
45
installing the previous function as a method for this operation. This will
46
check automatically if the arguments are ring elements from the common ring:
47
48
 Example 
49

50
gap> DeclareOperation( "BetterCircleMultiplication", 
51
>  [IsRingElement,IsRingElement] );
52
gap> InstallMethod( BetterCircleMultiplication,
53
>  IsIdenticalObj,
54
>  [IsRingElement,IsRingElement], 
55
>  CircleMultiplication );
56
gap> BetterCircleMultiplication(2,3);
57
11
58
gap> BetterCircleMultiplication( ZmodnZObj(2,8), ZmodnZObj(5,8) );
59
ZmodnZObj( 1, 8 )
60

61

62
63
Nevertheless, the functionality gained from such operation would be rather
64
limited. You will not be able to compute circle product via the infix
65
operator *, and, moreover, you will not be able to create higher level
66
objects such as semigroups and groups with respect to the circle
67
multiplication.
68
69
In order to "integrate" the circle multiplication into the GAP library
70
properly, instead of defining new operations for existing objects, we should
71
define new objects for which the infix operator * will perform the circle
72
multiplication. This approach is explained in the next two sections.
73
74
75
2.2 Defining circle objects
76
77
Thus, we are going to implement circle objects, for which we can envisage
78
the following functionality:
79
80
 Example 
81

82
gap> CircleObject( 2 ) * CircleObject( 3 ); 
83
CircleObject( 11 )
84

85

86
87
First we need to distinguish these new objects from other GAP objects. This
88
is done via the type of the objects, that is mainly determined by their
89
category, representation and family.
90
91
We start with declaring the category IsCircleObject as a subcategory of
92
IsAssociativeElement> and IsMultiplicativeElementWithInverse. Thus, each
93
circle object will "know" that it is IsAssociativeElement and
94
IsMultiplicativeElementWithInverse, and this will make it possible to apply
95
to circle objects such operations as One and Inverse (the latter is allowed
96
to return fail for a given circle object), and construct semigroups
97
generated by circle objects.
98
99
 Example 
100

101
gap> DeclareCategory( "IsMyCircleObject", 
102
> IsAssociativeElement and IsMultiplicativeElementWithInverse );
103

104

105
106
Further we would like to create semigroups and groups generated by circle
107
objects. Such structures will be collections of circle objects, so they will
108
be in the category CategoryCollections( IsCircleObject ). This is why
109
immediately after we declare the underlying category of circle objects, we
110
need also to declare the category of their collections:
111
112
 Example 
113

114
gap> DeclareCategoryCollections( "IsMyCircleObject" );
115

116

117
118
On the next step we should think about the internal representation of circle
119
objects. A natural way would be to store the underlying ring element in a
120
list-like structure at its first position. We do not foresee any other data
121
that we need to store internally in the circle object. This is quite common
122
situation, so we may define first IsPositionalObjectOneSlotRep that is the
123
list-like representation with only one position in the list, and then
124
declare a synonym IsDefaultCircleObject that means that we are dealing with
125
a circle object in one-slot representation:
126
127
 Example 
128

129
gap> DeclareRepresentation( "IsMyPositionalObjectOneSlotRep",
130
>  IsPositionalObjectRep, [ 1 ] );
131
gap> DeclareSynonym( "IsMyDefaultCircleObject",
132
>  IsMyCircleObject and IsMyPositionalObjectOneSlotRep );
133

134

135
136
Until now we are still unable to create circle objects, because we did not
137
specify to which family they will belong. Naturally, having a ring, we want
138
to have all circle objects for elements of this ring in the same family to
139
be able to multiply them, and we expect circle objects for elements of
140
different rings to be placed in different families. Thus, it would be nice
141
to establish one-to-one correspondence between the family of ring elements
142
and a family of circle elements for this ring. We can store the
143
corresponding circle family as an attribute of the ring elements family. To
144
do this first we declare an attribute CircleFamily for families:
145
146
 Example 
147

148
gap> DeclareAttribute( "MyCircleFamily", IsFamily );
149

150

151
152
Now we install the method that stores the corresponding circle family in
153
this attribute:
154
155
 Example 
156

157
gap> InstallMethod( MyCircleFamily,
158
>  "for a family",
159
>  [ IsFamily ],
160
>  function( Fam )
161
>  local F;
162
>  # create the family of circle elements
163
>  F:= NewFamily( "MyCircleFamily(...)", IsMyCircleObject );
164
>  if HasCharacteristic( Fam ) then
165
>  SetCharacteristic( F, Characteristic( Fam ) );
166
>  fi;
167
>  # store the type of objects in the output
168
>  F!.MyCircleType:= NewType( F, IsMyDefaultCircleObject );
169
>  # Return the circle family
170
>  return F;
171
> end );
172

173

174
175
Similarly, we want one-to-one correspondence between circle elements and
176
underlying ring elements. We declare an attribute CircleObject for a ring
177
element, and then install the method to create new circle object from the
178
ring element. This method takes the family of the ring element, finds
179
corresponding circle family, extracts from it the type of circle objects and
180
finally creates the new circle object of that type:
181
182
 Example 
183

184
gap> DeclareAttribute( "MyCircleObject", IsRingElement );
185
gap> InstallMethod( MyCircleObject,
186
>  "for a ring element",
187
>  [ IsRingElement ],
188
>  obj -> Objectify( MyCircleFamily( FamilyObj( obj ) )!.MyCircleType,
189
>  [ Immutable( obj ) ] ) );
190

191

192
193
Only after entering all code above we are able to create some circle object.
194
However, it is displayed just as <object>, though we can get the underlying
195
ring element using the "!" operator:
196
197
 Example 
198

199
gap> a:=MyCircleObject(2);
200
<object>
201
gap> a![1];
202
2
203

204

205
206
We can check that the intended relation between families holds:
207
208
 Example 
209

210
gap> FamilyObj( MyCircleObject ( 2 ) ) = MyCircleFamily( FamilyObj( 2 ) );
211
true
212

213

214
215
We can not multiply circle objects yet. But before implementing this, first
216
let us improve the output by installing the method for PrintObj:
217
218
 Example 
219

220
gap> InstallMethod( PrintObj,
221
>  "for object in `IsMyCircleObject'",
222
>  [ IsMyDefaultCircleObject ],
223
>  function( obj )
224
>  Print( "MyCircleObject( ", obj![1], " )" );
225
>  end );
226

227

228
229
This method will be used by Print function, and also by View, since we did
230
not install special method for ViewObj for circle objects. As a result of
231
this installation, the output became more meaningful:
232
233
 Example 
234

235
gap> a;
236
MyCircleObject( 2 )
237

238

239
240
We need to avoid the usage of "!" operator, which, in general, is not
241
recommended to the user (for example, if GAP developers will change the
242
internal representation of some object, all GAP functions that deal with it
243
must be adjusted appropriately, while if the user's code had direct access
244
to that representation via "!", an error may occur). To do this, we wrap
245
getting the first component of a circle object in the following operation:
246
247
 Example 
248

249
gap> DeclareAttribute("UnderlyingRingElement", IsMyCircleObject );
250
gap> InstallMethod( UnderlyingRingElement,
251
>  "for a circle object", 
252
>  [ IsMyCircleObject],
253
>  obj -> obj![1] );
254
gap> UnderlyingRingElement(a);
255
2
256

257

258
259
260
2.3 Installing operations for circle objects
261
262
Now we are finally able to install circle multiplication as a default method
263
for the multiplication of circle objects, and perform the computation that
264
we envisaged in the beginning:
265
266
 Example 
267

268
gap> InstallMethod( \*,
269
>  "for two objects in `IsMyCircleObject'",
270
>  IsIdenticalObj,
271
>  [ IsMyDefaultCircleObject, IsMyDefaultCircleObject ],
272
>  function( a, b )
273
>  return MyCircleObject( a![1] + b![1] + a![1]*b![1] );
274
>  end );
275
gap> MyCircleObject(2)*MyCircleObject(3);
276
MyCircleObject( 11 )
277

278

279
280
However, this functionality is not enough to form semigroups or groups
281
generated by circle elements. We need to be able to check whether two circle
282
objects are equal, and we need to define ordering for them (for example, to
283
be able to form sets of circle elements). Since we already have both
284
operations for underlying ring elements, this can be implemented in a
285
straightforward way:
286
287
 Example 
288

289
gap> InstallMethod( \=,
290
>  "for two objects in `IsMyCircleObject'",
291
>  IsIdenticalObj,
292
>  [ IsMyDefaultCircleObject, IsMyDefaultCircleObject ],
293
>  function( a, b )
294
>  return a![1] = b![1];
295
>  end );
296
gap> InstallMethod( \<,
297
>  "for two objects in `IsMyCircleObject'",
298
>  IsIdenticalObj,
299
>  [ IsMyDefaultCircleObject, IsMyDefaultCircleObject ],
300
>  function( a, b )
301
>  return a![1] < b![1];
302
>  end );
303

304

305
306
Further, zero element of the ring plays a role of the neutral element for
307
the circle multiplication, and we add this knowledge to our code in a form
308
of a method for OneOp that returns circle object for the corresponding zero
309
object:
310
311
 Example 
312

313
gap> InstallMethod( OneOp,
314
>  "for an object in `IsMyCircleObject'",
315
>  [ IsMyDefaultCircleObject ],
316
>  a -> MyCircleObject( Zero( a![1] ) ) );
317
gap> One(a);
318
MyCircleObject( 0 )
319

320

321
322
Now we are already able to create monoids generated by circle objects:
323
324
 Example 
325

326
gap> S:=Monoid(a);
327
<commutative monoid with 1 generator>
328
gap> One(S);
329
MyCircleObject( 0 )
330
gap> S:=Monoid( MyCircleObject( ZmodnZObj( 2,8) ) );
331
<commutative monoid with 1 generator>
332
gap> Size(S);
333
2
334
gap> AsList(S);
335
[ MyCircleObject( ZmodnZObj( 0, 8 ) ), MyCircleObject( ZmodnZObj( 2, 8 ) ) ]
336

337

338
339
Finally, to generate groups using circle objects, we need to add a method
340
for the InverseOp. In our implementation we will assume that the underlying
341
ring is a subring of the ring with one, thus, if the circle inverse for an
342
element x exists, than it can be computed as -x(1+x)^-1:
343
344
 Example 
345

346
gap> InstallMethod( InverseOp,
347
>  "for an object in `IsMyCircleObject'",
348
>  [ IsMyDefaultCircleObject ],
349
>  function( a )
350
>  local x;
351
>  x := Inverse( One( a![1] ) + a![1] );
352
>  if x = fail then
353
>  return fail;
354
>  else
355
>  return MyCircleObject( -a![1] * x );
356
>  fi;
357
>  end );
358
gap> MyCircleObject(-2)^-1; 
359
MyCircleObject( -2 )
360
gap> MyCircleObject(2)^-1; 
361
MyCircleObject( -2/3 )
362

363

364
365
The last method already makes it possible to create groups generated by
366
circle objects (the warning may be ignored):
367
368
 Example 
369

370
gap> Group( MyCircleObject(2) ); 
371
#I default `IsGeneratorsOfMagmaWithInverses' method returns `true' for
372
[ MyCircleObject( 2 ) ]
373
<group with 1 generators>
374
gap> G:=Group( [MyCircleObject( ZmodnZObj( 2,8 ) ) ]);
375
#I default `IsGeneratorsOfMagmaWithInverses' method returns `true' for
376
[ MyCircleObject( ZmodnZObj( 2, 8 ) ) ]
377
<group with 1 generators>
378
gap> Size(G);
379
2
380
gap> AsList(G);
381
[ MyCircleObject( ZmodnZObj( 0, 8 ) ), MyCircleObject( ZmodnZObj( 2, 8 ) ) ]
382

383

384
385
The GAP code used in this Chapter, is contained in the files
386
circle/lib/circle.gd and circle/lib/circle.gi (without My in identifiers).
387
For more examples of implementing new GAP objects and further details see
388
'Reference: Creating New Objects' and subsequent chapters in the GAP
389
Reference Manual.
390
391
392